aboutsummaryrefslogtreecommitdiffstats
path: root/config-proxy/src/test/java/com/yahoo/vespa/config/proxy/ProxyServerTest.java
blob: 0e25e62b925573b2fcbb225785bbd084b495c2d8 (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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.config.proxy;

import com.yahoo.config.subscription.ConfigSourceSet;
import com.yahoo.vespa.config.ConfigCacheKey;
import com.yahoo.vespa.config.ConfigKey;
import com.yahoo.vespa.config.ErrorCode;
import com.yahoo.vespa.config.RawConfig;
import com.yahoo.vespa.config.protocol.JRTServerConfigRequest;
import com.yahoo.vespa.config.protocol.Payload;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

import java.util.Optional;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

/**
 * @author hmusum
 */
public class ProxyServerTest {

    private final MockConfigSource source = new MockConfigSource();
    private final ConfigSourceClient client = new MockConfigSourceClient(source);
    private ProxyServer proxy;

    static final RawConfig fooConfig = ConfigTester.fooConfig;

    // errorConfig based on fooConfig
    private static final ConfigKey<?> errorConfigKey = new ConfigKey<>("error", fooConfig.getConfigId(), fooConfig.getNamespace());
    static final RawConfig errorConfig = new RawConfig(errorConfigKey, fooConfig.getDefMd5(), fooConfig.getPayload(),
                                                       fooConfig.getPayloadChecksums(), fooConfig.getGeneration(), false,
                                                       ErrorCode.UNKNOWN_DEFINITION, fooConfig.getDefContent(), Optional.empty());

    @Rule
    public TemporaryFolder temporaryFolder = new TemporaryFolder();

    @Before
    public void setup() {
        source.clear();
        source.put(fooConfig.getKey(), createConfigWithNextConfigGeneration(fooConfig, 0));
        source.put(errorConfigKey, createConfigWithNextConfigGeneration(fooConfig, ErrorCode.UNKNOWN_DEFINITION));
        proxy = createTestServer(source, client);
    }

    @After
    public void shutdown() {
        proxy.stop();
    }

    @Test
    public void basic() {
        assertTrue(proxy.getMode().isDefault());
        assertEquals(0, proxy.memoryCache().size());

        ConfigTester tester = new ConfigTester();
        MemoryCache memoryCache = proxy.memoryCache();
        assertEquals(0, memoryCache.size());
        RawConfig res = proxy.resolveConfig(tester.createRequest(fooConfig)).orElseThrow();
        assertEquals(ConfigTester.fooPayload.toString(), res.getPayload().toString());
        assertEquals(1, memoryCache.size());
        assertEquals(res, memoryCache.get(new ConfigCacheKey(fooConfig.getKey(), fooConfig.getDefMd5())).orElseThrow());
    }

    /**
     * Tests that the proxy server RPC commands for setting and getting mode works..
     */
    @Test
    public void testModeSwitch() {
        ProxyServer proxy = createTestServer(source, client);
        assertTrue(proxy.getMode().isDefault());

        for (String mode : Mode.modes()) {
            proxy.setMode(mode);
            assertEquals(mode, proxy.getMode().name());
        }

        // Try setting an invalid mode
        try {
            proxy.setMode("invalid");
            assert (false);
        } catch (IllegalArgumentException e) {
            assertEquals("Unrecognized mode 'invalid' supplied. Legal modes are '" + Mode.modes() + "'", e.getMessage());
        }

        // Also switch to DEFAULT mode, as that is not covered above
        proxy.setMode("default");
        assertTrue(proxy.getMode().isDefault());

        // Set mode to the same as the current mode
        proxy.setMode(proxy.getMode().name());
        assertTrue(proxy.getMode().isDefault());

        proxy.stop();
    }

    /**
     * Verifies that config is retrieved from the real server when it is not found in the cache,
     * that the cache is populated with the config and that the entry in the cache is used
     * when it is found there.
     */
    @Test
    public void testGetConfigAndCaching() {
        ConfigTester tester = new ConfigTester();
        MemoryCache memoryCache = proxy.memoryCache();
        assertEquals(0, memoryCache.size());
        RawConfig res = proxy.resolveConfig(tester.createRequest(fooConfig)).orElseThrow();
        assertEquals(ConfigTester.fooPayload.toString(), res.getPayload().toString());
        assertEquals(1, memoryCache.size());
        assertEquals(res, memoryCache.get(new ConfigCacheKey(fooConfig.getKey(), fooConfig.getDefMd5())).orElseThrow());

        // Trying same config again
        JRTServerConfigRequest newRequestBasedOnResponse = tester.createRequest(res);
        RawConfig res2 = proxy.resolveConfig(newRequestBasedOnResponse).orElseThrow();
        assertFalse(ProxyServer.configOrGenerationHasChanged(res2, newRequestBasedOnResponse));
        assertEquals(1, memoryCache.size());
    }

    /**
     * Verifies that error responses are not cached. When the config has been successfully retrieved,
     * it must be updated in cache.
     */
    @Test
    public void testNoCachingOfErrorRequests() {
        ConfigTester tester = new ConfigTester();
        // Simulate an error response
        source.put(fooConfig.getKey(), createConfigWithNextConfigGeneration(fooConfig, ErrorCode.INTERNAL_ERROR));

        MemoryCache memoryCache = proxy.memoryCache();
        assertEquals(0, memoryCache.size());

        RawConfig res = proxy.resolveConfig(tester.createRequest(fooConfig)).orElseThrow();
        assertNotNull(res.getPayload());
        assertTrue(res.isError());
        assertEquals(0, memoryCache.size());

        // Put a version of the same config into backend without error and see that it now works (i.e. we are
        // not getting a cached response (of the error in the previous request)
        source.put(fooConfig.getKey(), createConfigWithNextConfigGeneration(fooConfig, 0));

        // Verify that we get the config now and that it is cached
        res = proxy.resolveConfig(tester.createRequest(fooConfig)).orElseThrow();
        assertNotNull(res.getPayload().getData());
        assertEquals(ConfigTester.fooPayload.toString(), res.getPayload().toString());
        assertEquals(1, memoryCache.size());

        JRTServerConfigRequest newRequestBasedOnResponse = tester.createRequest(res);
        RawConfig res2 = proxy.resolveConfig(newRequestBasedOnResponse).orElseThrow();
        assertFalse(ProxyServer.configOrGenerationHasChanged(res2, newRequestBasedOnResponse));
        assertEquals(1, memoryCache.size());
    }

    /**
     * Verifies that config with generation 0 (used for empty sentinel config) is not cached.
     * If it was cached, it would be served even when newer config is available
     * (as they ask for config, saying that it now has config with generation 0).
     * When the config has been successfully retrieved it must be updated in cache.
     */
    @Test
    public void testNoCachingOfEmptyConfig() {
        ConfigTester tester = new ConfigTester();
        MemoryCache cache = proxy.memoryCache();

        assertEquals(0, cache.size());
        RawConfig res = proxy.resolveConfig(tester.createRequest(fooConfig)).orElseThrow();
        assertNotNull(res);
        assertEquals(ConfigTester.fooPayload.toString(), res.getPayload().toString());
        assertEquals(1, cache.size());

        // Simulate an empty response
        RawConfig emptyConfig = new RawConfig(fooConfig.getKey(), fooConfig.getDefMd5(), Payload.from("{}"),
                                              fooConfig.getPayloadChecksums(), 0, false,
                                              0, fooConfig.getDefContent(), Optional.empty());
        source.put(fooConfig.getKey(), emptyConfig);

        res = proxy.resolveConfig(tester.createRequest(fooConfig)).orElseThrow();
        assertEquals(emptyConfig.getPayload().toString(), res.getPayload().toString());
        assertEquals(0, cache.size());

        // Put a version of the same config into backend with new generation and see that it now works (i.e. we are
        // not getting a cached response (of the error in the previous request)
        source.put(fooConfig.getKey(), createConfigWithNextConfigGeneration(fooConfig, 0));

        // Verify that we get the config now and that it is cached
        res = proxy.resolveConfig(tester.createRequest(fooConfig)).orElseThrow();
        assertNotNull(res.getPayload().getData());
        assertEquals(ConfigTester.fooPayload.toString(), res.getPayload().toString());
        assertEquals(1, cache.size());
    }

    @Test
    public void testReconfiguration() {
        ConfigTester tester = new ConfigTester();
        RawConfig res = proxy.resolveConfig(tester.createRequest(fooConfig)).orElseThrow();
        assertEquals(ConfigTester.fooPayload.toString(), res.getPayload().toString());

        // Simulate deployment, add config with new config generation
        long previousGeneration = res.getGeneration();
        source.put(fooConfig.getKey(), createConfigWithNextConfigGeneration(res, 0));
        JRTServerConfigRequest newRequestBasedOnResponse = tester.createRequest(res);
        RawConfig res2 = proxy.resolveConfig(newRequestBasedOnResponse).orElseThrow();
        assertEquals(previousGeneration + 1, res2.getGeneration());
        assertTrue(ProxyServer.configOrGenerationHasChanged(res2, newRequestBasedOnResponse));
    }

    @Test
    public void testReadingSystemProperties() {
        ProxyServer.Properties properties = ProxyServer.getSystemProperties();
        assertEquals(1, properties.configSources.length);
        assertEquals(ProxyServer.DEFAULT_PROXY_CONFIG_SOURCES, properties.configSources[0]);
    }

    private static ProxyServer createTestServer(ConfigSourceSet source, ConfigSourceClient configSourceClient) {
        return new ProxyServer(null, source, configSourceClient);
    }

    static RawConfig createConfigWithNextConfigGeneration(RawConfig config, int errorCode) {
        return createConfigWithNextConfigGeneration(config, errorCode, config.getPayload());
    }

    private static RawConfig createConfigWithNextConfigGeneration(RawConfig config, int errorCode, Payload payload) {
        return createConfigWithNextConfigGeneration(config, errorCode, payload, config.getGeneration() + 1);
    }

    static RawConfig createConfigWithNextConfigGeneration(RawConfig config, int errorCode, Payload payload, long configGeneration) {
        return new RawConfig(config.getKey(), config.getDefMd5(),
                             payload, config.getPayloadChecksums(),
                             configGeneration, false,
                             errorCode, config.getDefContent(), Optional.empty());
    }

}