aboutsummaryrefslogtreecommitdiffstats
path: root/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextManagerTest.java
blob: 5e09c45d217c25d94daad50be71aacfa11cf82b7 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.node.admin.nodeagent;

import com.yahoo.jdisc.core.SystemTimer;
import com.yahoo.vespa.test.file.TestFileSystem;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;

import java.time.Duration;
import java.time.Instant;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;

import static com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContextSupplier.ContextSupplierInterruptedException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
 * @author freva
 */
public class NodeAgentContextManagerTest {

    private static final int TIMEOUT = 10_000;

    private final SystemTimer timer = new SystemTimer();
    private final NodeAgentContext initialContext = generateContext();
    private final NodeAgentContextManager manager = new NodeAgentContextManager(timer, initialContext);

    @Test
    @Timeout(TIMEOUT)
    void context_is_ignored_unless_scheduled_while_waiting() {
        NodeAgentContext context1 = generateContext();
        manager.scheduleTickWith(context1, timer.currentTime());
        assertSame(initialContext, manager.currentContext());

        AsyncExecutor<NodeAgentContext> async = new AsyncExecutor<>(manager::nextContext);
        manager.waitUntilWaitingForNextContext();
        assertFalse(async.isCompleted());

        NodeAgentContext context2 = generateContext();
        manager.scheduleTickWith(context2, timer.currentTime());

        assertSame(context2, async.awaitResult().response.get());
        assertSame(context2, manager.currentContext());
    }

    @Test
    @Timeout(TIMEOUT)
    void returns_no_earlier_than_at_given_time() {
        AsyncExecutor<NodeAgentContext> async = new AsyncExecutor<>(manager::nextContext);
        manager.waitUntilWaitingForNextContext();

        NodeAgentContext context1 = generateContext();
        Instant returnAt = timer.currentTime().plusMillis(500);
        manager.scheduleTickWith(context1, returnAt);

        assertSame(context1, async.awaitResult().response.get());
        assertSame(context1, manager.currentContext());
        // Is accurate to a millisecond
        assertFalse(timer.currentTime().plusMillis(1).isBefore(returnAt));
    }

    @Test
    @Timeout(TIMEOUT)
    void blocks_in_nextContext_until_one_is_scheduled() {
        AsyncExecutor<NodeAgentContext> async = new AsyncExecutor<>(manager::nextContext);
        manager.waitUntilWaitingForNextContext();
        assertFalse(async.isCompleted());

        NodeAgentContext context1 = generateContext();
        manager.scheduleTickWith(context1, timer.currentTime());

        async.awaitResult();
        assertEquals(Optional.of(context1), async.response);
        assertFalse(async.exception.isPresent());
    }

    @Test
    @Timeout(TIMEOUT)
    void blocks_in_nextContext_until_interrupt() {
        AsyncExecutor<NodeAgentContext> async = new AsyncExecutor<>(manager::nextContext);
        manager.waitUntilWaitingForNextContext();
        assertFalse(async.isCompleted());

        manager.interrupt();

        async.awaitResult();
        assertEquals(Optional.of(ContextSupplierInterruptedException.class), async.exception.map(Exception::getClass));
        assertFalse(async.response.isPresent());
    }

    @Test
    @Timeout(TIMEOUT)
    void setFrozen_does_not_block_with_no_timeout() {
        assertFalse(manager.setFrozen(false, Duration.ZERO));

        // Generate new context and get it from the supplier, this completes the unfreeze
        NodeAgentContext context1 = generateContext();
        AsyncExecutor<NodeAgentContext> async = new AsyncExecutor<>(manager::nextContext);
        manager.waitUntilWaitingForNextContext();
        manager.scheduleTickWith(context1, timer.currentTime());
        assertSame(context1, async.awaitResult().response.get());

        assertTrue(manager.setFrozen(false, Duration.ZERO));
    }

    @Test
    @Timeout(TIMEOUT)
    void setFrozen_blocks_at_least_for_duration_of_timeout() {
        long wantedDurationMillis = 100;
        long start = timer.currentTimeMillis();
        assertFalse(manager.setFrozen(false, Duration.ofMillis(wantedDurationMillis)));
        long actualDurationMillis = timer.currentTimeMillis() - start;

        assertTrue(actualDurationMillis >= wantedDurationMillis);
    }

    private static NodeAgentContext generateContext() {
        return NodeAgentContextImpl.builder("container-123.domain.tld").fileSystem(TestFileSystem.create()).build();
    }

    private static class AsyncExecutor<T> {
        private final CountDownLatch latch = new CountDownLatch(1);
        private volatile Optional<T> response = Optional.empty();
        private volatile Optional<Exception> exception = Optional.empty();

        private AsyncExecutor(Callable<T> supplier) {
            new Thread(() -> {
                try {
                    response = Optional.of(supplier.call());
                } catch (Exception e) {
                    exception = Optional.of(e);
                }
                latch.countDown();
            }).start();
        }

        private AsyncExecutor<T> awaitResult() {
            try {
                latch.await();
            } catch (InterruptedException ignored) { }
            return this;
        }

        private boolean isCompleted() {
            return latch.getCount() == 0;
        }
    }
}