aboutsummaryrefslogtreecommitdiffstats
path: root/configserver/src/test/java
diff options
context:
space:
mode:
authorJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
committerJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
commit72231250ed81e10d66bfe70701e64fa5fe50f712 (patch)
tree2728bba1131a6f6e5bdf95afec7d7ff9358dac50 /configserver/src/test/java
Publish
Diffstat (limited to 'configserver/src/test/java')
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationMapperTest.java66
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationSetTest.java56
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/CompressedApplicationInputStreamTest.java172
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/ConfigResponseFactoryTest.java50
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/ConfigServerBootstrapTest.java90
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/ConfigServerDBTest.java41
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/DelayedConfigResponseTest.java82
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/DeployHandlerLoggerTest.java51
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/GetConfigProcessorTest.java119
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/HostRegistryTest.java93
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/InjectedGlobalComponentRegistryTest.java87
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/MemoryGenerationCounter.java21
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/MiscTestCase.java56
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/MockReloadHandler.java24
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/MockRequestHandler.java109
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/MockRpc.java107
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/MockTenantListener.java31
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/MockTenantProvider.java30
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/ModelContextImplTest.java65
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/ModelFactoryRegistryTest.java95
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/ModelStub.java58
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/PortRangeAllocator.java65
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/RpcServerTest.java125
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/ServerCacheTest.java80
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/SuperModelControllerTest.java131
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/SuperModelRequestHandlerTest.java203
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/TenantRequestHandlerTest.java300
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/TenantTest.java61
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/TenantsTestCase.java181
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/TestComponentRegistry.java134
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/TestConfigDefinitionRepo.java32
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/TestWithCurator.java29
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/TestWithRpc.java105
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/TestWithTenant.java27
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/TimeoutBudgetTest.java65
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/a-music-indexer-correct.cfg78
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/a-sports-indexer-correct.cfg48
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/app_stripped/components/testbundle.jarbin0 -> 696 bytes
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/app_stripped/services.xml18
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/application/ApplicationConvergenceCheckerTest.java210
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/application/ApplicationRepoTest.java191
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/application/ApplicationTest.java155
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/application/MemoryApplicationRepo.java58
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/application/PermanentApplicationPackageTest.java35
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/configchange/ConfigChangeActionsBuilder.java43
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/configchange/ConfigChangeActionsSlimeConverterTest.java135
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/configchange/MockConfigChangeAction.java32
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/configchange/MockRefeedAction.java35
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/configchange/MockRestartAction.java17
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/configchange/RefeedActionsFormatterTest.java46
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/configchange/RefeedActionsTest.java74
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/configchange/RestartActionsFormatterTest.java44
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/configchange/RestartActionsTest.java84
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/configchange/Utils.java24
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/configdefs/a.def60
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/configdefs/b.def4
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/configdefs/c.def4
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/configdefs/compositeinclude.def6
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/configdefs/d.def4
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/configdefs/e.def4
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/configdefs/recursiveinclude.def9
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/configdefs/spooler.def16
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/deploy/HostedDeployTest.java104
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/deploy/MockDeployer.java21
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/deploy/RedeployTest.java82
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClientTest.java202
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperDeployerTest.java63
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/filedistribution/FileDistributionLockTest.java110
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/ContentHandlerTestBase.java103
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/HandlerTest.java35
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpConfigRequestTest.java46
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpConfigResponseTest.java36
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpErrorResponseTest.java36
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpGetConfigHandlerTest.java100
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpHandlerTest.java51
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpListConfigsHandlerTest.java115
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionActiveHandlerTestBase.java266
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionContentHandlerTestBase.java126
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionCreateHandlerTestBase.java216
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionExampleHandlerTest.java101
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionHandlerTest.java164
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionPrepareHandlerTestBase.java185
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationContentHandlerTest.java111
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java289
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/HostHandlerTest.java114
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/HttpGetConfigHandlerTest.java140
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/HttpListConfigsHandlerTest.java145
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ListApplicationsHandlerTest.java111
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ListTenantsResponseTest.java30
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ListTenantsTest.java49
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java217
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionContentHandlerTest.java58
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandlerTest.java135
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java246
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TenantHandlerTest.java115
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TenantTest.java55
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TestTenantBuilder.java61
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/model/LbServicesProducerTest.java206
-rwxr-xr-xconfigserver/src/test/java/com/yahoo/vespa/config/server/model/RoutingProducerTest.java109
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/model/TestModelFactory.java37
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/modelconfigs/a.search-clustermusic-c0-r0-indexer4.cfg44
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/modelconfigs/a.search-clustersports-c0-r0-indexer4.cfg2
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/modelconfigs/a.vespamodel.cfg1
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/modelconfigs/c.search-clustersports-c0-r0-indexer4.cfg2
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/modelconfigs/compositeinclude.search-qrservers-0.cfg2
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/modelconfigs/recursiveinclude.search-clustermusic-c0-r0.cfg1
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/modelconfigs/spooler.clients-spooler-spooler.cfg13
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/provision/StaticProvisionerTest.java60
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/restapi/impl/StatusResourceTest.java46
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/sdconfigs/a.search-clustermusic.cfg5
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/sdconfigs/a.search-clustersports.cfg2
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/sdconfigs/b.search-clustersports.cfg1
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/sdconfigs/compositeinclude.search-clusterlogical.cfg8
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/sdconfigs/compositeinclude.search-clustervideo.cfg8
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/sdconfigs/compositeinclude.search-part-clusterlogical.cfg14
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/sdconfigs/compositeinclude.search-part-clustervideo.cfg14
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/sdconfigs/recursiveinclude.search-clustermusic-conf1.4.cfg7
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/sdconfigs/recursiveinclude.search-clustermusic-conf2.4.cfg7
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/sdconfigs/recursiveinclude.search-clustermusic.cfg2
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/session/DummyTransaction.java54
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionRepoTest.java127
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionTest.java180
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/session/MockFileDistributionFactory.java27
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/session/MockSessionZKClient.java67
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/session/PrepareParamsTest.java89
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/session/RemoteSessionRepoTest.java161
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/session/RemoteSessionTest.java284
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionFactoryTest.java79
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionPreparerTest.java286
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionRepoTest.java38
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionTest.java33
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClientTest.java118
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/userconfigs/a.cfg18
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/userconfigs/b.search#cluster.sports#c0#r0#indexer4.cfg1
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/userconfigs/c.cfg1
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/userconfigs/d.cfg1
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/userconfigs/spooler.cfg1
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/version/VersionStateTest.java60
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/zookeeper/ConfigCuratorTest.java239
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/zookeeper/InitializedCounterTest.java35
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/zookeeper/ZKApplicationFileTest.java37
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/zookeeper/ZKApplicationPackageTest.java77
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/serviceview/ServiceModelTest.java109
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/serviceview/StateResourceTest.java96
144 files changed, 11322 insertions, 0 deletions
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationMapperTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationMapperTest.java
new file mode 100644
index 00000000000..e5d98bda7b5
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationMapperTest.java
@@ -0,0 +1,66 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Optional;
+
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.Version;
+import com.yahoo.vespa.config.server.application.Application;
+import com.yahoo.vespa.config.server.http.NotFoundException;
+import com.yahoo.vespa.config.server.monitoring.MetricUpdater;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class ApplicationMapperTest {
+ ApplicationId appId;
+ ApplicationMapper applicationMapper;
+ ArrayList<Version> vespaVersions = new ArrayList<>();
+ ArrayList<Application> applications = new ArrayList<>();
+
+ @Before
+ public void setUp() {
+ applicationMapper = new ApplicationMapper();
+ appId = new ApplicationId.Builder()
+ .tenant("test").applicationName("test").instanceName("test").build();
+ vespaVersions.add(Version.fromString("1.2.3"));
+ vespaVersions.add(Version.fromString("1.2.4"));
+ vespaVersions.add(Version.fromString("1.2.5"));
+ applications.add(new Application(new ModelStub(), null, 0, vespaVersions.get(0), MetricUpdater.createTestUpdater(), ApplicationId.defaultId()));
+ applications.add(new Application(new ModelStub(), null, 0, vespaVersions.get(1), MetricUpdater.createTestUpdater(), ApplicationId.defaultId()));
+ applications.add(new Application(new ModelStub(), null, 0, vespaVersions.get(2), MetricUpdater.createTestUpdater(), ApplicationId.defaultId()));
+ }
+
+ @Test
+ public void testGetForVersionReturnsCorrectVersion() {
+ applicationMapper.register(appId, ApplicationSet.fromList(applications));
+ assertEquals(applicationMapper.getForVersion(appId, Optional.of(vespaVersions.get(0))), applications.get(0));
+ assertEquals(applicationMapper.getForVersion(appId, Optional.of(vespaVersions.get(1))), applications.get(1));
+ assertEquals(applicationMapper.getForVersion(appId, Optional.of(vespaVersions.get(2))), applications.get(2));
+ }
+
+ @Test
+ public void testGetForVersionReturnsLatestVersion() {
+ applicationMapper.register(appId, ApplicationSet.fromList(applications));
+ assertEquals(applicationMapper.getForVersion(appId, Optional.empty()), applications.get(2));
+ }
+
+ @Test (expected = VersionDoesNotExistException.class)
+ public void testGetForVersionThrows() {
+ applicationMapper.register(appId, ApplicationSet.fromList(Arrays.asList(applications.get(0), applications.get(2))));
+
+ applicationMapper.getForVersion(appId, Optional.of(vespaVersions.get(1)));
+ }
+
+ @Test (expected = NotFoundException.class)
+ public void testGetForVersionThrows2() {
+ applicationMapper.register(appId, ApplicationSet.fromSingle(applications.get(0)));
+
+ applicationMapper.getForVersion(new ApplicationId.Builder()
+ .tenant("different").applicationName("different").instanceName("different").build(),
+ Optional.of(vespaVersions.get(1)));
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationSetTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationSetTest.java
new file mode 100644
index 00000000000..fce6139e18e
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationSetTest.java
@@ -0,0 +1,56 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.Version;
+import com.yahoo.vespa.config.server.application.Application;
+import com.yahoo.vespa.config.server.monitoring.MetricUpdater;
+import org.junit.Before;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author Vegard Sjonfjell
+ */
+public class ApplicationSetTest {
+
+ ApplicationSet applicationSet;
+ List<Version> vespaVersions = new ArrayList<>();
+ List<Application> applications = new ArrayList<>();
+
+ @Before
+ public void setUp() {
+ vespaVersions.add(Version.fromString("1.2.3"));
+ vespaVersions.add(Version.fromString("1.2.4"));
+ vespaVersions.add(Version.fromString("1.2.5"));
+ applications.add(new Application(new ModelStub(), null, 0, vespaVersions.get(0), MetricUpdater.createTestUpdater(), ApplicationId.defaultId()));
+ applications.add(new Application(new ModelStub(), null, 0, vespaVersions.get(1), MetricUpdater.createTestUpdater(), ApplicationId.defaultId()));
+ applications.add(new Application(new ModelStub(), null, 0, vespaVersions.get(2), MetricUpdater.createTestUpdater(), ApplicationId.defaultId()));
+ }
+
+ @Test
+ public void testGetForVersionOrLatestReturnsCorrectVersion() {
+ applicationSet = ApplicationSet.fromList(applications);
+ assertEquals(applicationSet.getForVersionOrLatest(Optional.of(vespaVersions.get(0))), applications.get(0));
+ assertEquals(applicationSet.getForVersionOrLatest(Optional.of(vespaVersions.get(1))), applications.get(1));
+ assertEquals(applicationSet.getForVersionOrLatest(Optional.of(vespaVersions.get(2))), applications.get(2));
+ }
+
+ @Test
+ public void testGetForVersionOrLatestReturnsLatestVersion() {
+ applicationSet = ApplicationSet.fromList(applications);
+ assertEquals(applicationSet.getForVersionOrLatest(Optional.empty()), applications.get(2));
+ }
+
+ @Test (expected = VersionDoesNotExistException.class)
+ public void testGetForVersionOrLatestThrows() {
+ applicationSet = ApplicationSet.fromList(Arrays.asList(applications.get(0), applications.get(2)));
+ applicationSet.getForVersionOrLatest(Optional.of(vespaVersions.get(1)));
+ }
+
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/CompressedApplicationInputStreamTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/CompressedApplicationInputStreamTest.java
new file mode 100644
index 00000000000..5dd0f889431
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/CompressedApplicationInputStreamTest.java
@@ -0,0 +1,172 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server;
+
+import com.google.common.io.ByteStreams;
+import org.apache.commons.compress.archivers.ArchiveOutputStream;
+import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
+import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
+import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
+import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
+import org.junit.Test;
+
+import java.io.*;
+import java.util.Arrays;
+import java.util.List;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class CompressedApplicationInputStreamTest {
+
+ static void writeFileToTar(ArchiveOutputStream taos, File file) throws IOException {
+ taos.putArchiveEntry(taos.createArchiveEntry(file, file.getName()));
+ ByteStreams.copy(new FileInputStream(file), taos);
+ taos.closeArchiveEntry();
+ }
+
+ public static File createArchiveFile(ArchiveOutputStream taos, File outFile) throws IOException {
+ File app = new File("src/test/resources/deploy/validapp");
+ writeFileToTar(taos, new File(app, "services.xml"));
+ writeFileToTar(taos, new File(app, "hosts.xml"));
+ taos.close();
+ return outFile;
+ }
+
+ public static File createTarFile() throws IOException {
+ File outFile = File.createTempFile("testapp", ".tar.gz");
+ ArchiveOutputStream archiveOutputStream = new TarArchiveOutputStream(new GZIPOutputStream(new FileOutputStream(outFile)));
+ return createArchiveFile(archiveOutputStream, outFile);
+ }
+
+ public static File createZipFile() throws IOException {
+ File outFile = File.createTempFile("testapp", ".tar.gz");
+ ArchiveOutputStream archiveOutputStream = new ZipArchiveOutputStream(new FileOutputStream(outFile));
+ return createArchiveFile(archiveOutputStream, outFile);
+ }
+
+ void assertTestApp(File outApp) {
+ String [] files = outApp.list();
+ assertThat(files.length, is(2));
+ if ("hosts.xml".equals(files[0])) {
+ assertThat(files[1], is("services.xml"));
+ } else if ("hosts.xml".equals(files[1])) {
+ assertThat(files[0], is("services.xml"));
+ } else {
+ fail("Both services.xml and hosts.xml should be contained in the unpacked application");
+ }
+ }
+
+ @Test
+ public void require_that_valid_tar_application_can_be_unpacked() throws IOException {
+ File outFile = createTarFile();
+ CompressedApplicationInputStream unpacked = CompressedApplicationInputStream.createFromCompressedStream(new TarArchiveInputStream(new GZIPInputStream(new FileInputStream(outFile))));
+ File outApp = unpacked.decompress();
+ assertTestApp(outApp);
+ }
+
+ @Test
+ public void require_that_valid_tar_application_in_subdir_can_be_unpacked() throws IOException {
+ File outFile = File.createTempFile("testapp", ".tar.gz");
+ ArchiveOutputStream archiveOutputStream = new TarArchiveOutputStream(new GZIPOutputStream(new FileOutputStream(outFile)));
+
+ File app = new File("src/test/resources/deploy/validapp");
+
+ File file = new File(app, "services.xml");
+ archiveOutputStream.putArchiveEntry(archiveOutputStream.createArchiveEntry(file, "application/" + file.getName()));
+ ByteStreams.copy(new FileInputStream(file), archiveOutputStream);
+ archiveOutputStream.closeArchiveEntry();
+ file = new File(app, "hosts.xml");
+ archiveOutputStream.putArchiveEntry(archiveOutputStream.createArchiveEntry(file, "application/" + file.getName()));
+ ByteStreams.copy(new FileInputStream(file), archiveOutputStream);
+ archiveOutputStream.closeArchiveEntry();
+
+ archiveOutputStream.close();
+
+ CompressedApplicationInputStream unpacked = CompressedApplicationInputStream.createFromCompressedStream(new TarArchiveInputStream(new GZIPInputStream(new FileInputStream(outFile))));
+ File outApp = unpacked.decompress();
+ assertThat(outApp.getName(), is("application")); // gets the name of the subdir
+ assertTestApp(outApp);
+ }
+
+ @Test
+ public void require_that_valid_zip_application_can_be_unpacked() throws IOException {
+ File outFile = createZipFile();
+ CompressedApplicationInputStream unpacked = CompressedApplicationInputStream.createFromCompressedStream(
+ new ZipArchiveInputStream(new FileInputStream(outFile)));
+ File outApp = unpacked.decompress();
+ assertTestApp(outApp);
+ }
+
+ @Test
+ public void require_that_gnu_tared_file_can_be_unpacked() throws IOException, InterruptedException {
+ File tmpTar = File.createTempFile("myapp", ".tar");
+ Process p = new ProcessBuilder("tar", "-C", "src/test/resources/deploy/validapp", "--exclude=.svn", "-cvf", tmpTar.getAbsolutePath(), ".").start();
+ p.waitFor();
+ p = new ProcessBuilder("gzip", tmpTar.getAbsolutePath()).start();
+ p.waitFor();
+ File gzFile = new File(tmpTar.getAbsolutePath() + ".gz");
+ assertTrue(gzFile.exists());
+ CompressedApplicationInputStream unpacked = CompressedApplicationInputStream.createFromCompressedStream(
+ new TarArchiveInputStream(new GZIPInputStream(new FileInputStream(gzFile))));
+ File outApp = unpacked.decompress();
+ assertTestApp(outApp);
+ }
+
+ @Test
+ public void require_that_nested_app_can_be_unpacked() throws IOException, InterruptedException {
+ File tmpTar = File.createTempFile("myapp", ".tar");
+ Process p = new ProcessBuilder("tar", "-C", "src/test/resources/deploy/advancedapp", "--exclude=.svn", "-cvf", tmpTar.getAbsolutePath(), ".").start();
+ p.waitFor();
+ p = new ProcessBuilder("gzip", tmpTar.getAbsolutePath()).start();
+ p.waitFor();
+ File gzFile = new File(tmpTar.getAbsolutePath() + ".gz");
+ assertTrue(gzFile.exists());
+ CompressedApplicationInputStream unpacked = CompressedApplicationInputStream.createFromCompressedStream(
+ new TarArchiveInputStream(new GZIPInputStream(new FileInputStream(gzFile))));
+ File outApp = unpacked.decompress();
+ List<File> files = Arrays.asList(outApp.listFiles());
+ assertThat(files.size(), is(4));
+ assertTrue(files.contains(new File(outApp, "services.xml")));
+ assertTrue(files.contains(new File(outApp, "hosts.xml")));
+ assertTrue(files.contains(new File(outApp, "searchdefinitions")));
+ assertTrue(files.contains(new File(outApp, "external")));
+ File sd = files.get(files.indexOf(new File(outApp, "searchdefinitions")));
+ assertTrue(sd.isDirectory());
+ assertThat(sd.listFiles().length, is(1));
+ assertThat(sd.listFiles()[0].getAbsolutePath(), is(new File(sd, "keyvalue.sd").getAbsolutePath()));
+
+ File ext = files.get(files.indexOf(new File(outApp, "external")));
+ assertTrue(ext.isDirectory());
+ assertThat(ext.listFiles().length, is(1));
+ assertThat(ext.listFiles()[0].getAbsolutePath(), is(new File(ext, "foo").getAbsolutePath()));
+
+ files = Arrays.asList(ext.listFiles());
+ File foo = files.get(files.indexOf(new File(ext, "foo")));
+ assertTrue(foo.isDirectory());
+ assertThat(foo.listFiles().length, is(1));
+ assertThat(foo.listFiles()[0].getAbsolutePath(), is(new File(foo, "bar").getAbsolutePath()));
+
+ files = Arrays.asList(foo.listFiles());
+ File bar = files.get(files.indexOf(new File(foo, "bar")));
+ assertTrue(bar.isDirectory());
+ assertThat(bar.listFiles().length, is(1));
+ assertTrue(bar.listFiles()[0].isFile());
+ assertThat(bar.listFiles()[0].getAbsolutePath(), is(new File(bar, "lol").getAbsolutePath()));
+ }
+
+
+ @Test(expected = IOException.class)
+ public void require_that_invalid_application_returns_error_when_unpacked() throws IOException {
+ File app = new File("src/test/resources/deploy/validapp/services.xml");
+ CompressedApplicationInputStream.createFromCompressedStream(
+ new TarArchiveInputStream(new GZIPInputStream(new FileInputStream(app))));
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/ConfigResponseFactoryTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/ConfigResponseFactoryTest.java
new file mode 100644
index 00000000000..f127d8716aa
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/ConfigResponseFactoryTest.java
@@ -0,0 +1,50 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server;
+
+import com.yahoo.config.SimpletypesConfig;
+import com.yahoo.config.codegen.DefParser;
+import com.yahoo.config.codegen.InnerCNode;
+import com.yahoo.text.StringUtilities;
+import com.yahoo.vespa.config.ConfigPayload;
+import com.yahoo.vespa.config.protocol.CompressionType;
+import com.yahoo.vespa.config.protocol.ConfigResponse;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.StringReader;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author lulf
+ * @since 5.19
+ */
+public class ConfigResponseFactoryTest {
+ private InnerCNode def;
+
+
+ @Before
+ public void setup() {
+ DefParser dParser = new DefParser(SimpletypesConfig.getDefName(), new StringReader(StringUtilities.implode(SimpletypesConfig.CONFIG_DEF_SCHEMA, "\n")));
+ def = dParser.getTree();
+ }
+
+ @Test
+ public void testUncompressedFacory() {
+ UncompressedConfigResponseFactory responseFactory = new UncompressedConfigResponseFactory();
+ ConfigResponse response = responseFactory.createResponse(ConfigPayload.empty(), def, 3);
+ assertThat(response.getCompressionInfo().getCompressionType(), is(CompressionType.UNCOMPRESSED));
+ assertThat(response.getGeneration(), is(3l));
+ assertThat(response.getPayload().getByteLength(), is(2));
+ }
+
+ @Test
+ public void testLZ4CompressedFacory() {
+ LZ4ConfigResponseFactory responseFactory = new LZ4ConfigResponseFactory();
+ ConfigResponse response = responseFactory.createResponse(ConfigPayload.empty(), def, 3);
+ assertThat(response.getCompressionInfo().getCompressionType(), is(CompressionType.LZ4));
+ assertThat(response.getGeneration(), is(3l));
+ assertThat(response.getPayload().getByteLength(), is(3));
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/ConfigServerBootstrapTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/ConfigServerBootstrapTest.java
new file mode 100644
index 00000000000..6f5b51e7914
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/ConfigServerBootstrapTest.java
@@ -0,0 +1,90 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server;
+
+import com.yahoo.cloud.config.ConfigserverConfig;
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.io.IOUtils;
+import com.yahoo.vespa.config.server.monitoring.Metrics;
+import com.yahoo.vespa.config.server.version.VersionState;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.io.FileReader;
+import java.util.ArrayList;
+import java.util.Optional;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class ConfigServerBootstrapTest extends TestWithTenant {
+ @Rule
+ public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+ @Test
+ public void testConfigServerBootstrap() throws Exception {
+ File versionFile = temporaryFolder.newFile();
+ ConfigserverConfig.Builder config = new ConfigserverConfig.Builder();
+ MockTenantRequestHandler myServer = new MockTenantRequestHandler(Metrics.createTestMetrics());
+ MockRpc rpc = new MockRpc(new ConfigserverConfig(config).rpcport());
+
+ assertFalse(myServer.started);
+ assertFalse(myServer.stopped);
+ VersionState versionState = new VersionState(versionFile);
+ assertTrue(versionState.isUpgraded());
+ ConfigServerBootstrap bootstrap = new ConfigServerBootstrap(tenants, rpc, (application, timeout) -> Optional.empty(), versionState);
+ assertFalse(versionState.isUpgraded());
+ assertThat(versionState.currentVersion(), is(versionState.storedVersion()));
+ assertThat(IOUtils.readAll(new FileReader(versionFile)), is(versionState.currentVersion().toSerializedForm()));
+ waitUntilStarted(rpc, 60000);
+ assertTrue(rpc.started);
+ assertFalse(rpc.stopped);
+ bootstrap.deconstruct();
+ assertTrue(rpc.started);
+ assertTrue(rpc.stopped);
+ }
+
+ private void waitUntilStarted(MockRpc server, long timeout) throws InterruptedException {
+ long start = System.currentTimeMillis();
+ while ((System.currentTimeMillis() - start) < timeout) {
+ if (server.started)
+ return;
+ Thread.sleep(10);
+ }
+ }
+
+ public static class MockTenantRequestHandler extends TenantRequestHandler {
+ public volatile boolean started = false;
+ public volatile boolean stopped = false;
+
+ public MockTenantRequestHandler(Metrics statistics) {
+ super(statistics, TenantName.from("testTenant"), new ArrayList<>(), new UncompressedConfigResponseFactory(), new HostRegistries());
+ }
+ }
+
+ public static class MockRpc extends com.yahoo.vespa.config.server.MockRpc {
+ public volatile boolean started = false;
+ public volatile boolean stopped = false;
+
+ public MockRpc(int port) {
+ super(port);
+ }
+
+ @Override
+ public void run() {
+ started = true;
+ }
+
+ @Override
+ public void stop() {
+ stopped = true;
+ }
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/ConfigServerDBTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/ConfigServerDBTest.java
new file mode 100644
index 00000000000..0b07fd5b8e7
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/ConfigServerDBTest.java
@@ -0,0 +1,41 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server;
+
+import com.google.common.io.Files;
+import com.yahoo.io.IOUtils;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class ConfigServerDBTest {
+ private ConfigServerDB serverDB;
+ private File tempDir;
+
+ @Before
+ public void setup() {
+ tempDir = Files.createTempDir();
+ serverDB = ConfigServerDB.createTestConfigServerDb(tempDir.getAbsolutePath());
+ }
+
+ private ConfigServerDB createInitializer(File pluginDir) throws IOException {
+ File existingDef = new File(serverDB.classes(), "test.def");
+ IOUtils.writeFile(existingDef, "hello", false);
+ return ConfigServerDB.createTestConfigServerDb(tempDir.getAbsolutePath());
+ }
+
+ @Test
+ public void require_that_existing_def_files_are_copied() throws IOException {
+ assertThat(serverDB.serverdefs().listFiles().length, is(0));
+ createInitializer(Files.createTempDir());
+ assertThat(serverDB.serverdefs().listFiles().length, is(1));
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/DelayedConfigResponseTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/DelayedConfigResponseTest.java
new file mode 100644
index 00000000000..7f9ac50c549
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/DelayedConfigResponseTest.java
@@ -0,0 +1,82 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server;
+
+import com.yahoo.config.provision.ApplicationId;
+
+import com.yahoo.jrt.Request;
+import com.yahoo.vespa.config.ConfigKey;
+import com.yahoo.vespa.config.protocol.CompressionType;
+import com.yahoo.vespa.config.protocol.DefContent;
+import com.yahoo.vespa.config.protocol.JRTClientConfigRequestV3;
+import com.yahoo.vespa.config.protocol.JRTServerConfigRequest;
+import com.yahoo.vespa.config.protocol.JRTServerConfigRequestV3;
+import com.yahoo.vespa.config.protocol.Trace;
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class DelayedConfigResponseTest {
+
+ @Test
+ public void testDelayedConfigResponses() {
+
+ MockRpc rpc = new MockRpc(13337);
+ DelayedConfigResponses responses = new DelayedConfigResponses(rpc, 1, false);
+ assertThat(responses.size(), is(0));
+ JRTServerConfigRequest req = createRequest("foo", "md5", "myid", "mymd5", 3, 1000000, "bar");
+ req.setDelayedResponse(true);
+ GetConfigContext context = GetConfigContext.testContext(ApplicationId.defaultId());
+ responses.delayResponse(req, context);
+ assertThat(responses.size(), is(0));
+
+ req.setDelayedResponse(false);
+ responses.delayResponse(req, context);
+ responses.delayResponse(createRequest("foolio", "md5", "myid", "mymd5", 3, 100000, "bar"), context);
+ assertThat(responses.size(), is(2));
+ assertTrue(req.isDelayedResponse());
+ List<DelayedConfigResponses.DelayedConfigResponse> it = responses.allDelayedResponses();
+ assertTrue(!it.isEmpty());
+ }
+
+ @Test
+ public void testDelayResponseRemove() {
+ GetConfigContext context = GetConfigContext.testContext(ApplicationId.defaultId());
+ MockRpc rpc = new MockRpc(13337);
+ DelayedConfigResponses responses = new DelayedConfigResponses(rpc, 1, false);
+ responses.delayResponse(createRequest("foolio", "md5", "myid", "mymd5", 3, 100000, "bar"), context);
+ assertThat(responses.size(), is(1));
+ responses.allDelayedResponses().get(0).cancel();
+ assertThat(responses.size(), is(0));
+ }
+
+ @Test
+ public void testDelayedConfigResponse() {
+ MockRpc rpc = new MockRpc(13337);
+ DelayedConfigResponses responses = new DelayedConfigResponses(rpc, 1, false);
+ assertThat(responses.size(), is(0));
+ assertThat(responses.toString(), is("DelayedConfigResponses. Average Size=0"));
+ JRTServerConfigRequest req = createRequest("foo", "md5", "myid", "mymd5", 3, 100, "bar");
+ responses.delayResponse(req, GetConfigContext.testContext(ApplicationId.defaultId()));
+ rpc.waitUntilSet(5000);
+ assertThat(rpc.latestRequest, is(req));
+ }
+
+ public JRTServerConfigRequest createRequest(String configName, String defMd5, String configId, String md5, long generation, long timeout, String namespace) {
+ Request request = JRTClientConfigRequestV3.
+ createWithParams(new ConfigKey<>(configName, configId, namespace, defMd5, null), DefContent.fromList(Collections.emptyList()),
+ "fromHost", md5, generation, timeout, Trace.createDummy(), CompressionType.UNCOMPRESSED,
+ Optional.empty()).getRequest();
+ return JRTServerConfigRequestV3.createFromRequest(request);
+ }
+
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/DeployHandlerLoggerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/DeployHandlerLoggerTest.java
new file mode 100644
index 00000000000..b34881bcba8
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/DeployHandlerLoggerTest.java
@@ -0,0 +1,51 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.log.LogLevel;
+import com.yahoo.slime.Cursor;
+import com.yahoo.slime.JsonFormat;
+import com.yahoo.slime.Slime;
+
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.regex.Pattern;
+
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class DeployHandlerLoggerTest {
+ @Test
+ public void test_verbose_logging() throws IOException {
+ testLogging(true, ".*time.*level\":\"DEBUG\".*message.*time.*level\":\"SPAM\".*message.*time.*level\":\"FINE\".*message.*time.*level\":\"WARNING\".*message.*");
+ }
+
+ @Test
+ public void test_normal_logging() throws IOException {
+ testLogging(false, ".*\\{\"time.*level\":\"WARNING\".*message.*");
+ }
+
+ private void testLogging(boolean verbose, String expectedPattern) throws IOException {
+ Slime slime = new Slime();
+ Cursor array = slime.setArray();
+ DeployLogger logger = new DeployHandlerLogger(array, verbose, new ApplicationId.Builder()
+ .tenant("testtenant").applicationName("testapp").build());
+ logMessages(logger);
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ new JsonFormat(true).encode(baos, slime);
+ assertTrue(Pattern.matches(expectedPattern, baos.toString()));
+ }
+
+ private void logMessages(DeployLogger logger) {
+ logger.log(LogLevel.DEBUG, "foobar");
+ logger.log(LogLevel.SPAM, "foobar");
+ logger.log(LogLevel.FINE, "baz");
+ logger.log(LogLevel.WARNING, "baz");
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/GetConfigProcessorTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/GetConfigProcessorTest.java
new file mode 100644
index 00000000000..cd37b4a31c7
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/GetConfigProcessorTest.java
@@ -0,0 +1,119 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server;
+
+import com.yahoo.cloud.config.SentinelConfig;
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.text.Utf8;
+import com.yahoo.text.Utf8Array;
+import com.yahoo.text.Utf8String;
+import com.yahoo.vespa.config.ConfigKey;
+
+import com.yahoo.vespa.config.protocol.CompressionInfo;
+import com.yahoo.vespa.config.protocol.CompressionType;
+import com.yahoo.vespa.config.protocol.ConfigResponse;
+import com.yahoo.vespa.config.protocol.DefContent;
+import com.yahoo.vespa.config.protocol.JRTClientConfigRequestV3;
+import com.yahoo.vespa.config.protocol.JRTServerConfigRequest;
+import com.yahoo.vespa.config.protocol.JRTServerConfigRequestV3;
+import com.yahoo.vespa.config.protocol.Trace;
+import org.junit.Test;
+import static org.junit.Assert.assertFalse;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class GetConfigProcessorTest {
+
+ @Test
+ public void testSentinelConfig() {
+ MockRpc rpc = new MockRpc(13337, false);
+ rpc.response = new MockConfigResponse("foo"); // should be a sentinel config, but it does not matter for this test
+
+ // one tenant, which has host1 assigned
+ boolean pretentToHaveLoadedApplications = true;
+ TenantName testTenant = TenantName.from("test");
+ rpc.onTenantCreate(testTenant, new MockTenantProvider(pretentToHaveLoadedApplications));
+ rpc.hostsUpdated(testTenant, Collections.singleton("host1"));
+
+ { // a config is returned normally
+ JRTServerConfigRequest req = createV3SentinelRequest("host1");
+ GetConfigProcessor proc = new GetConfigProcessor(rpc, req, false);
+ proc.run();
+ assertTrue(rpc.tryResolveConfig);
+ assertTrue(rpc.tryRespond);
+ assertThat(rpc.errorCode, is(0));
+ }
+
+ rpc.resetChecks();
+ // host1 is replaced by host2 for this tenant
+ rpc.hostsUpdated(testTenant, Collections.singleton("host2"));
+
+ { // this causes us to get an empty config instead of normal config resolution
+ JRTServerConfigRequest req = createV3SentinelRequest("host1");
+ GetConfigProcessor proc = new GetConfigProcessor(rpc, req, false);
+ proc.run();
+ assertFalse(rpc.tryResolveConfig); // <-- no normal config resolution happening
+ assertTrue(rpc.tryRespond);
+ assertThat(rpc.errorCode, is(0));
+ }
+ }
+
+ private static JRTServerConfigRequest createV3SentinelRequest(String fromHost) {
+ final ConfigKey<?> configKey = new ConfigKey<>(SentinelConfig.CONFIG_DEF_NAME, "myid", SentinelConfig.CONFIG_DEF_NAMESPACE);
+ return JRTServerConfigRequestV3.createFromRequest(JRTClientConfigRequestV3.
+ createWithParams(configKey, DefContent.fromList(Arrays.asList(SentinelConfig.CONFIG_DEF_SCHEMA)),
+ fromHost, "", 0, 100, Trace.createDummy(), CompressionType.UNCOMPRESSED,
+ Optional.empty()).getRequest());
+ }
+
+ private class MockConfigResponse implements ConfigResponse {
+
+ private final String line;
+ public MockConfigResponse(String line) {
+ this.line = line;
+ }
+
+ @Override
+ public Utf8Array getPayload() {
+ return new Utf8String("");
+ }
+
+ @Override
+ public List<String> getLegacyPayload() {
+ return Arrays.asList(line);
+ }
+
+ @Override
+ public long getGeneration() {
+ return 1;
+ }
+
+ @Override
+ public String getConfigMd5() {
+ return "mymd5";
+ }
+
+ @Override
+ public void serialize(OutputStream os, CompressionType uncompressed) throws IOException {
+ os.write(Utf8.toBytes(line));
+ }
+
+ @Override
+ public CompressionInfo getCompressionInfo() {
+ return CompressionInfo.uncompressed();
+ }
+ }
+
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/HostRegistryTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/HostRegistryTest.java
new file mode 100644
index 00000000000..1147d2d4c0d
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/HostRegistryTest.java
@@ -0,0 +1,93 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import org.junit.Test;
+
+import static org.hamcrest.collection.IsIterableContainingInOrder.contains;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.*;
+
+/**
+ * @author lulf
+ * @since 5.3
+ */
+public class HostRegistryTest {
+ @Test
+ public void old_hosts_are_removed() {
+ HostRegistry<String> reg = new HostRegistry<>();
+ assertNull(reg.getKeyForHost("foo.com"));
+ reg.update("fookey", Arrays.asList("foo.com", "bar.com", "baz.com"));
+ assertGetKey(reg, "foo.com", "fookey");
+ assertGetKey(reg, "bar.com", "fookey");
+ assertGetKey(reg, "baz.com", "fookey");
+ assertThat(reg.getAllHosts().size(), is(3));
+ reg.update("fookey", Arrays.asList("bar.com", "baz.com"));
+ assertNull(reg.getKeyForHost("foo.com"));
+ assertGetKey(reg, "bar.com", "fookey");
+ assertGetKey(reg, "baz.com", "fookey");
+
+ assertThat(reg.getAllHosts().size(), is(2));
+ assertThat(reg.getAllHosts(), contains("bar.com", "baz.com"));
+ reg.removeHostsForKey("fookey");
+ assertThat(reg.getAllHosts().size(), is(0));
+ assertNull(reg.getKeyForHost("foo.com"));
+ assertNull(reg.getKeyForHost("bar.com"));
+ }
+
+ @Test
+ public void multiple_keys_are_handled() {
+ HostRegistry<String> reg = new HostRegistry<>();
+ reg.update("fookey", Arrays.asList("foo.com", "bar.com"));
+ reg.update("barkey", Arrays.asList("baz.com", "quux.com"));
+ assertGetKey(reg, "foo.com", "fookey");
+ assertGetKey(reg, "bar.com", "fookey");
+ assertGetKey(reg, "baz.com", "barkey");
+ assertGetKey(reg, "quux.com", "barkey");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void keys_cannot_overlap() {
+ HostRegistry<String> reg = new HostRegistry<>();
+ reg.update("fookey", Arrays.asList("foo.com", "bar.com"));
+ reg.update("barkey", Arrays.asList("bar.com", "baz.com"));
+ }
+
+ @Test
+ public void all_hosts_are_returned() {
+ HostRegistry<String> reg = new HostRegistry<>();
+ reg.update("fookey", Arrays.asList("foo.com", "bar.com"));
+ reg.update("barkey", Arrays.asList("baz.com", "quux.com"));
+ assertThat(reg.getAllHosts().size(), is(4));
+ }
+
+ @Test
+ public void ensure_that_collection_is_copied() {
+ HostRegistry<String> reg = new HostRegistry<>();
+ List<String> hosts = new ArrayList<>(Arrays.asList("foo.com", "bar.com", "baz.com"));
+ reg.update("fookey", hosts);
+ assertThat(reg.getCurrentHosts("fookey").size(), is(3));
+ hosts.remove(2);
+ assertThat(reg.getCurrentHosts("fookey").size(), is(3));
+ }
+
+ @Test
+ public void ensure_that_underlying_hosts_do_not_change() {
+ HostRegistry<String> reg = new HostRegistry<>();
+ reg.update("fookey", new ArrayList<>(Arrays.asList("foo.com", "bar.com", "baz.com")));
+ Collection<String> hosts = reg.getAllHosts();
+ assertThat(hosts.size(), is(3));
+ reg.update("fookey", new ArrayList<>(Arrays.asList("foo.com")));
+ assertThat(hosts.size(), is(3));
+ }
+
+ private void assertGetKey(HostRegistry<String> reg, String host, String expectedKey) {
+ assertNotNull(reg.getKeyForHost(host));
+ assertThat(reg.getKeyForHost(host), is(expectedKey));
+ }
+
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/InjectedGlobalComponentRegistryTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/InjectedGlobalComponentRegistryTest.java
new file mode 100644
index 00000000000..09576d18b32
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/InjectedGlobalComponentRegistryTest.java
@@ -0,0 +1,87 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server;
+
+import com.google.common.io.Files;
+import com.yahoo.cloud.config.ConfigserverConfig;
+import com.yahoo.config.model.NullConfigModelRegistry;
+import com.yahoo.config.model.api.ConfigDefinitionRepo;
+import com.yahoo.config.provision.Zone;
+import com.yahoo.vespa.config.server.application.PermanentApplicationPackage;
+import com.yahoo.vespa.config.server.http.v2.SessionActiveHandlerTest;
+import com.yahoo.vespa.config.server.modelfactory.ModelFactoryRegistry;
+import com.yahoo.vespa.config.server.monitoring.Metrics;
+import com.yahoo.vespa.config.server.provision.HostProvisionerProvider;
+import com.yahoo.vespa.config.server.session.*;
+import com.yahoo.vespa.curator.mock.MockCurator;
+import com.yahoo.vespa.config.server.zookeeper.ConfigCurator;
+import com.yahoo.vespa.curator.Curator;
+import com.yahoo.vespa.model.VespaModelFactory;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Collections;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class InjectedGlobalComponentRegistryTest {
+
+ private Curator curator;
+ private ConfigCurator configCurator;
+ private Metrics metrics;
+ private ConfigServerDB serverDB;
+ private SessionPreparer sessionPreparer;
+ private ConfigserverConfig configserverConfig;
+ private RpcServer rpcServer;
+ private SuperModelGenerationCounter generationCounter;
+ private ConfigDefinitionRepo defRepo;
+ private PermanentApplicationPackage permanentApplicationPackage;
+ private HostRegistries hostRegistries;
+ private GlobalComponentRegistry globalComponentRegistry;
+ private ModelFactoryRegistry modelFactoryRegistry;
+ private HostProvisionerProvider hostProvisionerProvider;
+ private Zone zone;
+
+ @Before
+ public void setupRegistry() {
+ curator = new MockCurator();
+ configCurator = ConfigCurator.create(curator);
+ metrics = Metrics.createTestMetrics();
+ modelFactoryRegistry = new ModelFactoryRegistry(Collections.singletonList(new VespaModelFactory(new NullConfigModelRegistry())));
+ configserverConfig = new ConfigserverConfig(new ConfigserverConfig.Builder().configServerDBDir(Files.createTempDir().getAbsolutePath()));
+ serverDB = new ConfigServerDB(configserverConfig);
+ sessionPreparer = new SessionTest.MockSessionPreparer();
+ rpcServer = new RpcServer(configserverConfig, null, Metrics.createTestMetrics(), new HostRegistries());
+ generationCounter = new SuperModelGenerationCounter(curator);
+ defRepo = new StaticConfigDefinitionRepo();
+ permanentApplicationPackage = new PermanentApplicationPackage(configserverConfig);
+ hostRegistries = new HostRegistries();
+ hostProvisionerProvider = HostProvisionerProvider.withProvisioner(new SessionActiveHandlerTest.MockProvisioner());
+ zone = Zone.defaultZone();
+ globalComponentRegistry = new InjectedGlobalComponentRegistry(curator, configCurator, metrics, modelFactoryRegistry, serverDB, sessionPreparer, rpcServer, configserverConfig, generationCounter, defRepo, permanentApplicationPackage, hostRegistries, hostProvisionerProvider, zone);
+ }
+
+ @Test
+ public void testThatAllComponentsAreSetup() {
+ assertThat(globalComponentRegistry.getModelFactoryRegistry(), is(modelFactoryRegistry));
+ assertThat(globalComponentRegistry.getServerDB(), is(serverDB));
+ assertThat(globalComponentRegistry.getSessionPreparer(), is(sessionPreparer));
+ assertThat(globalComponentRegistry.getMetrics(), is(metrics));
+ assertThat(globalComponentRegistry.getCurator(), is(curator));
+ assertThat(globalComponentRegistry.getConfigserverConfig(), is(configserverConfig));
+ assertThat(globalComponentRegistry.getReloadListener().hashCode(), is(rpcServer.hashCode()));
+ assertThat(globalComponentRegistry.getTenantListener().hashCode(), is(rpcServer.hashCode()));
+ assertThat(globalComponentRegistry.getSuperModelGenerationCounter(), is(generationCounter));
+ assertThat(globalComponentRegistry.getConfigDefinitionRepo(), is(defRepo));
+ assertThat(globalComponentRegistry.getPermanentApplicationPackage(), is(permanentApplicationPackage));
+ assertThat(globalComponentRegistry.getHostRegistries(), is(hostRegistries));
+ assertThat(globalComponentRegistry.getZone(), is (zone));
+ assertTrue(globalComponentRegistry.getHostProvisioner().isPresent());
+ }
+
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/MemoryGenerationCounter.java b/configserver/src/test/java/com/yahoo/vespa/config/server/MemoryGenerationCounter.java
new file mode 100644
index 00000000000..461da73638c
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/MemoryGenerationCounter.java
@@ -0,0 +1,21 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server;
+
+import com.yahoo.vespa.config.GenerationCounter;
+
+/**
+ * @author lulf
+ * @since 5.
+ */
+public class MemoryGenerationCounter implements GenerationCounter {
+ long value;
+ @Override
+ public long increment() {
+ return ++value;
+ }
+
+ @Override
+ public long get() {
+ return value;
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/MiscTestCase.java b/configserver/src/test/java/com/yahoo/vespa/config/server/MiscTestCase.java
new file mode 100644
index 00000000000..1ba660dc5d1
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/MiscTestCase.java
@@ -0,0 +1,56 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server;
+
+import static org.junit.Assert.*;
+import java.io.*;
+import java.util.List;
+import java.util.ArrayList;
+import org.junit.Test;
+import com.yahoo.vespa.config.util.ConfigUtils;
+import com.yahoo.config.AppConfig;
+import com.yahoo.config.Md5testConfig;
+
+/**
+ * Tests that does not yet have a specific home due to removed classes, obsolete features etc.
+ *
+ * @author vegardh
+ */
+public class MiscTestCase {
+
+ /**
+ * Verifies that the md5 sum computed on the server is equal to that in the generated class.
+ *
+ * @throws java.io.IOException if an error in zk
+ */
+ @Test
+ public void testGetDefMd5() throws IOException {
+ System.out.println("\nStarting testGetDefMd5");
+ final String defDir = "src/test/resources/configdefinitions/";
+ assertEquals(AppConfig.CONFIG_DEF_MD5, ConfigUtils.getDefMd5(file2lines(new File(defDir + "app.def"))));
+ assertEquals(Md5testConfig.CONFIG_DEF_MD5, ConfigUtils.getDefMd5(file2lines(new File(defDir + "md5test.def"))));
+ }
+
+ private static List<String> file2lines(File file) throws IOException {
+ List<String> lines = new ArrayList<>();
+ LineNumberReader in = new LineNumberReader(new InputStreamReader(new FileInputStream(file), "UTF-8"));
+ String line;
+ while ((line = in.readLine()) != null) {
+ lines.add(line);
+ }
+ return lines;
+ }
+
+ @Test
+ public void testMd5StripSpaces() {
+ assertEquals("", ConfigUtils.stripSpaces(""));
+ assertEquals("foo", ConfigUtils.stripSpaces("foo"));
+ assertEquals(" foo", ConfigUtils.stripSpaces(" foo"));
+ assertEquals("bar ", ConfigUtils.stripSpaces("bar "));
+ assertEquals("bar ", ConfigUtils.stripSpaces("bar "));
+ assertEquals("b ar", ConfigUtils.stripSpaces("b \t ar"));
+ assertEquals("bar foo", ConfigUtils.stripSpaces("bar\t\tfoo"));
+ assertEquals("blabla string default=\"\t\"", ConfigUtils.stripSpaces("blabla string default=\"\t\""));
+ assertEquals("blabla string default=\"foo\tbar\"", ConfigUtils.stripSpaces("blabla string default=\"foo\tbar\""));
+ assertEquals("blabla string default=\" \t \"", ConfigUtils.stripSpaces("blabla string default=\" \t \""));
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/MockReloadHandler.java b/configserver/src/test/java/com/yahoo/vespa/config/server/MockReloadHandler.java
new file mode 100644
index 00000000000..d705203b5af
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/MockReloadHandler.java
@@ -0,0 +1,24 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server;
+
+import com.yahoo.config.provision.ApplicationId;
+
+/**
+ * @author lulf
+ * @since 5.1.24
+ */
+public class MockReloadHandler implements ReloadHandler {
+ public ApplicationSet current = null;
+ public ReloadListener listener = null;
+ public volatile ApplicationId lastRemoved = null;
+
+ @Override
+ public void reloadConfig(ApplicationSet application) {
+ this.current = application;
+ }
+
+ @Override
+ public void removeApplication(ApplicationId applicationId) {
+ lastRemoved = applicationId;
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/MockRequestHandler.java b/configserver/src/test/java/com/yahoo/vespa/config/server/MockRequestHandler.java
new file mode 100644
index 00000000000..373545f1a8b
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/MockRequestHandler.java
@@ -0,0 +1,109 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server;
+
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.Version;
+import com.yahoo.vespa.config.ConfigKey;
+import com.yahoo.vespa.config.GetConfigRequest;
+import com.yahoo.vespa.config.protocol.ConfigResponse;
+
+import java.util.*;
+
+/**
+ * Test utility class
+ * @author lulf
+ * @since 5.25
+ */
+public class MockRequestHandler implements RequestHandler, ReloadHandler, TenantHandlerProvider {
+
+ volatile String serverStats = "";
+ volatile boolean reloadResponse = false;
+ volatile boolean throwException = false;
+ public long appGeneration = 0;
+ private Set<ConfigKey<?>> allConfigs = new HashSet<>();
+ public volatile ConfigResponse responseConfig = null; // for some v1 mocking
+ public Map<ApplicationId, ConfigResponse> responses = new LinkedHashMap<>(); // for v2 mocking
+ private final boolean pretendToHaveLoadedAnyApplication;
+
+ public MockRequestHandler() {
+ this(false);
+ }
+
+ public MockRequestHandler(boolean pretendToHaveLoadedAnyApplication) {
+ this.pretendToHaveLoadedAnyApplication = pretendToHaveLoadedAnyApplication;
+ }
+
+ @Override
+ public ConfigResponse resolveConfig(ApplicationId appId, GetConfigRequest req, Optional<Version> vespaVersion) {
+ if (appId==null) {
+ checkThrow();
+ return responseConfig;
+ }
+ return responses.get(appId);
+ }
+
+ @Override
+ public Set<ConfigKey<?>> listConfigs(ApplicationId appId, Optional<Version> vespaVersion, boolean recursive) {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public void removeApplication(ApplicationId applicationId) {
+ }
+
+ @Override
+ public void reloadConfig(ApplicationSet application) {
+ checkThrow();
+ }
+
+ private void checkThrow() {
+ if (throwException) {
+ throw new RuntimeException("foo");
+ }
+ }
+
+ @Override
+ public Set<ConfigKey<?>> listNamedConfigs(ApplicationId appId, Optional<Version> vespaVersion, ConfigKey<?> key, boolean recursive) {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public Set<String> allConfigIds(ApplicationId appId, Optional<Version> vespaVersion) {
+ Set<String> ret = new HashSet<>();
+ for (ConfigKey<?> k : allConfigs) {
+ ret.add(k.getConfigId());
+ }
+ return ret;
+ }
+
+ @Override
+ public Set<ConfigKey<?>> allConfigsProduced(ApplicationId appId, Optional<Version> vespaVersion) {
+ return allConfigs;
+ }
+
+ public void setAllConfigs(Set<ConfigKey<?>> allConfigs) {
+ this.allConfigs = allConfigs;
+ }
+
+ @Override
+ public boolean hasApplication(ApplicationId appId, Optional<Version> vespaVersion) {
+ if (pretendToHaveLoadedAnyApplication) return true;
+ return responses.containsKey(appId);
+ }
+
+ @Override
+ public ApplicationId resolveApplicationId(String hostName) {
+ return ApplicationId.defaultId();
+ }
+
+ @Override
+ public RequestHandler getRequestHandler() {
+ return this;
+ }
+
+ @Override
+ public ReloadHandler getReloadHandler() {
+ return this;
+ }
+
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/MockRpc.java b/configserver/src/test/java/com/yahoo/vespa/config/server/MockRpc.java
new file mode 100644
index 00000000000..70e4b4f6bd0
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/MockRpc.java
@@ -0,0 +1,107 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server;
+
+import com.yahoo.cloud.config.ConfigserverConfig;
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.config.provision.Version;
+import com.yahoo.vespa.config.protocol.ConfigResponse;
+import com.yahoo.vespa.config.protocol.JRTServerConfigRequest;
+import com.yahoo.vespa.config.server.monitoring.Metrics;
+
+import java.util.Optional;
+import java.util.concurrent.CompletionService;
+
+/**
+ * Test utility mocking an RPC server.
+ *
+ * @author lulf
+ * @since 5.25
+ */
+public class MockRpc extends RpcServer {
+
+ public boolean forced = false;
+ public RuntimeException exception = null;
+ public int errorCode = 0;
+ public ConfigResponse response = null;
+
+ // Fields used to assert on the calls made to this from tests
+ public boolean tryResolveConfig = false;
+ public boolean tryRespond = false;
+ /** The last request received and responded to */
+ public volatile JRTServerConfigRequest latestRequest = null;
+
+
+ public MockRpc(int port, boolean createDefaultTenant, boolean pretendToHaveLoadedAnyApplication) {
+ super(createConfig(port), null, Metrics.createTestMetrics(), new HostRegistries());
+ if (createDefaultTenant) {
+ onTenantCreate(TenantName.from("default"), new MockTenantProvider(pretendToHaveLoadedAnyApplication));
+ }
+ }
+
+ public MockRpc(int port, boolean createDefaultTenant) {
+ this(port, createDefaultTenant, true);
+ }
+
+ public MockRpc(int port) {
+ this(port, true);
+ }
+
+ /** Reset fields used to assert on the calls made to this */
+ public void resetChecks() {
+ forced = false;
+ tryResolveConfig = false;
+ tryRespond = false;
+ latestRequest = null;
+ }
+
+ private static ConfigserverConfig createConfig(int port) {
+ ConfigserverConfig.Builder b = new ConfigserverConfig.Builder();
+ b.rpcport(port);
+ return new ConfigserverConfig(b);
+ }
+
+ public boolean waitUntilSet(int timeout) {
+ long start = System.currentTimeMillis();
+ long end = start + timeout;
+ while (start < end) {
+ if (latestRequest != null)
+ return true;
+ try {
+ Thread.sleep(10);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public Boolean addToRequestQueue(JRTServerConfigRequest request, boolean forceResponse, CompletionService<Boolean> completionService) {
+ latestRequest = request;
+ forced = forceResponse;
+ return true;
+ }
+
+ @Override
+ public void respond(JRTServerConfigRequest request) {
+ latestRequest = request;
+ tryRespond = true;
+ errorCode = request.errorCode();
+ }
+
+ @Override
+ public ConfigResponse resolveConfig(JRTServerConfigRequest request, GetConfigContext context, Optional<Version> vespaVersion) {
+ tryResolveConfig = true;
+ if (exception != null) {
+ throw exception;
+ }
+ return response;
+ }
+
+ @Override
+ public boolean isHostedVespa() { return true; }
+
+ @Override
+ public boolean allTenantsLoaded() { return true; }
+
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/MockTenantListener.java b/configserver/src/test/java/com/yahoo/vespa/config/server/MockTenantListener.java
new file mode 100644
index 00000000000..1fd92f2acc9
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/MockTenantListener.java
@@ -0,0 +1,31 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server;
+
+import com.yahoo.config.provision.TenantName;
+
+/**
+ * @author lulf
+ * @since 5.8
+ */
+public class MockTenantListener implements TenantListener {
+ TenantName tenantCreatedName;
+ TenantHandlerProvider provider;
+ TenantName tenantDeletedName;
+ boolean tenantsLoaded;
+
+ @Override
+ public void onTenantCreate(TenantName tenantName, TenantHandlerProvider provider) {
+ this.tenantCreatedName = tenantName;
+ this.provider = provider;
+ }
+
+ @Override
+ public void onTenantDelete(TenantName tenantName) {
+ this.tenantDeletedName = tenantName;
+ }
+
+ @Override
+ public void onTenantsLoaded() {
+ tenantsLoaded = true;
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/MockTenantProvider.java b/configserver/src/test/java/com/yahoo/vespa/config/server/MockTenantProvider.java
new file mode 100644
index 00000000000..4b92c91f581
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/MockTenantProvider.java
@@ -0,0 +1,30 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server;
+
+/**
+ * @author lulf
+ * @since 5.
+ */
+public class MockTenantProvider implements TenantHandlerProvider {
+
+ final MockRequestHandler requestHandler;
+ final MockReloadHandler reloadHandler;
+
+ public MockTenantProvider() {
+ this(false);
+ }
+
+ public MockTenantProvider(boolean pretendToHaveLoadedAnyApplication) {
+ this.requestHandler = new MockRequestHandler(pretendToHaveLoadedAnyApplication);
+ this.reloadHandler = new MockReloadHandler();
+ }
+
+ @Override
+ public RequestHandler getRequestHandler() { return requestHandler; }
+
+ @Override
+ public ReloadHandler getReloadHandler() {
+ return reloadHandler;
+ }
+
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/ModelContextImplTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/ModelContextImplTest.java
new file mode 100644
index 00000000000..0352f2c9f3e
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/ModelContextImplTest.java
@@ -0,0 +1,65 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server;
+
+import com.yahoo.config.model.api.ModelContext;
+import com.yahoo.config.model.application.provider.BaseDeployLogger;
+import com.yahoo.config.model.application.provider.MockFileRegistry;
+import com.yahoo.config.model.test.MockApplicationPackage;
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.Rotation;
+import com.yahoo.config.provision.Zone;
+import com.yahoo.vespa.config.server.deploy.ModelContextImpl;
+
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.Optional;
+import java.util.Set;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author lulf
+ */
+public class ModelContextImplTest {
+ @Test
+ public void testModelContextTest() {
+
+ final Rotation rotation = new Rotation("this.is.a.mock.rotation");
+ final Set<Rotation> rotations = Collections.singleton(rotation);
+
+ ModelContext context = new ModelContextImpl(
+ MockApplicationPackage.createEmpty(),
+ Optional.empty(),
+ Optional.empty(),
+ new BaseDeployLogger(),
+ new StaticConfigDefinitionRepo(),
+ new MockFileRegistry(),
+ Optional.empty(),
+ new ModelContextImpl.Properties(
+ ApplicationId.defaultId(),
+ true,
+ Collections.emptyList(),
+ false,
+ Zone.defaultZone(),
+ rotations),
+ Optional.empty(),
+ Optional.empty());
+ assertTrue(context.applicationPackage() instanceof MockApplicationPackage);
+ assertFalse(context.hostProvisioner().isPresent());
+ assertFalse(context.permanentApplicationPackage().isPresent());
+ assertFalse(context.previousModel().isPresent());
+ assertTrue(context.getFileRegistry() instanceof MockFileRegistry);
+ assertTrue(context.configDefinitionRepo() instanceof StaticConfigDefinitionRepo);
+ assertThat(context.properties().applicationId(), is(ApplicationId.defaultId()));
+ assertTrue(context.properties().configServerSpecs().isEmpty());
+ assertTrue(context.properties().multitenant());
+ assertTrue(context.properties().zone() instanceof Zone);
+ assertFalse(context.properties().hostedVespa());
+ assertThat(context.properties().rotations(), equalTo(rotations));
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/ModelFactoryRegistryTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/ModelFactoryRegistryTest.java
new file mode 100644
index 00000000000..3d0c50b9bcb
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/ModelFactoryRegistryTest.java
@@ -0,0 +1,95 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server;
+
+import com.yahoo.component.provider.ComponentRegistry;
+import com.yahoo.config.model.api.Model;
+import com.yahoo.config.model.api.ModelContext;
+import com.yahoo.config.model.api.ModelCreateResult;
+import com.yahoo.config.model.api.ModelFactory;
+import com.yahoo.config.provision.Version;
+import com.yahoo.vespa.config.server.http.UnknownVespaVersionException;
+import com.yahoo.vespa.config.server.modelfactory.ModelFactoryRegistry;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author lulf
+ */
+public class ModelFactoryRegistryTest {
+ @Test(expected = IllegalArgumentException.class)
+ public void testThatOneFactoryIsRequired() {
+ new ModelFactoryRegistry(new ComponentRegistry<>());
+ }
+
+ @Test
+ public void testThatLatestVersionIsSelected() {
+ Version versionA = Version.fromIntValues(5, 38, 4);
+ Version versionB = Version.fromIntValues(5, 58, 1);
+ Version versionC = Version.fromIntValues(5, 48, 44);
+ Version versionD = Version.fromIntValues(5, 18, 44);
+ TestFactory a = new TestFactory(versionA);
+ TestFactory b = new TestFactory(versionB);
+ TestFactory c = new TestFactory(versionC);
+ TestFactory d = new TestFactory(versionD);
+
+ for (int i = 0; i < 100; i++) {
+ List<ModelFactory> randomOrder = Arrays.asList(a, b, c, d);
+ Collections.shuffle(randomOrder);
+ ModelFactoryRegistry registry = new ModelFactoryRegistry(randomOrder);
+ assertThat(registry.getFactory(versionA), is(a));
+ assertThat(registry.getFactory(versionB), is(b));
+ assertThat(registry.getFactory(versionC), is(c));
+ assertThat(registry.getFactory(versionD), is(d));
+ }
+ }
+
+ @Test
+ public void testThatAllFactoriesAreReturned() {
+ TestFactory a = new TestFactory(Version.fromIntValues(5, 38, 4));
+ TestFactory b = new TestFactory(Version.fromIntValues(5, 58, 1));
+ TestFactory c = new TestFactory(Version.fromIntValues(5, 48, 44));
+ TestFactory d = new TestFactory(Version.fromIntValues(5, 18, 44));
+ ModelFactoryRegistry registry = new ModelFactoryRegistry(Arrays.asList(a, b, c, d));
+ assertThat(registry.getFactories().size(), is(4));
+ assertTrue(registry.getFactories().contains(a));
+ assertTrue(registry.getFactories().contains(b));
+ assertTrue(registry.getFactories().contains(c));
+ assertTrue(registry.getFactories().contains(d));
+ }
+
+ @Test(expected = UnknownVespaVersionException.class)
+ public void testThatUnknownVersionGivesError() {
+ ModelFactoryRegistry registry = new ModelFactoryRegistry(Arrays.asList(new TestFactory(Version.fromIntValues(1, 2, 3))));
+ registry.getFactory(Version.fromIntValues(3, 2, 1));
+ }
+
+ private static class TestFactory implements ModelFactory {
+ private final Version version;
+
+ public TestFactory(Version version) {
+ this.version = version;
+ }
+
+ @Override
+ public Version getVersion() {
+ return version;
+ }
+
+ @Override
+ public Model createModel(ModelContext modelContext) {
+ return null;
+ }
+
+ @Override
+ public ModelCreateResult createAndValidateModel(ModelContext modelContext, boolean ignoreValidationErrors) {
+ return null;
+ }
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/ModelStub.java b/configserver/src/test/java/com/yahoo/vespa/config/server/ModelStub.java
new file mode 100644
index 00000000000..2615622fce0
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/ModelStub.java
@@ -0,0 +1,58 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server;
+
+import com.yahoo.config.codegen.InnerCNode;
+import com.yahoo.config.model.api.FileDistribution;
+import com.yahoo.config.model.api.HostInfo;
+import com.yahoo.config.model.api.Model;
+import com.yahoo.config.provision.ProvisionInfo;
+import com.yahoo.vespa.config.ConfigKey;
+import com.yahoo.vespa.config.ConfigPayload;
+import com.yahoo.vespa.config.buildergen.ConfigDefinition;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * @author bratseth
+ */
+public class ModelStub implements Model {
+
+ @Override
+ public ConfigPayload getConfig(ConfigKey<?> configKey, ConfigDefinition targetDef, ConfigPayload override) throws IOException {
+ return null;
+ }
+
+ @Override
+ public ConfigPayload getConfig(ConfigKey<?> configKey, InnerCNode targetDef, ConfigPayload override) throws IOException {
+ return null;
+ }
+
+ @Override
+ public Set<ConfigKey<?>> allConfigsProduced() {
+ return null;
+ }
+
+ @Override
+ public Collection<HostInfo> getHosts() {
+ return null;
+ }
+
+ @Override
+ public Set<String> allConfigIds() {
+ return null;
+ }
+
+ @Override
+ public void distributeFiles(FileDistribution fileDistribution) {
+
+ }
+
+ @Override
+ public Optional<ProvisionInfo> getProvisionInfo() {
+ return null;
+ }
+
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/PortRangeAllocator.java b/configserver/src/test/java/com/yahoo/vespa/config/server/PortRangeAllocator.java
new file mode 100644
index 00000000000..0c76a907d82
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/PortRangeAllocator.java
@@ -0,0 +1,65 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server;
+
+import com.google.common.collect.ContiguousSet;
+import com.google.common.collect.DiscreteDomain;
+import com.google.common.collect.Range;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Stack;
+
+/**
+ * Allocates port ranges for all configserver tests.
+ *
+ * @author lulf
+ * @since 5.1.26
+ */
+public class PortRangeAllocator {
+ private final static PortRange portRange = new PortRange();
+
+ // Get the next port from a pre-allocated range
+ public static int findAvailablePort() throws InterruptedException {
+ return portRange.next();
+ }
+
+ public static void releasePort(int port) {
+ portRange.release(port);
+ }
+
+ private static class PortRange {
+ private final Set<Integer> takenPorts = new HashSet<>();
+ private final Stack<Integer> freePorts = new Stack<>();
+ private static final int first = 18651;
+ private static final int last = 18899; // see: factory/doc/port-ranges
+
+ public PortRange() {
+ freePorts.addAll(ContiguousSet.create(Range.closed(first, last), DiscreteDomain.integers()));
+ }
+
+ synchronized int next() throws InterruptedException {
+ if (freePorts.isEmpty()) {
+ wait(600_000);
+ if (freePorts.isEmpty()) {
+ throw new RuntimeException("no more ports in range " + first + "-" + last);
+ }
+ }
+ int port = freePorts.pop();
+ takenPorts.add(port);
+ return port;
+ }
+
+ synchronized void release(int port) {
+ if (port < first || port > last) {
+ throw new RuntimeException("trying to release port outside valid range " + port);
+ }
+ if (!takenPorts.contains(port)) {
+ throw new RuntimeException("trying to release port never acquired " + port);
+ }
+ takenPorts.remove(port);
+ freePorts.push(port);
+ notify();
+ }
+ }
+
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/RpcServerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/RpcServerTest.java
new file mode 100644
index 00000000000..d205bfe05e6
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/RpcServerTest.java
@@ -0,0 +1,125 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server;
+
+import com.google.common.base.Joiner;
+import com.yahoo.cloud.config.LbServicesConfig;
+import com.yahoo.config.SimpletypesConfig;
+import com.yahoo.config.codegen.DefParser;
+import com.yahoo.config.codegen.InnerCNode;
+import com.yahoo.config.model.test.MockApplicationPackage;
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.config.provision.Version;
+import com.yahoo.jrt.Request;
+import com.yahoo.vespa.config.*;
+import com.yahoo.vespa.config.protocol.*;
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.vespa.config.server.application.Application;
+import com.yahoo.vespa.config.server.monitoring.MetricUpdater;
+import com.yahoo.vespa.config.util.ConfigUtils;
+
+import com.yahoo.vespa.model.VespaModel;
+import org.junit.*;
+import org.junit.rules.TemporaryFolder;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.Optional;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.*;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class RpcServerTest extends TestWithRpc {
+
+ @Rule
+ public TemporaryFolder folder = new TemporaryFolder();
+
+ @Test
+ public void testRpcServer() throws IOException, SAXException, InterruptedException {
+ testPrintStatistics();
+ testGetConfig();
+ testEnabled();
+ testEmptyConfigHostedVespa();
+ }
+
+
+ private void testEmptyConfigHostedVespa() throws InterruptedException {
+ rpcServer.onTenantDelete(TenantName.defaultName());
+ rpcServer.onTenantsLoaded();
+ JRTClientConfigRequest clientReq = createSimpleRequest();
+ performRequest(clientReq.getRequest());
+ assertFalse(clientReq.validateResponse());
+ assertThat(clientReq.errorCode(), is(ErrorCode.APPLICATION_NOT_LOADED));
+ stopRpc();
+ createAndStartRpcServer(true);
+ rpcServer.onTenantsLoaded();
+ clientReq = createSimpleRequest();
+ performRequest(clientReq.getRequest());
+ assertTrue(clientReq.validateResponse());
+ }
+
+ private JRTClientConfigRequest createSimpleRequest() {
+ ConfigKey<?> key = new ConfigKey<>(SimpletypesConfig.class, "");
+ JRTClientConfigRequest clientReq = JRTClientConfigRequestV3.createFromRaw(new RawConfig(key, SimpletypesConfig.CONFIG_DEF_MD5), 120_000, Trace.createDummy(), CompressionType.UNCOMPRESSED, Optional.empty());
+ assertTrue(clientReq.validateParameters());
+ return clientReq;
+ }
+
+
+ private void testEnabled() throws IOException, SAXException {
+ generationCounter.increment();
+ Application app = new Application(new VespaModel(MockApplicationPackage.createEmpty()), new ServerCache(), 2l, Version.fromIntValues(1, 2, 3), MetricUpdater.createTestUpdater(), ApplicationId.defaultId());
+ ApplicationSet appSet = ApplicationSet.fromSingle(app);
+ rpcServer.configReloaded(TenantName.defaultName(), appSet);
+ ConfigKey<?> key = new ConfigKey<>(LbServicesConfig.class, "*");
+ JRTClientConfigRequest clientReq = JRTClientConfigRequestV3.createFromRaw(new RawConfig(key, LbServicesConfig.CONFIG_DEF_MD5), 120_000, Trace.createDummy(), CompressionType.UNCOMPRESSED, Optional.empty());
+ assertTrue(clientReq.validateParameters());
+ performRequest(clientReq.getRequest());
+ assertFalse(clientReq.validateResponse());
+ assertThat(clientReq.errorCode(), is(ErrorCode.APPLICATION_NOT_LOADED));
+
+ rpcServer.onTenantsLoaded();
+ clientReq = JRTClientConfigRequestV3.createFromRaw(new RawConfig(key, LbServicesConfig.CONFIG_DEF_MD5), 120_000, Trace.createDummy(), CompressionType.UNCOMPRESSED, Optional.empty());
+ assertTrue(clientReq.validateParameters());
+ performRequest(clientReq.getRequest());
+ boolean validResponse = clientReq.validateResponse();
+ assertTrue(clientReq.errorMessage(), validResponse);
+ assertThat(clientReq.errorCode(), is(0));
+ }
+
+ public void testGetConfig() {
+ tenantProvider.requestHandler.throwException = false;
+ ConfigKey<?> key = new ConfigKey<>(SimpletypesConfig.class, "brim");
+ tenantProvider.requestHandler.responses.put(ApplicationId.defaultId(), createResponse());
+ JRTClientConfigRequest req = JRTClientConfigRequestV3.createFromRaw(new RawConfig(key, SimpletypesConfig.CONFIG_DEF_MD5), 120_000, Trace.createDummy(), CompressionType.UNCOMPRESSED, Optional.empty());
+ assertTrue(req.validateParameters());
+ performRequest(req.getRequest());
+ assertThat(req.errorCode(), is(0));
+ assertTrue(req.validateResponse());
+ ConfigPayload payload = ConfigPayload.fromUtf8Array(req.getNewPayload().getData());
+ assertNotNull(payload);
+ SimpletypesConfig.Builder builder = new SimpletypesConfig.Builder();
+ new ConfigPayloadApplier<>(builder).applyPayload(payload);
+ SimpletypesConfig config = new SimpletypesConfig(builder);
+ assertThat(config.intval(), is(123));
+ }
+
+ public ConfigResponse createResponse() {
+ SimpletypesConfig.Builder builder = new SimpletypesConfig.Builder();
+ builder.intval(123);
+ SimpletypesConfig responseConfig = new SimpletypesConfig(builder);
+ ConfigPayload responsePayload = ConfigPayload.fromInstance(responseConfig);
+ InnerCNode targetDef = new DefParser(SimpletypesConfig.CONFIG_DEF_NAME, new StringReader(Joiner.on("\n").join(SimpletypesConfig.CONFIG_DEF_SCHEMA))).getTree();
+ return SlimeConfigResponse.fromConfigPayload(responsePayload, targetDef, 3l, ConfigUtils.getMd5(responsePayload));
+ }
+
+ public void testPrintStatistics() {
+ Request req = new Request("printStatistics");
+ rpcServer.printStatistics(req);
+ assertThat(req.returnValues().get(0).asString(), is("Delayed responses queue size: 0"));
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/ServerCacheTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/ServerCacheTest.java
new file mode 100644
index 00000000000..52179b2cf48
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/ServerCacheTest.java
@@ -0,0 +1,80 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server;
+
+import com.yahoo.vespa.config.ConfigCacheKey;
+import com.yahoo.vespa.config.ConfigDefinitionKey;
+import com.yahoo.vespa.config.ConfigKey;
+import com.yahoo.vespa.config.ConfigPayload;
+import com.yahoo.vespa.config.buildergen.ConfigDefinition;
+import com.yahoo.vespa.config.protocol.ConfigResponse;
+import com.yahoo.vespa.config.protocol.SlimeConfigResponse;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class ServerCacheTest {
+ private ServerCache cache;
+
+ private static String defMd5 = "595f44fec1e92a71d3e9e77456ba80d1";
+ private static String defMd5_2 = "a2f8edfc965802bf6d44826f9da7e2b0";
+ private static String configMd5 = "mymd5";
+ private static String configMd5_2 = "mymd5_2";
+ private static ConfigDefinition payload = new ConfigDefinition("mypayload", new String[0]);
+ private static ConfigDefinition payload_2 = new ConfigDefinition("otherpayload", new String[0]);
+
+ private static ConfigDefinitionKey fooBarDefKey = new ConfigDefinitionKey("foo", "bar");
+ private static ConfigDefinitionKey fooBazDefKey = new ConfigDefinitionKey("foo", "baz");
+ private static ConfigDefinitionKey fooBimDefKey = new ConfigDefinitionKey("foo", "bim");
+
+ private static ConfigKey<?> fooConfigKey = new ConfigKey<>("foo", "id", "bar");
+ private static ConfigKey<?> bazConfigKey = new ConfigKey<>("foo", "id2", "bar");
+
+ ConfigCacheKey fooBarCacheKey = new ConfigCacheKey(fooConfigKey, defMd5);
+ ConfigCacheKey bazQuuxCacheKey = new ConfigCacheKey(bazConfigKey, defMd5);
+ ConfigCacheKey fooBarCacheKeyDifferentMd5 = new ConfigCacheKey(fooConfigKey, defMd5_2);
+
+ @Before
+ public void setup() {
+ cache = new ServerCache();
+
+ cache.addDef(fooBarDefKey, payload);
+ cache.addDef(fooBazDefKey, new com.yahoo.vespa.config.buildergen.ConfigDefinition("baz", new String[0]));
+
+ cache.addDef(fooBimDefKey, new ConfigDefinition("mynode", new String[0]));
+
+ cache.put(fooBarCacheKey, SlimeConfigResponse.fromConfigPayload(ConfigPayload.empty(), payload.getCNode(), 2, configMd5), configMd5);
+ cache.put(bazQuuxCacheKey, SlimeConfigResponse.fromConfigPayload(ConfigPayload.empty(), payload.getCNode(), 2, configMd5), configMd5);
+ cache.put(fooBarCacheKeyDifferentMd5, SlimeConfigResponse.fromConfigPayload(ConfigPayload.empty(), payload_2.getCNode(), 2, configMd5_2), configMd5_2);
+ }
+
+ @Test
+ public void testThatCacheWorks() {
+ assertNotNull(cache.getDef(fooBazDefKey));
+ assertThat(cache.getDef(fooBarDefKey), is(payload));
+ assertThat(cache.getDef(fooBimDefKey).getCNode().getName(), is("mynode"));
+ ConfigResponse raw = cache.get(fooBarCacheKey);
+ assertThat(raw.getConfigMd5(), is(configMd5));
+ }
+
+ @Test
+ public void testThatCacheWorksWithSameKeyDifferentMd5() {
+ assertThat(cache.getDef(fooBarDefKey), is(payload));
+ ConfigResponse raw = cache.get(fooBarCacheKey);
+ assertThat(raw.getConfigMd5(), is(configMd5));
+ raw = cache.get(fooBarCacheKeyDifferentMd5);
+ assertThat(raw.getConfigMd5(), is(configMd5_2));
+ }
+
+ @Test
+ public void testThatCacheWorksWithDifferentKeySameMd5() {
+ assertTrue(cache.get(fooBarCacheKey) == cache.get(bazQuuxCacheKey));
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/SuperModelControllerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/SuperModelControllerTest.java
new file mode 100644
index 00000000000..a69cf547db0
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/SuperModelControllerTest.java
@@ -0,0 +1,131 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server;
+
+import com.yahoo.cloud.config.ConfigserverConfig;
+import com.yahoo.config.model.application.provider.FilesApplicationPackage;
+import com.yahoo.config.provision.Version;
+import com.yahoo.vespa.config.server.application.Application;
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.vespa.config.server.monitoring.MetricUpdater;
+import com.yahoo.vespa.curator.mock.MockCurator;
+import com.yahoo.vespa.model.VespaModel;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.xml.sax.SAXException;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Optional;
+
+import com.yahoo.cloud.config.ElkConfig;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.*;
+
+/**
+ * @author lulf
+ * @since 5.9
+ */
+public class SuperModelControllerTest {
+
+ private static final File testApp = new File("src/test/resources/deploy/app");
+ private SuperModelGenerationCounter counter;
+ private SuperModelController controller;
+
+ @Rule
+ public TemporaryFolder folder = new TemporaryFolder();
+
+ @Before
+ public void setup() throws IOException {
+ counter = new SuperModelGenerationCounter(new MockCurator());
+ controller = new SuperModelController(counter,
+ new TestConfigDefinitionRepo(), new ConfigserverConfig(new ConfigserverConfig.Builder()),
+ new ElkConfig(new ElkConfig.Builder()));
+ }
+
+ @Test
+ public void test_super_model_reload() throws IOException, SAXException {
+ TenantName tenantA = TenantName.from("a");
+ assertNotNull(controller.getHandler());
+ long gen = counter.increment();
+ controller.reloadConfig(tenantA, createApp(tenantA, "foo", 3l, 1));
+ assertNotNull(controller.getHandler());
+ assertThat(controller.getHandler().getGeneration(), is(gen));
+ controller.reloadConfig(tenantA, createApp(tenantA, "foo", 4l, 2));
+ assertThat(controller.getHandler().getGeneration(), is(gen));
+ // Test that a new app is used when there already exist an application with the same id
+ ApplicationId appId = new ApplicationId.Builder().tenant(tenantA).applicationName("foo").build();
+ assertThat(((TestApplication) controller.getHandler().getSuperModel().getCurrentModels().get(tenantA).get(appId)).version, is(2l));
+ gen = counter.increment();
+ controller.reloadConfig(tenantA, createApp(tenantA, "bar", 2l, 3));
+ assertThat(controller.getHandler().getGeneration(), is(gen));
+ }
+
+ @Test
+ public void test_super_model_remove() throws IOException, SAXException {
+ TenantName tenantA = TenantName.from("a");
+ TenantName tenantB = TenantName.from("b");
+ long gen = counter.increment();
+ controller.reloadConfig(tenantA, createApp(tenantA, "foo", 3l, 1));
+ controller.reloadConfig(tenantA, createApp(tenantA, "bar", 30l, 2));
+ controller.reloadConfig(tenantB, createApp(tenantB, "baz", 9l, 3));
+ assertThat(controller.getHandler().getGeneration(), is(gen));
+ assertThat(controller.getHandler().getSuperModel().getCurrentModels().size(), is(2));
+ assertThat(controller.getHandler().getSuperModel().getCurrentModels().get(TenantName.from("a")).size(), is(2));
+ controller.removeApplication(
+ new ApplicationId.Builder().tenant("a").applicationName("unknown").build());
+ assertThat(controller.getHandler().getGeneration(), is(gen));
+ assertThat(controller.getHandler().getSuperModel().getCurrentModels().size(), is(2));
+ assertThat(controller.getHandler().getSuperModel().getCurrentModels().get(TenantName.from("a")).size(), is(2));
+ gen = counter.increment();
+ controller.removeApplication(
+ new ApplicationId.Builder().tenant("a").applicationName("bar").build());
+ assertThat(controller.getHandler().getSuperModel().getCurrentModels().size(), is(2));
+ assertThat(controller.getHandler().getSuperModel().getCurrentModels().get(TenantName.from("a")).size(), is(1));
+ assertThat(controller.getHandler().getGeneration(), is(gen));
+ }
+
+ @Test
+ public void test_super_model_master_generation() throws IOException, SAXException {
+ TenantName tenantA = TenantName.from("a");
+ long masterGen = 10;
+ controller = new SuperModelController(counter,
+ new TestConfigDefinitionRepo(), new ConfigserverConfig(new ConfigserverConfig.Builder().masterGeneration(masterGen)),
+ new ElkConfig(new ElkConfig.Builder()));
+
+ long gen = counter.increment();
+ controller.reloadConfig(tenantA, createApp(tenantA, "foo", 3l, 1));
+ assertThat(controller.getHandler().getGeneration(), is(masterGen + gen));
+ }
+
+ @Test
+ public void test_super_model_has_application_when_enabled() {
+ assertFalse(controller.hasApplication(ApplicationId.global(), Optional.empty()));
+ controller.enable();
+ assertTrue(controller.hasApplication(ApplicationId.global(), Optional.empty()));
+ }
+
+ private ApplicationSet createApp(TenantName tenant, String application, long generation, long version) throws IOException, SAXException {
+ return ApplicationSet.fromSingle(
+ new TestApplication(
+ new VespaModel(FilesApplicationPackage.fromFile(testApp)),
+ new ServerCache(),
+ generation,
+ new ApplicationId.Builder().tenant(tenant).applicationName(application).build(),
+ version));
+ }
+
+ private static class TestApplication extends Application {
+ private long version = 0;
+
+ public TestApplication(VespaModel vespaModel, ServerCache cache, long appGeneration, ApplicationId app, long version) {
+ super(vespaModel, cache, appGeneration, Version.fromIntValues(1, 2, 3), MetricUpdater.createTestUpdater(), app);
+ this.version = version;
+ }
+ }
+
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/SuperModelRequestHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/SuperModelRequestHandlerTest.java
new file mode 100644
index 00000000000..93a48094fac
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/SuperModelRequestHandlerTest.java
@@ -0,0 +1,203 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server;
+
+import com.yahoo.cloud.config.LbServicesConfig;
+import com.yahoo.cloud.config.ElkConfig;
+import com.yahoo.config.model.application.provider.FilesApplicationPackage;
+import com.yahoo.config.provision.*;
+import com.yahoo.jrt.Request;
+import com.yahoo.vespa.config.ConfigKey;
+import com.yahoo.vespa.config.GetConfigRequest;
+import com.yahoo.cloud.config.LbServicesConfig.Tenants.Applications;
+import com.yahoo.vespa.config.protocol.CompressionType;
+import com.yahoo.vespa.config.protocol.ConfigResponse;
+import com.yahoo.vespa.config.protocol.DefContent;
+import com.yahoo.vespa.config.protocol.JRTClientConfigRequestV3;
+import com.yahoo.vespa.config.protocol.JRTServerConfigRequestV3;
+import com.yahoo.vespa.config.protocol.Trace;
+import com.yahoo.vespa.config.protocol.VespaVersion;
+import com.yahoo.vespa.config.server.application.Application;
+import com.yahoo.vespa.config.server.model.SuperModel;
+import com.yahoo.vespa.config.server.monitoring.MetricUpdater;
+import com.yahoo.vespa.model.VespaModel;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.xml.sax.SAXException;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Optional;
+
+import com.yahoo.cloud.config.ElkConfig.Logstash;
+
+import com.yahoo.vespa.config.server.model.ElkProducer;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author lulf
+ * @since 5.9
+ */
+public class SuperModelRequestHandlerTest {
+
+ private SuperModelRequestHandler handler;
+
+ @Before
+ public void setupHandler() throws IOException, SAXException {
+ Map<TenantName, Map<ApplicationId, Application>> models = new LinkedHashMap<>();
+ models.put(TenantName.from("a"), new LinkedHashMap<>());
+ File testApp = new File("src/test/resources/deploy/app");
+ ApplicationId app = ApplicationId.from(TenantName.from("a"),
+ ApplicationName.from("foo"), InstanceName.defaultName());
+ models.get(app.tenant()).put(app, new Application(new VespaModel(FilesApplicationPackage.fromFile(testApp)), new ServerCache(), 4l, Version.fromIntValues(1, 2, 3), MetricUpdater.createTestUpdater(), app));
+ handler = new SuperModelRequestHandler(new SuperModel(models, new ElkConfig(new ElkConfig.Builder()), Zone.defaultZone()), new TestConfigDefinitionRepo(), 2, new UncompressedConfigResponseFactory());
+ }
+
+ @Test
+ public void test_super_model_resolve_elk() {
+ ConfigResponse response = handler.resolveConfig(new GetConfigRequest() {
+ @Override
+ public ConfigKey<?> getConfigKey() {
+ return new ConfigKey<>(ElkConfig.class, "dontcare");
+ }
+
+ @Override
+ public DefContent getDefContent() {
+ return DefContent.fromClass(ElkConfig.class);
+ }
+
+ @Override
+ public Optional<VespaVersion> getVespaVersion() {
+ return Optional.empty();
+ }
+
+ @Override
+ public boolean noCache() {
+ return false;
+ }
+ });
+ assertThat(response.getGeneration(), is(2l));
+ }
+
+ @Test
+ public void test_lb_config_simple() {
+ LbServicesConfig.Builder lb = new LbServicesConfig.Builder();
+ handler.getSuperModel().getConfig(lb);
+ LbServicesConfig lbc = new LbServicesConfig(lb);
+ assertThat(lbc.tenants().size(), is(1));
+ assertThat(lbc.tenants("a").applications().size(), is(1));
+ Applications app = lbc.tenants("a").applications("foo:prod:default:default");
+ assertTrue(app.hosts().size() > 0);
+ }
+
+
+ @Test(expected = UnknownConfigDefinitionException.class)
+ public void test_unknown_config_definition() {
+ String md5 = "asdfasf";
+ Request request = JRTClientConfigRequestV3.createWithParams(new ConfigKey<>("foo", "id", "bar", md5, null), DefContent.fromList(Collections.emptyList()),
+ "fromHost", md5, 1, 1, Trace.createDummy(), CompressionType.UNCOMPRESSED,
+ Optional.empty())
+ .getRequest();
+ JRTServerConfigRequestV3 v3Request = JRTServerConfigRequestV3.createFromRequest(request);
+ handler.resolveConfig(v3Request);
+ }
+
+ @Test
+ public void test_lb_config_multiple_apps() throws IOException, SAXException {
+ Map<TenantName, Map<ApplicationId, Application>> models = new LinkedHashMap<>();
+ models.put(TenantName.from("t1"), new LinkedHashMap<>());
+ models.put(TenantName.from("t2"), new LinkedHashMap<>());
+ File testApp1 = new File("src/test/resources/deploy/app");
+ File testApp2 = new File("src/test/resources/deploy/advancedapp");
+ File testApp3 = new File("src/test/resources/deploy/advancedapp");
+ // TODO must fix equals, hashCode on Tenant
+ Version vespaVersion = Version.fromIntValues(1, 2, 3);
+ models.get(TenantName.from("t1")).put(applicationId("mysimpleapp"),
+ new Application(new VespaModel(FilesApplicationPackage.fromFile(testApp1)), new ServerCache(), 4l, vespaVersion, MetricUpdater.createTestUpdater(), applicationId("mysimpleapp")));
+ models.get(TenantName.from("t1")).put(applicationId("myadvancedapp"),
+ new Application(new VespaModel(FilesApplicationPackage.fromFile(testApp2)), new ServerCache(), 4l, vespaVersion, MetricUpdater.createTestUpdater(), applicationId("myadvancedapp")));
+ models.get(TenantName.from("t2")).put(applicationId("minetooadvancedapp"),
+ new Application(new VespaModel(FilesApplicationPackage.fromFile(testApp3)), new ServerCache(), 4l, vespaVersion, MetricUpdater.createTestUpdater(), applicationId("minetooadvancedapp")));
+
+ SuperModelRequestHandler han = new SuperModelRequestHandler(new SuperModel(models, new ElkConfig(new ElkConfig.Builder()), Zone.defaultZone()), new TestConfigDefinitionRepo(), 2, new UncompressedConfigResponseFactory());
+ LbServicesConfig.Builder lb = new LbServicesConfig.Builder();
+ han.getSuperModel().getConfig(lb);
+ LbServicesConfig lbc = new LbServicesConfig(lb);
+ assertThat(lbc.tenants().size(), is(2));
+ assertThat(lbc.tenants("t1").applications().size(), is(2));
+ assertThat(lbc.tenants("t2").applications().size(), is(1));
+ assertThat(lbc.tenants("t2").applications("minetooadvancedapp:prod:default:default").hosts().size(), is(1));
+ assertQrServer(lbc.tenants("t2").applications("minetooadvancedapp:prod:default:default"));
+ }
+
+ private ApplicationId applicationId(String applicationName) {
+ return ApplicationId.from(TenantName.defaultName(),
+ ApplicationName.from(applicationName), InstanceName.defaultName());
+ }
+
+ private void assertQrServer(Applications app) {
+ String host = app.hosts().keySet().iterator().next();
+ Applications.Hosts hosts = app.hosts(host);
+ assertThat(hosts.hostname(), is(host));
+ for (Map.Entry<String, Applications.Hosts.Services> e : app.hosts(host).services().entrySet()) {
+ System.out.println(e);
+ if ("qrserver".equals(e.getKey())) {
+ Applications.Hosts.Services s = e.getValue();
+ assertThat(s.type(), is("qrserver"));
+ assertThat(s.ports().size(), is(4));
+ assertThat(s.index(), is(0));
+ return;
+ }
+ }
+ org.junit.Assert.fail("No qrserver service in config");
+ }
+
+ @Test
+ public void testElkConfig() {
+ ElkConfig ec = new ElkConfig(new ElkConfig.Builder().elasticsearch(new ElkConfig.Elasticsearch.Builder().host("es1").port(99)).
+ logstash(new ElkConfig.Logstash.Builder().
+ config_file("/cfgfile").
+ source_field("srcfield").
+ spool_size(345).
+ network(new Logstash.Network.Builder().
+ servers(new Logstash.Network.Servers.Builder().
+ host("ls1").
+ port(999)).
+ servers(new Logstash.Network.Servers.Builder().
+ host("ls2").
+ port(998)).
+ timeout(78)).
+ files(new ElkConfig.Logstash.Files.Builder().
+ paths("path1").
+ paths("path2").
+ fields("field1", "f1val").
+ fields("field2", "f2val"))));
+ ElkProducer ep = new ElkProducer(ec);
+ ElkConfig.Builder newBuilder = new ElkConfig.Builder();
+ ep.getConfig(newBuilder);
+ ElkConfig elkConfig = new ElkConfig(newBuilder);
+ assertThat(elkConfig.elasticsearch(0).host(), is("es1"));
+ assertThat(elkConfig.elasticsearch(0).port(), is(99));
+ assertThat(elkConfig.logstash().network().servers(0).host(), is("ls1"));
+ assertThat(elkConfig.logstash().network().servers(0).port(), is(999));
+ assertThat(elkConfig.logstash().network().servers(1).host(), is("ls2"));
+ assertThat(elkConfig.logstash().network().servers(1).port(), is(998));
+ assertThat(elkConfig.logstash().network().timeout(), is(78));
+ assertThat(elkConfig.logstash().config_file(), is("/cfgfile"));
+ assertThat(elkConfig.logstash().source_field(), is("srcfield"));
+ assertThat(elkConfig.logstash().spool_size(), is(345));
+ assertThat(elkConfig.logstash().files().size(), is(1));
+ assertThat(elkConfig.logstash().files(0).paths(0), is("path1"));
+ assertThat(elkConfig.logstash().files(0).paths(1), is("path2"));
+ assertThat(elkConfig.logstash().files(0).fields("field1"), is("f1val"));
+ assertThat(elkConfig.logstash().files(0).fields("field2"), is("f2val"));
+ }
+ }
+
+
+
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/TenantRequestHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/TenantRequestHandlerTest.java
new file mode 100644
index 00000000000..94e08fdf1b3
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/TenantRequestHandlerTest.java
@@ -0,0 +1,300 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server;
+
+import com.yahoo.config.ConfigInstance;
+import com.yahoo.config.SimpletypesConfig;
+import com.yahoo.config.model.NullConfigModelRegistry;
+import com.yahoo.config.model.application.provider.BaseDeployLogger;
+import com.yahoo.config.model.application.provider.FilesApplicationPackage;
+import com.yahoo.config.model.application.provider.MockFileRegistry;
+import com.yahoo.config.provision.ApplicationName;
+import com.yahoo.config.provision.Version;
+import com.yahoo.io.IOUtils;
+import com.yahoo.path.Path;
+import com.yahoo.vespa.config.ConfigKey;
+import com.yahoo.vespa.config.ConfigPayload;
+import com.yahoo.vespa.config.GetConfigRequest;
+import com.yahoo.vespa.config.protocol.ConfigResponse;
+import com.yahoo.vespa.config.protocol.DefContent;
+import com.yahoo.vespa.config.protocol.VespaVersion;
+import com.yahoo.vespa.config.server.application.Application;
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.vespa.config.server.deploy.ZooKeeperDeployer;
+import com.yahoo.vespa.config.server.model.TestModelFactory;
+import com.yahoo.vespa.config.server.modelfactory.ModelFactoryRegistry;
+import com.yahoo.vespa.config.server.monitoring.MetricUpdater;
+import com.yahoo.vespa.config.server.monitoring.Metrics;
+import com.yahoo.vespa.config.server.session.RemoteSession;
+import com.yahoo.vespa.config.server.session.SessionZooKeeperClient;
+import com.yahoo.vespa.model.VespaModel;
+import com.yahoo.vespa.model.VespaModelFactory;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.xml.sax.SAXException;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.*;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class TenantRequestHandlerTest extends TestWithCurator {
+
+ private static final Version vespaVersion = new VespaModelFactory(new NullConfigModelRegistry()).getVersion();
+ private TenantRequestHandler server;
+ private MockReloadListener listener = new MockReloadListener();
+ private File app1 = new File("src/test/apps/cs1");
+ private File app2 = new File("src/test/apps/cs2");
+ private TenantName tenant = TenantName.from("mytenant");
+ private TestComponentRegistry componentRegistry;
+
+ @Rule
+ public TemporaryFolder tempFolder = new TemporaryFolder();
+
+ @Before
+ public void setUp() throws IOException, SAXException {
+ feedApp(app1, 1);
+ Metrics sh = Metrics.createTestMetrics();
+ List<ReloadListener> listeners = new ArrayList<>();
+ listeners.add(listener);
+ server = new TenantRequestHandler(sh, tenant, listeners, new UncompressedConfigResponseFactory(), new HostRegistries());
+ componentRegistry = new TestComponentRegistry(curator, configCurator, createRegistry());
+ }
+
+ private void feedApp(File appDir, long sessionId) throws IOException {
+ feedApp(appDir, sessionId, new ApplicationId.Builder().applicationName(ApplicationName.defaultName()).tenant(tenant).build());
+ }
+
+ private void feedApp(File appDir, long sessionId, ApplicationId appId) throws IOException {
+ SessionZooKeeperClient zkc = new SessionZooKeeperClient(curator, configCurator, new PathProvider(Path.createRoot()).getSessionDir(sessionId), new TestConfigDefinitionRepo(), "");
+ zkc.writeApplicationId(appId);
+ File app = tempFolder.newFolder();
+ IOUtils.copyDirectory(appDir, app);
+ ZooKeeperDeployer deployer = zkc.createDeployer(new BaseDeployLogger());
+ deployer.deploy(FilesApplicationPackage.fromFile(appDir), Collections.singletonMap(vespaVersion, new MockFileRegistry()), Collections.emptyMap());
+ }
+
+ private ApplicationSet reloadConfig(long id) {
+ return reloadConfig(id, "default");
+ }
+
+ private ApplicationSet reloadConfig(long id, String application) {
+ SessionZooKeeperClient zkc = new SessionZooKeeperClient(curator, configCurator, new PathProvider(Path.createRoot()).getSessionDir(id), new TestConfigDefinitionRepo(), "");
+ zkc.writeApplicationId(new ApplicationId.Builder().tenant(tenant).applicationName(application).build());
+ RemoteSession session = new RemoteSession(tenant, id, componentRegistry, zkc);
+ return session.ensureApplicationLoaded();
+ }
+
+ private ModelFactoryRegistry createRegistry() {
+ return new ModelFactoryRegistry(Arrays.asList(new TestModelFactory(vespaVersion),
+ new TestModelFactory(Version.fromIntValues(3, 2, 1))));
+ }
+
+ public <T extends ConfigInstance> T resolve(Class<T> clazz, TenantRequestHandler tenantRequestHandler, String configId) {
+ return resolve(clazz, tenantRequestHandler, new ApplicationId.Builder().applicationName(ApplicationName.defaultName()).tenant(tenant).build(), vespaVersion, configId);
+ }
+
+ public <T extends ConfigInstance> T resolve(final Class<T> clazz, TenantRequestHandler tenantRequestHandler, ApplicationId appId, Version vespaVersion, final String configId) {
+ ConfigResponse response = tenantRequestHandler.resolveConfig(appId, new GetConfigRequest() {
+ @Override
+ public ConfigKey<T> getConfigKey() {
+ return new ConfigKey<T>(clazz, configId);
+ }
+
+ @Override
+ public DefContent getDefContent() {
+ return DefContent.fromClass(clazz);
+ }
+
+ @Override
+ public Optional<VespaVersion> getVespaVersion() {
+ return Optional.of(VespaVersion.fromString(vespaVersion.toSerializedForm()));
+ }
+
+ @Override
+ public boolean noCache() {
+ return false;
+ }
+ }, Optional.empty());
+ return ConfigPayload.fromUtf8Array(response.getPayload()).toInstance(clazz, configId);
+ }
+
+ @Test
+ public void testReloadConfig() throws IOException, SAXException {
+ ApplicationId applicationId = new ApplicationId.Builder().applicationName(ApplicationName.defaultName()).tenant(tenant).build();
+ server.reloadConfig(reloadConfig(1));
+ assertThat(listener.reloaded.get(), is(1));
+ // Using only payload list for this simple test
+ SimpletypesConfig config = resolve(SimpletypesConfig.class, server, "");
+ assertThat(config.intval(), is(1337));
+ assertThat(server.getApplicationGeneration(applicationId, Optional.of(vespaVersion)), is(1l));
+
+ server.reloadConfig(reloadConfig(1l));
+ config = resolve(SimpletypesConfig.class, server, "");
+ assertThat(config.intval(), is(1337));
+ assertThat(listener.reloaded.get(), is(2));
+ assertThat(server.getApplicationGeneration(applicationId, Optional.of(vespaVersion)), is(1l));
+ assertThat(listener.tenantHosts.size(), is(1));
+ assertThat(server.resolveApplicationId("mytesthost"), is(applicationId));
+
+ listener.reloaded.set(0);
+ feedApp(app2, 2);
+ server.reloadConfig(reloadConfig(2l));
+ config = resolve(SimpletypesConfig.class, server, "");
+ assertThat(config.intval(), is(1330));
+ assertThat(listener.reloaded.get(), is(1));
+ assertThat(server.getApplicationGeneration(applicationId, Optional.of(vespaVersion)), is(2l));
+ }
+
+ @Test
+ public void testRemoveApplication() {
+ server.reloadConfig(reloadConfig(1));
+ assertThat(listener.removed.get(), is(0));
+ server.removeApplication(new ApplicationId.Builder().applicationName(ApplicationName.defaultName()).tenant(tenant).build());
+ assertThat(listener.removed.get(), is(1));
+ }
+
+ @Test
+ public void testResolveForAppId() {
+ long id = 1l;
+ SessionZooKeeperClient zkc = new SessionZooKeeperClient(curator, configCurator, new PathProvider(Path.createRoot()).getSessionDir(id), new TestConfigDefinitionRepo(), "");
+ ApplicationId appId = new ApplicationId.Builder()
+ .tenant(tenant)
+ .applicationName("myapp").instanceName("myinst").build();
+ zkc.writeApplicationId(appId);
+ RemoteSession session = new RemoteSession(appId.tenant(), id, componentRegistry, zkc);
+ server.reloadConfig(session.ensureApplicationLoaded());
+ SimpletypesConfig config = resolve(SimpletypesConfig.class, server, appId, vespaVersion, "");
+ assertThat(config.intval(), is(1337));
+ }
+
+ @Test
+ public void testResolveMultipleApps() throws IOException, SAXException {
+ ApplicationId appId1 = new ApplicationId.Builder()
+ .tenant(tenant)
+ .applicationName("myapp1").instanceName("myinst1").build();
+ ApplicationId appId2 = new ApplicationId.Builder()
+ .tenant(tenant)
+ .applicationName("myapp2").instanceName("myinst2").build();
+ feedAndReloadApp(app1, 1, appId1);
+ SimpletypesConfig config = resolve(SimpletypesConfig.class, server, appId1, vespaVersion, "");
+ assertThat(config.intval(), is(1337));
+
+ feedAndReloadApp(app2, 2, appId2);
+ config = resolve(SimpletypesConfig.class, server, appId2, vespaVersion, "");
+ assertThat(config.intval(), is(1330));
+ }
+
+ @Test
+ public void testResolveMultipleVersions() throws IOException {
+ ApplicationId appId = new ApplicationId.Builder()
+ .tenant(tenant)
+ .applicationName("myapp1").instanceName("myinst1").build();
+ feedAndReloadApp(app1, 1, appId);
+ SimpletypesConfig config = resolve(SimpletypesConfig.class, server, appId, vespaVersion, "");
+ assertThat(config.intval(), is(1337));
+ config = resolve(SimpletypesConfig.class, server, appId, Version.fromIntValues(3, 2, 1), "");
+ assertThat(config.intval(), is(1337));
+ }
+
+ private void feedAndReloadApp(File appDir, long sessionId, ApplicationId appId) throws IOException {
+ feedApp(appDir, sessionId, appId);
+ SessionZooKeeperClient zkc = new SessionZooKeeperClient(curator, new PathProvider(Path.createRoot()).getSessionDir(sessionId));
+ zkc.writeApplicationId(appId);
+ RemoteSession session = new RemoteSession(tenant, sessionId, componentRegistry, zkc);
+ server.reloadConfig(session.ensureApplicationLoaded());
+ }
+
+ public static class MockReloadListener implements ReloadListener {
+ public AtomicInteger reloaded = new AtomicInteger(0);
+ public AtomicInteger removed = new AtomicInteger(0);
+ public Map<String, Collection<String>> tenantHosts = new LinkedHashMap<>();
+ @Override
+ public void configReloaded(TenantName tenant, ApplicationSet application) {
+ reloaded.incrementAndGet();
+ }
+
+ @Override
+ public void hostsUpdated(TenantName tenant, Collection<String> newHosts) {
+ tenantHosts.put(tenant.value(), newHosts);
+ }
+
+ @Override
+ public void verifyHostsAreAvailable(TenantName tenant, Collection<String> newHosts) {
+ }
+
+ @Override
+ public void applicationRemoved(ApplicationId applicationId) {
+ removed.incrementAndGet();
+ }
+ }
+
+ @Test
+ public void testHasApplication() throws IOException, SAXException {
+ assertdefaultAppNotFound();
+ server.reloadConfig(reloadConfig(1l));
+ assertTrue(server.hasApplication(new ApplicationId.Builder().applicationName(ApplicationName.defaultName()).tenant(tenant).build(), Optional.of(vespaVersion)));
+ }
+
+ private void assertdefaultAppNotFound() {
+ assertFalse(server.hasApplication(ApplicationId.defaultId(), Optional.of(vespaVersion)));
+ }
+
+ @Test
+ public void testMultipleApplicationsReload() {
+ assertdefaultAppNotFound();
+ server.reloadConfig(reloadConfig(1l, "foo"));
+ assertdefaultAppNotFound();
+ assertTrue(server.hasApplication(new ApplicationId.Builder().tenant(tenant).applicationName("foo").build(),
+ Optional.of(vespaVersion)));
+ assertThat(server.resolveApplicationId("doesnotexist"), is(ApplicationId.defaultId()));
+ assertThat(server.resolveApplicationId("mytesthost"), is(new ApplicationId.Builder()
+ .tenant(tenant)
+ .applicationName("foo").build())); // Host set in application package.
+ }
+
+ @Test
+ public void testListConfigs() throws IOException, SAXException {
+ assertdefaultAppNotFound();
+ /*assertTrue(server.allConfigIds(ApplicationId.defaultId()).isEmpty());
+ assertTrue(server.allConfigsProduced(ApplicationId.defaultId()).isEmpty());
+ assertTrue(server.listConfigs(ApplicationId.defaultId(), false).isEmpty());
+ assertTrue(server.listConfigs(ApplicationId.defaultId(), true).isEmpty());*/
+
+ VespaModel model = new VespaModel(FilesApplicationPackage.fromFile(new File("src/test/apps/app")));
+ server.reloadConfig(ApplicationSet.fromSingle(new Application(model, new ServerCache(), 1, vespaVersion, MetricUpdater.createTestUpdater(), ApplicationId.defaultId())));
+ Set<ConfigKey<?>> configNames = server.listConfigs(ApplicationId.defaultId(), Optional.of(vespaVersion), false);
+ assertTrue(configNames.contains(new ConfigKey<>("sentinel", "hosts", "cloud.config")));
+ //for (ConfigKey<?> ck : configNames) {
+ // assertTrue(!"".equals(ck.getConfigId()));
+ //}
+
+ configNames = server.listConfigs(ApplicationId.defaultId(), Optional.of(vespaVersion), true);
+ System.out.println(configNames);
+ assertTrue(configNames.contains(new ConfigKey<>("feeder", "jdisc", "vespaclient.config")));
+ assertTrue(configNames.contains(new ConfigKey<>("documentmanager", "jdisc", "document.config")));
+ assertTrue(configNames.contains(new ConfigKey<>("documentmanager", "", "document.config")));
+ assertTrue(configNames.contains(new ConfigKey<>("documenttypes", "", "document")));
+ assertTrue(configNames.contains(new ConfigKey<>("documentmanager", "jdisc", "document.config")));
+ assertTrue(configNames.contains(new ConfigKey<>("health-monitor", "jdisc", "container.jdisc.config")));
+ assertTrue(configNames.contains(new ConfigKey<>("specific", "jdisc", "project")));
+ }
+
+ @Test
+ public void testAppendIdsInNonRecursiveListing() {
+ assertEquals(server.appendOneLevelOfId("search/music", "search/music/qrservers/default/qr.0"), "search/music/qrservers");
+ assertEquals(server.appendOneLevelOfId("search", "search/music/qrservers/default/qr.0"), "search/music");
+ assertEquals(server.appendOneLevelOfId("search/music/qrservers/default/qr.0", "search/music/qrservers/default/qr.0"), "search/music/qrservers/default/qr.0");
+ assertEquals(server.appendOneLevelOfId("", "search/music/qrservers/default/qr.0"), "search");
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/TenantTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/TenantTest.java
new file mode 100644
index 00000000000..c4eb8786917
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/TenantTest.java
@@ -0,0 +1,61 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server;
+
+import com.google.common.testing.EqualsTester;
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.vespa.config.server.application.MemoryApplicationRepo;
+import com.yahoo.vespa.config.server.http.v2.TestTenantBuilder;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.*;
+
+/**
+ * @author lulf
+ * @since 5.3
+ */
+public class TenantTest extends TestWithCurator {
+
+ private Tenant t1;
+ private Tenant t2;
+ private Tenant t3;
+ private Tenant t4;
+
+ @Before
+ public void setupTenant() throws Exception {
+ t1 = createTenant("foo");
+ t2 = createTenant("foo");
+ t3 = createTenant("bar");
+ t4 = createTenant("baz");
+ }
+
+ private Tenant createTenant(String name) throws Exception {
+ return new TestTenantBuilder().createTenant(TenantName.from(name)).build();
+ }
+
+ @Test
+ public void equals() {
+ new EqualsTester()
+ .addEqualityGroup(t1, t2)
+ .addEqualityGroup(t3)
+ .addEqualityGroup(t4)
+ .testEquals();
+ }
+
+ @Test
+ public void hashcode() {
+ assertThat(t1.hashCode(), is(t2.hashCode()));
+ assertThat(t1.hashCode(), is(not(t3.hashCode())));
+ assertThat(t1.hashCode(), is(not(t4.hashCode())));
+ }
+
+ @Test
+ public void close() {
+ MemoryApplicationRepo repo = (MemoryApplicationRepo) t1.getApplicationRepo();
+ assertTrue(repo.isOpen());
+ t1.close();
+ assertFalse(repo.isOpen());
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/TenantsTestCase.java b/configserver/src/test/java/com/yahoo/vespa/config/server/TenantsTestCase.java
new file mode 100644
index 00000000000..9f71393115f
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/TenantsTestCase.java
@@ -0,0 +1,181 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server;
+
+import com.yahoo.config.model.test.MockApplicationPackage;
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.ApplicationName;
+import com.yahoo.config.provision.InstanceName;
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.config.provision.Version;
+import com.yahoo.vespa.config.server.application.Application;
+import com.yahoo.vespa.config.server.deploy.MockDeployer;
+import com.yahoo.vespa.config.server.monitoring.MetricUpdater;
+import com.yahoo.vespa.config.server.monitoring.Metrics;
+import com.yahoo.vespa.model.VespaModel;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class TenantsTestCase extends TestWithCurator {
+ private Tenants tenants;
+ TestComponentRegistry globalComponentRegistry;
+ private TenantRequestHandlerTest.MockReloadListener listener;
+ private MockTenantListener tenantListener;
+ private final TenantName tenant1 = TenantName.from("tenant1");
+ private final TenantName tenant2 = TenantName.from("tenant2");
+ private final TenantName tenant3 = TenantName.from("tenant3");
+
+ @Before
+ public void setupSessions() throws Exception {
+ globalComponentRegistry = new TestComponentRegistry(curator);
+ listener = globalComponentRegistry.reloadListener;
+ tenantListener = globalComponentRegistry.tenantListener;
+ tenantListener.tenantsLoaded = false;
+ tenants = new Tenants(globalComponentRegistry, Metrics.createTestMetrics());
+ assertTrue(tenantListener.tenantsLoaded);
+ tenants.createTenant(tenant1);
+ tenants.createTenant(tenant2);
+ }
+
+ @After
+ public void closeSessions() throws IOException {
+ tenants.close();
+ }
+
+ @Test
+ public void testStartUp() {
+ assertEquals(tenants.tenantsCopy().get(tenant1).getName(), tenant1);
+ assertEquals(tenants.tenantsCopy().get(tenant2).getName(), tenant2);
+ }
+
+ @Test
+ public void testListenersAdded() throws IOException, SAXException {
+ tenants.tenantsCopy().get(tenant1).getReloadHandler().reloadConfig(ApplicationSet.fromSingle(new Application(new VespaModel(MockApplicationPackage.createEmpty()), new ServerCache(), 4l, Version.fromIntValues(1, 2, 3), MetricUpdater.createTestUpdater(), ApplicationId.defaultId())));
+ assertThat(listener.reloaded.get(), is(1));
+ }
+
+ private List<String> readZKChildren(String path) throws Exception {
+ return curator.framework().getChildren().forPath(path);
+ }
+
+ @Test
+ public void testTenantListenersNotified() throws Exception {
+ tenants.createTenant(tenant3);
+ assertThat("tenant3 not the last created tenant. Tenants: " + tenants.tenantsCopy().keySet() + ", /config/v2/tenants: " + readZKChildren("/config/v2/tenants"), tenantListener.tenantCreatedName, is(tenant3));
+ tenants.deleteTenant(tenant2);
+ assertFalse(tenants.tenantsCopy().containsKey(tenant2));
+ assertThat(tenantListener.tenantDeletedName, is(tenant2));
+ }
+
+ @Test
+ public void testAddTenant() throws Exception {
+ Map<TenantName, Tenant> tenantsCopy = tenants.tenantsCopy();
+ assertEquals(tenantsCopy.get(tenant1).getName(), tenant1);
+ assertEquals(tenantsCopy.get(tenant2).getName(), tenant2);
+ tenants.createTenant(tenant3);
+ tenantsCopy = tenants.tenantsCopy();
+ assertEquals(tenantsCopy.get(tenant1).getName(), tenant1);
+ assertEquals(tenantsCopy.get(tenant2).getName(), tenant2);
+ assertEquals(tenantsCopy.get(tenant3).getName(), tenant3);
+ }
+
+ @Test
+ public void testPutAdd() throws Exception {
+ tenants.createTenant(tenant3);
+ assertNotNull(globalComponentRegistry.getCurator().framework().checkExists().forPath(tenants.tenantZkPath(tenant3)));
+ }
+
+ @Test
+ public void testRemove() throws Exception {
+ assertNotNull(globalComponentRegistry.getCurator().framework().checkExists().forPath(tenants.tenantZkPath(tenant1)));
+ tenants.deleteTenant(tenant1);
+ assertFalse(tenants.tenantsCopy().containsKey(tenant1));
+ }
+
+ @Test
+ public void testTenantsChanged() throws Exception {
+ tenants.close(); // close the Tenants instance created in setupSession, we do not want to use one with a PatchChildrenCache listener
+ tenants = new Tenants(globalComponentRegistry, Metrics.createTestMetrics(), new ArrayList<>());
+ Set<TenantName> newTenants = new LinkedHashSet<>();
+ TenantName defaultTenant = TenantName.defaultName();
+ newTenants.add(tenant2);
+ newTenants.add(defaultTenant);
+ tenants.tenantsChanged(newTenants);
+ Map<TenantName, Tenant> tenantsCopy = tenants.tenantsCopy();
+ assertEquals(tenantsCopy.get(tenant2).getName(), tenant2);
+ assertEquals(tenantsCopy.get(defaultTenant).getName().value(), "default");
+ assertNull(tenantsCopy.get(tenant1));
+ newTenants.clear();
+ tenants.tenantsChanged(newTenants);
+ tenantsCopy = tenants.tenantsCopy();
+ assertNull(tenantsCopy.get(tenant1));
+ assertNull(tenantsCopy.get(tenant2));
+ assertNull(tenantsCopy.get(defaultTenant));
+ newTenants.clear();
+ TenantName foo = TenantName.from("foo");
+ TenantName bar = TenantName.from("bar");
+ newTenants.add(tenant2);
+ newTenants.add(foo);
+ newTenants.add(bar);
+ tenants.tenantsChanged(newTenants);
+ tenantsCopy = tenants.tenantsCopy();
+ assertNotNull(tenantsCopy.get(tenant2));
+ assertNotNull(tenantsCopy.get(foo));
+ assertNotNull(tenantsCopy.get(bar));
+ assertEquals(tenantsCopy.get(tenant2).getName(), tenant2);
+ assertEquals(tenantsCopy.get(foo).getName(), foo);
+ assertEquals(tenantsCopy.get(bar).getName(), bar);
+ }
+
+ @Test
+ public void testTenantWatching() throws Exception {
+ TestComponentRegistry reg = new TestComponentRegistry(curator);
+ Tenants t = new Tenants(reg, Metrics.createTestMetrics());
+ try {
+ assertEquals(t.tenantsCopy().get(TenantName.defaultName()).getName(), TenantName.defaultName());
+ reg.getCurator().framework().create().forPath(tenants.tenantZkPath(TenantName.from("newTenant")));
+ // Poll for the watcher to pick up the tenant from zk, and add it
+ int tries=0;
+ while(true) {
+ if (tries > 500) fail("Didn't react on watch");
+ Tenant nt = t.tenantsCopy().get(TenantName.from("newTenant"));
+ if (nt != null) {
+ assertEquals(nt.getName().value(), "newTenant");
+ return;
+ }
+ tries++;
+ Thread.sleep(100);
+ }
+ } finally {
+ t.close();
+ }
+ }
+
+ @Test
+ public void testTenantRedeployment() throws Exception {
+ MockDeployer deployer = new MockDeployer();
+ Tenant tenant = tenants.tenantsCopy().get(tenant1);
+ ApplicationId id = ApplicationId.from(tenant1, ApplicationName.defaultName(), InstanceName.defaultName());
+ tenant.getApplicationRepo().createPutApplicationTransaction(id, 3).commit();
+ tenants.redeployApplications(deployer);
+ assertThat(deployer.lastDeployed, is(id));
+ }
+
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/TestComponentRegistry.java b/configserver/src/test/java/com/yahoo/vespa/config/server/TestComponentRegistry.java
new file mode 100644
index 00000000000..d202f55e38a
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/TestComponentRegistry.java
@@ -0,0 +1,134 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server;
+
+import com.google.common.io.Files;
+import com.yahoo.cloud.config.ConfigserverConfig;
+import com.yahoo.config.model.NullConfigModelRegistry;
+import com.yahoo.config.model.api.ConfigDefinitionRepo;
+import com.yahoo.config.provision.Provisioner;
+import com.yahoo.config.provision.Zone;
+import com.yahoo.vespa.config.server.application.PermanentApplicationPackage;
+import com.yahoo.vespa.config.server.modelfactory.ModelFactoryRegistry;
+import com.yahoo.vespa.config.server.monitoring.Metrics;
+import com.yahoo.vespa.config.server.provision.HostProvisionerProvider;
+import com.yahoo.vespa.config.server.session.FileDistributionFactory;
+import com.yahoo.vespa.config.server.session.MockFileDistributionFactory;
+import com.yahoo.vespa.config.server.session.SessionPreparer;
+import com.yahoo.vespa.curator.Curator;
+import com.yahoo.vespa.curator.mock.MockCurator;
+import com.yahoo.vespa.config.server.zookeeper.ConfigCurator;
+import com.yahoo.vespa.model.VespaModelFactory;
+
+import java.util.Collections;
+import java.util.Optional;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+// TODO Use a Builder to avoid so many constructors
+public class TestComponentRegistry implements GlobalComponentRegistry {
+
+ private final Curator curator;
+ private final ConfigCurator configCurator;
+ private final Metrics metrics;
+ private final ConfigServerDB serverDB;
+ private final SessionPreparer sessionPreparer;
+ private final ConfigserverConfig configserverConfig;
+ private final SuperModelGenerationCounter superModelGenerationCounter;
+ private final ConfigDefinitionRepo defRepo;
+ final TenantRequestHandlerTest.MockReloadListener reloadListener;
+ final MockTenantListener tenantListener;
+ private final PermanentApplicationPackage permanentApplicationPackage;
+ private final HostRegistries hostRegistries;
+ private final FileDistributionFactory fileDistributionFactory;
+ private final ModelFactoryRegistry modelFactoryRegistry;
+ private final Optional<Provisioner> hostProvisioner;
+
+ public TestComponentRegistry() { this(new MockCurator()); }
+
+ public TestComponentRegistry(Curator curator) {
+ this(curator, ConfigCurator.create(curator), new ModelFactoryRegistry(Collections.singletonList(new VespaModelFactory(new NullConfigModelRegistry()))));
+ }
+
+ public TestComponentRegistry(Curator curator, ConfigCurator configCurator, FileDistributionFactory fileDistributionFactory) {
+ this(curator, configCurator, new ModelFactoryRegistry(Collections.singletonList(new VespaModelFactory(new NullConfigModelRegistry()))), fileDistributionFactory);
+ }
+
+ public TestComponentRegistry(Curator curator, ModelFactoryRegistry modelFactoryRegistry) {
+ this(curator, ConfigCurator.create(curator), modelFactoryRegistry, Optional.empty());
+ }
+
+ public TestComponentRegistry(Curator curator, ConfigCurator configCurator, ModelFactoryRegistry modelFactoryRegistry) {
+ this(curator, configCurator, modelFactoryRegistry, Optional.empty());
+ }
+
+ public TestComponentRegistry(Curator curator, ConfigCurator configCurator, ModelFactoryRegistry modelFactoryRegistry, FileDistributionFactory fileDistributionFactory) {
+ this(curator, configCurator, modelFactoryRegistry, Optional.empty(), fileDistributionFactory);
+ }
+
+ public TestComponentRegistry(Curator curator, ModelFactoryRegistry modelFactoryRegistry, Optional<PermanentApplicationPackage> permanentApplicationPackage) {
+ this(curator, ConfigCurator.create(curator), modelFactoryRegistry, permanentApplicationPackage, new MockFileDistributionFactory());
+ }
+
+ public TestComponentRegistry(Curator curator, ConfigCurator configCurator, ModelFactoryRegistry modelFactoryRegistry, Optional<PermanentApplicationPackage> permanentApplicationPackage) {
+ this(curator, configCurator, modelFactoryRegistry, permanentApplicationPackage, new MockFileDistributionFactory());
+ }
+
+ public TestComponentRegistry(Curator curator, ConfigCurator configCurator, ModelFactoryRegistry modelFactoryRegistry, Optional<PermanentApplicationPackage> permanentApplicationPackage, FileDistributionFactory fileDistributionFactory) {
+ this.curator = curator;
+ this.configCurator = configCurator;
+ metrics = Metrics.createTestMetrics();
+ configserverConfig = new ConfigserverConfig(new ConfigserverConfig.Builder().configServerDBDir(Files.createTempDir().getAbsolutePath()));
+ serverDB = new ConfigServerDB(configserverConfig);
+ reloadListener = new TenantRequestHandlerTest.MockReloadListener();
+ tenantListener = new MockTenantListener();
+ this.superModelGenerationCounter = new SuperModelGenerationCounter(curator);
+ this.defRepo = new StaticConfigDefinitionRepo();
+ this.permanentApplicationPackage = permanentApplicationPackage.orElse(new PermanentApplicationPackage(configserverConfig));
+ this.hostRegistries = new HostRegistries();
+ this.fileDistributionFactory = fileDistributionFactory;
+ this.modelFactoryRegistry = modelFactoryRegistry;
+ this.hostProvisioner = Optional.empty();
+ sessionPreparer = new SessionPreparer(modelFactoryRegistry, fileDistributionFactory, HostProvisionerProvider.empty(), this.permanentApplicationPackage, configserverConfig, defRepo, curator, new Zone(configserverConfig));
+ }
+
+ @Override
+ public Curator getCurator() { return curator; }
+ @Override
+ public ConfigCurator getConfigCurator() { return configCurator; }
+ @Override
+ public Metrics getMetrics() { return metrics; }
+ @Override
+ public ConfigServerDB getServerDB() { return serverDB; }
+ @Override
+ public SessionPreparer getSessionPreparer() { return sessionPreparer; }
+ @Override
+ public ConfigserverConfig getConfigserverConfig() { return configserverConfig; }
+ @Override
+ public TenantListener getTenantListener() { return tenantListener; }
+ @Override
+ public ReloadListener getReloadListener() { return reloadListener; }
+ @Override
+ public SuperModelGenerationCounter getSuperModelGenerationCounter() { return superModelGenerationCounter; }
+ @Override
+ public ConfigDefinitionRepo getConfigDefinitionRepo() { return defRepo; }
+ @Override
+ public PermanentApplicationPackage getPermanentApplicationPackage() { return permanentApplicationPackage; }
+ @Override
+ public HostRegistries getHostRegistries() { return hostRegistries;}
+ @Override
+ public ModelFactoryRegistry getModelFactoryRegistry() { return modelFactoryRegistry; }
+
+ @Override
+ public Optional<Provisioner> getHostProvisioner() {
+ return hostProvisioner;
+ }
+
+ @Override
+ public Zone getZone() {
+ return Zone.defaultZone();
+ }
+
+ public FileDistributionFactory getFileDistributionFactory() { return fileDistributionFactory; }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/TestConfigDefinitionRepo.java b/configserver/src/test/java/com/yahoo/vespa/config/server/TestConfigDefinitionRepo.java
new file mode 100644
index 00000000000..2a0c78ce7a5
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/TestConfigDefinitionRepo.java
@@ -0,0 +1,32 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server;
+
+import com.yahoo.cloud.config.LbServicesConfig;
+import com.yahoo.config.SimpletypesConfig;
+import com.yahoo.config.codegen.InnerCNode;
+import com.yahoo.config.model.api.ConfigDefinitionRepo;
+import com.yahoo.vespa.config.ConfigDefinitionKey;
+import com.yahoo.vespa.config.buildergen.ConfigDefinition;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * @author lulf
+ * @since 5.
+ */
+public class TestConfigDefinitionRepo implements ConfigDefinitionRepo {
+ private final Map<ConfigDefinitionKey, ConfigDefinition> repo = new LinkedHashMap<>();
+ public TestConfigDefinitionRepo() {
+ repo.put(new ConfigDefinitionKey(SimpletypesConfig.CONFIG_DEF_NAME, SimpletypesConfig.CONFIG_DEF_NAMESPACE),
+ new ConfigDefinition(SimpletypesConfig.CONFIG_DEF_NAME, SimpletypesConfig.CONFIG_DEF_SCHEMA));
+ repo.put(new ConfigDefinitionKey(LbServicesConfig.CONFIG_DEF_NAME, LbServicesConfig.CONFIG_DEF_NAMESPACE),
+ new ConfigDefinition(LbServicesConfig.CONFIG_DEF_NAME, LbServicesConfig.CONFIG_DEF_SCHEMA));
+ }
+
+ @Override
+ public Map<ConfigDefinitionKey, ConfigDefinition> getConfigDefinitions() {
+ return repo;
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/TestWithCurator.java b/configserver/src/test/java/com/yahoo/vespa/config/server/TestWithCurator.java
new file mode 100644
index 00000000000..6f68aa07634
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/TestWithCurator.java
@@ -0,0 +1,29 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server;
+
+import com.yahoo.vespa.curator.Curator;
+import com.yahoo.vespa.curator.mock.MockCurator;
+import com.yahoo.vespa.config.server.zookeeper.ConfigCurator;
+import org.apache.curator.framework.CuratorFramework;
+import org.junit.Before;
+
+/**
+ * For tests that require a Curator instance
+ *
+ * @author lulf
+ * @since 5.16
+ */
+public class TestWithCurator {
+
+ protected ConfigCurator configCurator;
+ protected CuratorFramework curatorFramework;
+ protected Curator curator;
+
+ @Before
+ public void setupZKProvider() throws Exception {
+ curator = new MockCurator();
+ configCurator = ConfigCurator.create(curator);
+ curatorFramework = curator.framework();
+ }
+
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/TestWithRpc.java b/configserver/src/test/java/com/yahoo/vespa/config/server/TestWithRpc.java
new file mode 100644
index 00000000000..3e5431deccd
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/TestWithRpc.java
@@ -0,0 +1,105 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server;
+
+import com.yahoo.cloud.config.ConfigserverConfig;
+import com.yahoo.cloud.config.ElkConfig;
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.jrt.Request;
+import com.yahoo.jrt.Spec;
+import com.yahoo.jrt.Supervisor;
+import com.yahoo.jrt.Transport;
+import com.yahoo.vespa.config.GenerationCounter;
+import com.yahoo.vespa.config.server.monitoring.Metrics;
+import org.junit.After;
+import org.junit.Before;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test running rpc server.
+ *
+ * @author lulf
+ * @since 5.17
+ */
+public class TestWithRpc {
+
+ protected RpcServer rpcServer;
+ protected MockTenantProvider tenantProvider;
+ protected GenerationCounter generationCounter;
+ private Thread t;
+ private Supervisor sup;
+ private Spec spec;
+ private int port;
+
+ private List<Integer> allocatedPorts;
+
+ @Before
+ public void setupRpc() throws InterruptedException {
+ allocatedPorts = new ArrayList<>();
+ port = allocatePort();
+ spec = createSpec(port);
+ tenantProvider = new MockTenantProvider();
+ generationCounter = new MemoryGenerationCounter();
+ createAndStartRpcServer(false);
+ }
+
+ @After
+ public void teardownPortAllocator() {
+ for (Integer port : allocatedPorts) {
+ PortRangeAllocator.releasePort(port);
+ }
+ }
+
+ protected int allocatePort() throws InterruptedException {
+ int port = PortRangeAllocator.findAvailablePort();
+ allocatedPorts.add(port);
+ return port;
+ }
+
+ protected void createAndStartRpcServer(boolean hostedVespa) {
+ rpcServer = new RpcServer(new ConfigserverConfig(new ConfigserverConfig.Builder().rpcport(port).numthreads(1).maxgetconfigclients(1).hostedVespa(hostedVespa)),
+ new SuperModelController(generationCounter,
+ new TestConfigDefinitionRepo(), new ConfigserverConfig(new ConfigserverConfig.Builder()), new ElkConfig(new ElkConfig.Builder())),
+ Metrics.createTestMetrics(), new HostRegistries());
+ rpcServer.onTenantCreate(TenantName.from("default"), tenantProvider);
+ t = new Thread(rpcServer);
+ t.start();
+ sup = new Supervisor(new Transport());
+ pingServer();
+ }
+
+ @After
+ public void stopRpc() throws InterruptedException {
+ rpcServer.stop();
+ t.join();
+ }
+
+ private Spec createSpec(int port) {
+ return new Spec("tcp/localhost:" + port);
+ }
+
+ private void pingServer() {
+ long endTime = System.currentTimeMillis() + 60_000;
+ Request req = new Request("ping");
+ while (System.currentTimeMillis() < endTime) {
+ performRequest(req);
+ if (!req.isError() && req.returnValues().size() > 0 && req.returnValues().get(0).asInt32() == 0) {
+ break;
+ }
+ req = new Request("ping");
+ }
+ assertFalse(req.isError());
+ assertTrue(req.returnValues().size() > 0);
+ assertThat(req.returnValues().get(0).asInt32(), is(0));
+ }
+
+ protected void performRequest(Request req) {
+ sup.connect(spec).invokeSync(req, 120.0);
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/TestWithTenant.java b/configserver/src/test/java/com/yahoo/vespa/config/server/TestWithTenant.java
new file mode 100644
index 00000000000..3892bf505b9
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/TestWithTenant.java
@@ -0,0 +1,27 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server;
+
+import com.yahoo.vespa.config.server.monitoring.Metrics;
+import org.junit.Before;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Utility for a test using a single default tenant.
+ *
+ * @author lulf
+ * @since 5.35
+ */
+public class TestWithTenant extends TestWithCurator {
+
+ protected Tenants tenants;
+ protected Tenant tenant;
+
+ @Before
+ public void setupTenant() throws Exception {
+ tenants = new Tenants(new TestComponentRegistry(curator), Metrics.createTestMetrics());
+ tenant = tenants.defaultTenant();
+ }
+
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/TimeoutBudgetTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/TimeoutBudgetTest.java
new file mode 100644
index 00000000000..224a759896f
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/TimeoutBudgetTest.java
@@ -0,0 +1,65 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server;
+
+import com.yahoo.test.ManualClock;
+import org.junit.Test;
+
+import java.time.Clock;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.util.concurrent.TimeUnit;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class TimeoutBudgetTest {
+ public static TimeoutBudget day() {
+ return new TimeoutBudget(new ManualClock(Instant.now()), Duration.ofDays(1));
+ }
+
+ @Test
+ public void testTimeLeft() {
+ ManualClock clock = new ManualClock();
+
+ TimeoutBudget budget = new TimeoutBudget(clock, Duration.ofMillis(7));
+ assertThat(budget.timeLeft().toMillis(), is(7l));
+ clock.advance(Duration.ofMillis(1));
+ assertThat(budget.timeLeft().toMillis(), is(6l));
+ clock.advance(Duration.ofMillis(5));
+ assertThat(budget.timeLeft().toMillis(), is(1l));
+ assertThat(budget.timeLeft().toMillis(), is(1l));
+ clock.advance(Duration.ofMillis(1));
+ assertThat(budget.timeLeft().toMillis(), is(0l));
+ clock.advance(Duration.ofMillis(5));
+ assertThat(budget.timeLeft().toMillis(), is(0l));
+
+ clock.advance(Duration.ofMillis(1));
+ assertThat(budget.timesUsed(), is("[0 ms, 1 ms, 5 ms, 0 ms, 1 ms, 5 ms, total: 13 ms]"));
+ }
+
+ @Test
+ public void testHasTimeLeft() {
+ ManualClock clock = new ManualClock();
+
+ TimeoutBudget budget = new TimeoutBudget(clock, Duration.ofMillis(7));
+ assertThat(budget.hasTimeLeft(), is(true));
+ clock.advance(Duration.ofMillis(1));
+ assertThat(budget.hasTimeLeft(), is(true));
+ clock.advance(Duration.ofMillis(5));
+ assertThat(budget.hasTimeLeft(), is(true));
+ assertThat(budget.hasTimeLeft(), is(true));
+ clock.advance(Duration.ofMillis(1));
+ assertThat(budget.hasTimeLeft(), is(false));
+ clock.advance(Duration.ofMillis(5));
+ assertThat(budget.hasTimeLeft(), is(false));
+
+ clock.advance(Duration.ofMillis(1));
+ assertThat(budget.timesUsed(), is("[0 ms, 1 ms, 5 ms, 0 ms, 1 ms, 5 ms, total: 13 ms]"));
+ }
+
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/a-music-indexer-correct.cfg b/configserver/src/test/java/com/yahoo/vespa/config/server/a-music-indexer-correct.cfg
new file mode 100644
index 00000000000..8b43ff9c793
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/a-music-indexer-correct.cfg
@@ -0,0 +1,78 @@
+accesslog "/home/vespa/logs/vespa/foo.log"
+partialsd "sd"
+partialsd2 "global2"
+asyncfetchocc 10
+a 0
+b 1
+c 2
+d 3
+e 4
+onlyindef 45
+listenport 13700
+rangecheck2 10
+rangecheck3 10
+kanon -78.56
+rangecheck1 10.0
+testref search/cluster.music/c0/r0/indexer.4
+testref2 some/babbel
+mode BATCH
+functionmodules[0]
+storage[2]
+storage[0].feeder[1]
+storage[0].feeder[0] "test"
+storage[1].id search/cluster.music/c0/r0/indexer.4
+storage[1].id2 pjatt
+storage[1].feeder[2]
+storage[1].feeder[0] "me"
+storage[1].feeder[1] "now"
+search[3]
+search[0].feeder[1]
+search[0].feeder[0] "foofeeder"
+search[1].feeder[4]
+search[1].feeder[0] "barfeeder1_1"
+search[1].feeder[1] "barfeeder2"
+search[1].feeder[2] ""
+search[1].feeder[3] "barfeeder2_1"
+search[2].feeder[2]
+search[2].feeder[0] ""
+search[2].feeder[1] "bazfeeder"
+f[1]
+f[0].a "A"
+f[0].b "B"
+f[0].c "C"
+f[0].h "H"
+f[0].f "F"
+config[1]
+config[0].role "rtx"
+config[0].usewrapper false
+config[0].id search/cluster.music/rtx/0
+routingtable[1]
+routingtable[0].hop[3]
+routingtable[0].hop[0].name "docproc/cluster.music.indexing/chain.music.indexing"
+routingtable[0].hop[0].selector "docproc/cluster.music.indexing/*/chain.music.indexing"
+routingtable[0].hop[0].recipient[0]
+routingtable[0].hop[1].name "search/cluster.music"
+routingtable[0].hop[1].selector "search/cluster.music/[SearchColumn]/[SearchRow]/feed-destination"
+routingtable[0].hop[1].recipient[1]
+routingtable[0].hop[1].recipient[0] "search/cluster.music/c0/r0/feed-destination"
+routingtable[0].hop[2].selector "[DocumentRouteSelector]"
+routingtable[0].hop[2].name "indexing"
+routingtable[0].hop[2].recipient[1]
+routingtable[0].hop[2].recipient[0] "search/cluster.music"
+speciallog[1]
+speciallog[0].filehandler.name "QueryAccessLog"
+speciallog[0].filehandler.pattern "logs/vespa/qrs/QueryAccessLog.%Y%m%d%H%M%S"
+speciallog[0].filehandler.rotation "0 1 ..."
+speciallog[0].cachehandler.name "QueryAccessLog"
+speciallog[0].name "QueryAccessLog"
+speciallog[0].type "file"
+speciallog[0].cachehandler.size 1000
+rulebase[4]
+rulebase[0].name "cjk"
+rulebase[0].rules "# Use unicode equivalents in java source:\n#\n# 佳:\u4f73\n# 能:\u80fd\n# 索:\u7d22\n# 尼:\u5c3c\n# 惠:\u60e0\n# 普:\u666e\n\n@default\n\na索 -> 索a;\n\n[brand] -> brand:[brand];\n\n[brand] :- 索尼,惠普,佳能;\n"
+rulebase[1].name "common"
+rulebase[1].rules "## Some test rules\n\n# Spelling correction\nbahc -> bach;\n\n# Stopwords\nsomelongstopword -> ;\n[stopword] -> ;\n[stopword] :- someotherlongstopword, yetanotherstopword;\n\n# \n[song] by [artist] -> song:[song] artist:[artist];\n\n[song] :- together, imagine, tinseltown;\n[artist] :- youngbloods, beatles, zappa;\n\n# Negative\nvarious +> -kingz;\n\n\n"
+rulebase[2].name "egyik"
+rulebase[2].rules "@include(common.sr)\n@automata(/home/vespa/etc/vespa/fsa/stopwords.fsa)\n[stopwords] -> ;\n\n"
+rulebase[3].name "masik"
+rulebase[3].rules "@include(common.sr)\n[stopwords] :- etaoin, shrdlu;\n[stopwords] -> ;\n\n"
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/a-sports-indexer-correct.cfg b/configserver/src/test/java/com/yahoo/vespa/config/server/a-sports-indexer-correct.cfg
new file mode 100644
index 00000000000..927ff8a26c9
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/a-sports-indexer-correct.cfg
@@ -0,0 +1,48 @@
+accesslog "/home/vespa/logs/vespa/foo.log"
+partialsd "global"
+partialsd2 "global2"
+asyncfetchocc 10
+a 0
+b 1
+c 67
+d 89
+e 4
+onlyindef 45
+listenport 13700
+rangecheck2 10
+rangecheck3 10
+rangecheck1 10.0
+mode BATCH
+functionmodules[0]
+storage[0]
+search[3]
+search[0].feeder[1]
+search[0].feeder[0] "foofeeder"
+search[1].feeder[4]
+search[1].feeder[0] "barfeeder1_1"
+search[1].feeder[1] "sportsfeeder1"
+search[1].feeder[2] ""
+search[1].feeder[3] "barfeeder2_1"
+search[2].feeder[2]
+search[2].feeder[0] ""
+search[2].feeder[1] "bazfeeder"
+f[0]
+config[0]
+routingtable[0]
+speciallog[1]
+speciallog[0].filehandler.name "QueryAccessLog"
+speciallog[0].filehandler.pattern "logs/vespa/qrs/QueryAccessLog.%Y%m%d%H%M%S"
+speciallog[0].filehandler.rotation "0 1 ..."
+speciallog[0].cachehandler.name "QueryAccessLog"
+speciallog[0].name "QueryAccessLog"
+speciallog[0].type "file"
+speciallog[0].cachehandler.size 1000
+rulebase[4]
+rulebase[0].name "cjk"
+rulebase[0].rules "# Use unicode equivalents in java source:\n#\n# 佳:\u4f73\n# 能:\u80fd\n# 索:\u7d22\n# 尼:\u5c3c\n# 惠:\u60e0\n# 普:\u666e\n\n@default\n\na索 -> 索a;\n\n[brand] -> brand:[brand];\n\n[brand] :- 索尼,惠普,佳能;\n"
+rulebase[1].name "common"
+rulebase[1].rules "## Some test rules\n\n# Spelling correction\nbahc -> bach;\n\n# Stopwords\nsomelongstopword -> ;\n[stopword] -> ;\n[stopword] :- someotherlongstopword, yetanotherstopword;\n\n# \n[song] by [artist] -> song:[song] artist:[artist];\n\n[song] :- together, imagine, tinseltown;\n[artist] :- youngbloods, beatles, zappa;\n\n# Negative\nvarious +> -kingz;\n\n\n"
+rulebase[2].name "egyik"
+rulebase[2].rules "@include(common.sr)\n@automata(/home/vespa/etc/vespa/fsa/stopwords.fsa)\n[stopwords] -> ;\n\n"
+rulebase[3].name "masik"
+rulebase[3].rules "@include(common.sr)\n[stopwords] :- etaoin, shrdlu;\n[stopwords] -> ;\n\n"
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/app_stripped/components/testbundle.jar b/configserver/src/test/java/com/yahoo/vespa/config/server/app_stripped/components/testbundle.jar
new file mode 100644
index 00000000000..69f6e335092
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/app_stripped/components/testbundle.jar
Binary files differ
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/app_stripped/services.xml b/configserver/src/test/java/com/yahoo/vespa/config/server/app_stripped/services.xml
new file mode 100644
index 00000000000..53d7c599817
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/app_stripped/services.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<services version="1.0">
+
+ <admin version="2.0">
+ <adminserver hostalias="node1"/>
+ </admin>
+
+ <content version="1.0">
+ <redundancy>1</redundancy>
+ <documents>
+ <document type="music" mode="index"/>
+ </documents>
+ <nodes>>
+ <node hostalias="node1" distribution-key="0"/>
+ </nodes>
+ </content>
+</services>
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/application/ApplicationConvergenceCheckerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/application/ApplicationConvergenceCheckerTest.java
new file mode 100644
index 00000000000..eefa4b6176d
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/application/ApplicationConvergenceCheckerTest.java
@@ -0,0 +1,210 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.application;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.yahoo.cloud.config.ModelConfig;
+import com.yahoo.config.codegen.InnerCNode;
+import com.yahoo.config.model.api.FileDistribution;
+import com.yahoo.config.model.api.HostInfo;
+import com.yahoo.config.model.api.Model;
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.ApplicationName;
+import com.yahoo.config.provision.InstanceName;
+import com.yahoo.config.provision.ProvisionInfo;
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.config.provision.Version;
+import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.vespa.config.ConfigKey;
+import com.yahoo.vespa.config.ConfigPayload;
+import com.yahoo.vespa.config.buildergen.ConfigDefinition;
+import com.yahoo.vespa.config.server.ServerCache;
+import com.yahoo.vespa.config.server.TimeoutBudget;
+import com.yahoo.vespa.config.server.monitoring.MetricUpdater;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.xml.sax.SAXException;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.time.Clock;
+import java.time.Duration;
+import java.util.Collection;
+import java.util.Optional;
+import java.util.Set;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+/**
+ * @author lulf
+ */
+public class ApplicationConvergenceCheckerTest {
+
+ private TenantName tenant = TenantName.from("mytenant");
+ private ApplicationId appId = ApplicationId.from(tenant, ApplicationName.from("myapp"), InstanceName.from("myinstance"));
+ private ObjectMapper mapper = new ObjectMapper();
+ private Application application;
+
+ @Rule
+ public TemporaryFolder folder = new TemporaryFolder();
+
+ @Before
+ public void setup() throws IOException, SAXException, InterruptedException {
+ Model mockModel = new MockModel(1337);
+ application = new Application(mockModel, new ServerCache(), 3, Version.fromIntValues(0, 0, 0), MetricUpdater.createTestUpdater(), appId);
+ }
+
+ private void assertJsonResponseEquals(HttpResponse httpResponse, String expected) throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ httpResponse.render(out);
+ String response = out.toString(StandardCharsets.UTF_8.name());
+ ObjectMapper mapper = new ObjectMapper();
+ JsonNode jsonResponse = mapper.readTree(response);
+ JsonNode jsonExpected = mapper.readTree(expected);
+ if (jsonExpected.equals(jsonResponse)) {
+ return;
+ }
+ fail("Not equal, response is '" + response + "' expected '"+ expected + "'");
+ }
+
+ @Test
+ public void converge() throws IOException, SAXException {
+ ApplicationConvergenceChecker checker = new ApplicationConvergenceChecker((client, serviceUri) -> () -> string2json("{\"config\":{\"generation\":3}}"));
+ checker.waitForConfigConverged(application, new TimeoutBudget(Clock.systemUTC(), Duration.ofSeconds(1)));
+ }
+
+ @Test
+ public void convergeV2() throws IOException, SAXException {
+ ApplicationConvergenceChecker checker = new ApplicationConvergenceChecker((client, serviceUri) -> () -> string2json("{\"config\":{\"generation\":3}}"));
+ final HttpResponse httpResponse = checker.listConfigConvergence(application, URI.create("http://foo:234/serviceconvergence"));
+ assertThat(httpResponse.getStatus(), is(200));
+ assertJsonResponseEquals(httpResponse, "{\"services\":[" +
+ "{\"port\":1337,\"host\":\"localhost\"," +
+ "\"url\":\"http://foo:234/serviceconvergence/localhost:1337\"," +
+ "\"type\":\"container\"}]," +
+ "\"debug\":{\"wantedVersion\":3}," +
+ "\"url\":\"http://foo:234/serviceconvergence\"}");
+ final HttpResponse nodeHttpResponse = checker.nodeConvergenceCheck(application, "localhost:1337", URI.create("http://foo:234/serviceconvergence"));
+ assertThat(nodeHttpResponse.getStatus(), is(200));
+ assertJsonResponseEquals(nodeHttpResponse, "{" +
+ "\"converged\":true," +
+ "\"debug\":{\"wantedGeneration\":3," +
+ "\"currentGeneration\":3," +
+ "\"host\":\"localhost:1337\"}," +
+ "\"url\":\"http://foo:234/serviceconvergence\"}");
+ final HttpResponse hostMissingHttpResponse = checker.nodeConvergenceCheck(application, "notPresent:1337", URI.create("http://foo:234/serviceconvergence"));
+ assertThat(hostMissingHttpResponse.getStatus(), is(410));
+ assertJsonResponseEquals(hostMissingHttpResponse, "{\"debug\":{" +
+ "\"problem\":\"Host:port (service) no longer part of application, refetch list of services.\"," +
+ "\"wantedGeneration\":3," +
+ "\"host\":\"notPresent:1337\"}," +
+ "\"url\":\"http://foo:234/serviceconvergence\"}");
+ }
+
+ // When config server constantly redeploys applications we might end up with a higher version than expected, which is OK
+ @Test
+ public void convergeGenerationIsLargerThanExpected() throws IOException, SAXException {
+ ApplicationConvergenceChecker checker = new ApplicationConvergenceChecker((client, serviceUri) -> () -> string2json("{\"config\":{\"generation\":4}}"));
+ checker.waitForConfigConverged(application, new TimeoutBudget(Clock.systemUTC(), Duration.ofSeconds(1)));
+ }
+
+ private JsonNode string2json(String data) {
+ try {
+ return mapper.readTree(data);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Test
+ public void convergeFailure() throws IOException {
+ ApplicationConvergenceChecker checker = new ApplicationConvergenceChecker((client, serviceUri) -> () -> string2json("{\"config\":{\"generation\":2}}"));
+ try {
+ checker.waitForConfigConverged(application, new TimeoutBudget(Clock.systemUTC(), Duration.ofSeconds(1)));
+ fail("Converge should fail due to config generation not being updated");
+ } catch (ConfigNotConvergedException e) {
+ assertThat(e.getMessage(), is("Timed out waiting for service to use config generation 3 (checking http://localhost:1337/state/v1/config), generation was 2."));
+ }
+ final HttpResponse nodeHttpResponse = checker.nodeConvergenceCheck(application, "localhost:1337", URI.create("http://foo:234/serviceconvergence"));
+ assertThat(nodeHttpResponse.getStatus(), is(200));
+ assertJsonResponseEquals(nodeHttpResponse, "{" +
+ "\"converged\":false," +
+ "\"debug\":{\"wantedGeneration\":3,\"currentGeneration\":2,\"host\":\"localhost:1337\" }," +
+ "\"url\":\"http://foo:234/serviceconvergence\"}");
+ }
+
+ @Test
+ public void stateApiFailure() throws IOException {
+ ApplicationConvergenceChecker checker = new ApplicationConvergenceChecker((client, serviceUri) -> () -> string2json("{\"config\":{}}"));
+ try {
+ checker.waitForConfigConverged(application, new TimeoutBudget(Clock.systemUTC(), Duration.ofSeconds(1)));
+ fail("Converge should fail due to config generation not being updated");
+ } catch (ConfigNotConvergedException e) {
+ assertThat(e.getMessage(), is("Timed out waiting for service to use config generation 3 (checking http://localhost:1337/state/v1/config), could not connect."));
+ }
+ }
+
+ private static class MockModel implements Model {
+ private final int statePort;
+ public MockModel(int statePort) {
+ this.statePort = statePort;
+ }
+
+ @Override
+ public ConfigPayload getConfig(ConfigKey<?> configKey, ConfigDefinition targetDef, ConfigPayload override) throws IOException {
+ if (configKey.equals(new ConfigKey<>(ModelConfig.class, ""))) {
+ return createModelConfig();
+ }
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ConfigPayload getConfig(ConfigKey<?> configKey, InnerCNode targetDef, ConfigPayload override) throws IOException {
+ return getConfig(configKey, (ConfigDefinition)null, override);
+ }
+
+ private ConfigPayload createModelConfig() {
+ ModelConfig.Builder builder = new ModelConfig.Builder();
+ ModelConfig.Hosts.Builder hostBuilder = new ModelConfig.Hosts.Builder();
+ hostBuilder.name("localhost");
+ ModelConfig.Hosts.Services.Builder serviceBuilder = new ModelConfig.Hosts.Services.Builder();
+ serviceBuilder.type("container");
+ serviceBuilder.ports(new ModelConfig.Hosts.Services.Ports.Builder().number(statePort).tags("state"));
+ hostBuilder.services(serviceBuilder);
+ builder.hosts(hostBuilder);
+ ModelConfig config = new ModelConfig(builder);
+ return ConfigPayload.fromInstance(config);
+ }
+
+ @Override
+ public Set<ConfigKey<?>> allConfigsProduced() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Collection<HostInfo> getHosts() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Set<String> allConfigIds() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void distributeFiles(FileDistribution fileDistribution) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Optional<ProvisionInfo> getProvisionInfo() {
+ throw new UnsupportedOperationException();
+ }
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/application/ApplicationRepoTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/application/ApplicationRepoTest.java
new file mode 100644
index 00000000000..eea951e5c9c
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/application/ApplicationRepoTest.java
@@ -0,0 +1,191 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.application;
+
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.path.Path;
+import com.yahoo.text.Utf8;
+import com.yahoo.vespa.config.server.MockReloadHandler;
+import com.yahoo.vespa.config.server.TestWithCurator;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.*;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class ApplicationRepoTest extends TestWithCurator {
+
+ @Test
+ public void require_that_applications_are_read_from_zookeeper() throws Exception {
+ curatorFramework.create().creatingParentsIfNeeded().forPath("/foo:dev:baz:bim", Utf8.toAsciiBytes(3));
+ curatorFramework.create().creatingParentsIfNeeded().forPath("/bar:test:bim:quux", Utf8.toAsciiBytes(4));
+ curatorFramework.create().creatingParentsIfNeeded().forPath("/bario:staging:bala:bong", Utf8.toAsciiBytes(5));
+ ApplicationRepo repo = createZKAppRepo();
+ List<ApplicationId> applications = repo.listApplications();
+ assertThat(applications.size(), is(3));
+ assertThat(applications.get(0).application().value(), is("bario"));
+ assertThat(applications.get(1).application().value(), is("bar"));
+ assertThat(applications.get(2).application().value(), is("foo"));
+ assertThat(repo.getSessionIdForApplication(applications.get(0)), is(5l));
+ assertThat(repo.getSessionIdForApplication(applications.get(1)), is(4l));
+ assertThat(repo.getSessionIdForApplication(applications.get(2)), is(3l));
+ }
+
+ @Test
+ public void require_that_legacy_application_ids_are_rewritten() throws Exception {
+ curatorFramework.create().creatingParentsIfNeeded().forPath("/foo:default:baz:bim", Utf8.toAsciiBytes(3));
+ curatorFramework.create().creatingParentsIfNeeded().forPath("/bar:test:bim:quux", Utf8.toAsciiBytes(4));
+ ApplicationRepo repo = createZKAppRepo();
+ List<ApplicationId> applications = repo.listApplications();
+ assertThat(applications.size(), is(2));
+ assertThat(applications.get(0).application().value(), is("bar"));
+ assertThat(applications.get(1).application().value(), is("foo"));
+ assertThat(applications.get(0).instance().value(), is("quux"));
+ assertThat(applications.get(1).instance().value(), is("bim"));
+ assertNotNull(curatorFramework.checkExists().forPath("/mytenant:foo:bim"));
+ assertNotNull(curatorFramework.checkExists().forPath("/mytenant:foo:bim"));
+ assertThat(repo.getSessionIdForApplication(applications.get(0)), is(4l));
+ assertThat(repo.getSessionIdForApplication(applications.get(1)), is(3l));
+ }
+
+ @Test
+ public void require_that_legacy_application_ids_are_ignored() throws Exception {
+ curatorFramework.create().creatingParentsIfNeeded().forPath("/foo:default:baz:bim", Utf8.toAsciiBytes(3));
+ curatorFramework.create().creatingParentsIfNeeded().forPath("/foo:prod:baz:bim", Utf8.toAsciiBytes(3));
+ curatorFramework.create().creatingParentsIfNeeded().forPath("/bar:test:bim:quux", Utf8.toAsciiBytes(4));
+ ApplicationRepo repo = createZKAppRepo();
+ List<ApplicationId> applications = repo.listApplications();
+ assertThat(applications.size(), is(2));
+ assertThat(repo.getSessionIdForApplication(applications.get(0)), is(4l));
+ assertThat(repo.getSessionIdForApplication(applications.get(1)), is(3l));
+ }
+
+ @Test
+ public void require_that_invalid_entries_are_skipped() throws Exception {
+ curatorFramework.create().creatingParentsIfNeeded().forPath("/foo:dev:baz:bim");
+ curatorFramework.create().creatingParentsIfNeeded().forPath("/invalid");
+ ApplicationRepo repo = createZKAppRepo();
+ List<ApplicationId> applications = repo.listApplications();
+ assertThat(applications.size(), is(1));
+ assertThat(applications.get(0).application().value(), is("foo"));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void require_that_requesting_session_for_unknown_application_throws_exception() throws Exception {
+ curatorFramework.create().creatingParentsIfNeeded().forPath("/foo:dev:baz:bim");
+ ApplicationRepo repo = createZKAppRepo();
+ repo.getSessionIdForApplication(new ApplicationId.Builder()
+ .tenant("exist")
+ .applicationName("tenant").instanceName("here").build());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void require_that_requesting_session_for_empty_application_throws_exception() throws Exception {
+ curatorFramework.create().creatingParentsIfNeeded().forPath("/foo:dev:baz:bim");
+ ApplicationRepo repo = createZKAppRepo();
+ repo.getSessionIdForApplication(new ApplicationId.Builder()
+ .tenant("tenant")
+ .applicationName("foo").instanceName("bim").build());
+ }
+
+ @Test
+ public void require_that_application_ids_can_be_written() throws Exception {
+ ApplicationRepo repo = createZKAppRepo();
+ repo.createPutApplicationTransaction(createAppplicationId("myapp"), 3l).commit();
+ String path = "/mytenant:myapp:myinst";
+ assertTrue(curatorFramework.checkExists().forPath(path) != null);
+ assertThat(Utf8.toString(curatorFramework.getData().forPath(path)), is("3"));
+ repo.createPutApplicationTransaction(createAppplicationId("myapp"), 5l).commit();
+ assertTrue(curatorFramework.checkExists().forPath(path) != null);
+ assertThat(Utf8.toString(curatorFramework.getData().forPath(path)), is("5"));
+ }
+
+ @Test
+ public void require_that_application_ids_can_be_deleted() throws Exception {
+ ApplicationRepo repo = createZKAppRepo();
+ ApplicationId id1 = createAppplicationId("myapp");
+ ApplicationId id2 = createAppplicationId("myapp2");
+ repo.createPutApplicationTransaction(id1, 1).commit();
+ repo.createPutApplicationTransaction(id2, 1).commit();
+ assertThat(repo.listApplications().size(), is(2));
+ repo.deleteApplication(id1);
+ assertThat(repo.listApplications().size(), is(1));
+ repo.deleteApplication(id2);
+ assertThat(repo.listApplications().size(), is(0));
+ repo.deleteApplication(id2);
+ assertThat(repo.listApplications().size(), is(0));
+ }
+
+ @Test
+ public void require_that_repos_behave_similarly() throws Exception {
+ ApplicationRepo zkRepo = createZKAppRepo();
+ ApplicationRepo memRepo = new MemoryApplicationRepo();
+ for (ApplicationRepo repo : Arrays.asList(zkRepo, memRepo)) {
+ ApplicationId id1 = createAppplicationId("myapp");
+ ApplicationId id2 = createAppplicationId("myapp2");
+ repo.createPutApplicationTransaction(id1, 4).commit();
+ repo.createPutApplicationTransaction(id2, 5).commit();
+ List<ApplicationId> lst = repo.listApplications();
+ Collections.sort(lst);
+ assertThat(lst.size(), is(2));
+ assertThat(lst.get(0).application(), is(id1.application()));
+ assertThat(lst.get(1).application(), is(id2.application()));
+ assertThat(repo.getSessionIdForApplication(id1), is(4l));
+ assertThat(repo.getSessionIdForApplication(id2), is(5l));
+ repo.createPutApplicationTransaction(id1, 6).commit();
+ lst = repo.listApplications();
+ Collections.sort(lst);
+ assertThat(lst.size(), is(2));
+ assertThat(lst.get(0).application(), is(id1.application()));
+ assertThat(lst.get(1).application(), is(id2.application()));
+ assertThat(repo.getSessionIdForApplication(id1), is(6l));
+ assertThat(repo.getSessionIdForApplication(id2), is(5l));
+ repo.deleteApplication(id1);
+ assertThat(repo.listApplications().size(), is(1));
+ repo.deleteApplication(id2);
+ assertThat(repo.listApplications().size(), is(0));
+ repo.deleteApplication(id2);
+ }
+ }
+
+ @Test
+ public void require_that_reload_handler_is_called_when_apps_are_removed() throws Exception {
+ curatorFramework.create().creatingParentsIfNeeded().forPath("/foo:test:baz:bim", Utf8.toAsciiBytes(3));
+ curatorFramework.create().creatingParentsIfNeeded().forPath("/bar:dev:bim:quux", Utf8.toAsciiBytes(4));
+ curatorFramework.create().creatingParentsIfNeeded().forPath("/bario:staging:bala:bong", Utf8.toAsciiBytes(5));
+ MockReloadHandler reloadHandler = new MockReloadHandler();
+ ApplicationRepo repo = createZKAppRepo(reloadHandler);
+ assertNull(reloadHandler.lastRemoved);
+ repo.deleteApplication(new ApplicationId.Builder()
+ .tenant("mytenant")
+ .applicationName("bar").instanceName("quux").build());
+ long endTime = System.currentTimeMillis() + 60_000;
+ while (System.currentTimeMillis() < endTime && reloadHandler.lastRemoved == null) {
+ Thread.sleep(100);
+ }
+ assertNotNull(reloadHandler.lastRemoved);
+ assertThat(reloadHandler.lastRemoved.serializedForm(), is("mytenant:bar:quux"));
+ }
+
+ private ApplicationRepo createZKAppRepo() {
+ return createZKAppRepo(new MockReloadHandler());
+ }
+
+ private ApplicationRepo createZKAppRepo(MockReloadHandler reloadHandler) {
+ return ZKApplicationRepo.create(curator, Path.createRoot(), reloadHandler, TenantName.from("mytenant"));
+ }
+
+ private static ApplicationId createAppplicationId(String name) {
+ return new ApplicationId.Builder()
+ .tenant("mytenant")
+ .applicationName(name).instanceName("myinst").build();
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/application/ApplicationTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/application/ApplicationTest.java
new file mode 100644
index 00000000000..87313ed5b42
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/application/ApplicationTest.java
@@ -0,0 +1,155 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.application;
+
+import com.yahoo.cloud.config.ModelConfig;
+import com.yahoo.cloud.config.SlobroksConfig;
+import com.yahoo.cloud.config.log.LogdConfig;
+import com.yahoo.config.SimpletypesConfig;
+import com.yahoo.config.model.application.provider.FilesApplicationPackage;
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.ApplicationName;
+import com.yahoo.config.provision.InstanceName;
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.config.provision.Version;
+import com.yahoo.jrt.Request;
+import com.yahoo.vespa.config.ConfigDefinitionKey;
+import com.yahoo.vespa.config.ConfigKey;
+import com.yahoo.vespa.config.GetConfigRequest;
+import com.yahoo.vespa.config.protocol.CompressionType;
+import com.yahoo.vespa.config.protocol.ConfigResponse;
+import com.yahoo.vespa.config.protocol.DefContent;
+import com.yahoo.vespa.config.protocol.JRTClientConfigRequestV3;
+import com.yahoo.vespa.config.protocol.JRTServerConfigRequestV3;
+import com.yahoo.vespa.config.protocol.Trace;
+import com.yahoo.vespa.config.server.ModelStub;
+import com.yahoo.vespa.config.server.ServerCache;
+import com.yahoo.vespa.config.server.UnknownConfigDefinitionException;
+import com.yahoo.vespa.config.server.monitoring.MetricUpdater;
+import com.yahoo.vespa.config.server.monitoring.Metrics;
+import com.yahoo.vespa.model.VespaModel;
+import org.junit.Before;
+import org.junit.Test;
+import org.xml.sax.SAXException;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.Optional;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author lulf
+ * @since 5.1.14
+ */
+public class ApplicationTest {
+
+ @Test
+ public void testThatApplicationIsInitialized() throws IOException, SAXException {
+ ApplicationId appId = ApplicationId.from(TenantName.defaultName(),
+ ApplicationName.from("foobar"), InstanceName.defaultName());
+ ServerCache cache = new ServerCache();
+ Version vespaVersion = Version.fromIntValues(1, 2, 3);
+ Application app = new Application(new ModelStub(), cache, 1337, vespaVersion, MetricUpdater.createTestUpdater(), appId);
+ assertThat(app.getApplicationGeneration(), is(1337l));
+ assertNotNull(app.getModel());
+ assertThat(app.getCache(), is(cache));
+ assertThat(app.getName(), is("foobar"));
+ assertThat(app.getVespaVersion(), is(vespaVersion));
+ assertThat(app.toString(), is("application 'foobar', generation 1337, vespa version 1.2.3"));
+ }
+
+ private static final String[] emptySchema = new String[0];
+
+ private Application handler;
+
+ @Before
+ public void setupHandler() throws IOException, SAXException {
+ File testApp = new File("src/test/apps/app");
+ ServerCache cache = createCacheAndAddContent();
+ VespaModel model = new VespaModel(FilesApplicationPackage.fromFile(testApp));
+ final ApplicationId applicationId = new ApplicationId.Builder().tenant("foo").applicationName("foo").build();
+ handler = new Application(model, cache, 1, Version.fromIntValues(1, 2, 3),
+ new MetricUpdater(Metrics.createTestMetrics(), Metrics.createDimensions(applicationId)), applicationId);
+ }
+
+ private static ServerCache createCacheAndAddContent() {
+ ServerCache cache = new ServerCache();
+
+ final ConfigDefinitionKey key = new ConfigDefinitionKey(SimpletypesConfig.CONFIG_DEF_NAME, SimpletypesConfig.CONFIG_DEF_NAMESPACE);
+ com.yahoo.vespa.config.buildergen.ConfigDefinition def = getDef(key, SimpletypesConfig.CONFIG_DEF_SCHEMA);
+ // TODO Why do we have to use empty def md5 here?
+ cache.addDef(key, def);
+
+ final ConfigDefinitionKey key2 = new ConfigDefinitionKey(SlobroksConfig.CONFIG_DEF_NAME, SlobroksConfig.CONFIG_DEF_NAMESPACE);
+ com.yahoo.vespa.config.buildergen.ConfigDefinition def2 = getDef(key2, SlobroksConfig.CONFIG_DEF_SCHEMA);
+ cache.addDef(key2, def2);
+
+ final ConfigDefinitionKey key3 = new ConfigDefinitionKey(LogdConfig.CONFIG_DEF_NAME, LogdConfig.CONFIG_DEF_NAMESPACE);
+ com.yahoo.vespa.config.buildergen.ConfigDefinition def3 = getDef(key3, LogdConfig.CONFIG_DEF_SCHEMA);
+ cache.addDef(key3, def3);
+
+ return cache;
+ }
+
+ private static com.yahoo.vespa.config.buildergen.ConfigDefinition getDef(ConfigDefinitionKey key, String[] schema) {
+ return new com.yahoo.vespa.config.buildergen.ConfigDefinition(key.getName(), schema);
+ }
+
+ @Test(expected = UnknownConfigDefinitionException.class)
+ public void require_that_def_file_must_exist() {
+ handler.resolveConfig(createRequest("unknown", "namespace", "a", emptySchema));
+ }
+
+ @Test
+ public void require_that_known_config_defs_are_found() throws IOException, SAXException {
+ handler.resolveConfig(createSimpleConfigRequest(emptySchema));
+ }
+
+ @Test
+ public void require_that_build_config_can_be_resolved() throws IOException, SAXException {
+ List<String> payload = handler.resolveConfig(createRequest(ModelConfig.CONFIG_DEF_NAME, ModelConfig.CONFIG_DEF_NAMESPACE, ModelConfig.CONFIG_DEF_MD5, ModelConfig.CONFIG_DEF_SCHEMA)).getLegacyPayload();
+ assertTrue(payload.get(1).contains("host"));
+ }
+
+ @Test
+ public void require_that_non_existent_fields_in_schema_is_skipped() throws IOException, SAXException {
+ // Ask for config without schema and check that we get correct default value back
+ List<String> payload = handler.resolveConfig(createSimpleConfigRequest(emptySchema)).getLegacyPayload();
+ assertThat(payload.get(0), is("boolval false"));
+ // Ask for config with wrong schema
+ String[] schema = new String[1];
+ schema[0] = "boolval bool default=true"; // changed to be true, original is false
+ payload = handler.resolveConfig(createRequest(SimpletypesConfig.CONFIG_DEF_NAME, SimpletypesConfig.CONFIG_DEF_NAMESPACE, "", schema)).getLegacyPayload();
+ assertThat(payload.size(), is(1));
+ assertThat(payload.get(0), is("boolval true"));
+ }
+
+ @Test
+ public void require_that_configs_are_cached() {
+ ConfigResponse response = handler.resolveConfig(createRequest(ModelConfig.CONFIG_DEF_NAME, ModelConfig.CONFIG_DEF_NAMESPACE, ModelConfig.CONFIG_DEF_MD5, ModelConfig.CONFIG_DEF_SCHEMA));
+ assertNotNull(response);
+ ConfigResponse cached_response = handler.resolveConfig(createRequest(ModelConfig.CONFIG_DEF_NAME, ModelConfig.CONFIG_DEF_NAMESPACE, ModelConfig.CONFIG_DEF_MD5, ModelConfig.CONFIG_DEF_SCHEMA));
+ assertNotNull(cached_response);
+ assertTrue(response == cached_response);
+ }
+
+ private static GetConfigRequest createRequest(String name, String namespace, String defMd5, String[] schema, String configId) {
+ Request request = JRTClientConfigRequestV3.
+ createWithParams(new ConfigKey<>(name, configId, namespace, defMd5, null), DefContent.fromArray(schema),
+ "fromHost", "", 0, 100, Trace.createDummy(), CompressionType.UNCOMPRESSED,
+ Optional.empty()).getRequest();
+ return JRTServerConfigRequestV3.createFromRequest(request);
+ }
+
+ private static GetConfigRequest createRequest(String name, String namespace, String defMd5, String[] schema) {
+ return createRequest(name, namespace, defMd5, schema, "admin/model");
+ }
+
+ private static GetConfigRequest createSimpleConfigRequest(String[] schema) {
+ return createRequest(SimpletypesConfig.CONFIG_DEF_NAME, SimpletypesConfig.CONFIG_DEF_NAMESPACE, SimpletypesConfig.CONFIG_DEF_MD5, schema);
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/application/MemoryApplicationRepo.java b/configserver/src/test/java/com/yahoo/vespa/config/server/application/MemoryApplicationRepo.java
new file mode 100644
index 00000000000..c725775e467
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/application/MemoryApplicationRepo.java
@@ -0,0 +1,58 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.application;
+
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.transaction.Transaction;
+import com.yahoo.vespa.config.server.session.DummyTransaction;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * In memory {@link ApplicationRepo} to be used when testing.
+ *
+ * @author lulf
+ * @since 5.1
+ */
+public class MemoryApplicationRepo implements ApplicationRepo {
+ private final Map<ApplicationId, Long> applications = new LinkedHashMap<>();
+ private boolean isOpen = true;
+
+ @Override
+ public List<ApplicationId> listApplications() {
+ List<ApplicationId> lst = new ArrayList<>();
+ lst.addAll(applications.keySet());
+ return lst;
+ }
+
+ @Override
+ public Transaction createPutApplicationTransaction(ApplicationId applicationId, long sessionId) {
+ return new DummyTransaction().add((DummyTransaction.RunnableOperation) () -> {
+ applications.put(applicationId, sessionId);
+ });
+ }
+
+ @Override
+ public long getSessionIdForApplication(ApplicationId id) {
+ if (applications.containsKey(id)) {
+ return applications.get(id);
+ }
+ return 0;
+ }
+
+ @Override
+ public void deleteApplication(ApplicationId id) {
+ applications.remove(id);
+ }
+
+ @Override
+ public void close() {
+ isOpen = false;
+ }
+
+ public boolean isOpen() {
+ return isOpen;
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/application/PermanentApplicationPackageTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/application/PermanentApplicationPackageTest.java
new file mode 100644
index 00000000000..a710384701f
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/application/PermanentApplicationPackageTest.java
@@ -0,0 +1,35 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.application;
+
+import com.yahoo.cloud.config.ConfigserverConfig;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author lulf
+ * @since 5.15
+ */
+public class PermanentApplicationPackageTest {
+ @Test
+ public void testNonexistingApplication() {
+ PermanentApplicationPackage permanentApplicationPackage = new PermanentApplicationPackage(new ConfigserverConfig(new ConfigserverConfig.Builder().applicationDirectory("_no_such_dir")));
+ assertFalse(permanentApplicationPackage.applicationPackage().isPresent());
+ }
+
+ @Rule
+ public TemporaryFolder folder = new TemporaryFolder();
+
+ @Test
+ public void testExistingApplication() throws IOException {
+ File tmpDir = folder.newFolder();
+ PermanentApplicationPackage permanentApplicationPackage = new PermanentApplicationPackage(new ConfigserverConfig(new ConfigserverConfig.Builder().applicationDirectory(tmpDir.getAbsolutePath())));
+ assertTrue(permanentApplicationPackage.applicationPackage().isPresent());
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/ConfigChangeActionsBuilder.java b/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/ConfigChangeActionsBuilder.java
new file mode 100644
index 00000000000..c99561fe7d2
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/ConfigChangeActionsBuilder.java
@@ -0,0 +1,43 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.configchange;
+
+import com.google.common.collect.ImmutableMap;
+import com.yahoo.config.model.api.ConfigChangeAction;
+import com.yahoo.config.model.api.ServiceInfo;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @author geirst
+ * @since 5.44
+ */
+public class ConfigChangeActionsBuilder {
+
+ private final List<ConfigChangeAction> actions = new ArrayList<>();
+
+ private static ServiceInfo createService(String clusterName, String clusterType, String serviceType, String serviceName) {
+ return new ServiceInfo(serviceName, serviceType, null,
+ ImmutableMap.of("clustername", clusterName, "clustertype", clusterType),
+ serviceType + "/" + serviceName, "hostname");
+ }
+
+ public ConfigChangeActionsBuilder restart(String message, String clusterName, String clusterType, String serviceType, String serviceName) {
+ actions.add(new MockRestartAction(message,
+ Arrays.asList(createService(clusterName, clusterType, serviceType, serviceName))));
+ return this;
+ }
+
+ public ConfigChangeActionsBuilder refeed(String name, boolean allowed, String message, String documentType, String clusterName, String serviceName) {
+ actions.add(new MockRefeedAction(name,
+ allowed,
+ message,
+ Arrays.asList(createService(clusterName, "myclustertype", "myservicetype", serviceName)), documentType));
+ return this;
+ }
+
+ public ConfigChangeActions build() {
+ return new ConfigChangeActions(actions);
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/ConfigChangeActionsSlimeConverterTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/ConfigChangeActionsSlimeConverterTest.java
new file mode 100644
index 00000000000..84c69fef3a1
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/ConfigChangeActionsSlimeConverterTest.java
@@ -0,0 +1,135 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.configchange;
+
+import com.yahoo.slime.Cursor;
+import com.yahoo.slime.JsonFormat;
+import com.yahoo.slime.Slime;
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import static org.junit.Assert.assertEquals;
+import static com.yahoo.vespa.config.server.configchange.Utils.*;
+
+/**
+ * @author geirst
+ * @since 5.44
+ */
+public class ConfigChangeActionsSlimeConverterTest {
+
+ private static String toJson(ConfigChangeActions actions) throws IOException {
+ Slime slime = new Slime();
+ Cursor root = slime.setObject();
+ new ConfigChangeActionsSlimeConverter(actions).toSlime(root);
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ new JsonFormat(false).encode(outputStream, slime);
+ return outputStream.toString();
+ }
+
+ @Test
+ public void json_representation_of_empty_actions() throws IOException {
+ ConfigChangeActions actions = new ConfigChangeActionsBuilder().build();
+ assertEquals( "{\n" +
+ " \"configChangeActions\": {\n" +
+ " \"restart\": [\n" +
+ " ],\n" +
+ " \"refeed\": [\n" +
+ " ]\n" +
+ " }\n" +
+ "}\n",
+ toJson(actions));
+ }
+
+ @Test
+ public void json_representation_of_restart_actions() throws IOException {
+ ConfigChangeActions actions = new ConfigChangeActionsBuilder().
+ restart(CHANGE_MSG, CLUSTER, CLUSTER_TYPE, SERVICE_TYPE, SERVICE_NAME).
+ restart(CHANGE_MSG, CLUSTER, CLUSTER_TYPE, SERVICE_TYPE, SERVICE_NAME_2).
+ restart(CHANGE_MSG_2, CLUSTER, CLUSTER_TYPE, SERVICE_TYPE, SERVICE_NAME).
+ restart(CHANGE_MSG_2, CLUSTER, CLUSTER_TYPE, SERVICE_TYPE, SERVICE_NAME_2).build();
+ assertEquals("{\n" +
+ " \"configChangeActions\": {\n" +
+ " \"restart\": [\n" +
+ " {\n" +
+ " \"clusterName\": \"foo\",\n" +
+ " \"clusterType\": \"search\",\n" +
+ " \"serviceType\": \"searchnode\",\n" +
+ " \"messages\": [\n" +
+ " \"change\",\n" +
+ " \"other change\"\n" +
+ " ],\n" +
+ " \"services\": [\n" +
+ " {\n" +
+ " \"serviceName\": \"baz\",\n" +
+ " \"serviceType\": \"searchnode\",\n" +
+ " \"configId\": \"searchnode/baz\",\n" +
+ " \"hostName\": \"hostname\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"serviceName\": \"qux\",\n" +
+ " \"serviceType\": \"searchnode\",\n" +
+ " \"configId\": \"searchnode/qux\",\n" +
+ " \"hostName\": \"hostname\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ],\n" +
+ " \"refeed\": [\n" +
+ " ]\n" +
+ " }\n" +
+ "}\n",
+ toJson(actions));
+ }
+
+ @Test
+ public void json_representation_of_refeed_actions() throws IOException {
+ ConfigChangeActions actions = new ConfigChangeActionsBuilder().
+ refeed(CHANGE_ID, true, CHANGE_MSG, DOC_TYPE, CLUSTER, SERVICE_TYPE).
+ refeed(CHANGE_ID_2, false, CHANGE_MSG, DOC_TYPE_2, CLUSTER, SERVICE_TYPE).build();
+ assertEquals("{\n" +
+ " \"configChangeActions\": {\n" +
+ " \"restart\": [\n" +
+ " ],\n" +
+ " \"refeed\": [\n" +
+ " {\n" +
+ " \"name\": \"change-id\",\n" +
+ " \"allowed\": true,\n" +
+ " \"documentType\": \"music\",\n" +
+ " \"clusterName\": \"foo\",\n" +
+ " \"messages\": [\n" +
+ " \"change\"\n" +
+ " ],\n" +
+ " \"services\": [\n" +
+ " {\n" +
+ " \"serviceName\": \"searchnode\",\n" +
+ " \"serviceType\": \"myservicetype\",\n" +
+ " \"configId\": \"myservicetype/searchnode\",\n" +
+ " \"hostName\": \"hostname\"\n" +
+ " }\n" +
+ " ]\n" +
+ " },\n" +
+ " {\n" +
+ " \"name\": \"other-change-id\",\n" +
+ " \"allowed\": false,\n" +
+ " \"documentType\": \"book\",\n" +
+ " \"clusterName\": \"foo\",\n" +
+ " \"messages\": [\n" +
+ " \"change\"\n" +
+ " ],\n" +
+ " \"services\": [\n" +
+ " {\n" +
+ " \"serviceName\": \"searchnode\",\n" +
+ " \"serviceType\": \"myservicetype\",\n" +
+ " \"configId\": \"myservicetype/searchnode\",\n" +
+ " \"hostName\": \"hostname\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ "}\n",
+ toJson(actions));
+ }
+
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/MockConfigChangeAction.java b/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/MockConfigChangeAction.java
new file mode 100644
index 00000000000..88be73bedba
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/MockConfigChangeAction.java
@@ -0,0 +1,32 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.configchange;
+
+import com.yahoo.config.model.api.ConfigChangeAction;
+import com.yahoo.config.model.api.ServiceInfo;
+
+import java.util.List;
+
+/**
+ * @author geirst
+ * @since 5.44
+ */
+public abstract class MockConfigChangeAction implements ConfigChangeAction {
+
+ private final String message;
+ private final List<ServiceInfo> services;
+
+ protected MockConfigChangeAction(String message, List<ServiceInfo> services) {
+ this.message = message;
+ this.services = services;
+ }
+
+ @Override
+ public String getMessage() {
+ return message;
+ }
+
+ @Override
+ public List<ServiceInfo> getServices() {
+ return services;
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/MockRefeedAction.java b/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/MockRefeedAction.java
new file mode 100644
index 00000000000..9043b90b15f
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/MockRefeedAction.java
@@ -0,0 +1,35 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.configchange;
+
+import com.yahoo.config.model.api.ConfigChangeRefeedAction;
+import com.yahoo.config.model.api.ServiceInfo;
+
+import java.util.List;
+
+/**
+ * @author geirst
+ * @since 5.44
+ */
+public class MockRefeedAction extends MockConfigChangeAction implements ConfigChangeRefeedAction {
+
+ private final String name;
+ private final boolean allowed;
+ private final String documentType;
+
+ public MockRefeedAction(String name, boolean allowed, String message, List<ServiceInfo> services, String documentType) {
+ super(message, services);
+ this.name = name;
+ this.allowed = allowed;
+ this.documentType = documentType;
+ }
+
+ @Override
+ public String name() { return name; }
+
+ @Override
+ public boolean allowed() { return allowed; }
+
+ @Override
+ public String getDocumentType() { return documentType; }
+
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/MockRestartAction.java b/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/MockRestartAction.java
new file mode 100644
index 00000000000..1d546ae65c0
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/MockRestartAction.java
@@ -0,0 +1,17 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.configchange;
+
+import com.yahoo.config.model.api.ConfigChangeRestartAction;
+import com.yahoo.config.model.api.ServiceInfo;
+
+import java.util.List;
+
+/**
+ * @author geirst
+ * @since 5.44
+ */
+public class MockRestartAction extends MockConfigChangeAction implements ConfigChangeRestartAction {
+ public MockRestartAction(String message, List<ServiceInfo> services) {
+ super(message, services);
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/RefeedActionsFormatterTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/RefeedActionsFormatterTest.java
new file mode 100644
index 00000000000..cf4dda7d090
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/RefeedActionsFormatterTest.java
@@ -0,0 +1,46 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.configchange;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static com.yahoo.vespa.config.server.configchange.Utils.*;
+
+/**
+ * @author geirst
+ * @since 5.44
+ */
+public class RefeedActionsFormatterTest {
+
+ @Test
+ public void formatting_of_single_action() {
+ RefeedActions actions = new ConfigChangeActionsBuilder().
+ refeed(CHANGE_ID, false, CHANGE_MSG, DOC_TYPE, CLUSTER, SERVICE_NAME).
+ build().getRefeedActions();
+ assertEquals("change-id: Consider removing data and re-feed document type 'music' in cluster 'foo' because:\n" +
+ " 1) change\n",
+ new RefeedActionsFormatter(actions).format());
+ }
+
+ @Test
+ public void formatting_of_multiple_actions() {
+ RefeedActions actions = new ConfigChangeActionsBuilder().
+ refeed(CHANGE_ID, false, CHANGE_MSG, DOC_TYPE, CLUSTER, SERVICE_NAME).
+ refeed(CHANGE_ID, false, CHANGE_MSG_2, DOC_TYPE, CLUSTER, SERVICE_NAME).
+ refeed(CHANGE_ID_2, false, CHANGE_MSG_2, DOC_TYPE, CLUSTER, SERVICE_NAME).
+ refeed(CHANGE_ID_2, true, CHANGE_MSG_2, DOC_TYPE, CLUSTER, SERVICE_NAME).
+ refeed(CHANGE_ID, false, CHANGE_MSG_2, DOC_TYPE_2, CLUSTER, SERVICE_NAME).
+ build().getRefeedActions();
+ assertEquals("change-id: Consider removing data and re-feed document type 'book' in cluster 'foo' because:\n" +
+ " 1) other change\n" +
+ "change-id: Consider removing data and re-feed document type 'music' in cluster 'foo' because:\n" +
+ " 1) change\n" +
+ " 2) other change\n" +
+ "other-change-id: Consider removing data and re-feed document type 'music' in cluster 'foo' because:\n" +
+ " 1) other change\n" +
+ "(allowed) other-change-id: Consider removing data and re-feed document type 'music' in cluster 'foo' because:\n" +
+ " 1) other change\n",
+ new RefeedActionsFormatter(actions).format());
+ }
+
+} \ No newline at end of file
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/RefeedActionsTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/RefeedActionsTest.java
new file mode 100644
index 00000000000..f1bb48eef21
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/RefeedActionsTest.java
@@ -0,0 +1,74 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.configchange;
+
+import org.junit.Test;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.assertThat;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static com.yahoo.vespa.config.server.configchange.Utils.*;
+
+/**
+ * @author geirst
+ * @since 5.44
+ */
+public class RefeedActionsTest {
+
+ private String toString(RefeedActions.Entry entry) {
+ StringBuilder builder = new StringBuilder();
+ builder.append(entry.getDocumentType() + "." + entry.getClusterName() + ":");
+ builder.append(entry.getServices().stream().
+ map(service -> service.getServiceName()).
+ sorted().
+ collect(Collectors.joining(",", "[", "]")));
+ builder.append(entry.getMessages().stream().
+ collect(Collectors.joining(",", "[", "]")));
+ return builder.toString();
+ }
+
+ @Test
+ public void action_with_multiple_reasons() {
+ List<RefeedActions.Entry> entries = new ConfigChangeActionsBuilder().
+ refeed("change-id", false, CHANGE_MSG, DOC_TYPE, CLUSTER, SERVICE_NAME).
+ refeed("change-id", false, CHANGE_MSG_2, DOC_TYPE, CLUSTER, SERVICE_NAME).
+ build().getRefeedActions().getEntries();
+ assertThat(entries.size(), is(1));
+ assertThat(toString(entries.get(0)), equalTo("music.foo:[baz][change,other change]"));
+ }
+
+ @Test
+ public void actions_with_multiple_services() {
+ List<RefeedActions.Entry> entries = new ConfigChangeActionsBuilder().
+ refeed("change-id", false, CHANGE_MSG, DOC_TYPE, CLUSTER, SERVICE_NAME).
+ refeed("change-id", false, CHANGE_MSG, DOC_TYPE, CLUSTER, SERVICE_NAME_2).
+ build().getRefeedActions().getEntries();
+ assertThat(entries.size(), is(1));
+ assertThat(toString(entries.get(0)), equalTo("music.foo:[baz,qux][change]"));
+ }
+
+ @Test
+ public void actions_with_multiple_document_types() {
+ List<RefeedActions.Entry> entries = new ConfigChangeActionsBuilder().
+ refeed("change-id", false, CHANGE_MSG, DOC_TYPE, CLUSTER, SERVICE_NAME).
+ refeed("change-id", false, CHANGE_MSG, DOC_TYPE_2, CLUSTER, SERVICE_NAME).
+ build().getRefeedActions().getEntries();
+ assertThat(entries.size(), is(2));
+ assertThat(toString(entries.get(0)), equalTo("book.foo:[baz][change]"));
+ assertThat(toString(entries.get(1)), equalTo("music.foo:[baz][change]"));
+ }
+
+ @Test
+ public void actions_with_multiple_clusters() {
+ List<RefeedActions.Entry> entries = new ConfigChangeActionsBuilder().
+ refeed("change-id", false, CHANGE_MSG, DOC_TYPE, CLUSTER, SERVICE_NAME).
+ refeed("change-id", false, CHANGE_MSG, DOC_TYPE, CLUSTER_2, SERVICE_NAME).
+ build().getRefeedActions().getEntries();
+ assertThat(entries.size(), is(2));
+ assertThat(toString(entries.get(0)), equalTo("music.bar:[baz][change]"));
+ assertThat(toString(entries.get(1)), equalTo("music.foo:[baz][change]"));
+ }
+
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/RestartActionsFormatterTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/RestartActionsFormatterTest.java
new file mode 100644
index 00000000000..3363e022034
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/RestartActionsFormatterTest.java
@@ -0,0 +1,44 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.configchange;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertThat;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static com.yahoo.vespa.config.server.configchange.Utils.*;
+
+/**
+ * @author geirst
+ * @since 5.44
+ */
+public class RestartActionsFormatterTest {
+
+ @Test
+ public void formatting_of_single_action() {
+ RestartActions actions = new ConfigChangeActionsBuilder().
+ restart(CHANGE_MSG, CLUSTER, CLUSTER_TYPE, SERVICE_TYPE, SERVICE_NAME).
+ build().getRestartActions();
+ assertThat(new RestartActionsFormatter(actions).format(),
+ equalTo("In cluster 'foo' of type 'search':\n" +
+ " Restart services of type 'searchnode' because:\n" +
+ " 1) change\n"));
+ }
+
+ @Test
+ public void formatting_of_multiple_actions() {
+ RestartActions actions = new ConfigChangeActionsBuilder().
+ restart(CHANGE_MSG, CLUSTER, CLUSTER_TYPE, SERVICE_TYPE, SERVICE_NAME).
+ restart(CHANGE_MSG_2, CLUSTER, CLUSTER_TYPE, SERVICE_TYPE, SERVICE_NAME).
+ restart(CHANGE_MSG, CLUSTER_2, CLUSTER_TYPE, SERVICE_TYPE, SERVICE_NAME).
+ build().getRestartActions();
+ assertThat(new RestartActionsFormatter(actions).format(),
+ equalTo("In cluster 'bar' of type 'search':\n" +
+ " Restart services of type 'searchnode' because:\n" +
+ " 1) change\n" +
+ "In cluster 'foo' of type 'search':\n" +
+ " Restart services of type 'searchnode' because:\n" +
+ " 1) change\n" +
+ " 2) other change\n"));
+ }
+
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/RestartActionsTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/RestartActionsTest.java
new file mode 100644
index 00000000000..4d866e7e2f6
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/RestartActionsTest.java
@@ -0,0 +1,84 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.configchange;
+
+import org.junit.Test;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.assertThat;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static com.yahoo.vespa.config.server.configchange.Utils.*;
+
+/**
+ * @author geirst
+ * @since 5.44
+ */
+public class RestartActionsTest {
+
+ private String toString(RestartActions.Entry entry) {
+ StringBuilder builder = new StringBuilder();
+ builder.append(entry.getClusterType() + "." + entry.getClusterName() + "." + entry.getServiceType() + ":");
+ builder.append(entry.getServices().stream().
+ map(service -> service.getServiceName()).
+ sorted().
+ collect(Collectors.joining(",", "[", "]")));
+ builder.append(entry.getMessages().stream().
+ collect(Collectors.joining(",", "[", "]")));
+ return builder.toString();
+ }
+
+ @Test
+ public void actions_with_multiple_reasons() {
+ ConfigChangeActions actions = new ConfigChangeActionsBuilder().
+ restart(CHANGE_MSG, CLUSTER, CLUSTER_TYPE, SERVICE_TYPE, SERVICE_NAME).
+ restart(CHANGE_MSG_2, CLUSTER, CLUSTER_TYPE, SERVICE_TYPE, SERVICE_NAME).build();
+ List<RestartActions.Entry> entries = actions.getRestartActions().getEntries();
+ assertThat(entries.size(), is(1));
+ assertThat(toString(entries.get(0)), equalTo("search.foo.searchnode:[baz][change,other change]"));
+ }
+
+ @Test
+ public void actions_with_same_service_type() {
+ ConfigChangeActions actions = new ConfigChangeActionsBuilder().
+ restart(CHANGE_MSG, CLUSTER, CLUSTER_TYPE, SERVICE_TYPE, SERVICE_NAME).
+ restart(CHANGE_MSG, CLUSTER, CLUSTER_TYPE, SERVICE_TYPE, SERVICE_NAME_2).build();
+ List<RestartActions.Entry> entries = actions.getRestartActions().getEntries();
+ assertThat(entries.size(), is(1));
+ assertThat(toString(entries.get(0)), equalTo("search.foo.searchnode:[baz,qux][change]"));
+ }
+
+ @Test
+ public void actions_with_multiple_service_types() {
+ ConfigChangeActions actions = new ConfigChangeActionsBuilder().
+ restart(CHANGE_MSG, CLUSTER, CLUSTER_TYPE, SERVICE_TYPE, SERVICE_NAME).
+ restart(CHANGE_MSG, CLUSTER, CLUSTER_TYPE, SERVICE_TYPE_2, SERVICE_NAME).build();
+ List<RestartActions.Entry> entries = actions.getRestartActions().getEntries();
+ assertThat(entries.size(), is(2));
+ assertThat(toString(entries.get(0)), equalTo("search.foo.distributor:[baz][change]"));
+ assertThat(toString(entries.get(1)), equalTo("search.foo.searchnode:[baz][change]"));
+ }
+
+ @Test
+ public void actions_with_multiple_clusters_of_same_type() {
+ ConfigChangeActions actions = new ConfigChangeActionsBuilder().
+ restart(CHANGE_MSG, CLUSTER, CLUSTER_TYPE, SERVICE_TYPE, SERVICE_NAME).
+ restart(CHANGE_MSG, CLUSTER_2, CLUSTER_TYPE, SERVICE_TYPE, SERVICE_NAME).build();
+ List<RestartActions.Entry> entries = actions.getRestartActions().getEntries();
+ assertThat(entries.size(), is(2));
+ assertThat(toString(entries.get(0)), equalTo("search.bar.searchnode:[baz][change]"));
+ assertThat(toString(entries.get(1)), equalTo("search.foo.searchnode:[baz][change]"));
+ }
+
+ @Test
+ public void actions_with_multiple_clusters_of_different_type() {
+ ConfigChangeActions actions = new ConfigChangeActionsBuilder().
+ restart(CHANGE_MSG, CLUSTER, CLUSTER_TYPE, SERVICE_TYPE, SERVICE_NAME).
+ restart(CHANGE_MSG, CLUSTER, CLUSTER_TYPE_2, SERVICE_TYPE, SERVICE_NAME).build();
+ List<RestartActions.Entry> entries = actions.getRestartActions().getEntries();
+ assertThat(entries.size(), is(2));
+ assertThat(toString(entries.get(0)), equalTo("content.foo.searchnode:[baz][change]"));
+ assertThat(toString(entries.get(1)), equalTo("search.foo.searchnode:[baz][change]"));
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/Utils.java b/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/Utils.java
new file mode 100644
index 00000000000..66772488c60
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/configchange/Utils.java
@@ -0,0 +1,24 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.configchange;
+
+/**
+ * @author geirst
+ * @since 5.44
+ */
+public class Utils {
+
+ final static String CHANGE_ID = "change-id";
+ final static String CHANGE_ID_2 = "other-change-id";
+ final static String CHANGE_MSG = "change";
+ final static String CHANGE_MSG_2 = "other change";
+ final static String DOC_TYPE = "music";
+ final static String DOC_TYPE_2 = "book";
+ final static String CLUSTER = "foo";
+ final static String CLUSTER_2 = "bar";
+ final static String CLUSTER_TYPE = "search";
+ final static String CLUSTER_TYPE_2 = "content";
+ final static String SERVICE_TYPE = "searchnode";
+ final static String SERVICE_TYPE_2 = "distributor";
+ final static String SERVICE_NAME = "baz";
+ final static String SERVICE_NAME_2 = "qux";
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/configdefs/a.def b/configserver/src/test/java/com/yahoo/vespa/config/server/configdefs/a.def
new file mode 100644
index 00000000000..983f16ac932
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/configdefs/a.def
@@ -0,0 +1,60 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+version=1
+storage[].feeder[] string
+search[].feeder[] string
+storage[].id reference
+storage[].id2 reference
+accesslog string default=""
+asyncfetchocc int default=0
+a int default=0
+b int default=0
+functionmodules[] string restart
+c int default=0
+d int default=0
+e int default=0
+kanon double
+testref reference
+testref2 reference
+onlyindef int
+model string
+f[].b string
+f[].a string
+f[].c string
+f[].f string
+f[].h string
+
+# The name of predefined roles.
+config[].role string
+
+## Reference to the config to be used by the role.
+config[].id reference
+
+## Wether the NC should start the corresponding role using the
+## slavewrapper utility application or not.
+config[].usewrapper bool default=false
+
+routingtable[].hop[].name string
+routingtable[].hop[].selector string
+routingtable[].hop[].recipient[] string
+listenport int default=13700
+
+speciallog[].name string
+speciallog[].type string
+speciallog[].filehandler.name string default="THEDEF"
+speciallog[].filehandler.pattern string default="THEDEF.%Y%m%d%H%M%S"
+speciallog[].filehandler.rotation string default="THEDEF0 60 ..."
+speciallog[].cachehandler.name string default="THEDEF"
+speciallog[].cachehandler.size int default=1000
+
+partialsd string default = "def"
+partialsd2 string default = "def2"
+
+rulebase[].name string
+rulebase[].isdefault bool default=false
+rulebase[].automata string default=""
+rulebase[].rules string
+
+mode enum { BATCH, REALTIME, INCREMENTAL} default=BATCH
+rangecheck1 double default=10 range=[-1.6,54]
+rangecheck2 int default=10 range=[1,100]
+rangecheck3 long default=10 range=[9,13]
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/configdefs/b.def b/configserver/src/test/java/com/yahoo/vespa/config/server/configdefs/b.def
new file mode 100644
index 00000000000..706a5c4c4f6
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/configdefs/b.def
@@ -0,0 +1,4 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+version=1
+gaff int default=0
+usercfgwithid int
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/configdefs/c.def b/configserver/src/test/java/com/yahoo/vespa/config/server/configdefs/c.def
new file mode 100644
index 00000000000..61a5fa045d1
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/configdefs/c.def
@@ -0,0 +1,4 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+version=1
+foo string
+gaz int
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/configdefs/compositeinclude.def b/configserver/src/test/java/com/yahoo/vespa/config/server/configdefs/compositeinclude.def
new file mode 100644
index 00000000000..2173d72635b
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/configdefs/compositeinclude.def
@@ -0,0 +1,6 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+version=1
+classes[].id int
+classes[].name string
+classes[].fields[].name string
+classes[].fields[].type string
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/configdefs/d.def b/configserver/src/test/java/com/yahoo/vespa/config/server/configdefs/d.def
new file mode 100644
index 00000000000..69cbeb31342
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/configdefs/d.def
@@ -0,0 +1,4 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+version=1
+thestring string default="g"
+theint int default=6
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/configdefs/e.def b/configserver/src/test/java/com/yahoo/vespa/config/server/configdefs/e.def
new file mode 100644
index 00000000000..ba11bca16cd
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/configdefs/e.def
@@ -0,0 +1,4 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+version=1
+# this one will be implicit, no cfg
+fo int default=-45
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/configdefs/recursiveinclude.def b/configserver/src/test/java/com/yahoo/vespa/config/server/configdefs/recursiveinclude.def
new file mode 100644
index 00000000000..699649ffebf
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/configdefs/recursiveinclude.def
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+version=4
+rec int
+ursive string
+national int
+teatern int
+ilscript[].name string
+ilscript[].doctype string
+ilscript[].content[] string
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/configdefs/spooler.def b/configserver/src/test/java/com/yahoo/vespa/config/server/configdefs/spooler.def
new file mode 100644
index 00000000000..09b5d6718e6
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/configdefs/spooler.def
@@ -0,0 +1,16 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+version=2
+
+# Which directory to find spool files in.
+directory string default="/home/vespa/var/spool/vespa"
+
+# If true, move successfully processed files to <directory>/success
+keepsuccess bool default=false
+
+# Trace level on error messages from messagebus
+tracelevel int default=5
+
+# Which parsers to use and config for each of them.
+parsers[].classname string
+parsers[].parameters[].key string
+parsers[].parameters[].value string
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/HostedDeployTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/HostedDeployTest.java
new file mode 100644
index 00000000000..fbd4f791f83
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/HostedDeployTest.java
@@ -0,0 +1,104 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.deploy;
+
+import com.yahoo.cloud.config.ConfigserverConfig;
+import com.yahoo.config.model.api.HostProvisioner;
+import com.yahoo.config.model.provision.InMemoryProvisioner;
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.ApplicationName;
+import com.yahoo.config.provision.Capacity;
+import com.yahoo.config.provision.ClusterSpec;
+import com.yahoo.config.provision.HostFilter;
+import com.yahoo.config.provision.HostSpec;
+import com.yahoo.config.provision.InstanceName;
+import com.yahoo.config.provision.ProvisionLogger;
+import com.yahoo.config.provision.Provisioner;
+import com.yahoo.path.Path;
+import com.yahoo.transaction.NestedTransaction;
+import com.yahoo.vespa.config.server.TestWithTenant;
+import com.yahoo.vespa.config.server.TimeoutBudget;
+import com.yahoo.vespa.config.server.provision.HostProvisionerProvider;
+import com.yahoo.vespa.config.server.session.LocalSession;
+import com.yahoo.vespa.config.server.session.PrepareParams;
+import com.yahoo.vespa.config.server.session.SilentDeployLogger;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.time.Clock;
+import java.time.Duration;
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author bratseth
+ */
+public class HostedDeployTest extends TestWithTenant {
+
+ private static final Path appPath = Path.createRoot().append("testapp");
+ private File testApp = new File("src/test/apps/hosted/");
+ private Path tenantPath = appPath;
+
+ @Test
+ public void testRedeploy() throws InterruptedException, IOException {
+ ApplicationId id = deployApp();
+
+ Deployer deployer = new Deployer(tenants, HostProvisionerProvider.withProvisioner(createHostProvisioner()),
+ new ConfigserverConfig(new ConfigserverConfig.Builder()), curator);
+
+ Optional<com.yahoo.config.provision.Deployment> deployment = deployer.deployFromLocalActive(id, Duration.ofSeconds(60));
+ assertTrue(deployment.isPresent());
+ deployment.get().prepare();
+ deployment.get().activate();
+ }
+
+ /**
+ * Do the initial "deploy" with the existing API-less code as the deploy API doesn't support first deploys yet.
+ */
+ private ApplicationId deployApp() throws InterruptedException, IOException {
+ LocalSession session = tenant.getSessionFactory().createSession(testApp, "default", new SilentDeployLogger(), new TimeoutBudget(Clock.systemUTC(), Duration.ofSeconds(60)));
+ ApplicationId id = ApplicationId.from(tenant.getName(), ApplicationName.from("myapp"), InstanceName.defaultName());
+ session.prepare(new SilentDeployLogger(), new PrepareParams(new ConfigserverConfig(new ConfigserverConfig.Builder())).applicationId(id), Optional.empty(), tenantPath);
+ session.createActivateTransaction().commit();
+ tenant.getLocalSessionRepo().addSession(session);
+ return id;
+ }
+
+ private Provisioner createHostProvisioner() {
+ return new ProvisionerAdapter(new InMemoryProvisioner(true, "host0", "host1", "host2"));
+ }
+
+ private static class ProvisionerAdapter implements Provisioner {
+
+ private final HostProvisioner hostProvisioner;
+
+ public ProvisionerAdapter(HostProvisioner hostProvisioner) {
+ this.hostProvisioner = hostProvisioner;
+ }
+
+ @Override
+ public List<HostSpec> prepare(ApplicationId applicationId, ClusterSpec cluster, Capacity capacity, int groups, ProvisionLogger logger) {
+ return hostProvisioner.prepare(cluster, capacity, groups, logger);
+ }
+
+ @Override
+ public void activate(NestedTransaction transaction, ApplicationId application, Collection<HostSpec> hosts) {
+ // noop
+ }
+
+ @Override
+ public void removed(ApplicationId application) {
+ // noop
+ }
+
+ @Override
+ public void restart(ApplicationId application, HostFilter filter) {
+ // noop
+ }
+
+ }
+
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/MockDeployer.java b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/MockDeployer.java
new file mode 100644
index 00000000000..0dd01404109
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/MockDeployer.java
@@ -0,0 +1,21 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.deploy;
+
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.Deployment;
+
+import java.time.Duration;
+import java.util.Optional;
+
+/**
+ * @author lulf
+ */
+public class MockDeployer implements com.yahoo.config.provision.Deployer {
+ public ApplicationId lastDeployed;
+
+ @Override
+ public Optional<Deployment> deployFromLocalActive(ApplicationId application, Duration timeout) {
+ lastDeployed = application;
+ return Optional.empty();
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/RedeployTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/RedeployTest.java
new file mode 100644
index 00000000000..6175e8cf1fc
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/RedeployTest.java
@@ -0,0 +1,82 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.deploy;
+
+import com.yahoo.cloud.config.ConfigserverConfig;
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.ApplicationName;
+import com.yahoo.config.provision.InstanceName;
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.path.Path;
+import com.yahoo.vespa.config.server.TestWithTenant;
+import com.yahoo.vespa.config.server.TimeoutBudget;
+import com.yahoo.vespa.config.server.provision.HostProvisionerProvider;
+import com.yahoo.vespa.config.server.session.LocalSession;
+import com.yahoo.vespa.config.server.session.PrepareParams;
+import com.yahoo.vespa.config.server.session.SilentDeployLogger;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.time.Clock;
+import java.time.Duration;
+import java.util.Optional;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests redeploying of an already existing application.
+ *
+ * @author bratseth
+ */
+public class RedeployTest extends TestWithTenant {
+
+ private static final Path appPath = Path.createRoot().append("testapp");
+ private File testApp = new File("src/test/apps/app");
+ private Path tenantPath = appPath;
+
+ @Test
+ public void testRedeploy() throws InterruptedException, IOException {
+ ApplicationId id = deployApp();
+
+ Deployer deployer = new Deployer(tenants, HostProvisionerProvider.empty(),
+ new ConfigserverConfig(new ConfigserverConfig.Builder()), curator);
+
+ Optional<com.yahoo.config.provision.Deployment> deployment = deployer.deployFromLocalActive(id, Duration.ofSeconds(60));
+ assertTrue(deployment.isPresent());
+ long activeSessionIdBefore = tenant.getLocalSessionRepo().getActiveSession(id).getSessionId();
+ assertEquals(id, tenant.getLocalSessionRepo().getSession(activeSessionIdBefore).getApplicationId());
+ deployment.get().prepare();
+ deployment.get().activate();
+ long activeSessionIdAfter = tenant.getLocalSessionRepo().getActiveSession(id).getSessionId();
+ assertEquals(activeSessionIdAfter, activeSessionIdBefore + 1);
+ assertEquals(id, tenant.getLocalSessionRepo().getSession(activeSessionIdAfter).getApplicationId());
+ }
+
+ /** No deploYMENT is done because there isn't a local active session. */
+ @Test
+ public void testNoRedeploy() {
+ ApplicationId id = ApplicationId.from(TenantName.from("default"),
+ ApplicationName.from("default"),
+ InstanceName.from("default"));
+
+ Deployer deployer = new Deployer(tenants, HostProvisionerProvider.empty(),
+ new ConfigserverConfig(new ConfigserverConfig.Builder()), curator);
+
+ assertFalse(deployer.deployFromLocalActive(id, Duration.ofSeconds(60)).isPresent());
+ }
+
+ /**
+ * Do the initial "deploy" with the existing API-less code as the deploy API doesn't support first deploys yet.
+ */
+ private ApplicationId deployApp() throws InterruptedException, IOException {
+ LocalSession session = tenant.getSessionFactory().createSession(testApp, "default", new SilentDeployLogger(), new TimeoutBudget(Clock.systemUTC(), Duration.ofSeconds(60)));
+ ApplicationId id = ApplicationId.from(tenant.getName(), ApplicationName.from("myapp"), InstanceName.defaultName());
+ session.prepare(new SilentDeployLogger(), new PrepareParams(new ConfigserverConfig(new ConfigserverConfig.Builder())).applicationId(id), Optional.empty(), tenantPath);
+ session.createActivateTransaction().commit();
+ tenant.getLocalSessionRepo().addSession(session);
+ return id;
+ }
+
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClientTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClientTest.java
new file mode 100644
index 00000000000..03cb5ada8ad
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClientTest.java
@@ -0,0 +1,202 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.deploy;
+
+import com.yahoo.config.application.api.ApplicationMetaData;
+import com.yahoo.config.application.api.ApplicationPackage;
+import com.yahoo.config.application.api.FileRegistry;
+import com.yahoo.config.model.application.provider.*;
+import com.yahoo.config.provision.*;
+import com.yahoo.path.Path;
+import com.yahoo.vespa.config.server.TestWithCurator;
+import com.yahoo.vespa.config.server.zookeeper.ZKApplicationPackage;
+import com.yahoo.vespa.curator.mock.MockCurator;
+import com.yahoo.vespa.config.server.zookeeper.ConfigCurator;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.*;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.*;
+
+
+/**
+ * Unit tests for ZooKeeperClient.
+ *
+ * @author <a href="mailto:musum@yahoo-inc.com">Harald Musum</a>
+ */
+public class ZooKeeperClientTest extends TestWithCurator {
+
+ @Rule
+ public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+ private ConfigCurator zk;
+ private String appPath = "/1";
+
+ @Before
+ public void setupZK() throws IOException {
+ this.zk = ConfigCurator.create(curator);
+ ZooKeeperClient zkc = new ZooKeeperClient(zk, new BaseDeployLogger(), true, Path.fromString(appPath));
+ ApplicationPackage app = FilesApplicationPackage.fromFileWithDeployData(new File("src/test/apps/zkfeed"), new DeployData("foo", "/bar/baz", "appName", 1345l, 3l, 2l));
+ Map<Version, FileRegistry> fileRegistries = createFileRegistries();
+ app.writeMetaData();
+ zkc.setupZooKeeper();
+ zkc.feedZooKeeper(app);
+ zkc.feedZKFileRegistries(fileRegistries);
+ }
+
+ private Map<Version, FileRegistry> createFileRegistries() {
+ FileRegistry a = new MockFileRegistry();
+ a.addFile("fileA");
+ FileRegistry b = new MockFileRegistry();
+ b.addFile("fileB");
+ Map<Version, FileRegistry> registryMap = new HashMap<>();
+ registryMap.put(Version.fromIntValues(1, 2, 3), a);
+ registryMap.put(Version.fromIntValues(3, 2, 1), b);
+ return registryMap;
+ }
+
+ @Test
+ public void testInitZooKeeper() throws IOException {
+ ConfigCurator zk = ConfigCurator.create(new MockCurator());
+ BaseDeployLogger logger = new BaseDeployLogger();
+ long generation = 1L;
+ ZooKeeperClient zooKeeperClient = new ZooKeeperClient(zk, logger, true, Path.fromString("/1"));
+ zooKeeperClient.setupZooKeeper();
+ String appPath = "/";
+ assertThat(zk.getChildren(appPath).size(), is(1));
+ assertTrue(zk.exists("/" + String.valueOf(generation)));
+ String currentAppPath = appPath + String.valueOf(generation);
+ assertTrue(zk.exists(currentAppPath, ConfigCurator.DEFCONFIGS_ZK_SUBPATH.replaceFirst("/", "")));
+ assertThat(zk.getChildren(currentAppPath).size(), is(4));
+ }
+
+ @Test
+ public void testFeedDefFilesToZooKeeper() {
+ String defsPath = appPath + ConfigCurator.DEFCONFIGS_ZK_SUBPATH;
+ assertTrue(zk.exists(appPath, ConfigCurator.DEFCONFIGS_ZK_SUBPATH.replaceFirst("/", "")));
+ List<String> children = zk.getChildren(defsPath);
+ assertEquals(defsPath + " children", 2, children.size());
+ Collections.sort(children);
+ assertThat(children.get(0), is("a.b.test2,"));
+
+ assertTrue(zk.exists(appPath, ConfigCurator.USER_DEFCONFIGS_ZK_SUBPATH.replaceFirst("/", "")));
+ String userDefsPath = appPath + ConfigCurator.USER_DEFCONFIGS_ZK_SUBPATH;
+ children = zk.getChildren(userDefsPath);
+ assertThat(children.size(), is(2));
+ Collections.sort(children);
+ assertThat(children.get(0), is("a.b.test2,"));
+ }
+
+ // TODO: Evaluate if we want this or not
+ @Test
+ @Ignore
+ public void testFeedComponentsFileReferencesToZooKeeper() throws IOException {
+ final String appDir = "src/test/apps/app_sdbundles";
+ ConfigCurator zk = ConfigCurator.create(new MockCurator());
+ BaseDeployLogger logger = new BaseDeployLogger();
+ Path app = Path.fromString("/1");
+ ZooKeeperClient zooKeeperClient = new ZooKeeperClient(zk, logger, true, app);
+ zooKeeperClient.setupZooKeeper();
+
+ String currentAppPath = app.getAbsolute();
+ assertTrue(zk.exists(currentAppPath, ConfigCurator.USERAPP_ZK_SUBPATH.replaceFirst("/", "")));
+ assertTrue(zk.exists(currentAppPath + ConfigCurator.USERAPP_ZK_SUBPATH, "components"));
+ assertTrue(zk.exists(currentAppPath + ConfigCurator.USERAPP_ZK_SUBPATH + "/components", "testbundle.jar"));
+ assertTrue(zk.exists(currentAppPath + ConfigCurator.USERAPP_ZK_SUBPATH + "/components", "testbundle2.jar"));
+ String data = zk.getData(currentAppPath + ConfigCurator.USERAPP_ZK_SUBPATH + "/components", "testbundle2.jar");
+ assertThat(data, is(new File(appDir + "/components/testbundle2.jar").getAbsolutePath()));
+ }
+
+ @Test
+ public void testFeedUserDefinedFiles() {
+ assertEquals(zk.getData(appPath+ ConfigCurator.USERAPP_ZK_SUBPATH + "/files", "foo.json"), "foo : foo\n");
+ assertEquals(zk.getData(appPath+ ConfigCurator.USERAPP_ZK_SUBPATH + "/files/sub", "bar.json"), "bar : bar\n");
+ }
+
+ @Test
+ public void testFeedAppMetaDataToZooKeeper() {
+ assertTrue(zk.exists(appPath, ConfigCurator.META_ZK_PATH));
+ ApplicationMetaData metaData = ApplicationMetaData.fromJsonString(zk.getData(appPath, ConfigCurator.META_ZK_PATH));
+ assertThat(metaData.getApplicationName(), is("appName"));
+ assertTrue(metaData.getCheckSum().length() > 0);
+ assertThat(metaData.getDeployedByUser(), is("foo"));
+ assertThat(metaData.getDeployPath(), is("/bar/baz"));
+ assertThat(metaData.getDeployTimestamp(), is(1345l));
+ assertThat(metaData.getGeneration(), is(3l));
+ assertThat(metaData.getPreviousActiveGeneration(), is(2l));
+ }
+
+ @Test
+ public void testVersionedFileRegistry() {
+ String fileRegPath = appPath + "/" + ZKApplicationPackage.fileRegistryNode;
+ assertTrue(zk.exists(fileRegPath));
+ assertTrue(zk.exists(fileRegPath + "/1.2.3"));
+ assertTrue(zk.exists(fileRegPath + "/3.2.1"));
+ // assertNull("Data at " + fileRegPath, zk.getData(fileRegPath)); Not null any more .. hm
+ }
+
+ @Test
+ public void include_dirs_are_written_to_ZK() {
+ assertTrue(zk.exists(appPath + ConfigCurator.USERAPP_ZK_SUBPATH + "/" + "dir1", "default.xml"));
+ assertTrue(zk.exists(appPath + ConfigCurator.USERAPP_ZK_SUBPATH + "/nested/" + "dir2", "chain2.xml"));
+ assertTrue(zk.exists(appPath + ConfigCurator.USERAPP_ZK_SUBPATH + "/nested/" + "dir2", "chain3.xml"));
+ }
+
+ @Test
+ public void search_chain_dir_written_to_ZK() {
+ assertTrue(zk.exists(appPath().append("search").append("chains").append("dir1").append("default.xml").getAbsolute()));
+ assertTrue(zk.exists(appPath().append("search").append("chains").append("dir2").append("chain2.xml").getAbsolute()));
+ assertTrue(zk.exists(appPath().append("search").append("chains").append("dir2").append("chain3.xml").getAbsolute()));
+ }
+
+ @Test
+ public void search_definitions_written_to_ZK() {
+ assertTrue(zk.exists(appPath().append(ApplicationPackage.SEARCH_DEFINITIONS_DIR).append("music.sd").getAbsolute()));
+ assertTrue(zk.exists(appPath().append(ApplicationPackage.SEARCH_DEFINITIONS_DIR).append("base.sd").getAbsolute()));
+ assertTrue(zk.exists(appPath().append(ApplicationPackage.SEARCH_DEFINITIONS_DIR).append("video.sd").getAbsolute()));
+ assertTrue(zk.exists(appPath().append(ApplicationPackage.SEARCH_DEFINITIONS_DIR).append("book.sd").getAbsolute()));
+ assertTrue(zk.exists(appPath().append(ApplicationPackage.SEARCH_DEFINITIONS_DIR).append("pc.sd").getAbsolute()));
+ assertTrue(zk.exists(appPath().append(ApplicationPackage.SEARCH_DEFINITIONS_DIR).append("laptop.sd").getAbsolute()));
+ assertTrue(zk.exists(appPath().append(ApplicationPackage.SEARCH_DEFINITIONS_DIR).append("product.sd").getAbsolute()));
+ assertTrue(zk.exists(appPath().append(ApplicationPackage.SEARCH_DEFINITIONS_DIR).append("sock.sd").getAbsolute()));
+ assertTrue(zk.exists(appPath().append(ApplicationPackage.SEARCH_DEFINITIONS_DIR).append("foo.expression").getAbsolute()));
+ assertTrue(zk.exists(appPath().append(ApplicationPackage.SEARCH_DEFINITIONS_DIR).append("bar.expression").getAbsolute()));
+ }
+
+ private Path appPath() {
+ return Path.fromString(appPath).append(ConfigCurator.USERAPP_ZK_SUBPATH);
+ }
+
+ @Test
+ public void testWritingHostNamesToZooKeeper() throws IOException {
+ ConfigCurator zk = ConfigCurator.create(new MockCurator());
+ BaseDeployLogger logger = new BaseDeployLogger();
+ Path app = Path.fromString("/1");
+ ZooKeeperClient zooKeeperClient = new ZooKeeperClient(zk, logger, true, app);
+ zooKeeperClient.setupZooKeeper();
+ zooKeeperClient.feedProvisionInfos(createProvisionInfos());
+ Path hostsPath = app.append(ZKApplicationPackage.allocatedHostsNode);
+ assertTrue(zk.exists(hostsPath.getAbsolute()));
+ assertEquals(0, zk.getBytes(hostsPath.getAbsolute()).length); // Changed from null
+ assertTrue(zk.exists(hostsPath.append("1.2.3").getAbsolute()));
+ assertTrue(zk.exists(hostsPath.append("3.2.1").getAbsolute()));
+ assertTrue(zk.getBytes(hostsPath.append("1.2.3").getAbsolute()).length > 0);
+ assertTrue(zk.getBytes(hostsPath.append("3.2.1").getAbsolute()).length > 0);
+ }
+
+ private Map<Version, ProvisionInfo> createProvisionInfos() {
+ Map<Version, ProvisionInfo> provisionInfoMap = new HashMap<>();
+ ProvisionInfo a = ProvisionInfo.withHosts(Collections.singleton(new HostSpec("host.yahoo.com", Collections.emptyList())));
+ ProvisionInfo b = ProvisionInfo.withHosts(Collections.singleton(new HostSpec("host2.yahoo.com", Collections.emptyList())));
+ provisionInfoMap.put(Version.fromIntValues(1, 2, 3), a);
+ provisionInfoMap.put(Version.fromIntValues(3, 2, 1), b);
+ return provisionInfoMap;
+ }
+
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperDeployerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperDeployerTest.java
new file mode 100644
index 00000000000..f69e3aa87fa
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperDeployerTest.java
@@ -0,0 +1,63 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.deploy;
+
+import com.yahoo.config.application.api.ApplicationPackage;
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.config.model.application.provider.*;
+import com.yahoo.config.provision.Version;
+import com.yahoo.io.IOUtils;
+import com.yahoo.path.Path;
+import com.yahoo.prelude.semantics.parser.ParseException;
+import com.yahoo.vespa.curator.mock.MockCurator;
+import com.yahoo.vespa.config.server.zookeeper.ConfigCurator;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.logging.Level;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class ZooKeeperDeployerTest {
+
+ @Rule
+ public TemporaryFolder folder = new TemporaryFolder();
+ private static final String defFile = "test2.def";
+
+ @Test
+ public void require_that_deployer_is_initialized() throws IOException, ParseException {
+ ConfigCurator zkfacade = ConfigCurator.create(new MockCurator());
+ File serverdbDir = folder.newFolder("serverdb");
+ File defsDir = new File(serverdbDir, "serverdefs");
+ try {
+ IOUtils.createWriter(new File(defsDir, defFile), true);
+ } catch (IOException e) {
+ e.printStackTrace();
+ fail();
+ }
+ deploy(FilesApplicationPackage.fromFile(new File("src/test/apps/content")), zkfacade, Path.fromString("/1"));
+ deploy(FilesApplicationPackage.fromFile(new File("src/test/apps/content")), zkfacade, Path.fromString("/2"));
+ }
+
+ public void deploy(ApplicationPackage applicationPackage, ConfigCurator configCurator, Path appPath) throws IOException {
+ MockDeployLogger logger = new MockDeployLogger();
+ ZooKeeperClient client = new ZooKeeperClient(configCurator, logger, true, appPath);
+ ZooKeeperDeployer deployer = new ZooKeeperDeployer(client);
+
+ deployer.deploy(applicationPackage, Collections.singletonMap(Version.fromIntValues(1, 0, 0), new MockFileRegistry()), Collections.emptyMap());
+ assertTrue(configCurator.exists(appPath.getAbsolute()));
+ }
+
+ private static class MockDeployLogger implements DeployLogger {
+ @Override
+ public void log(Level level, String message) { }
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/filedistribution/FileDistributionLockTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/filedistribution/FileDistributionLockTest.java
new file mode 100644
index 00000000000..02626bffa9d
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/filedistribution/FileDistributionLockTest.java
@@ -0,0 +1,110 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.filedistribution;
+
+import com.yahoo.vespa.config.server.TestWithCurator;
+import com.yahoo.vespa.curator.recipes.CuratorLockException;
+import com.yahoo.vespa.curator.mock.MockCurator;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.*;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.*;
+
+/**
+ * @author lulf
+ */
+public class FileDistributionLockTest extends TestWithCurator {
+
+ FileDistributionLock lock;
+ private int value = 0;
+
+ @Before
+ public void setupLock() {
+ lock = new FileDistributionLock(curator, "/lock");
+ value = 0;
+ }
+
+ @Test
+ public void testDistributedLock() throws InterruptedException, TimeoutException, ExecutionException {
+ ExecutorService executor = Executors.newFixedThreadPool(20);
+
+ List<Future<?>> futureList = new ArrayList<>();
+ for (int i = 0; i < 20; i++) {
+ futureList.add(executor.submit(() -> {
+ lock.lock();
+ value++;
+ lock.unlock();
+ }));
+ }
+
+ for (Future<?> future : futureList) {
+ future.get(600, TimeUnit.SECONDS);
+ }
+ assertThat(value, is(20));
+ }
+
+ @Test
+ public void testDistributedTryLockFailure() throws InterruptedException {
+ MockCurator mockCurator = new MockCurator();
+ lock = new FileDistributionLock(mockCurator, "/mocklock");
+ mockCurator.timeoutOnLock = true;
+ assertFalse(lock.tryLock(600, TimeUnit.SECONDS));
+ mockCurator.timeoutOnLock = false;
+ // Second time should not be blocking
+ Thread t = new Thread(() -> {
+ try {
+ if (lock.tryLock(6, TimeUnit.SECONDS)) {
+ value = 1;
+ lock.unlock();
+ }
+ } catch (InterruptedException e) {
+ }
+ });
+ assertThat(value, is(0));
+ t.start();
+ t.join();
+ assertThat(value, is(1));
+ }
+
+ @Test
+ public void testDistributedLockExceptionFailure() throws InterruptedException {
+ MockCurator mockCurator = new MockCurator();
+ lock = new FileDistributionLock(mockCurator, "/mocklock");
+ mockCurator.throwExceptionOnLock = true;
+ try {
+ lock.lock();
+ fail("Lock call should not succeed");
+ } catch (CuratorLockException e) {
+ // ignore
+ }
+ mockCurator.throwExceptionOnLock = false;
+ // Second time should not be blocking
+ Thread t = new Thread(() -> {
+ try {
+ lock.lock();
+ value = 1;
+ lock.unlock();
+ } catch (Exception e) {
+ fail("Should not fail");
+ }
+ });
+ assertThat(value, is(0));
+ t.start();
+ t.join();
+ assertThat(value, is(1));
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testConditionNotSupported() {
+ lock.newCondition();
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testLockInterruptiblyNotSupported() throws InterruptedException {
+ lock.lockInterruptibly();
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/ContentHandlerTestBase.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/ContentHandlerTestBase.java
new file mode 100644
index 00000000000..a3ba3b26b50
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/ContentHandlerTestBase.java
@@ -0,0 +1,103 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.http;
+
+import static com.yahoo.jdisc.Response.Status.BAD_REQUEST;
+import static com.yahoo.jdisc.Response.Status.NOT_FOUND;
+import static com.yahoo.jdisc.Response.Status.OK;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import javax.annotation.Nullable;
+
+import org.junit.Test;
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.collect.Collections2;
+import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.jdisc.http.HttpRequest;
+
+public abstract class ContentHandlerTestBase extends SessionHandlerTest {
+ protected String baseUrl = "http://foo:1337/application/v2/tenant/default/session/1/content/";
+
+ @Test
+ public void require_that_content_can_be_retrieved() throws IOException {
+ assertContent("/test.txt", "foo\n");
+ assertContent("/foo/", generateResultArray("foo/bar/", "foo/test1.txt", "foo/test2.txt"));
+ assertContent("/foo", generateResultArray("foo/"));
+ assertContent("/foo/test1.txt", "bar\n");
+ assertContent("/foo/test2.txt", "baz\n");
+ assertContent("/foo/bar/", generateResultArray("foo/bar/test.txt"));
+ assertContent("/foo/bar", generateResultArray("foo/bar/"));
+ assertContent("/foo/bar/test.txt", "bim\n");
+ assertContent("/foo/?recursive=true", generateResultArray("foo/bar/", "foo/bar/test.txt", "foo/test1.txt", "foo/test2.txt"));
+ }
+
+ @Test
+ public void require_that_nonexistant_file_returns_not_found() throws IOException {
+ HttpResponse response = doRequest(HttpRequest.Method.GET, "/test2.txt");
+ assertNotNull(response);
+ assertThat(response.getStatus(), is(NOT_FOUND));
+ }
+
+ @Test
+ public void require_that_return_property_is_used() throws IOException {
+ assertContent("/test.txt?return=content", "foo\n");
+ }
+
+ @Test
+ public void require_that_illegal_return_property_fails() {
+ HttpResponse response = doRequest(HttpRequest.Method.GET, "/test.txt?return=foo");
+ assertThat(response.getStatus(), is(BAD_REQUEST));
+ }
+
+ @Test
+ public void require_that_status_can_be_retrieved() throws IOException {
+ assertStatus("/test.txt?return=status",
+ "{\"status\":\"new\",\"md5\":\"d3b07384d113edec49eaa6238ad5ff00\",\"name\":\"" + baseUrl + "test.txt\"}");
+ assertStatus("/foo/?return=status",
+ "[{\"status\":\"new\",\"md5\":\"\",\"name\":\"" + baseUrl + "foo/bar\"}," +
+ "{\"status\":\"new\",\"md5\":\"c157a79031e1c40f85931829bc5fc552\",\"name\":\"" + baseUrl + "foo/test1.txt\"}," +
+ "{\"status\":\"new\",\"md5\":\"258622b1688250cb619f3c9ccaefb7eb\",\"name\":\"" + baseUrl + "foo/test2.txt\"}]");
+ assertStatus("/foo/?return=status&recursive=true",
+ "[{\"status\":\"new\",\"md5\":\"\",\"name\":\"" + baseUrl + "foo/bar\"}," +
+ "{\"status\":\"new\",\"md5\":\"579cae6111b269c0129af36a2243b873\",\"name\":\"" + baseUrl + "foo/bar/test.txt\"}," +
+ "{\"status\":\"new\",\"md5\":\"c157a79031e1c40f85931829bc5fc552\",\"name\":\"" + baseUrl + "foo/test1.txt\"}," +
+ "{\"status\":\"new\",\"md5\":\"258622b1688250cb619f3c9ccaefb7eb\",\"name\":\"" + baseUrl + "foo/test2.txt\"}]");
+ }
+
+ protected void assertContent(String path, String expectedContent) throws IOException {
+ HttpResponse response = doRequest(HttpRequest.Method.GET, path);
+ assertNotNull(response);
+ final String renderedString = SessionHandlerTest.getRenderedString(response);
+ assertThat(renderedString, response.getStatus(), is(OK));
+ assertThat(renderedString, is(expectedContent));
+ }
+
+ protected void assertStatus(String path, String expectedContent) throws IOException {
+ HttpResponse response = doRequest(HttpRequest.Method.GET, path);
+ assertNotNull(response);
+ final String renderedString = SessionHandlerTest.getRenderedString(response);
+ assertThat(renderedString, response.getStatus(), is(OK));
+ assertThat(renderedString, is(expectedContent));
+ }
+
+ protected abstract HttpResponse doRequest(HttpRequest.Method method, String path);
+
+ private String generateResultArray(String... files) {
+ Collection<String> output = Collections2.transform(Arrays.asList(files), new Function<String, String>() {
+ @Override
+ public String apply(@Nullable String input) {
+ return "\"" + baseUrl + input + "\"";
+ }
+ });
+ StringBuilder sb = new StringBuilder();
+ sb.append("[");
+ sb.append(Joiner.on(",").join(output));
+ sb.append("]");
+ return sb.toString();
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/HandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/HandlerTest.java
new file mode 100644
index 00000000000..22607e8fc26
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/HandlerTest.java
@@ -0,0 +1,35 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.http;
+
+import com.yahoo.container.jdisc.HttpResponse;
+
+import java.io.IOException;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.*;
+
+/**
+ * Base class for handler tests
+ *
+ * @author musum
+ * @since 5.1.14
+ */
+public class HandlerTest {
+ public static void assertHttpStatusCodeErrorCodeAndMessage(HttpResponse response, int statusCode, HttpErrorResponse.errorCodes errorCode, String message) throws IOException {
+ assertNotNull(response);
+ String renderedString = SessionHandlerTest.getRenderedString(response);
+ if (renderedString == null) {
+ renderedString = "assert failed";
+ }
+ assertThat(renderedString, response.getStatus(), is(statusCode));
+ if (errorCode != null) {
+ assertThat(renderedString, containsString(errorCode.name()));
+ }
+ assertThat(renderedString, containsString(message));
+ }
+
+ public static void assertHttpStatusCodeAndMessage(HttpResponse response, int statusCode, String message) throws IOException {
+ assertHttpStatusCodeErrorCodeAndMessage(response, statusCode, null, message);
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpConfigRequestTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpConfigRequestTest.java
new file mode 100644
index 00000000000..8525a152403
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpConfigRequestTest.java
@@ -0,0 +1,46 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.http;
+
+import java.io.IOException;
+
+import com.yahoo.container.jdisc.HttpRequest;
+import com.yahoo.vespa.config.ConfigKey;
+
+import org.junit.Test;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import static com.yahoo.jdisc.http.HttpRequest.Method.GET;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class HttpConfigRequestTest {
+ @Test
+ public void require_that_request_can_be_created() {
+ final ConfigKey<?> configKey = new ConfigKey<>("foo", "myid", "bar");
+
+ HttpConfigRequest request = HttpConfigRequest.createFromRequestV1(HttpRequest.createTestRequest("http://example.yahoo.com:8080/config/v1/" +
+ configKey.getNamespace() + "." + configKey.getName() + "/" + configKey.getConfigId(), GET));
+ assertThat(request.getConfigKey(), is(configKey));
+ assertTrue(request.getDefContent().isEmpty());
+ }
+
+ @Test
+ public void require_namespace_can_have_dots() {
+ final ConfigKey<?> configKey = new ConfigKey<>("foo", "myid", "bar.baz");
+ HttpConfigRequest request = HttpConfigRequest.createFromRequestV1(HttpRequest.createTestRequest("http://example.yahoo.com:8080/config/v1/" +
+ configKey.getNamespace() + "." + configKey.getName() + "/" + configKey.getConfigId(), GET));
+ assertEquals(request.getConfigKey().getNamespace(), "bar.baz");
+ }
+
+ @Test
+ public void require_that_request_can_be_created_with_advanced_uri() throws IOException {
+ HttpConfigRequest.createFromRequestV1(HttpRequest.createTestRequest(
+ "http://example.yahoo.com:19071/config/v1/vespa.config.cloud.sentinel/host-01.example.yahoo.com", GET));
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpConfigResponseTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpConfigResponseTest.java
new file mode 100644
index 00000000000..b7d41e2835e
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpConfigResponseTest.java
@@ -0,0 +1,36 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.http;
+
+import com.yahoo.config.SimpletypesConfig;
+import com.yahoo.config.codegen.DefParser;
+import com.yahoo.config.codegen.InnerCNode;
+import com.yahoo.text.StringUtilities;
+import com.yahoo.vespa.config.ConfigPayload;
+import com.yahoo.vespa.config.protocol.ConfigResponse;
+
+import com.yahoo.vespa.config.protocol.SlimeConfigResponse;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.StringReader;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class HttpConfigResponseTest {
+ @Test
+ public void require_that_response_is_created_from_config() throws IOException {
+ final long generation = 1L;
+ ConfigPayload payload = ConfigPayload.fromInstance(new SimpletypesConfig(new SimpletypesConfig.Builder()));
+ // TODO: Hope to be able to remove this mess soon.
+ DefParser dParser = new DefParser(SimpletypesConfig.getDefName(), new StringReader(StringUtilities.implode(SimpletypesConfig.CONFIG_DEF_SCHEMA, "\n")));
+ InnerCNode targetDef = dParser.getTree();
+ ConfigResponse configResponse = SlimeConfigResponse.fromConfigPayload(payload, targetDef, generation, "mymd5");
+ HttpConfigResponse response = HttpConfigResponse.createFromConfig(configResponse);
+ assertThat(SessionHandlerTest.getRenderedString(response), is("{\"boolval\":false,\"doubleval\":0.0,\"enumval\":\"VAL1\",\"intval\":0,\"longval\":0,\"stringval\":\"s\"}"));
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpErrorResponseTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpErrorResponseTest.java
new file mode 100644
index 00000000000..cd18823ea1c
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpErrorResponseTest.java
@@ -0,0 +1,36 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.http;
+
+import org.junit.Test;
+
+import java.io.IOException;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+
+import static com.yahoo.jdisc.http.HttpResponse.Status.*;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class HttpErrorResponseTest {
+ @Test
+ public void testThatHttpErrorResponseIsRenderedAsJson() throws IOException {
+ HttpErrorResponse response = HttpErrorResponse.badRequest("Error doing something");
+ assertThat(response.getJdiscResponse().getStatus(), is(BAD_REQUEST));
+ assertThat(SessionHandlerTest.getRenderedString(response), is("{\"error-code\":\"BAD_REQUEST\",\"message\":\"Error doing something\"}"));
+ }
+
+ @Test
+ public void testThatHttpErrorResponseProvidesCorrectErrorMessage() throws IOException {
+ HttpErrorResponse response = HttpErrorResponse.badRequest("Error doing something");
+ HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(response, BAD_REQUEST, HttpErrorResponse.errorCodes.BAD_REQUEST, "Error doing something");
+ }
+
+ @Test
+ public void testThatHttpErrorResponseHasJsonContentType() throws IOException {
+ HttpErrorResponse response = HttpErrorResponse.badRequest("Error doing something");
+ assertThat(response.getContentType(), is("application/json"));
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpGetConfigHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpGetConfigHandlerTest.java
new file mode 100644
index 00000000000..d1c4c5e5e2e
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpGetConfigHandlerTest.java
@@ -0,0 +1,100 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.http;
+
+import com.yahoo.config.SimpletypesConfig;
+import com.yahoo.config.codegen.DefParser;
+import com.yahoo.config.codegen.InnerCNode;
+import com.yahoo.container.jdisc.HttpRequest;
+import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.container.logging.AccessLog;
+import com.yahoo.text.StringUtilities;
+import com.yahoo.vespa.config.ConfigKey;
+import com.yahoo.vespa.config.ConfigPayload;
+import com.yahoo.vespa.config.server.MockRequestHandler;
+import com.yahoo.vespa.config.protocol.SlimeConfigResponse;
+import com.yahoo.config.provision.ApplicationId;
+
+import org.junit.Before;
+import org.junit.Test;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.concurrent.Executor;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.*;
+
+import static com.yahoo.jdisc.http.HttpRequest.Method.GET;
+import static com.yahoo.jdisc.http.HttpResponse.Status.*;
+
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class HttpGetConfigHandlerTest {
+ private static final String configUri = "http://yahoo.com:8080/config/v1/foo.bar/myid";
+
+ private MockRequestHandler mockRequestHandler;
+ private HttpGetConfigHandler handler;
+
+ @Before
+ public void setUp() {
+ mockRequestHandler = new MockRequestHandler();
+ mockRequestHandler.setAllConfigs(new HashSet<ConfigKey<?>>() {{
+ add(new ConfigKey<>("bar", "myid", "foo"));
+ }} );
+ handler = new HttpGetConfigHandler(new Executor() {
+ @SuppressWarnings("NullableProblems")
+ @Override
+ public void execute(Runnable command) {
+ command.run();
+ }
+ }, mockRequestHandler, AccessLog.voidAccessLog());
+ }
+
+ @Test
+ public void require_that_handler_can_be_created() throws IOException {
+ // Define config response for mock handler
+ final long generation = 1L;
+ ConfigPayload payload = ConfigPayload.fromInstance(new SimpletypesConfig(new SimpletypesConfig.Builder()));
+ InnerCNode targetDef = getInnerCNode();
+ mockRequestHandler.responses.put(ApplicationId.defaultId(), SlimeConfigResponse.fromConfigPayload(payload, targetDef, generation, "mymd5"));
+ HttpResponse response = handler.handle(HttpRequest.createTestRequest(configUri, GET));
+ assertThat(SessionHandlerTest.getRenderedString(response), is("{\"boolval\":false,\"doubleval\":0.0,\"enumval\":\"VAL1\",\"intval\":0,\"longval\":0,\"stringval\":\"s\"}"));
+ }
+
+ @Test
+ public void require_correct_error_response() throws IOException {
+ final String nonExistingConfigNameUri = "http://yahoo.com:8080/config/v1/nonexisting.config/myid";
+ final String nonExistingConfigUri = "http://yahoo.com:8080/config/v1/foo.bar/myid/nonexisting/id";
+ final String illegalConfigNameUri = "http://yahoo.com:8080/config/v1/foobar/myid";
+
+ HttpResponse response = handler.handle(HttpRequest.createTestRequest(nonExistingConfigNameUri, GET));
+ HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(response, NOT_FOUND, HttpErrorResponse.errorCodes.NOT_FOUND, "No such config: nonexisting.config");
+ assertTrue(SessionHandlerTest.getRenderedString(response).contains("No such config:"));
+ response = handler.handle(HttpRequest.createTestRequest(nonExistingConfigUri, GET));
+ HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(response, NOT_FOUND, HttpErrorResponse.errorCodes.NOT_FOUND, "No such config id: myid/nonexisting/id");
+ assertEquals(response.getContentType(), "application/json");
+ assertTrue(SessionHandlerTest.getRenderedString(response).contains("No such config id:"));
+ response = handler.handle(HttpRequest.createTestRequest(illegalConfigNameUri, GET));
+ HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(response, BAD_REQUEST, HttpErrorResponse.errorCodes.BAD_REQUEST, "Illegal config, must be of form namespace.name.");
+ }
+
+ @Test
+ public void require_that_nocache_property_works() throws IOException {
+ long generation = 1L;
+ ConfigPayload payload = ConfigPayload.fromInstance(new SimpletypesConfig(new SimpletypesConfig.Builder()));
+ InnerCNode targetDef = getInnerCNode();
+ mockRequestHandler.responses.put(ApplicationId.defaultId(), SlimeConfigResponse.fromConfigPayload(payload, targetDef, generation, "mymd5"));
+ final HttpRequest request = HttpRequest.createTestRequest(configUri, GET, null, Collections.singletonMap("nocache", "true"));
+ HttpResponse response = handler.handle(request);
+ assertThat(SessionHandlerTest.getRenderedString(response), is("{\"boolval\":false,\"doubleval\":0.0,\"enumval\":\"VAL1\",\"intval\":0,\"longval\":0,\"stringval\":\"s\"}"));
+ }
+
+ private InnerCNode getInnerCNode() {
+ // TODO: Hope to be able to remove this mess soon.
+ DefParser dParser = new DefParser(SimpletypesConfig.getDefName(), new StringReader(StringUtilities.implode(SimpletypesConfig.CONFIG_DEF_SCHEMA, "\n")));
+ return dParser.getTree();
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpHandlerTest.java
new file mode 100644
index 00000000000..7868909f65f
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpHandlerTest.java
@@ -0,0 +1,51 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.http;
+
+import com.yahoo.container.jdisc.HttpRequest;
+import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.container.logging.AccessLog;
+import com.yahoo.jdisc.Response;
+import com.yahoo.slime.JsonDecoder;
+import com.yahoo.slime.Slime;
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author lulf
+ * @since 5.34
+ */
+public class HttpHandlerTest {
+ @Test
+ public void testResponse() throws IOException {
+ final String message = "failed";
+ HttpHandler httpHandler = new HttpTestHandler(Executors.newSingleThreadExecutor(), AccessLog.voidAccessLog(), new InvalidApplicationException(message));
+ HttpResponse response = httpHandler.handle(HttpRequest.createTestRequest("foo", com.yahoo.jdisc.http.HttpRequest.Method.GET));
+ assertThat(response.getStatus(), is(Response.Status.BAD_REQUEST));
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ response.render(baos);
+ Slime data = new Slime();
+ new JsonDecoder().decode(data, baos.toByteArray());
+ assertThat(data.get().field("error-code").asString(), is(HttpErrorResponse.errorCodes.INVALID_APPLICATION_PACKAGE.name()));
+ assertThat(data.get().field("message").asString(), is(message));
+ }
+
+ private static class HttpTestHandler extends HttpHandler {
+ private RuntimeException exception;
+ public HttpTestHandler(Executor executor, AccessLog accessLog, RuntimeException exception) {
+ super(executor, accessLog);
+ this.exception = exception;
+ }
+
+ @Override
+ public HttpResponse handleGET(HttpRequest request) {
+ throw exception;
+ }
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpListConfigsHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpListConfigsHandlerTest.java
new file mode 100644
index 00000000000..ad917c5db6d
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/HttpListConfigsHandlerTest.java
@@ -0,0 +1,115 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.http;
+
+import com.yahoo.container.jdisc.HttpRequest;
+import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.container.logging.AccessLog;
+import com.yahoo.vespa.config.ConfigKey;
+import com.yahoo.vespa.config.server.MockRequestHandler;
+import com.yahoo.vespa.config.server.http.HttpListConfigsHandler.ListConfigsResponse;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.*;
+import java.util.concurrent.Executor;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.*;
+
+import static com.yahoo.jdisc.http.HttpResponse.Status.*;
+import static com.yahoo.jdisc.http.HttpRequest.Method.GET;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class HttpListConfigsHandlerTest {
+
+ private MockRequestHandler mockRequestHandler;
+ private HttpListConfigsHandler handler;
+ private HttpListNamedConfigsHandler namedHandler;
+
+ @Before
+ public void setUp() {
+ mockRequestHandler = new MockRequestHandler();
+ mockRequestHandler.setAllConfigs(new HashSet<ConfigKey<?>>() {{
+ add(new ConfigKey<>("bar", "conf/id/", "foo"));
+ }} );
+ handler = new HttpListConfigsHandler(new Executor() {
+ @Override
+ public void execute(Runnable command) {
+ command.run();
+ }
+ }, AccessLog.voidAccessLog(), mockRequestHandler);
+ namedHandler = new HttpListNamedConfigsHandler(new Executor() {
+ @Override
+ public void execute(Runnable command) {
+ command.run();
+ }
+ }, mockRequestHandler, AccessLog.voidAccessLog());
+ }
+
+ @Test
+ public void require_that_handler_can_be_created() throws IOException {
+ HttpResponse response = handler.handle(HttpRequest.createTestRequest("/config/v1/", GET));
+ assertThat(SessionHandlerTest.getRenderedString(response), is("{\"children\":[],\"configs\":[]}"));
+ }
+
+ @Test
+ public void require_that_named_handler_can_be_created() throws IOException {
+ HttpRequest req = HttpRequest.createTestRequest("http://foo.com:8080/config/v1/foo.bar/conf/id/", GET);
+ req.getJDiscRequest().parameters().put("http.path", Arrays.asList("foo.bar"));
+ HttpResponse response = namedHandler.handle(req);
+ assertThat(SessionHandlerTest.getRenderedString(response), is("{\"children\":[],\"configs\":[]}"));
+ }
+
+ @Test
+ public void require_child_listings_correct() {
+ Set<ConfigKey<?>> keys = new LinkedHashSet<ConfigKey<?>>() {{
+ add(new ConfigKey<>("name1", "id/1", "ns1"));
+ add(new ConfigKey<>("name1", "id/1", "ns1"));
+ add(new ConfigKey<>("name1", "id/2", "ns1"));
+ add(new ConfigKey<>("name1", "", "ns1"));
+ add(new ConfigKey<>("name1", "id/1/1", "ns1"));
+ add(new ConfigKey<>("name1", "id2", "ns1"));
+ add(new ConfigKey<>("name1", "id/2/1", "ns1"));
+ add(new ConfigKey<>("name1", "id/2/1/5/6", "ns1"));
+ }};
+ Set<ConfigKey<?>> keysThatHaveChild = HttpListConfigsHandler.ListConfigsResponse.keysThatHaveAChildWithSameName(keys, keys);
+ assertEquals(keysThatHaveChild.size(), 3);
+ }
+
+ @Test
+ public void require_url_building_and_mimetype_correct() {
+ HttpListConfigsHandler.ListConfigsResponse resp = new ListConfigsResponse(new HashSet<ConfigKey<?>>(), null, "http://foo.com/config/v1/", true);
+ assertEquals(resp.toUrl(new ConfigKey<>("myconfig", "my/id", "mynamespace"), true), "http://foo.com/config/v1/mynamespace.myconfig/my/id");
+ assertEquals(resp.toUrl(new ConfigKey<>("myconfig", "my/id", "mynamespace"), false), "http://foo.com/config/v1/mynamespace.myconfig/my/id/");
+ assertEquals(resp.getContentType(), "application/json");
+
+ }
+
+ @Test
+ public void require_error_on_bad_request() throws IOException {
+ HttpRequest req = HttpRequest.createTestRequest("http://foo.com:8080/config/v1/foobar/conf/id/", GET);
+ HttpResponse resp = namedHandler.handle(req);
+ HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(resp, BAD_REQUEST, HttpErrorResponse.errorCodes.BAD_REQUEST, "Illegal config, must be of form namespace.name.");
+ req = HttpRequest.createTestRequest("http://foo.com:8080/config/v1/foo.barNOPE/conf/id/", GET);
+ resp = namedHandler.handle(req);
+ HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(resp, NOT_FOUND, HttpErrorResponse.errorCodes.NOT_FOUND, "No such config: foo.barNOPE");
+ req = HttpRequest.createTestRequest("http://foo.com:8080/config/v1/foo.bar/conf/id/NOPE/", GET);
+ resp = namedHandler.handle(req);
+ HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(resp, NOT_FOUND, HttpErrorResponse.errorCodes.NOT_FOUND, "No such config id: conf/id/NOPE/");
+ }
+
+ @Test
+ public void require_correct_error_response_on_no_model() throws IOException {
+ mockRequestHandler.setAllConfigs(new HashSet<ConfigKey<?>>());
+ HttpResponse response = namedHandler.handle(HttpRequest.createTestRequest("http://yahoo.com:8080/config/v1/foo.bar/myid/", GET));
+ HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(response, NOT_FOUND,
+ HttpErrorResponse.errorCodes.NOT_FOUND,
+ "Config not available, verify that an application package has been deployed and activated.");
+ }
+
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionActiveHandlerTestBase.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionActiveHandlerTestBase.java
new file mode 100644
index 00000000000..2e5576869b5
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionActiveHandlerTestBase.java
@@ -0,0 +1,266 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.http;
+
+import static com.yahoo.jdisc.Response.Status.*;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Optional;
+
+import com.yahoo.config.application.api.ApplicationMetaData;
+import com.yahoo.config.model.application.provider.FilesApplicationPackage;
+import com.yahoo.config.model.application.provider.MockFileRegistry;
+import com.yahoo.config.provision.*;
+import com.yahoo.vespa.config.server.modelfactory.ModelFactoryRegistry;
+import com.yahoo.vespa.config.server.SuperModelGenerationCounter;
+import com.yahoo.vespa.config.server.TestComponentRegistry;
+import com.yahoo.vespa.config.server.application.ApplicationRepo;
+import com.yahoo.vespa.config.server.deploy.TenantFileSystemDirs;
+import com.yahoo.vespa.config.server.deploy.ZooKeeperClient;
+import com.yahoo.vespa.config.server.session.*;
+
+import com.yahoo.vespa.curator.Curator;
+import com.yahoo.vespa.curator.mock.MockCurator;
+import com.yahoo.vespa.model.VespaModelFactory;
+import org.hamcrest.core.Is;
+import org.junit.Test;
+
+import com.yahoo.config.model.NullConfigModelRegistry;
+import com.yahoo.config.application.api.ApplicationPackage;
+import com.yahoo.config.model.application.provider.BaseDeployLogger;
+import com.yahoo.config.model.application.provider.DeployData;
+import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.jdisc.http.HttpRequest;
+import com.yahoo.vespa.config.server.HostRegistry;
+import com.yahoo.vespa.config.server.PathProvider;
+import com.yahoo.vespa.config.server.zookeeper.ConfigCurator;
+
+public abstract class SessionActiveHandlerTestBase extends SessionHandlerTest {
+
+ private File testApp = new File("src/test/apps/app");
+ protected static final String appName = "default";
+ protected TenantName tenant = null;
+ protected ConfigCurator configCurator;
+ protected Curator curator;
+ protected RemoteSessionRepo remoteSessionRepo;
+ protected LocalSessionRepo localRepo;
+ protected PathProvider pathProvider;
+ protected ApplicationRepo applicationRepo;
+ protected String activatedMessage = " activated.";
+ protected String tenantMessage = "";
+
+ @Test
+ public void testThatPreviousSessionIsDeactivated() throws Exception {
+ RemoteSession firstSession = activateAndAssertOK(90l, 0l);
+ activateAndAssertOK(91l, 90l);
+ assertThat(firstSession.getStatus(), is(Session.Status.DEACTIVATE));
+ }
+
+ @Test
+ public void testForceActivationWithActivationInBetween() throws Exception {
+ activateAndAssertOK(90l, 0l);
+ activateAndAssertOK(92l, 89l, "?force=true");
+ }
+
+ @Test
+ public void testUnknownSession() throws Exception {
+ HttpResponse response = createHandler().handle(SessionHandlerTest.createTestRequest(pathPrefix, HttpRequest.Method.PUT, Cmd.ACTIVE, 9999L, "?timeout=1.0"));
+ assertEquals(response.getStatus(), 404);
+ }
+
+ @Test
+ public void testActivationWithActivationInBetween() throws Exception {
+ activateAndAssertOK(90l, 0l);
+ activateAndAssertError(92l, 89l,
+ HttpErrorResponse.errorCodes.BAD_REQUEST,
+ getActivateLogPre() +
+ "Cannot activate session 92 because the currently active session (90) has changed since session 92 was created (was 89 at creation time)");
+ }
+
+ protected abstract String getActivateLogPre();
+
+ @Test
+ public void testActivationOfUnpreparedSession() throws Exception {
+ // Needed so we can test that previous active session is still active after a failed activation
+ RemoteSession firstSession = activateAndAssertOK(90l, 0l);
+ long sessionId = 91l;
+ ActivateRequest activateRequest = new ActivateRequest(sessionId, 0l, Session.Status.NEW, "").invoke();
+ HttpResponse actResponse = activateRequest.getActResponse();
+ RemoteSession session = activateRequest.getSession();
+ assertThat(actResponse.getStatus(), is(BAD_REQUEST));
+ assertThat(getRenderedString(actResponse), is("{\"error-code\":\"BAD_REQUEST\",\"message\":\"" + getActivateLogPre() + "Session " + sessionId + " is not prepared\"}"));
+ assertThat(session.getStatus(), is(not(Session.Status.ACTIVATE)));
+ assertThat(firstSession.getStatus(), is(Session.Status.ACTIVATE));
+ }
+
+ @Test
+ public void testActivationWithBarrierTimeout() throws Exception {
+ // Needed so we can test that previous active session is still active after a failed activation
+ activateAndAssertOK(90l, 0l);
+ ((MockCurator) curator).timeoutBarrierOnEnter(true);
+ ActivateRequest activateRequest = new ActivateRequest(91l, 90l, "").invoke();
+ HttpResponse actResponse = activateRequest.getActResponse();
+ assertThat(actResponse.getStatus(), is(INTERNAL_SERVER_ERROR));
+ }
+
+ @Test
+ public void testActivationOfSessionThatDoesNotExistAsLocalSession() throws Exception {
+ ActivateRequest activateRequest = new ActivateRequest(90l, 0l, "").invoke(false);
+ HttpResponse actResponse = activateRequest.getActResponse();
+ assertThat(actResponse.getStatus(), is(NOT_FOUND));
+ String message = getRenderedString(actResponse);
+ assertThat(message, is("{\"error-code\":\"NOT_FOUND\",\"message\":\"Session 90 was not found\"}"));
+ }
+
+ @Test
+ public void require_that_session_created_from_active_that_is_no_longer_active_cannot_be_activated() throws Exception {
+ long sessionId = 1;
+ activateAndAssertOK(1, 0);
+ sessionId++;
+ activateAndAssertOK(sessionId, 1);
+
+ sessionId++;
+ ActivateRequest activateRequest = new ActivateRequest(sessionId, 1, "").invoke();
+ HttpResponse actResponse = activateRequest.getActResponse();
+ String message = getRenderedString(actResponse);
+ assertThat(message, actResponse.getStatus(), Is.is(BAD_REQUEST));
+ assertThat(message,
+ containsString("Cannot activate session 3 because the currently active session (2) has changed since session 3 was created (was 1 at creation time)"));
+ }
+
+ @Test
+ public void testAlreadyActivatedSession() throws Exception {
+ activateAndAssertOK(1, 0);
+ HttpResponse response = createHandler().handle(SessionHandlerTest.createTestRequest(pathPrefix, HttpRequest.Method.PUT, Cmd.ACTIVE, 1l));
+ String message = getRenderedString(response);
+ assertThat(message, response.getStatus(), Is.is(BAD_REQUEST));
+ assertThat(message, containsString("Session 1 is already active"));
+ }
+
+ protected abstract SessionHandler createHandler() throws Exception;
+
+ private RemoteSession createRemoteSession(long sessionId, Session.Status status, SessionZooKeeperClient zkClient) throws IOException {
+ zkClient.writeStatus(status);
+ ZooKeeperClient zkC = new ZooKeeperClient(configCurator, new BaseDeployLogger(), false, pathProvider.getSessionDirs().append(String.valueOf(sessionId)));
+ VespaModelFactory modelFactory = new VespaModelFactory(new NullConfigModelRegistry());
+ zkC.feedZKFileRegistries(Collections.singletonMap(modelFactory.getVersion(), new MockFileRegistry()));
+ zkC.feedProvisionInfos(Collections.singletonMap(modelFactory.getVersion(), ProvisionInfo.withHosts(Collections.emptySet())));
+ RemoteSession session = new RemoteSession(TenantName.from("default"), sessionId, new TestComponentRegistry(curator, configCurator, new ModelFactoryRegistry(Collections.singletonList(modelFactory))), zkClient);
+ remoteSessionRepo.addSession(session);
+ return session;
+ }
+
+ private LocalSessionRepo addLocalSession(long sessionId, DeployData deployData, SessionZooKeeperClient zkc) {
+ writeApplicationId(zkc, deployData.getApplicationName());
+ TenantFileSystemDirs tenantFileSystemDirs = TenantFileSystemDirs.createTestDirs(tenant);
+ ApplicationPackage app = FilesApplicationPackage.fromFileWithDeployData(testApp,
+ deployData
+ );
+ localRepo.addSession(new LocalSession(tenant, sessionId, new SessionTest.MockSessionPreparer(), new SessionContext(app, zkc, new File(tenantFileSystemDirs.path(), String.valueOf(sessionId)), applicationRepo, new HostRegistry<>(), new SuperModelGenerationCounter(curator))));
+ return localRepo;
+ }
+
+ protected abstract void writeApplicationId(SessionZooKeeperClient zkc, String applicationName);
+
+ protected abstract Session activateAndAssertOK(long sessionId, long previousSessionId, String subPath) throws Exception;
+
+ protected abstract RemoteSession activateAndAssertOK(long sessionId, long previousSessionId) throws Exception;
+
+ protected ActivateRequest activateAndAssertOKPut(long sessionId, long previousSessionId, String subPath) throws Exception {
+ ActivateRequest activateRequest = new ActivateRequest(sessionId, previousSessionId, subPath).invoke();
+ HttpResponse actResponse = activateRequest.getActResponse();
+ String message = getRenderedString(actResponse);
+ assertThat(message, actResponse.getStatus(), is(OK));
+ assertActivationMessageOK(activateRequest, message);
+ RemoteSession session = activateRequest.getSession();
+ assertThat(session.getStatus(), is(Session.Status.ACTIVATE));
+ return activateRequest;
+ }
+
+ protected abstract void assertActivationMessageOK(ActivateRequest activateRequest, String message) throws IOException;
+
+ protected abstract void activateAndAssertError(long sessionId, long previousSessionId, HttpErrorResponse.errorCodes errorCode, String expectedError) throws Exception;
+
+ protected ActivateRequest activateAndAssertErrorPut(long sessionId, long previousSessionId, HttpErrorResponse.errorCodes errorCode, String expectedError) throws Exception {
+ ActivateRequest activateRequest = new ActivateRequest(sessionId, previousSessionId, "").invoke();
+ HttpResponse actResponse = activateRequest.getActResponse();
+ RemoteSession session = activateRequest.getSession();
+ assertThat(actResponse.getStatus(), is(BAD_REQUEST));
+ String message = getRenderedString(actResponse);
+ assertThat(message, is("{\"error-code\":\"" + errorCode.name() + "\",\"message\":\"" + expectedError + "\"}"));
+ assertThat(session.getStatus(), is(Session.Status.PREPARE));
+ return activateRequest;
+ }
+
+ protected void testUnsupportedMethod(com.yahoo.container.jdisc.HttpRequest request) throws Exception {
+ HttpResponse response = createHandler().handle(request);
+ HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(response, METHOD_NOT_ALLOWED,
+ HttpErrorResponse.errorCodes.METHOD_NOT_ALLOWED,
+ "Method '" + request.getMethod().name() + "' is not supported");
+ }
+
+ protected class ActivateRequest {
+ private long sessionId;
+ private RemoteSession session;
+ private SessionHandler handler;
+ private HttpResponse actResponse;
+ private Session.Status initialStatus;
+ private DeployData deployData;
+ private ApplicationMetaData metaData;
+ private String subPath;
+
+ public ActivateRequest(long sessionId, long previousSessionId, String subPath) {
+ this(sessionId, previousSessionId, Session.Status.PREPARE, subPath);
+ }
+
+ public ActivateRequest(long sessionId, long previousSessionId, Session.Status initialStatus, String subPath) {
+ this.sessionId = sessionId;
+ this.initialStatus = initialStatus;
+ this.deployData = new DeployData("foo", "bar", appName, 0l, sessionId, previousSessionId);
+ this.subPath = subPath;
+ }
+
+ public RemoteSession getSession() {
+ return session;
+ }
+
+ public SessionHandler getHandler() {
+ return handler;
+ }
+
+ public HttpResponse getActResponse() {
+ return actResponse;
+ }
+
+ public long getSessionId() {
+ return sessionId;
+ }
+
+ public ApplicationMetaData getMetaData() {
+ return metaData;
+ }
+
+ public ActivateRequest invoke() throws Exception {
+ return invoke(true);
+ }
+
+ public ActivateRequest invoke(boolean createLocalSession) throws Exception {
+ SessionZooKeeperClient zkClient = new MockSessionZKClient(curator, pathProvider.getSessionDirs().append(String.valueOf(sessionId)), Optional.of(ProvisionInfo.withHosts(Collections.singleton(new HostSpec("bar", Collections.emptyList())))));
+ session = createRemoteSession(sessionId, initialStatus, zkClient);
+ if (createLocalSession) {
+ LocalSessionRepo repo = addLocalSession(sessionId, deployData, zkClient);
+ metaData = repo.getSession(sessionId).getMetaData();
+ }
+ handler = createHandler();
+ actResponse = handler.handle(SessionHandlerTest.createTestRequest(pathPrefix, HttpRequest.Method.PUT, Cmd.ACTIVE, sessionId, subPath));
+ return this;
+ }
+ }
+
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionContentHandlerTestBase.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionContentHandlerTestBase.java
new file mode 100644
index 00000000000..43fdb2d747a
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionContentHandlerTestBase.java
@@ -0,0 +1,126 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.http;
+
+import com.google.common.io.Files;
+import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.jdisc.Response;
+import com.yahoo.jdisc.http.HttpRequest;
+import com.yahoo.text.Utf8;
+import org.apache.commons.io.FileUtils;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+
+public abstract class SessionContentHandlerTestBase extends ContentHandlerTestBase {
+
+ @Test
+ public void require_that_directories_can_be_created() throws IOException {
+ assertMkdir("/bar/");
+ assertMkdir("/bar/brask/");
+ assertMkdir("/bar/brask/");
+ assertMkdir("/bar/brask/bram/");
+ assertMkdir("/brask/og/bram/");
+ }// TODO: Enable when we have a predictable way of checking request body existence.
+
+ @Test
+ @Ignore
+ public void require_that_mkdir_with_body_is_illegal() throws IOException {
+ HttpResponse response = put("/foobio/", "foo");
+ assertNotNull(response);
+ assertThat(response.getStatus(), is(Response.Status.BAD_REQUEST));
+ }
+
+ @Test
+ public void require_that_nonexistant_session_returns_not_found() throws IOException {
+ HttpResponse response = doRequest(HttpRequest.Method.GET, "/test.txt", 2l);
+ assertNotNull(response);
+ assertThat(response.getStatus(), is(Response.Status.NOT_FOUND));
+ }
+
+ protected HttpResponse put(String path, String content) {
+ ByteArrayInputStream data = new ByteArrayInputStream(Utf8.toBytes(content));
+ return doRequest(HttpRequest.Method.PUT, path, data);
+ }
+
+ @Test
+ public void require_that_file_write_without_body_is_illegal() throws IOException {
+ HttpResponse response = doRequest(HttpRequest.Method.PUT, "/foobio.txt");
+ assertNotNull(response);
+ assertThat(response.getStatus(), is(Response.Status.BAD_REQUEST));
+ }
+
+ @Test
+ public void require_that_files_can_be_written() throws IOException {
+ assertWriteFile("/foo/minfil.txt", "Mycontent");
+ assertWriteFile("/foo/minfil.txt", "Differentcontent");
+ }
+
+ @Test
+ public void require_that_nonexistant_file_returs_not_found_when_deleted() throws IOException {
+ assertDeleteFile(Response.Status.NOT_FOUND, "/test2.txt", "{\"error-code\":\"NOT_FOUND\",\"message\":\"Session 1 does not contain a file 'test2.txt'\"}");
+ }
+
+ @Test
+ public void require_that_files_can_be_deleted() throws IOException {
+ assertDeleteFile(Response.Status.OK, "/test.txt");
+ assertDeleteFile(Response.Status.NOT_FOUND, "/test.txt", "{\"error-code\":\"NOT_FOUND\",\"message\":\"Session 1 does not contain a file 'test.txt'\"}");
+ assertDeleteFile(Response.Status.BAD_REQUEST, "/newtest", "{\"error-code\":\"BAD_REQUEST\",\"message\":\"File 'newtest' is not an empty directory\"}");
+ assertDeleteFile(Response.Status.OK, "/newtest/testfile.txt");
+ assertDeleteFile(Response.Status.OK, "/newtest");
+ }
+
+ @Test
+ public void require_that_status_is_given_for_new_files() throws IOException {
+ assertStatus("/test.txt?return=status",
+ "{\"status\":\"new\",\"md5\":\"d3b07384d113edec49eaa6238ad5ff00\",\"name\":\"http://foo:1337" + pathPrefix + "1/content/test.txt\"}");
+ assertWriteFile("/test.txt", "Mycontent");
+ assertStatus("/test.txt?return=status",
+ "{\"status\":\"changed\",\"md5\":\"01eabd73c69d78d0009ec93cd62d7f77\",\"name\":\"http://foo:1337" + pathPrefix + "1/content/test.txt\"}");
+ }
+
+ private void assertWriteFile(String path, String content) throws IOException {
+ HttpResponse response = put(path, content);
+ assertNotNull(response);
+ assertThat(response.getStatus(), is(Response.Status.OK));
+ assertContent(path, content);
+ assertThat(SessionHandlerTest.getRenderedString(response),
+ is("{\"prepared\":\"http://foo:1337" + pathPrefix + "1/prepared\"}"));
+ }
+
+ private void assertDeleteFile(int statusCode, String filePath) throws IOException {
+ assertDeleteFile(statusCode, filePath, "{\"prepared\":\"http://foo:1337" + pathPrefix + "1/prepared\"}");
+ }
+
+ private void assertDeleteFile(int statusCode, String filePath, String expectedResponse) throws IOException {
+ HttpResponse response = doRequest(HttpRequest.Method.DELETE, filePath);
+ assertNotNull(response);
+ assertThat(response.getStatus(), is(statusCode));
+ assertThat(SessionHandlerTest.getRenderedString(response), is(expectedResponse));
+ }
+
+ private void assertMkdir(String path) throws IOException {
+ HttpResponse response = doRequest(HttpRequest.Method.PUT, path);
+ assertNotNull(response);
+ assertThat(response.getStatus(), is(Response.Status.OK));
+ assertThat(SessionHandlerTest.getRenderedString(response),
+ is("{\"prepared\":\"http://foo:1337" + pathPrefix + "1/prepared\"}"));
+ }
+
+ protected File createTestApp() throws IOException {
+ File testApp = Files.createTempDir();
+ FileUtils.copyDirectory(new File("src/test/apps/content"), testApp);
+ return testApp;
+ }
+
+ protected abstract HttpResponse doRequest(HttpRequest.Method method, String path, long sessionId);
+ protected abstract HttpResponse doRequest(HttpRequest.Method method, String path, InputStream data);
+ protected abstract HttpResponse doRequest(HttpRequest.Method method, String path, long sessionId, InputStream data);
+} \ No newline at end of file
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionCreateHandlerTestBase.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionCreateHandlerTestBase.java
new file mode 100644
index 00000000000..6a6a4097319
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionCreateHandlerTestBase.java
@@ -0,0 +1,216 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.http;
+
+import com.google.common.io.Files;
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.config.model.application.provider.FilesApplicationPackage;
+import com.yahoo.container.jdisc.HttpRequest;
+import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.io.IOUtils;
+import com.yahoo.vespa.config.server.CompressedApplicationInputStreamTest;
+import com.yahoo.vespa.config.server.TimeoutBudget;
+import com.yahoo.vespa.config.server.application.ApplicationRepo;
+import com.yahoo.vespa.config.server.session.LocalSession;
+import com.yahoo.vespa.config.server.session.LocalSessionRepo;
+import com.yahoo.vespa.config.server.session.SessionFactory;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.yahoo.jdisc.Response.Status.*;
+import static com.yahoo.jdisc.http.HttpRequest.Method.GET;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests for session create handlers, to make it easier to have
+ * similar tests for more than one version of the API.
+ *
+ * @author musum
+ * @since 5.1.28
+ */
+public abstract class SessionCreateHandlerTestBase extends SessionHandlerTest {
+
+ public static final HashMap<String, String> postHeaders = new HashMap<>();
+
+ protected String pathPrefix = "/application/v2/session/";
+ protected String createdMessage = " created.\"";
+ protected String tenantMessage = "";
+
+ public File testApp = new File("src/test/apps/app");
+ public LocalSessionRepo localSessionRepo;
+ public ApplicationRepo applicationRepo;
+
+ static {
+ postHeaders.put(SessionCreate.contentTypeHeader, SessionCreate.APPLICATION_X_GZIP);
+ }
+
+ @Ignore
+ @Test
+ public void require_that_from_parameter_cannot_be_set_if_data_in_request() throws IOException {
+ HttpRequest request = post(Collections.singletonMap("from", "active"));
+ HttpResponse response = createHandler().handle(request);
+ HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(response, BAD_REQUEST, HttpErrorResponse.errorCodes.BAD_REQUEST, "Parameter 'from' is illegal for POST");
+ }
+
+ @Test
+ public void require_that_post_request_must_contain_data() throws IOException {
+ HttpResponse response = createHandler().handle(post());
+ HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(response, BAD_REQUEST, HttpErrorResponse.errorCodes.BAD_REQUEST, "Request contains no data");
+ }
+
+ @Test
+ public void require_that_post_request_must_have_correct_content_type() throws IOException {
+ HashMap<String, String> headers = new HashMap<>(); // no Content-Type header
+ File outFile = CompressedApplicationInputStreamTest.createTarFile();
+ HttpResponse response = createHandler().handle(post(outFile, headers, null));
+ HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(response, BAD_REQUEST, HttpErrorResponse.errorCodes.BAD_REQUEST, "Request contains no Content-Type header");
+ }
+
+ @Test
+ public void require_that_application_name_is_given_from_parameter() throws IOException {
+ Map<String, String> params = Collections.singletonMap("name", "ulfio");
+ File outFile = CompressedApplicationInputStreamTest.createTarFile();
+ MockSessionFactory factory = new MockSessionFactory();
+ createHandler(factory).handle(post(outFile, postHeaders, params));
+ assertTrue(factory.createCalled);
+ assertThat(factory.applicationName, is("ulfio"));
+ }
+
+ protected void assertFromParameter(String expected, String from) throws IOException {
+ HttpRequest request = post(Collections.singletonMap("from", from));
+ MockSessionFactory factory = new MockSessionFactory();
+ factory.applicationPackage = testApp;
+ HttpResponse response = createHandler(factory).handle(request);
+ assertNotNull(response);
+ assertThat(response.getStatus(), is(OK));
+ assertTrue(factory.createFromCalled);
+ assertThat(SessionHandlerTest.getRenderedString(response),
+ is("{\"log\":[]" + tenantMessage + ",\"session-id\":\"" + expected + "\",\"prepared\":\"http://" + hostname + ":" + port + pathPrefix +
+ expected + "/prepared\",\"content\":\"http://" + hostname + ":" + port + pathPrefix +
+ expected + "/content/\",\"message\":\"Session " + expected + createdMessage + "}"));
+ }
+
+ protected void assertIllegalFromParameter(String fromValue) throws IOException {
+ File outFile = CompressedApplicationInputStreamTest.createTarFile();
+ HttpRequest request = post(outFile, postHeaders, Collections.singletonMap("from", fromValue));
+ HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(createHandler().handle(request), BAD_REQUEST, HttpErrorResponse.errorCodes.BAD_REQUEST, "Parameter 'from' has illegal value '" + fromValue + "'");
+ }
+
+ @Test
+ public void require_that_prepare_url_is_returned_on_success() throws IOException {
+ File outFile = CompressedApplicationInputStreamTest.createTarFile();
+ Map<String, String> parameters = Collections.singletonMap("name", "foo");
+ HttpResponse response = createHandler().handle(post(outFile, postHeaders, parameters));
+ assertNotNull(response);
+ assertThat(response.getStatus(), is(OK));
+ assertThat(SessionHandlerTest.getRenderedString(response),
+ is("{\"log\":[]" + tenantMessage + ",\"session-id\":\"0\",\"prepared\":\"http://" +
+ hostname + ":" + port + pathPrefix + "0/prepared\",\"content\":\"http://" +
+ hostname + ":" + port + pathPrefix + "0/content/\",\"message\":\"Session 0" + createdMessage + "}"));
+ }
+
+ @Test
+ public void require_that_session_factory_is_called() throws IOException {
+ MockSessionFactory sessionFactory = new MockSessionFactory();
+ File outFile = CompressedApplicationInputStreamTest.createTarFile();
+ createHandler(sessionFactory).handle(post(outFile));
+ assertTrue(sessionFactory.createCalled);
+ }
+
+ @Test
+ public void require_that_handler_does_not_support_get() throws IOException {
+ HttpResponse response = createHandler().handle(HttpRequest.createTestRequest(pathPrefix, GET));
+ HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(response, METHOD_NOT_ALLOWED,
+ HttpErrorResponse.errorCodes.METHOD_NOT_ALLOWED,
+ "Method 'GET' is not supported");
+ }
+
+ @Test
+ public void require_internal_error_when_exception() throws IOException {
+ MockSessionFactory factory = new MockSessionFactory();
+ factory.doThrow = true;
+ File outFile = CompressedApplicationInputStreamTest.createTarFile();
+ HttpResponse response = createHandler(factory).handle(post(outFile));
+ HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(response, INTERNAL_SERVER_ERROR,
+ HttpErrorResponse.errorCodes.INTERNAL_SERVER_ERROR,
+ "foo");
+ }
+
+ @Test
+ public void require_that_handler_unpacks_application() throws IOException {
+ MockSessionFactory sessionFactory = new MockSessionFactory();
+ File outFile = CompressedApplicationInputStreamTest.createTarFile();
+ createHandler(sessionFactory).handle(post(outFile));
+ assertTrue(sessionFactory.createCalled);
+ final File applicationPackage = sessionFactory.applicationPackage;
+ assertNotNull(applicationPackage);
+ assertTrue(applicationPackage.exists());
+ final File[] files = applicationPackage.listFiles();
+ assertNotNull(files);
+ assertThat(files.length, is(2));
+ }
+
+ @Test
+ public void require_that_session_is_stored_in_repo() throws IOException {
+ File outFile = CompressedApplicationInputStreamTest.createTarFile();
+ createHandler(new MockSessionFactory()).handle(post(outFile));
+ assertNotNull(localSessionRepo.getSession(0l));
+ }
+
+ public abstract SessionHandler createHandler();
+
+ public abstract SessionHandler createHandler(SessionFactory sessionFactory);
+
+ public abstract HttpRequest post() throws FileNotFoundException;
+
+ public abstract HttpRequest post(File file) throws FileNotFoundException;
+
+ public abstract HttpRequest post(File file, Map<String, String> headers, Map<String, String> parameters) throws FileNotFoundException;
+
+ public abstract HttpRequest post(Map<String, String> parameters) throws FileNotFoundException;
+
+ public static class MockSessionFactory implements SessionFactory {
+ public boolean createCalled = false;
+ public boolean createFromCalled = false;
+ public boolean doThrow = false;
+ public File applicationPackage;
+ public String applicationName;
+
+ @Override
+ public LocalSession createSession(File applicationDirectory, String applicationName, DeployLogger logger, TimeoutBudget timeoutBudget) {
+ createCalled = true;
+ this.applicationName = applicationName;
+ if (doThrow) {
+ throw new RuntimeException("foo");
+ }
+ final File tempDir = Files.createTempDir();
+ try {
+ IOUtils.copyDirectory(applicationDirectory, tempDir);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ this.applicationPackage = tempDir;
+ return new SessionHandlerTest.MockSession(0, FilesApplicationPackage.fromFile(applicationPackage));
+ }
+
+ @Override
+ public LocalSession createSessionFromExisting(LocalSession existingSession, DeployLogger logger, TimeoutBudget timeoutBudget) {
+ if (doThrow) {
+ throw new RuntimeException("foo");
+ }
+ createFromCalled = true;
+ return new SessionHandlerTest.MockSession(existingSession.getSessionId() + 1, FilesApplicationPackage.fromFile(applicationPackage));
+ }
+ }
+}
+
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionExampleHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionExampleHandlerTest.java
new file mode 100644
index 00000000000..2d7e293fb3c
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionExampleHandlerTest.java
@@ -0,0 +1,101 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.http;
+
+import com.yahoo.container.jdisc.HttpRequest;
+import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.container.jdisc.ThreadedHttpRequestHandler;
+import com.yahoo.slime.Cursor;
+import com.yahoo.slime.JsonFormat;
+import com.yahoo.slime.Slime;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+import static com.yahoo.jdisc.http.HttpResponse.Status.*;
+import static com.yahoo.jdisc.http.HttpRequest.Method.*;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author musum
+ * @since 5.1.14
+ */
+public class SessionExampleHandlerTest {
+ private static final String URI = "http://localhost:19071/session/example";
+
+ @Test
+ public void basicPut() throws IOException {
+ final SessionExampleHandler handler = new SessionExampleHandler(Executors.newCachedThreadPool());
+ final HttpRequest request = HttpRequest.createTestRequest(URI, PUT);
+ HttpResponse response = handler.handle(request);
+ assertThat(response.getStatus(), is(OK));
+ assertThat(SessionHandlerTest.getRenderedString(response), is("{\"test\":\"PUT received\"}"));
+ }
+
+ @Test
+ public void invalidMethod() {
+ final SessionExampleHandler handler = new SessionExampleHandler(Executors.newCachedThreadPool());
+ final HttpRequest request = HttpRequest.createTestRequest(URI, GET);
+ HttpResponse response = handler.handle(request);
+ assertThat(response.getStatus(), is(METHOD_NOT_ALLOWED));
+ }
+
+
+ /**
+ * A handler that prepares a session given by an id in the request.
+ *
+ * @author musum
+ * @since 5.1.14
+ */
+ public static class SessionExampleHandler extends ThreadedHttpRequestHandler {
+
+ public SessionExampleHandler(Executor executor) {
+ super(executor);
+ }
+
+ @Override
+ public HttpResponse handle(HttpRequest request) {
+ final com.yahoo.jdisc.http.HttpRequest.Method method = request.getMethod();
+ switch (method) {
+ case PUT:
+ return handlePUT(request);
+ case GET:
+ return new SessionExampleResponse(METHOD_NOT_ALLOWED, "Method '" + method + "' is not supported");
+ default:
+ return new SessionExampleResponse(INTERNAL_SERVER_ERROR);
+ }
+ }
+
+ @SuppressWarnings({"UnusedDeclaration"})
+ HttpResponse handlePUT(HttpRequest request) {
+ return new SessionExampleResponse(OK, "PUT received");
+ }
+
+ private static class SessionExampleResponse extends HttpResponse {
+ private final Slime slime = new Slime();
+ private final Cursor root = slime.setObject();
+ private final String message;
+
+
+ private SessionExampleResponse(int status) {
+ this(status, "");
+ headers().put("Cache-Control","max-age=120");
+ }
+
+ private SessionExampleResponse(int status, String message) {
+ super(status);
+ this.message = message;
+ }
+
+ @Override
+ public void render(OutputStream outputStream) throws IOException {
+ root.setString("test", message);
+ new JsonFormat(true).encode(outputStream, slime);
+ }
+ }
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionHandlerTest.java
new file mode 100644
index 00000000000..d38b7f9e586
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionHandlerTest.java
@@ -0,0 +1,164 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.http;
+
+import com.yahoo.config.application.api.ApplicationFile;
+import com.yahoo.config.application.api.ApplicationPackage;
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.config.model.test.MockApplicationPackage;
+import com.yahoo.transaction.Transaction;
+import com.yahoo.log.LogLevel;
+import com.yahoo.path.Path;
+import com.yahoo.container.jdisc.HttpRequest;
+import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.vespa.config.server.ApplicationSet;
+import com.yahoo.vespa.config.server.HostRegistry;
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.vespa.config.server.configchange.ConfigChangeActions;
+import com.yahoo.vespa.config.server.session.*;
+
+import java.io.*;
+import java.util.Optional;
+
+/**
+ * Base class for session handler tests
+ *
+ * @author musum
+ * @since 5.1.14
+ */
+public class SessionHandlerTest {
+
+ protected String pathPrefix = "/application/v2/session/";
+ public static final String hostname = "foo";
+ public static final int port = 1337;
+
+ public static HttpRequest createTestRequest(String path, com.yahoo.jdisc.http.HttpRequest.Method method, Cmd cmd, Long id, String subPath, InputStream data) {
+ return HttpRequest.createTestRequest("http://" + hostname + ":" + port + path + "/" + id + "/" + cmd.toString() + subPath, method, data);
+ }
+
+ public static HttpRequest createTestRequest(String path, com.yahoo.jdisc.http.HttpRequest.Method method, Cmd cmd, Long id, String subPath) {
+ return HttpRequest.createTestRequest("http://" + hostname + ":" + port + path + "/" + id + "/" + cmd.toString() + subPath, method);
+ }
+
+ public static HttpRequest createTestRequest(String path, com.yahoo.jdisc.http.HttpRequest.Method method, Cmd cmd, Long id) {
+ return createTestRequest(path, method, cmd, id, "");
+ }
+
+ public static HttpRequest createTestRequest(String path) {
+ return HttpRequest.createTestRequest("http://" + hostname + ":" + port + path, com.yahoo.jdisc.http.HttpRequest.Method.PUT);
+ }
+
+ public static String getRenderedString(HttpResponse response) throws IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ response.render(baos);
+ return baos.toString("UTF-8");
+ }
+
+ public static class MockSession extends LocalSession {
+
+ public boolean doVerboseLogging = false;
+ public Session.Status status;
+ private final SessionPreparer preparer;
+ private final ApplicationPackage app;
+ private ConfigChangeActions actions = new ConfigChangeActions();
+ private long createTime = System.currentTimeMillis() / 1000;
+ private ApplicationId applicationId;
+
+ public MockSession(long id, ApplicationPackage app) {
+ super(TenantName.defaultName(), id, null, new SessionContext(null, new MockSessionZKClient(MockApplicationPackage.createEmpty()), null, null, new HostRegistry<>(), null));
+ this.app = app;
+ this.preparer = new SessionTest.MockSessionPreparer();
+ }
+
+ public MockSession(long sessionId, ApplicationPackage applicationPackage, long createTime) {
+ this(sessionId, applicationPackage);
+ this.createTime = createTime;
+ }
+
+ public MockSession(long sessionId, ApplicationPackage applicationPackage, ConfigChangeActions actions) {
+ this(sessionId, applicationPackage);
+ this.actions = actions;
+ }
+
+ public MockSession(long sessionId, ApplicationPackage app, ApplicationId applicationId) {
+ this(sessionId, app);
+ this.applicationId = applicationId;
+ }
+
+ @Override
+ public ConfigChangeActions prepare(DeployLogger logger, PrepareParams params, Optional<ApplicationSet> application, Path tenantPath) {
+ status = Session.Status.PREPARE;
+ if (doVerboseLogging) {
+ logger.log(LogLevel.DEBUG, "debuglog");
+ }
+ return actions;
+ }
+
+ public void setStatus(Session.Status status) {
+ this.status = status;
+ }
+
+ @Override
+ public Session.Status getStatus() {
+ return this.status;
+ }
+
+ @Override
+ public Transaction createDeactivateTransaction() {
+ return new DummyTransaction().add((DummyTransaction.RunnableOperation) () -> {
+ status = Status.DEACTIVATE;
+ });
+ }
+
+ @Override
+ public Transaction createActivateTransaction() {
+ return new DummyTransaction().add((DummyTransaction.RunnableOperation) () -> {
+ status = Status.ACTIVATE;
+ });
+ }
+
+ @Override
+ public ApplicationFile getApplicationFile(Path relativePath, Mode mode) {
+ if (mode == Mode.WRITE) {
+ status = Status.NEW;
+ }
+ if (preparer == null) {
+ return null;
+ }
+ ApplicationPackage pkg = app;
+ if (pkg == null) {
+ return null;
+ }
+ return pkg.getFile(relativePath);
+ }
+
+ @Override
+ public ApplicationId getApplicationId() {
+ return applicationId;
+ }
+
+ @Override
+ public long getCreateTime() {
+ return createTime;
+ }
+
+ @Override
+ public void delete() { }
+ }
+
+ public static enum Cmd {
+ PREPARED("prepared"),
+ ACTIVE("active"),
+ CONTENT("content");
+ private final String name;
+
+ private Cmd(String s) {
+ this.name = s;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionPrepareHandlerTestBase.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionPrepareHandlerTestBase.java
new file mode 100644
index 00000000000..885d4164196
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionPrepareHandlerTestBase.java
@@ -0,0 +1,185 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.http;
+
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.container.jdisc.HttpRequest;
+import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.path.Path;
+import com.yahoo.vespa.config.server.*;
+import com.yahoo.vespa.config.server.session.*;
+
+import com.yahoo.vespa.curator.Curator;
+import org.junit.Test;
+
+import java.io.IOException;
+
+import static com.yahoo.jdisc.http.HttpRequest.Method;
+import static com.yahoo.jdisc.http.HttpResponse.Status.*;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.endsWith;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author musum
+ * @since 5.1.14
+ */
+public abstract class SessionPrepareHandlerTestBase extends SessionHandlerTest {
+
+ protected Curator curator;
+ private SessionZooKeeperClient zooKeeperClient;
+ protected LocalSessionRepo localRepo;
+
+ protected String preparedMessage = " prepared.\"}";
+ protected String tenantMessage = "";
+
+
+ @Test
+ public void require_error_when_session_id_does_not_exist() throws Exception {
+ // No session with this id exists
+ HttpResponse response = createHandler().handle(SessionHandlerTest.createTestRequest(pathPrefix, Method.PUT, Cmd.PREPARED, 9999L));
+ HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(response, NOT_FOUND, HttpErrorResponse.errorCodes.NOT_FOUND, "Session 9999 was not found");
+ }
+
+ @Test
+ public void require_error_when_session_id_not_a_number() throws Exception {
+ final String session = "notanumber/prepared";
+ HttpResponse response = createHandler().handle(SessionHandlerTest.createTestRequest(pathPrefix + session));
+ HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(response, BAD_REQUEST,
+ HttpErrorResponse.errorCodes.BAD_REQUEST,
+ "Session id in request is not a number, request was 'http://" + hostname + ":" + port + pathPrefix + session + "'");
+ }
+
+ @Test
+ public void require_that_handler_gives_error_for_unsupported_methods() throws Exception {
+ testUnsupportedMethod(SessionHandlerTest.createTestRequest(pathPrefix, Method.POST, Cmd.PREPARED, 1L));
+ testUnsupportedMethod(SessionHandlerTest.createTestRequest(pathPrefix, Method.DELETE, Cmd.PREPARED, 1L));
+ }
+
+ protected void testUnsupportedMethod(HttpRequest request) throws Exception {
+ HttpResponse response = createHandler().handle(request);
+ HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(response, METHOD_NOT_ALLOWED,
+ HttpErrorResponse.errorCodes.METHOD_NOT_ALLOWED,
+ "Method '" + request.getMethod().name() + "' is not supported");
+ }
+
+ @Test
+ public void require_that_activate_url_is_returned_on_success() throws Exception {
+ MockSession session = new MockSession(1, null);
+ localRepo.addSession(session);
+ HttpResponse response = createHandler().handle(SessionHandlerTest.createTestRequest(pathPrefix, Method.PUT, Cmd.PREPARED, 1L));
+ assertThat(session.getStatus(), is(Session.Status.PREPARE));
+ assertNotNull(response);
+ assertThat(response.getStatus(), is(OK));
+ assertResponseContains(response, "\"activate\":\"http://foo:1337" + pathPrefix + "1/active\",\"message\":\"Session 1" + preparedMessage);
+ }
+
+ @Test
+ public void require_debug() throws Exception {
+ HttpResponse response = createHandler().handle(SessionHandlerTest.createTestRequest(pathPrefix, Method.PUT, Cmd.PREPARED, 9999L, "?debug=true"));
+ assertThat(response.getStatus(), is(NOT_FOUND));
+ assertThat(SessionHandlerTest.getRenderedString(response), containsString("NotFoundException"));
+ }
+
+ @Test
+ public void require_verbose() throws Exception {
+ MockSession session = new MockSession(1, null);
+ session.doVerboseLogging = true;
+ localRepo.addSession(session);
+ HttpResponse response = createHandler().handle(SessionHandlerTest.createTestRequest(pathPrefix, Method.PUT, Cmd.PREPARED, 1L, "?verbose=true"));
+ assertThat(response.getStatus(), is(OK));
+ assertThat(SessionHandlerTest.getRenderedString(response), containsString("debuglog"));
+ }
+
+ /**
+ * A mock remote session repo based on contents of local repo
+ */
+ private RemoteSessionRepo fromLocalSessionRepo(LocalSessionRepo localRepo) {
+ RemoteSessionRepo remoteRepo = new RemoteSessionRepo();
+ PathProvider pathProvider = new PathProvider(Path.createRoot());
+ for (LocalSession ls : localRepo.listSessions()) {
+
+ zooKeeperClient = new MockSessionZKClient(curator, pathProvider.getSessionDirs().append(String.valueOf(ls.getSessionId())));
+ if (ls.getStatus()!=null) zooKeeperClient.writeStatus(ls.getStatus());
+ RemoteSession remSess = new RemoteSession(TenantName.from("default"), ls.getSessionId(),
+ new TestComponentRegistry(),
+ zooKeeperClient);
+ remoteRepo.addSession(remSess);
+ }
+ return remoteRepo;
+ }
+
+ @Test
+ public void require_get_response_activate_url_on_ok() throws Exception {
+ MockSession session = new MockSession(1, null);
+ localRepo.addSession(session);
+ SessionHandler sessHandler = createHandler(fromLocalSessionRepo(localRepo));
+ sessHandler.handle(SessionHandlerTest.createTestRequest(pathPrefix, Method.PUT, Cmd.PREPARED, 1L));
+ session.setStatus(Session.Status.PREPARE);
+ zooKeeperClient.writeStatus(Session.Status.PREPARE);
+ HttpResponse getResponse = sessHandler.handle(SessionHandlerTest.createTestRequest(pathPrefix, Method.GET, Cmd.PREPARED, 1L));
+ assertResponseContains(getResponse, "\"activate\":\"http://foo:1337" + pathPrefix + "1/active\",\"message\":\"Session 1" + preparedMessage);
+ }
+
+ @Test
+ public void require_get_response_error_on_not_prepared() throws Exception {
+ MockSession session = new MockSession(1, null);
+ localRepo.addSession(session);
+ SessionHandler sessHandler = createHandler(fromLocalSessionRepo(localRepo));
+ session.setStatus(Session.Status.NEW);
+ zooKeeperClient.writeStatus(Session.Status.NEW);
+ HttpResponse getResponse = sessHandler.handle(SessionHandlerTest.createTestRequest(pathPrefix, Method.GET, Cmd.PREPARED, 1L));
+ HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(getResponse, BAD_REQUEST,
+ HttpErrorResponse.errorCodes.BAD_REQUEST,
+ "Session not prepared: 1");
+ session.setStatus(Session.Status.ACTIVATE);
+ zooKeeperClient.writeStatus(Session.Status.ACTIVATE);
+ getResponse = sessHandler.handle(SessionHandlerTest.createTestRequest(pathPrefix, Method.GET, Cmd.PREPARED, 1L));
+ HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(getResponse, BAD_REQUEST,
+ HttpErrorResponse.errorCodes.BAD_REQUEST,
+ "Session is active: 1");
+ }
+
+ @Test
+ public void require_cannot_prepare_active_session() throws Exception {
+ MockSession session = new MockSession(1, null);
+ localRepo.addSession(session);
+ session.setStatus(Session.Status.ACTIVATE);
+ SessionHandler sessionHandler = createHandler(fromLocalSessionRepo(localRepo));
+ HttpResponse putResponse = sessionHandler.handle(SessionHandlerTest.createTestRequest(pathPrefix, Method.PUT, Cmd.PREPARED, 1L));
+ HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(putResponse, BAD_REQUEST,
+ HttpErrorResponse.errorCodes.BAD_REQUEST,
+ "Session is active: 1");
+ }
+
+ @Test
+ public void require_get_response_error_when_session_id_does_not_exist() throws Exception {
+ MockSession session = new MockSession(1, null);
+ localRepo.addSession(session);
+ SessionHandler sessHandler = createHandler(fromLocalSessionRepo(localRepo));
+ HttpResponse getResponse = sessHandler.handle(SessionHandlerTest.createTestRequest(pathPrefix, Method.GET, Cmd.PREPARED, 9999L));
+ HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(getResponse, NOT_FOUND,
+ HttpErrorResponse.errorCodes.NOT_FOUND,
+ "Session 9999 was not found");
+ }
+
+ protected static void assertResponse(HttpResponse response, String activateString) throws IOException {
+ // TODO Test when more logging is added
+ //assertThat(baos.toString(), startsWith("{\"log\":[{\"time\":"));
+ assertThat(SessionHandlerTest.getRenderedString(response), endsWith(activateString));
+ }
+
+ protected static void assertResponseContains(HttpResponse response, String string) throws IOException {
+ assertThat(SessionHandlerTest.getRenderedString(response), containsString(string));
+ }
+
+ protected static void assertResponseNotContains(HttpResponse response, String string) throws IOException {
+ assertThat(SessionHandlerTest.getRenderedString(response), not(containsString(string)));
+ }
+
+ public abstract SessionHandler createHandler() throws Exception;
+
+ public abstract SessionHandler createHandler(RemoteSessionRepo remoteSessionRepo) throws Exception ;
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationContentHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationContentHandlerTest.java
new file mode 100644
index 00000000000..1e1bf1a644c
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationContentHandlerTest.java
@@ -0,0 +1,111 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.http.v2;
+
+import com.yahoo.config.model.application.provider.FilesApplicationPackage;
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.config.provision.Zone;
+import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.container.jdisc.HttpRequest;
+import com.yahoo.container.logging.AccessLog;
+import com.yahoo.jdisc.Response;
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.vespa.config.server.http.ContentHandlerTestBase;
+import com.yahoo.vespa.config.server.provision.HostProvisionerProvider;
+import com.yahoo.vespa.config.server.session.Session;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class ApplicationContentHandlerTest extends ContentHandlerTestBase {
+
+ private ApplicationHandler handler;
+ private TenantName tenant1 = TenantName.from("mofet");
+ private TenantName tenant2 = TenantName.from("bla");
+ private String baseServer = "http://foo:1337";
+
+ private ApplicationId idTenant1 = new ApplicationId.Builder()
+ .tenant(tenant1)
+ .applicationName("foo").instanceName("quux").build();
+ private ApplicationId idTenant2 = new ApplicationId.Builder()
+ .tenant(tenant2)
+ .applicationName("foo").instanceName("quux").build();
+ private MockSession session2;
+
+ @Before
+ public void setupHandler() throws Exception {
+ TestTenantBuilder testTenantBuilder = new TestTenantBuilder();
+ testTenantBuilder.createTenant(tenant1);
+ testTenantBuilder.createTenant(tenant2);
+ session2 = new MockSession(2l, FilesApplicationPackage.fromFile(new File("src/test/apps/content")));
+ testTenantBuilder.tenants().get(tenant1).getLocalSessionRepo().addSession(session2);
+ testTenantBuilder.tenants().get(tenant2).getLocalSessionRepo().addSession(new MockSession(3l, FilesApplicationPackage.fromFile(new File("src/test/apps/content2"))));
+ testTenantBuilder.tenants().get(tenant1).getApplicationRepo().createPutApplicationTransaction(idTenant1, 2l).commit();
+ testTenantBuilder.tenants().get(tenant2).getApplicationRepo().createPutApplicationTransaction(idTenant2, 3l).commit();
+ handler = new ApplicationHandler(command -> command.run(), AccessLog.voidAccessLog(), testTenantBuilder.createTenants(), HostProvisionerProvider.empty(), Zone.defaultZone(), null, null);
+ pathPrefix = createPath(idTenant1, Zone.defaultZone());
+ baseUrl = baseServer + pathPrefix;
+ }
+
+ private String createPath(ApplicationId applicationId, Zone zone) {
+ return "/application/v2/tenant/"
+ + applicationId.tenant().value()
+ + "/application/"
+ + applicationId.application().value()
+ + "/environment/"
+ + zone.environment().value()
+ + "/region/"
+ + zone.region().value()
+ + "/instance/"
+ + applicationId.instance().value()
+ + "/content/";
+ }
+
+ @Test
+ public void require_that_nonexistant_application_returns_not_found() throws IOException {
+ assertNotFound(HttpRequest.createTestRequest(baseServer + createPath(new ApplicationId.Builder()
+ .tenant("tenant")
+ .applicationName("notexist").instanceName("baz").build(), Zone.defaultZone()),
+ com.yahoo.jdisc.http.HttpRequest.Method.GET));
+ assertNotFound(HttpRequest.createTestRequest(baseServer + createPath(new ApplicationId.Builder()
+ .tenant("unknown")
+ .applicationName("notexist").instanceName("baz").build(), Zone.defaultZone()),
+ com.yahoo.jdisc.http.HttpRequest.Method.GET));
+ }
+
+ @Test
+ public void require_that_multiple_tenants_are_handled() throws IOException {
+ assertContent("/test.txt", "foo\n");
+ pathPrefix = createPath(idTenant2, Zone.defaultZone());
+ baseUrl = baseServer + pathPrefix;
+ assertContent("/test.txt", "bar\n");
+ }
+
+ @Test
+ public void require_that_get_does_not_set_write_flag() throws IOException {
+ session2.status = Session.Status.PREPARE;
+ assertContent("/test.txt", "foo\n");
+ assertThat(session2.status, is(Session.Status.PREPARE));
+ }
+
+ private void assertNotFound(HttpRequest request) {
+ HttpResponse response = handler.handle(request);
+ assertNotNull(response);
+ assertThat(response.getStatus(), is(Response.Status.NOT_FOUND));
+ }
+
+ @Override
+ protected HttpResponse doRequest(com.yahoo.jdisc.http.HttpRequest.Method method, String path) {
+ HttpRequest request = HttpRequest.createTestRequest(baseUrl + path, method);
+ return handler.handle(request);
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java
new file mode 100644
index 00000000000..1eb38902e3f
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java
@@ -0,0 +1,289 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.http.v2;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.yahoo.config.application.api.ApplicationPackage;
+import com.yahoo.config.model.NullConfigModelRegistry;
+import com.yahoo.config.model.application.provider.FilesApplicationPackage;
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.ApplicationName;
+import com.yahoo.config.provision.Provisioner;
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.config.provision.Zone;
+import com.yahoo.container.jdisc.HttpRequest;
+import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.container.logging.AccessLog;
+import com.yahoo.jdisc.Response;
+import com.yahoo.vespa.config.server.MockReloadHandler;
+import com.yahoo.vespa.config.server.modelfactory.ModelFactoryRegistry;
+import com.yahoo.vespa.config.server.Tenant;
+import com.yahoo.vespa.config.server.Tenants;
+import com.yahoo.vespa.config.server.TestComponentRegistry;
+import com.yahoo.vespa.config.server.application.ApplicationConvergenceChecker;
+import com.yahoo.vespa.config.server.application.LogServerLogGrabber;
+import com.yahoo.vespa.config.server.http.HandlerTest;
+import com.yahoo.vespa.config.server.http.HttpErrorResponse;
+import com.yahoo.vespa.config.server.http.SessionHandlerTest;
+import com.yahoo.vespa.config.server.provision.HostProvisionerProvider;
+import com.yahoo.vespa.config.server.session.LocalSessionRepo;
+import com.yahoo.vespa.config.server.session.MockSessionZKClient;
+import com.yahoo.vespa.config.server.session.RemoteSession;
+import com.yahoo.vespa.config.server.session.RemoteSessionRepo;
+import com.yahoo.vespa.curator.mock.MockCurator;
+import com.yahoo.vespa.model.VespaModelFactory;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import javax.ws.rs.client.Client;
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author musum
+ * @since 5.4
+ */
+public class ApplicationHandlerTest {
+
+ private static File testApp = new File("src/test/apps/app");
+
+ private ApplicationHandler handler;
+ private ListApplicationsHandler listApplicationsHandler;
+ private final static TenantName mytenant = TenantName.from("mytenant");
+ private final static TenantName foobar = TenantName.from("foobar");
+ private Tenants tenants;
+ private SessionActiveHandlerTest.MockProvisioner provisioner;
+ private MockStateApiFactory stateApiFactory = new MockStateApiFactory();
+
+ @Before
+ public void setup() throws Exception {
+ TestTenantBuilder testBuilder = new TestTenantBuilder();
+ testBuilder.createTenant(mytenant).withReloadHandler(new MockReloadHandler());
+ testBuilder.createTenant(foobar).withReloadHandler(new MockReloadHandler());
+
+ tenants = testBuilder.createTenants();
+ provisioner = new SessionActiveHandlerTest.MockProvisioner();
+ handler = createApplicationHandler(
+ provisioner, new ApplicationConvergenceChecker(stateApiFactory), new LogServerLogGrabber());
+ listApplicationsHandler = new ListApplicationsHandler(
+ Runnable::run, AccessLog.voidAccessLog(), tenants, Zone.defaultZone());
+ }
+
+ private ApplicationHandler createApplicationHandler(
+ Provisioner provisioner,
+ ApplicationConvergenceChecker convergeChecker,
+ LogServerLogGrabber logServerLogGrabber) {
+ return new ApplicationHandler(
+ Runnable::run,
+ AccessLog.voidAccessLog(),
+ tenants,
+ HostProvisionerProvider.withProvisioner(provisioner),
+ Zone.defaultZone(),
+ convergeChecker,
+ logServerLogGrabber);
+ }
+
+ @Test
+ public void testDelete() throws Exception {
+ ApplicationId defaultId = new ApplicationId.Builder().applicationName(ApplicationName.defaultName()).tenant(mytenant).build();
+ assertApplicationExists(mytenant, null, Zone.defaultZone());
+
+ long sessionId = 1;
+ addApplication(tenants.tenantsCopy().get(mytenant), defaultId, sessionId);
+ assertApplicationExists(mytenant, defaultId, Zone.defaultZone());
+ assertFalse(provisioner.removed);
+ deleteAndAssertOKResponse(defaultId);
+ assertTrue(provisioner.removed);
+ assertThat(provisioner.lastApplicationId.tenant(), is(mytenant));
+ assertThat(provisioner.lastApplicationId, is(defaultId));
+ sessionId++;
+ addApplication(tenants.tenantsCopy().get(mytenant), defaultId, sessionId);
+ deleteAndAssertOKResponse(defaultId, true);
+
+ ApplicationId fooId = new ApplicationId.Builder()
+ .tenant(mytenant)
+ .applicationName("foo").instanceName("quux").build();
+ sessionId++;
+ addApplication(tenants.tenantsCopy().get(mytenant), fooId, sessionId);
+ addApplication(tenants.tenantsCopy().get(foobar), fooId, sessionId);
+ assertApplicationExists(mytenant, fooId, Zone.defaultZone());
+ assertApplicationExists(foobar, fooId, Zone.defaultZone());
+ deleteAndAssertOKResponse(fooId, true);
+ assertThat(provisioner.lastApplicationId.tenant(), is(mytenant));
+ assertThat(provisioner.lastApplicationId, is(fooId));
+ assertApplicationExists(mytenant, null, Zone.defaultZone());
+ assertApplicationExists(foobar, fooId, Zone.defaultZone());
+
+
+ sessionId++;
+ ApplicationId baliId = new ApplicationId.Builder()
+ .tenant(mytenant)
+ .applicationName("bali").instanceName("quux").build();
+ addApplication(tenants.tenantsCopy().get(mytenant), baliId, sessionId);
+ deleteAndAssertOKResponse(baliId, true);
+ assertApplicationExists(mytenant, null, Zone.defaultZone());
+ }
+
+ @Test
+ public void testGet() throws Exception {
+ long sessionId = 1;
+ ApplicationId defaultId = new ApplicationId.Builder().applicationName(ApplicationName.defaultName()).tenant(mytenant).build();
+ addApplication(tenants.tenantsCopy().get(mytenant), defaultId, sessionId);
+ assertApplicationGeneration(defaultId, Zone.defaultZone(), 1, true);
+ assertApplicationGeneration(defaultId, Zone.defaultZone(), 1, false);
+ }
+
+ @Test
+ public void testRestart() throws Exception {
+ long sessionId = 1;
+ ApplicationId application = new ApplicationId.Builder().applicationName(ApplicationName.defaultName()).tenant(mytenant).build();
+ addApplication(tenants.tenantsCopy().get(mytenant), application, sessionId);
+ assertFalse(provisioner.restarted);
+ restart(application, Zone.defaultZone());
+ assertTrue(provisioner.restarted);
+ assertEquals(application, provisioner.lastApplicationId);
+ }
+
+ @Test
+ public void testConverge() throws Exception {
+ long sessionId = 1;
+ ApplicationId application = new ApplicationId.Builder().applicationName(ApplicationName.defaultName()).tenant(mytenant).build();
+ addApplication(tenants.tenantsCopy().get(mytenant), application, sessionId);
+ assertFalse(stateApiFactory.createdApi);
+ converge(application, Zone.defaultZone());
+ assertTrue(stateApiFactory.createdApi);
+ }
+
+ @Test
+ public void testPutIsIllegal() throws IOException {
+ assertNotAllowed(com.yahoo.jdisc.http.HttpRequest.Method.PUT);
+ }
+
+ @Test
+ @Ignore
+ public void testFailingProvisioner() throws Exception {
+ provisioner = new SessionActiveHandlerTest.FailingMockProvisioner();
+ handler = createApplicationHandler(
+ provisioner, new ApplicationConvergenceChecker(stateApiFactory), new LogServerLogGrabber());
+ final ApplicationId applicationId = ApplicationId.defaultId();
+ addApplication(tenants.tenantsCopy().get(mytenant), applicationId, 1);
+ assertApplicationExists(mytenant, applicationId, Zone.defaultZone());
+ provisioner.activated = true;
+
+ String url = "http://myhost:14000/application/v2/tenant/" + mytenant + "/application/" + applicationId.application();
+ deleteAndAssertResponse(url, 500, null, "{\"message\":\"Cannot remove application\"}", com.yahoo.jdisc.http.HttpRequest.Method.DELETE);
+ assertApplicationExists(mytenant, applicationId, Zone.defaultZone());
+ Assert.assertTrue(provisioner.activated);
+ }
+
+ static void addApplication(Tenant tenant, ApplicationId applicationId, long sessionId) throws Exception {
+ tenant.getApplicationRepo().createPutApplicationTransaction(applicationId, sessionId).commit();
+ ApplicationPackage app = FilesApplicationPackage.fromFile(testApp);
+ addLocalSession(tenant, app, sessionId, applicationId);
+ addRemoteSession(tenant, app, sessionId);
+ }
+
+ static void addLocalSession(Tenant tenant, ApplicationPackage app, long sessionId, ApplicationId applicationId) {
+ LocalSessionRepo localRepo = tenant.getLocalSessionRepo();
+ localRepo.addSession(new SessionHandlerTest.MockSession(sessionId, app, applicationId));
+ }
+
+ static void addRemoteSession(Tenant tenant, ApplicationPackage app, long sessionId) {
+ RemoteSessionRepo remoteRepo = tenant.getRemoteSessionRepo();
+ remoteRepo.addSession(new RemoteSession(tenant.getName(), sessionId, new TestComponentRegistry(new MockCurator(), new ModelFactoryRegistry(Collections.singletonList(new VespaModelFactory(new NullConfigModelRegistry())))), new MockSessionZKClient(app)));
+ }
+
+ private void assertNotAllowed(com.yahoo.jdisc.http.HttpRequest.Method method) throws IOException {
+ String url = "http://myhost:14000/application/v2/tenant/" + mytenant + "/application/default";
+ deleteAndAssertResponse(url, Response.Status.METHOD_NOT_ALLOWED, HttpErrorResponse.errorCodes.METHOD_NOT_ALLOWED, "{\"error-code\":\"METHOD_NOT_ALLOWED\",\"message\":\"Method '" + method + "' is not supported\"}",
+ method);
+ }
+
+ private void deleteAndAssertOKResponse(ApplicationId applicationId) throws IOException {
+ deleteAndAssertOKResponse(applicationId, true);
+ }
+
+ private void deleteAndAssertOKResponse(ApplicationId applicationId, boolean fullAppIdInUrl) throws IOException {
+ long sessionId = tenants.tenantsCopy().get(applicationId.tenant()).getApplicationRepo().getSessionIdForApplication(applicationId);
+ deleteAndAssertResponse(applicationId, Zone.defaultZone(), Response.Status.OK, null, fullAppIdInUrl);
+ assertNull(tenants.tenantsCopy().get(applicationId.tenant()).getLocalSessionRepo().getSession(sessionId));
+ }
+
+ private void deleteAndAssertResponse(ApplicationId applicationId, Zone zone, int expectedStatus, HttpErrorResponse.errorCodes errorCode, boolean fullAppIdInUrl) throws IOException {
+ String expectedResponse = "{\"message\":\"Application '" + applicationId + "' deleted\"}";
+ deleteAndAssertResponse(toUrlPath(applicationId, zone, fullAppIdInUrl), expectedStatus, errorCode, expectedResponse, com.yahoo.jdisc.http.HttpRequest.Method.DELETE);
+ }
+
+ private void assertApplicationGeneration(ApplicationId applicationId, Zone zone, long expectedGeneration, boolean fullAppIdInUrl) throws IOException {
+ assertApplicationGeneration(toUrlPath(applicationId, zone, fullAppIdInUrl), expectedGeneration);
+ }
+
+ private String toUrlPath(ApplicationId application, Zone zone, boolean fullAppIdInUrl) {
+ String url = "http://myhost:14000/application/v2/tenant/" + application.tenant().value() + "/application/" + application.application().value();
+ if (fullAppIdInUrl)
+ url = url + "/environment/" + zone.environment().value() + "/region/" + zone.region().value() + "/instance/" + application.instance().value();
+ return url;
+ }
+
+ private void assertApplicationGeneration(String url, long expectedGeneration) throws IOException {
+ HttpResponse response = handler.handle(HttpRequest.createTestRequest(url, com.yahoo.jdisc.http.HttpRequest.Method.GET));
+ HandlerTest.assertHttpStatusCodeAndMessage(response, 200, "{\"generation\":" + expectedGeneration + "}");
+ }
+
+ private void deleteAndAssertResponse(String url, int expectedStatus, HttpErrorResponse.errorCodes errorCode, String expectedResponse, com.yahoo.jdisc.http.HttpRequest.Method method) throws IOException {
+ HttpResponse response = handler.handle(HttpRequest.createTestRequest(url, method));
+ if (expectedStatus == 200) {
+ HandlerTest.assertHttpStatusCodeAndMessage(response, 200, expectedResponse);
+ } else {
+ HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(response, expectedStatus, errorCode, expectedResponse);
+ }
+ }
+
+ private void assertApplicationExists(TenantName tenantName, ApplicationId applicationId, Zone zone) throws IOException {
+ String expected = applicationId == null ? "[]" : "[\"http://myhost:14000/application/v2/tenant/" + tenantName + "/application/" + applicationId.application().value() +
+ "/environment/" + zone.environment().value() +
+ "/region/" + zone.region().value() +
+ "/instance/" + applicationId.instance().value() + "\"]";
+ ListApplicationsHandlerTest.assertResponse(listApplicationsHandler, "http://myhost:14000/application/v2/tenant/" + tenantName + "/application/",
+ Response.Status.OK,
+ expected,
+ com.yahoo.jdisc.http.HttpRequest.Method.GET);
+ }
+
+ private void restart(ApplicationId application, Zone zone) throws IOException {
+ String restartUrl = toUrlPath(application, zone, true) + "/restart";
+ HttpResponse response = handler.handle(HttpRequest.createTestRequest(restartUrl, com.yahoo.jdisc.http.HttpRequest.Method.POST));
+ HandlerTest.assertHttpStatusCodeAndMessage(response, 200, "");
+ }
+
+ private void converge(ApplicationId application, Zone zone) throws IOException {
+ String restartUrl = toUrlPath(application, zone, true) + "/converge";
+ HttpResponse response = handler.handle(HttpRequest.createTestRequest(restartUrl, com.yahoo.jdisc.http.HttpRequest.Method.GET));
+ HandlerTest.assertHttpStatusCodeAndMessage(response, 200, "");
+ }
+
+ private static class MockStateApiFactory implements ApplicationConvergenceChecker.StateApiFactory {
+ public boolean createdApi = false;
+ @Override
+ public ApplicationConvergenceChecker.StateApi createStateApi(Client client, URI serviceUri) {
+ createdApi = true;
+ return () -> {
+ try {
+ return new ObjectMapper().readTree("{\"config\":{\"generation\":1}}");
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ };
+ }
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/HostHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/HostHandlerTest.java
new file mode 100644
index 00000000000..50ef9176771
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/HostHandlerTest.java
@@ -0,0 +1,114 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.http.v2;
+
+import com.yahoo.config.provision.*;
+import com.yahoo.container.jdisc.HttpRequest;
+import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.container.logging.AccessLog;
+import com.yahoo.jdisc.Response;
+import com.yahoo.vespa.config.server.*;
+import com.yahoo.vespa.config.server.http.HandlerTest;
+import com.yahoo.vespa.config.server.http.HttpErrorResponse;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author musum
+ * @since 5.4
+ */
+public class HostHandlerTest {
+ private static final String urlPrefix = "http://myhost:14000/application/v2/host/";
+
+ private HostHandler handler;
+ private final static TenantName mytenant = TenantName.from("mytenant");
+ private final static String hostname = "testhost";
+ private Tenants tenants;
+ private HostRegistries hostRegistries;
+ private HostHandler hostHandler;
+
+ @Before
+ public void setup() throws Exception {
+ TestTenantBuilder testBuilder = new TestTenantBuilder();
+ testBuilder.createTenant(mytenant).withReloadHandler(new MockReloadHandler());
+
+ tenants = testBuilder.createTenants();
+ handler = createHostHandler();
+ }
+
+ private HostHandler createHostHandler() {
+ final HostRegistry<TenantName> hostRegistry = new HostRegistry<>();
+ hostRegistry.update(mytenant, Collections.singletonList(hostname));
+ TestComponentRegistry testComponentRegistry = new TestComponentRegistry();
+ hostRegistries = testComponentRegistry.getHostRegistries();
+ hostRegistries.createApplicationHostRegistry(mytenant).update(ApplicationId.from(mytenant, ApplicationName.defaultName(), InstanceName.defaultName()), Collections.singletonList(hostname));
+ hostRegistries.getTenantHostRegistry().update(mytenant, Collections.singletonList(hostname));
+ hostHandler = new HostHandler(command -> {
+ command.run();
+ }, AccessLog.voidAccessLog(), testComponentRegistry);
+ return hostHandler;
+ }
+
+ @Test
+ public void require_correct_tenant_and_application_for_hostname() throws Exception {
+ assertThat(hostRegistries, is(hostHandler.hostRegistries));
+ long sessionId = 1;
+ ApplicationId id = ApplicationId.from(mytenant, ApplicationName.defaultName(), InstanceName.defaultName());
+ ApplicationHandlerTest.addApplication(tenants.tenantsCopy().get(mytenant), id, sessionId);
+ assertApplicationForHost(hostname, mytenant, id, Zone.defaultZone());
+ }
+
+ @Test
+ public void require_that_handler_gives_error_for_unknown_hostname() throws Exception {
+ long sessionId = 1;
+ ApplicationHandlerTest.addApplication(tenants.tenantsCopy().get(mytenant), ApplicationId.defaultId(), sessionId);
+ final String hostname = "unknown";
+ assertErrorForHost(hostname,
+ Response.Status.NOT_FOUND,
+ HttpErrorResponse.errorCodes.NOT_FOUND,
+ "{\"error-code\":\"NOT_FOUND\",\"message\":\"Could not find any application using host '" + hostname + "'\"}");
+ }
+
+ @Test
+ public void require_that_only_get_method_is_allowed() throws IOException {
+ assertNotAllowed(com.yahoo.jdisc.http.HttpRequest.Method.PUT);
+ assertNotAllowed(com.yahoo.jdisc.http.HttpRequest.Method.POST);
+ assertNotAllowed(com.yahoo.jdisc.http.HttpRequest.Method.DELETE);
+ }
+
+ private void assertNotAllowed(com.yahoo.jdisc.http.HttpRequest.Method method) throws IOException {
+ String url = urlPrefix + hostname;
+ deleteAndAssertResponse(url, Response.Status.METHOD_NOT_ALLOWED,
+ HttpErrorResponse.errorCodes.METHOD_NOT_ALLOWED,
+ "{\"error-code\":\"METHOD_NOT_ALLOWED\",\"message\":\"Method '" + method + "' is not supported\"}",
+ method);
+ }
+
+ private void assertApplicationForHost(String hostname, TenantName expectedTenantName, ApplicationId expectedApplicationId, Zone zone) throws IOException {
+ String url = urlPrefix + hostname;
+ HttpResponse response = handler.handle(HttpRequest.createTestRequest(url, com.yahoo.jdisc.http.HttpRequest.Method.GET));
+ HandlerTest.assertHttpStatusCodeAndMessage(response, Response.Status.OK,
+ "{\"tenant\":\"" + expectedTenantName.value() + "\"," +
+ "\"application\":\"" + expectedApplicationId.application().value() + "\"," +
+ "\"environment\":\"" + zone.environment().value() + "\"," +
+ "\"region\":\"" + zone.region().value() + "\"," +
+ "\"instance\":\"" + expectedApplicationId.instance().value() + "\"}"
+ );
+ }
+
+ private void assertErrorForHost(String hostname, int expectedStatus, HttpErrorResponse.errorCodes errorCode, String expectedResponse) throws IOException {
+ String url = urlPrefix + hostname;
+ HttpResponse response = handler.handle(HttpRequest.createTestRequest(url, com.yahoo.jdisc.http.HttpRequest.Method.GET));
+ HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(response, expectedStatus, errorCode, expectedResponse);
+ }
+
+ private void deleteAndAssertResponse(String url, int expectedStatus, HttpErrorResponse.errorCodes errorCode, String expectedResponse, com.yahoo.jdisc.http.HttpRequest.Method method) throws IOException {
+ HttpResponse response = handler.handle(HttpRequest.createTestRequest(url, method));
+ HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(response, expectedStatus, errorCode, expectedResponse);
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/HttpGetConfigHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/HttpGetConfigHandlerTest.java
new file mode 100644
index 00000000000..b1a330e5f99
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/HttpGetConfigHandlerTest.java
@@ -0,0 +1,140 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.http.v2;
+
+import static com.yahoo.jdisc.Response.Status.BAD_REQUEST;
+import static com.yahoo.jdisc.Response.Status.NOT_FOUND;
+import static com.yahoo.jdisc.http.HttpRequest.Method.GET;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.*;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.concurrent.Executor;
+
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.vespa.config.server.http.HttpErrorResponse;
+import org.junit.Before;
+import org.junit.Test;
+import com.yahoo.config.SimpletypesConfig;
+import com.yahoo.config.codegen.DefParser;
+import com.yahoo.config.codegen.InnerCNode;
+import com.yahoo.container.jdisc.HttpRequest;
+import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.container.logging.AccessLog;
+import com.yahoo.text.StringUtilities;
+import com.yahoo.vespa.config.ConfigKey;
+import com.yahoo.vespa.config.ConfigPayload;
+import com.yahoo.vespa.config.protocol.SlimeConfigResponse;
+import com.yahoo.vespa.config.server.Tenants;
+import com.yahoo.vespa.config.server.MockRequestHandler;
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.vespa.config.server.http.HandlerTest;
+import com.yahoo.vespa.config.server.http.HttpConfigRequest;
+import com.yahoo.vespa.config.server.http.SessionHandlerTest;
+
+public class HttpGetConfigHandlerTest {
+
+ private static final TenantName tenant = TenantName.from("mytenant");
+ private static final String EXPECTED_RENDERED_STRING = "{\"boolval\":false,\"doubleval\":0.0,\"enumval\":\"VAL1\",\"intval\":0,\"longval\":0,\"stringval\":\"s\"}";
+ private static final String configUri = "http://yahoo.com:8080/config/v2/tenant/" + tenant.value() + "/application/myapplication/foo.bar/myid";
+ private MockRequestHandler mockRequestHandler;
+ private HttpGetConfigHandler handler;
+
+ @Before
+ public void setUp() throws Exception {
+ mockRequestHandler = new MockRequestHandler();
+ mockRequestHandler.setAllConfigs(new HashSet<ConfigKey<?>>() {{
+ add(new ConfigKey<>("bar", "myid", "foo"));
+ }} );
+ TestTenantBuilder tb = new TestTenantBuilder();
+ tb.createTenant(tenant).withRequestHandler(mockRequestHandler).build();
+ Tenants tenants = tb.createTenants();
+ handler = new HttpGetConfigHandler(command -> {
+ command.run();
+ }, AccessLog.voidAccessLog(), tenants);
+ }
+
+ @Test
+ public void require_that_handler_can_be_created() throws IOException {
+ // Define config response for mock handler
+ final long generation = 1L;
+ ConfigPayload payload = ConfigPayload.fromInstance(new SimpletypesConfig(new SimpletypesConfig.Builder()));
+ InnerCNode targetDef = getInnerCNode();
+ mockRequestHandler.responses.put(new ApplicationId.Builder().tenant(tenant).applicationName("myapplication").build(),
+ SlimeConfigResponse.fromConfigPayload(payload, targetDef, generation, "mymd5"));
+ HttpResponse response = handler.handle(HttpRequest.createTestRequest(configUri, GET));
+ assertThat(SessionHandlerTest.getRenderedString(response), is(EXPECTED_RENDERED_STRING));
+ }
+
+ @Test
+ public void require_that_handler_can_handle_long_appid_request_with_configid() throws IOException {
+ String uriLongAppId = "http://yahoo.com:8080/config/v2/tenant/" + tenant.value() +
+ "/application/myapplication/environment/staging/region/myregion/instance/myinstance/foo.bar/myid";
+ final long generation = 1L;
+ ConfigPayload payload = ConfigPayload.fromInstance(new SimpletypesConfig(new SimpletypesConfig.Builder()));
+ InnerCNode targetDef = getInnerCNode();
+ mockRequestHandler.responses.put(new ApplicationId.Builder()
+ .tenant(tenant)
+ .applicationName("myapplication").instanceName("myinstance").build(),
+ SlimeConfigResponse.fromConfigPayload(payload, targetDef, generation, "mymd5"));
+ HttpResponse response = handler.handle(HttpRequest.createTestRequest(uriLongAppId, GET));
+ assertThat(SessionHandlerTest.getRenderedString(response), is(EXPECTED_RENDERED_STRING));
+ }
+
+ @Test
+ public void require_that_request_gets_correct_fields_with_full_appid() {
+ String uriLongAppId = "http://yahoo.com:8080/config/v2/tenant/bill/application/sookie/environment/dev/region/bellefleur/instance/sam/foo.bar/myid";
+ HttpRequest r = HttpRequest.createTestRequest(uriLongAppId, GET);
+ HttpConfigRequest req = HttpConfigRequest.createFromRequestV2(r);
+ assertThat(req.getApplicationId().tenant().value(), is("bill"));
+ assertThat(req.getApplicationId().application().value(), is("sookie"));
+ assertThat(req.getApplicationId().instance().value(), is("sam"));
+ }
+
+ @Test
+ public void require_that_request_gets_correct_fields_with_short_appid() {
+ String uriShortAppId = "http://yahoo.com:8080/config/v2/tenant/jason/application/alcide/foo.bar/myid";
+ HttpRequest r = HttpRequest.createTestRequest(uriShortAppId, GET);
+ HttpConfigRequest req = HttpConfigRequest.createFromRequestV2(r);
+ assertThat(req.getApplicationId().tenant().value(), is("jason"));
+ assertThat(req.getApplicationId().application().value(), is("alcide"));
+ assertThat(req.getApplicationId().instance().value(), is("default"));
+ }
+
+ @Test
+ public void require_correct_error_response() throws IOException {
+ final String nonExistingConfigNameUri = "http://yahoo.com:8080/config/v2/tenant/mytenant/application/myapplication/nonexisting.config/myid";
+ final String nonExistingConfigUri = "http://yahoo.com:8080/config/v2/tenant/mytenant/application/myapplication//foo.bar/myid/nonexisting/id";
+ final String illegalConfigNameUri = "http://yahoo.com:8080/config/v2/tenant/mytenant/application/myapplication//foobar/myid";
+
+ HttpResponse response = handler.handle(HttpRequest.createTestRequest(nonExistingConfigNameUri, GET));
+ HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(response, NOT_FOUND, HttpErrorResponse.errorCodes.NOT_FOUND, "No such config: nonexisting.config");
+ assertTrue(SessionHandlerTest.getRenderedString(response).contains("No such config:"));
+ response = handler.handle(HttpRequest.createTestRequest(nonExistingConfigUri, GET));
+ HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(response, NOT_FOUND, HttpErrorResponse.errorCodes.NOT_FOUND, "No such config id: myid/nonexisting/id");
+ assertEquals(response.getContentType(), "application/json");
+ assertTrue(SessionHandlerTest.getRenderedString(response).contains("No such config id:"));
+ response = handler.handle(HttpRequest.createTestRequest(illegalConfigNameUri, GET));
+ HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(response, BAD_REQUEST, HttpErrorResponse.errorCodes.BAD_REQUEST, "Illegal config, must be of form namespace.name.");
+ }
+
+ @Test
+ public void require_that_nocache_property_works() throws IOException {
+ long generation = 1L;
+ ConfigPayload payload = ConfigPayload.fromInstance(new SimpletypesConfig(new SimpletypesConfig.Builder()));
+ InnerCNode targetDef = getInnerCNode();
+ mockRequestHandler.responses.put(new ApplicationId.Builder().tenant(tenant).applicationName("myapplication").build(),
+ SlimeConfigResponse.fromConfigPayload(payload, targetDef, generation, "mymd5"));
+ final HttpRequest request = HttpRequest.createTestRequest(configUri, GET, null, Collections.singletonMap("nocache", "true"));
+ HttpResponse response = handler.handle(request);
+ assertThat(SessionHandlerTest.getRenderedString(response), is(EXPECTED_RENDERED_STRING));
+ }
+
+ private InnerCNode getInnerCNode() {
+ // TODO: Hope to be able to remove this mess soon.
+ DefParser dParser = new DefParser(SimpletypesConfig.getDefName(), new StringReader(StringUtilities.implode(SimpletypesConfig.CONFIG_DEF_SCHEMA, "\n")));
+ return dParser.getTree();
+ }
+
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/HttpListConfigsHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/HttpListConfigsHandlerTest.java
new file mode 100644
index 00000000000..cad7cbd583c
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/HttpListConfigsHandlerTest.java
@@ -0,0 +1,145 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.http.v2;
+
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.config.provision.Zone;
+import com.yahoo.container.jdisc.HttpRequest;
+import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.container.logging.AccessLog;
+import com.yahoo.vespa.config.ConfigKey;
+import com.yahoo.vespa.config.server.MockRequestHandler;
+import com.yahoo.vespa.config.server.Tenants;
+import com.yahoo.vespa.config.server.http.HandlerTest;
+import com.yahoo.vespa.config.server.http.HttpErrorResponse;
+import com.yahoo.vespa.config.server.http.SessionHandlerTest;
+import com.yahoo.vespa.config.server.http.v2.HttpListConfigsHandler.ListConfigsResponse;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.*;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.*;
+
+import static com.yahoo.jdisc.http.HttpResponse.Status.*;
+import static com.yahoo.jdisc.http.HttpRequest.Method.GET;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class HttpListConfigsHandlerTest {
+
+ private MockRequestHandler mockRequestHandler;
+ private HttpListConfigsHandler handler;
+ private HttpListNamedConfigsHandler namedHandler;
+
+ @Before
+ public void setUp() throws Exception {
+ mockRequestHandler = new MockRequestHandler();
+ mockRequestHandler.setAllConfigs(new HashSet<ConfigKey<?>>() {{
+ add(new ConfigKey<>("bar", "conf/id", "foo"));
+ }} );
+ TestTenantBuilder tb = new TestTenantBuilder();
+ tb.createTenant(TenantName.from("mytenant")).withRequestHandler(mockRequestHandler).build();
+ Tenants tenants = tb.createTenants();
+ handler = new HttpListConfigsHandler(command -> {
+ command.run();
+ }, AccessLog.voidAccessLog(), tenants, Zone.defaultZone());
+ namedHandler = new HttpListNamedConfigsHandler(command -> {
+ command.run();
+ }, AccessLog.voidAccessLog(), tenants, Zone.defaultZone());
+ }
+
+ @Test
+ public void require_that_handler_can_be_created() throws IOException {
+ HttpResponse response = handler.handle(HttpRequest.createTestRequest("http://yahoo.com:8080/config/v2/tenant/mytenant/application/myapplication/", GET));
+ assertThat(SessionHandlerTest.getRenderedString(response), is("{\"children\":[],\"configs\":[]}"));
+ }
+
+ @Test
+ public void require_that_request_can_be_created_from_full_appid() throws IOException {
+ HttpResponse response = handler.handle(HttpRequest.createTestRequest(
+ "http://yahoo.com:8080/config/v2/tenant/mytenant/application/myapplication/environment/test/region/myregion/instance/myinstance/", GET));
+ assertThat(SessionHandlerTest.getRenderedString(response), is("{\"children\":[],\"configs\":[]}"));
+ }
+
+ @Test
+ public void require_that_named_handler_can_be_created() throws IOException {
+ HttpRequest req = HttpRequest.createTestRequest("http://foo.com:8080/config/v2/tenant/mytenant/application/myapplication/foo.bar/conf/id/", GET);
+ req.getJDiscRequest().parameters().put("http.path", Arrays.asList("foo.bar"));
+ HttpResponse response = namedHandler.handle(req);
+ assertThat(SessionHandlerTest.getRenderedString(response), is("{\"children\":[],\"configs\":[]}"));
+ }
+
+ @Test
+ public void require_that_named_handler_can_be_created_from_full_appid() throws IOException {
+ HttpRequest req = HttpRequest.createTestRequest("http://foo.com:8080/config/v2/tenant/mytenant/application/myapplication/environment/prod/region/myregion/instance/myinstance/foo.bar/conf/id/", GET);
+ req.getJDiscRequest().parameters().put("http.path", Arrays.asList("foo.bar"));
+ HttpResponse response = namedHandler.handle(req);
+ assertThat(SessionHandlerTest.getRenderedString(response), is("{\"children\":[],\"configs\":[]}"));
+ }
+
+ @Test
+ public void require_child_listings_correct() {
+ Set<ConfigKey<?>> keys = new LinkedHashSet<ConfigKey<?>>() {{
+ add(new ConfigKey<>("name1", "id/1", "ns1"));
+ add(new ConfigKey<>("name1", "id/1", "ns1"));
+ add(new ConfigKey<>("name1", "id/2", "ns1"));
+ add(new ConfigKey<>("name1", "", "ns1"));
+ add(new ConfigKey<>("name1", "id/1/1", "ns1"));
+ add(new ConfigKey<>("name1", "id2", "ns1"));
+ add(new ConfigKey<>("name1", "id/2/1", "ns1"));
+ add(new ConfigKey<>("name1", "id/2/1/5/6", "ns1"));
+ }};
+ Set<ConfigKey<?>> keysThatHaveChild = ListConfigsResponse.keysThatHaveAChildWithSameName(keys, keys);
+ assertEquals(keysThatHaveChild.size(), 3);
+ }
+
+ @Test
+ public void require_url_building_and_mimetype_correct() {
+ ListConfigsResponse resp = new ListConfigsResponse(new HashSet<ConfigKey<?>>(), null, "http://foo.com/config/v2/tenant/mytenant/application/mya/", true);
+ assertEquals(resp.toUrl(new ConfigKey<>("myconfig", "my/id", "mynamespace"), true), "http://foo.com/config/v2/tenant/mytenant/application/mya/mynamespace.myconfig/my/id");
+ assertEquals(resp.toUrl(new ConfigKey<>("myconfig", "my/id", "mynamespace"), false), "http://foo.com/config/v2/tenant/mytenant/application/mya/mynamespace.myconfig/my/id/");
+ assertEquals(resp.toUrl(new ConfigKey<>("myconfig", "", "mynamespace"), false), "http://foo.com/config/v2/tenant/mytenant/application/mya/mynamespace.myconfig");
+ assertEquals(resp.toUrl(new ConfigKey<>("myconfig", "", "mynamespace"), true), "http://foo.com/config/v2/tenant/mytenant/application/mya/mynamespace.myconfig");
+ assertEquals(resp.getContentType(), "application/json");
+
+ }
+
+ @Test
+ public void require_error_on_bad_request() throws IOException {
+ HttpRequest req = HttpRequest.createTestRequest("http://foo.com:8080/config/v2/tenant/mytenant/application/myapplication/foobar/conf/id/", GET);
+ HttpResponse resp = namedHandler.handle(req);
+ HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(resp, BAD_REQUEST, HttpErrorResponse.errorCodes.BAD_REQUEST, "Illegal config, must be of form namespace.name.");
+ req = HttpRequest.createTestRequest("http://foo.com:8080/config/v2/tenant/mytenant/application/myapplication/foo.barNOPE/conf/id/", GET);
+ resp = namedHandler.handle(req);
+ HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(resp, NOT_FOUND, HttpErrorResponse.errorCodes.NOT_FOUND, "No such config: foo.barNOPE");
+ req = HttpRequest.createTestRequest("http://foo.com:8080/config/v2/tenant/mytenant/application/myapplication/foo.bar/conf/id/NOPE/", GET);
+ resp = namedHandler.handle(req);
+ HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(resp, NOT_FOUND, HttpErrorResponse.errorCodes.NOT_FOUND, "No such config id: conf/id/NOPE");
+ }
+
+ @Test
+ public void require_correct_error_response_on_no_model() throws IOException {
+ mockRequestHandler.setAllConfigs(new HashSet<ConfigKey<?>>());
+ HttpResponse response = namedHandler.handle(HttpRequest.createTestRequest("http://yahoo.com:8080/config/v2/tenant/mytenant/application/myapplication/foo.bar/myid/", GET));
+ HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(response, NOT_FOUND,
+ HttpErrorResponse.errorCodes.NOT_FOUND,
+ "Config not available, verify that an application package has been deployed and activated.");
+ }
+
+ @Test
+ public void require_correct_configid_parent() {
+ assertEquals(ListConfigsResponse.parentConfigId(null), null);
+ assertEquals(ListConfigsResponse.parentConfigId("foo"), "");
+ assertEquals(ListConfigsResponse.parentConfigId(""), "");
+ assertEquals(ListConfigsResponse.parentConfigId("/"), "");
+ assertEquals(ListConfigsResponse.parentConfigId("foo/bar"), "foo");
+ assertEquals(ListConfigsResponse.parentConfigId("foo/bar/baz"), "foo/bar");
+ assertEquals(ListConfigsResponse.parentConfigId("foo/bar/"), "foo/bar");
+
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ListApplicationsHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ListApplicationsHandlerTest.java
new file mode 100644
index 00000000000..2e75d1b02e6
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ListApplicationsHandlerTest.java
@@ -0,0 +1,111 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.http.v2;
+
+import com.yahoo.config.provision.*;
+import com.yahoo.container.jdisc.HttpRequest;
+import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.jdisc.http.HttpRequest.Method;
+import com.yahoo.container.logging.AccessLog;
+import com.yahoo.jdisc.Response;
+import com.yahoo.vespa.config.server.*;
+import com.yahoo.vespa.config.server.application.ApplicationRepo;
+import com.yahoo.vespa.config.server.http.SessionHandlerTest;
+import org.junit.Test;
+import org.junit.Before;
+
+import java.io.IOException;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+
+import static com.yahoo.jdisc.http.HttpRequest.Method.*;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class ListApplicationsHandlerTest {
+ private ApplicationRepo applicationRepo, applicationRepo2;
+ private ListApplicationsHandler handler;
+
+ @Before
+ public void setup() throws Exception {
+ TestTenantBuilder testBuilder = new TestTenantBuilder();
+ TenantName mytenant = TenantName.from("mytenant");
+ TenantName foobar = TenantName.from("foobar");
+ testBuilder.createTenant(mytenant);
+ testBuilder.createTenant(foobar);
+ applicationRepo = testBuilder.tenants().get(mytenant).getApplicationRepo();
+ applicationRepo2 = testBuilder.tenants().get(foobar).getApplicationRepo();
+ Tenants tenants = testBuilder.createTenants();
+ handler = new ListApplicationsHandler(command -> {
+ command.run();
+ }, AccessLog.voidAccessLog(), tenants, new Zone(Environment.dev, RegionName.from("us-east")));
+ }
+
+ @Test
+ public void require_that_applications_are_listed() throws Exception {
+ final String url = "http://myhost:14000/application/v2/tenant/mytenant/application/";
+ assertResponse(url, Response.Status.OK,
+ "[]");
+ applicationRepo.createPutApplicationTransaction(
+ new ApplicationId.Builder().tenant("tenant").applicationName("foo").instanceName("quux").build(),
+ 1).commit();
+ assertResponse(url, Response.Status.OK,
+ "[\"" + url + "foo/environment/dev/region/us-east/instance/quux\"]");
+ applicationRepo.createPutApplicationTransaction(
+ new ApplicationId.Builder().tenant("tenant").applicationName("bali").instanceName("quux").build(),
+ 1).commit();
+ assertResponse(url, Response.Status.OK,
+ "[\"" + url + "foo/environment/dev/region/us-east/instance/quux\"," +
+ "\"" + url + "bali/environment/dev/region/us-east/instance/quux\"]"
+ );
+ }
+
+ @Test
+ public void require_that_get_is_required() throws IOException {
+ final String url = "http://myhost:14000/application/v2/tenant/mytenant/application/";
+ assertResponse(url, Response.Status.METHOD_NOT_ALLOWED,
+ createMethodNotAllowedMessage(DELETE), DELETE);
+ assertResponse(url, Response.Status.METHOD_NOT_ALLOWED,
+ createMethodNotAllowedMessage(PUT), PUT);
+ assertResponse(url, Response.Status.METHOD_NOT_ALLOWED,
+ createMethodNotAllowedMessage(POST), POST);
+ }
+
+ private static String createMethodNotAllowedMessage(Method method) {
+ return "{\"error-code\":\"METHOD_NOT_ALLOWED\",\"message\":\"Method '" + method.name() + "' is not supported\"}";
+ }
+
+ @Test
+ public void require_that_listing_works_with_multiple_tenants() throws Exception {
+ applicationRepo.createPutApplicationTransaction(new ApplicationId.Builder()
+ .tenant("tenant")
+ .applicationName("foo").instanceName("quux").build(), 1).commit();
+ applicationRepo2.createPutApplicationTransaction(new ApplicationId.Builder()
+ .tenant("tenant")
+ .applicationName("quux").instanceName("foo").build(), 1).commit();
+ String url = "http://myhost:14000/application/v2/tenant/mytenant/application/";
+ assertResponse(url, Response.Status.OK,
+ "[\"" + url + "foo/environment/dev/region/us-east/instance/quux\"]");
+ url = "http://myhost:14000/application/v2/tenant/foobar/application/";
+ assertResponse(url, Response.Status.OK,
+ "[\"" + url + "quux/environment/dev/region/us-east/instance/foo\"]");
+ }
+
+ void assertResponse(String url, int expectedStatus, String expectedResponse) throws IOException {
+ assertResponse(url, expectedStatus, expectedResponse, GET);
+ }
+
+ private void assertResponse(String url, int expectedStatus, String expectedResponse, Method method) throws IOException {
+ assertResponse(handler, url, expectedStatus, expectedResponse, method);
+ }
+
+ static void assertResponse(ListApplicationsHandler handler, String url, int expectedStatus, String expectedResponse, Method method) throws IOException {
+ HttpResponse response = handler.handle(HttpRequest.createTestRequest(url, method));
+ assertNotNull(response);
+ assertThat(response.getStatus(), is(expectedStatus));
+ assertThat(SessionHandlerTest.getRenderedString(response), is(expectedResponse));
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ListTenantsResponseTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ListTenantsResponseTest.java
new file mode 100644
index 00000000000..9bcc462035a
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ListTenantsResponseTest.java
@@ -0,0 +1,30 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.http.v2;
+
+import com.yahoo.config.provision.TenantName;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+
+public class ListTenantsResponseTest extends TenantTest {
+
+ private final TenantName a = TenantName.from("a");
+ private final TenantName b = TenantName.from("b");
+ private final TenantName c = TenantName.from("c");
+
+ @Test
+ public void testJsonSerialization() throws Exception {
+ final Collection<TenantName> tenantNames = Arrays.asList(a, b, c);
+ final ListTenantsResponse response = new ListTenantsResponse(tenantNames);
+ assertResponseEquals(response, "{\"tenants\":[\"a\",\"b\",\"c\"]}");
+ }
+
+ @Test
+ public void testJsonSerializationNoTenants() throws Exception {
+ final Collection<TenantName> tenantNames = Collections.emptyList();
+ final ListTenantsResponse response = new ListTenantsResponse(tenantNames);
+ assertResponseEquals(response, "{\"tenants\":[]}");
+ }
+} \ No newline at end of file
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ListTenantsTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ListTenantsTest.java
new file mode 100644
index 00000000000..e4f985b7d9f
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ListTenantsTest.java
@@ -0,0 +1,49 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.http.v2;
+
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.vespa.config.server.TestWithTenant;
+import org.junit.Test;
+import com.yahoo.container.jdisc.HttpRequest;
+import com.yahoo.jdisc.http.HttpRequest.Method;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.junit.Assert.assertTrue;
+
+public class ListTenantsTest extends TenantTest {
+
+ private final TenantName a = TenantName.from("a");
+ private final TenantName b = TenantName.from("b");
+ private final TenantName c = TenantName.from("c");
+
+ @Test
+ public void testListTenants() throws Exception {
+ tenants.createTenant(a);
+ tenants.createTenant(b);
+ tenants.createTenant(c);
+
+ ListTenantsHandler listTenantsHandler = new ListTenantsHandler(testExecutor(), null, tenants);
+
+ ListTenantsResponse response = (ListTenantsResponse) listTenantsHandler.handleGET(HttpRequest.createTestRequest("/blabla", Method.GET));
+ final Collection<TenantName> responseTenantNames = response.getTenantNames();
+ assertTrue(responseTenantNames.containsAll(Arrays.asList(a, b, c)));
+ assertContainsSystemTenants(responseTenantNames);
+ }
+
+ private static void assertContainsSystemTenants(final Collection<TenantName> tenantNames) {
+ assertTrue(tenantNames.contains(TenantName.defaultName()));
+ assertTrue(tenantNames.contains(ApplicationId.HOSTED_VESPA_TENANT));
+ }
+
+ @Test
+ public void testEmptyTenants() throws Exception {
+ ListTenantsHandler listTenantsHandler = new ListTenantsHandler(testExecutor(), null, tenants);
+
+ ListTenantsResponse response = (ListTenantsResponse) listTenantsHandler.handleGET(HttpRequest.createTestRequest("/blabla", Method.GET));
+ final Collection<TenantName> responseTenantNames = response.getTenantNames();
+ assertContainsSystemTenants(responseTenantNames);
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java
new file mode 100644
index 00000000000..659a435ca6d
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java
@@ -0,0 +1,217 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.http.v2;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+import com.yahoo.config.provision.*;
+import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.container.logging.AccessLog;
+import com.yahoo.jdisc.Response;
+import com.yahoo.jdisc.http.HttpRequest;
+import com.yahoo.slime.JsonFormat;
+import com.yahoo.transaction.NestedTransaction;
+import com.yahoo.vespa.config.server.*;
+import com.yahoo.vespa.config.server.http.HttpErrorResponse;
+import com.yahoo.vespa.config.server.http.SessionHandlerTest;
+import com.yahoo.vespa.config.server.provision.HostProvisionerProvider;
+import com.yahoo.vespa.config.server.session.LocalSessionRepo;
+import com.yahoo.vespa.config.server.session.RemoteSession;
+import com.yahoo.vespa.config.server.session.RemoteSessionRepo;
+import com.yahoo.vespa.config.server.session.Session;
+import com.yahoo.vespa.config.server.session.SessionZooKeeperClient;
+
+import com.yahoo.vespa.curator.mock.MockCurator;
+import com.yahoo.vespa.config.server.zookeeper.ConfigCurator;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.Before;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.*;
+
+import com.yahoo.path.Path;
+import com.yahoo.vespa.config.server.application.MemoryApplicationRepo;
+import com.yahoo.vespa.config.server.http.SessionActiveHandlerTestBase;
+import com.yahoo.vespa.config.server.http.SessionHandler;
+import com.yahoo.vespa.config.server.http.SessionCreateHandlerTestBase.MockSessionFactory;
+
+public class SessionActiveHandlerTest extends SessionActiveHandlerTestBase {
+
+ private MockProvisioner hostProvisioner;
+
+ @Before
+ public void setup() throws Exception {
+ tenant = TenantName.from("activatetest");
+ remoteSessionRepo = new RemoteSessionRepo();
+ applicationRepo = new MemoryApplicationRepo();
+ curator = new MockCurator();
+ configCurator = ConfigCurator.create(curator);
+ localRepo = new LocalSessionRepo(applicationRepo);
+ pathPrefix = "/application/v2/tenant/" + tenant + "/session/";
+ tenantMessage = ",\"tenant\":\"" + tenant + "\"";
+ pathProvider = new PathProvider(Path.createRoot());
+ activatedMessage = " for tenant '" + tenant + "' activated.";
+ hostProvisioner = new MockProvisioner();
+ }
+
+ @Test
+ public void require_correct_response_on_success() throws Exception {
+ activateAndAssertOK(1, 0);
+ }
+
+ @Test
+ public void testActivationWithActivationInBetween() throws Exception {
+ activateAndAssertOK(90l, 0l);
+ activateAndAssertError(92l, 89l,
+ HttpErrorResponse.errorCodes.BAD_REQUEST,
+ "tenant:"+tenant+" app:default:default Cannot activate session 92 because the currently active session (90) has changed since session 92 was created (was 89 at creation time)");
+ }
+
+
+ @Test
+ public void testActivationOfUnpreparedSession() throws Exception {
+ // Needed so we can test that previous active session is still active after a failed activation
+ RemoteSession firstSession = activateAndAssertOK(90l, 0l);
+ long sessionId = 91l;
+ ActivateRequest activateRequest = new ActivateRequest(sessionId, 0l, Session.Status.NEW, "").invoke();
+ HttpResponse actResponse = activateRequest.getActResponse();
+ RemoteSession session = activateRequest.getSession();
+ assertThat(actResponse.getStatus(), is(Response.Status.BAD_REQUEST));
+ assertThat(getRenderedString(actResponse), is("{\"error-code\":\"BAD_REQUEST\",\"message\":\"tenant:"+tenant+" app:default:default Session " + sessionId + " is not prepared\"}"));
+ assertThat(session.getStatus(), is(not(Session.Status.ACTIVATE)));
+ assertThat(firstSession.getStatus(), is(Session.Status.ACTIVATE));
+ }
+
+ @Test
+ public void require_that_handler_gives_error_for_unsupported_methods() throws Exception {
+ testUnsupportedMethod(SessionHandlerTest.createTestRequest(pathPrefix, HttpRequest.Method.POST, Cmd.PREPARED, 1L));
+ testUnsupportedMethod(SessionHandlerTest.createTestRequest(pathPrefix, HttpRequest.Method.DELETE, Cmd.PREPARED, 1L));
+ testUnsupportedMethod(SessionHandlerTest.createTestRequest(pathPrefix, HttpRequest.Method.GET, Cmd.PREPARED, 1L));
+ }
+
+ @Test
+ @Ignore
+ public void require_that_handler_gives_error_when_provisioner_activated_fails() throws Exception {
+ hostProvisioner = new FailingMockProvisioner();
+ hostProvisioner.activated = false;
+ activateAndAssertError(1, 0, HttpErrorResponse.errorCodes.BAD_REQUEST, "Cannot activate application");
+ assertFalse(hostProvisioner.activated);
+ }
+
+ @Override
+ protected RemoteSession activateAndAssertOK(long sessionId, long previousSessionId) throws Exception {
+ ActivateRequest activateRequest = activateAndAssertOKPut(sessionId, previousSessionId, "");
+ return activateRequest.getSession();
+ }
+
+ @Override
+ protected Session activateAndAssertOK(long sessionId, long previousSessionId, String subPath) throws Exception {
+ ActivateRequest activateRequest = activateAndAssertOKPut(sessionId, previousSessionId, subPath);
+ return activateRequest.getSession();
+ }
+
+ @Override
+ protected void assertActivationMessageOK(ActivateRequest activateRequest, String message) throws IOException {
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ new JsonFormat(true).encode(byteArrayOutputStream, activateRequest.getMetaData().getSlime());
+ assertThat(message, containsString("\"tenant\":\"" + tenant + "\",\"message\":\"Session " + activateRequest.getSessionId() + activatedMessage));
+ assertThat(message, containsString("/application/v2/tenant/" + tenant +
+ "/application/" + appName +
+ "/environment/" + "prod" +
+ "/region/" + "default" +
+ "/instance/" + "default"));
+ assertTrue(hostProvisioner.activated);
+ assertThat(hostProvisioner.lastHosts.size(), is(1));
+ }
+
+ @Override
+ protected void activateAndAssertError(long sessionId, long previousSessionId, HttpErrorResponse.errorCodes errorCode, String expectedError) throws Exception {
+ hostProvisioner.activated = false;
+ activateAndAssertErrorPut(sessionId, previousSessionId, errorCode, expectedError);
+ assertFalse(hostProvisioner.activated);
+ }
+
+ @Override
+ protected void writeApplicationId(SessionZooKeeperClient zkc, String applicationName) {
+ ApplicationId id = ApplicationId.from(tenant,
+ ApplicationName.from(applicationName), InstanceName.defaultName());
+ zkc.writeApplicationId(id);
+ }
+
+ @Override
+ protected String getActivateLogPre() {
+ return "tenant:testtenant, app:default:default ";
+ }
+
+ @Override
+ protected SessionHandler createHandler() throws Exception {
+ final MockSessionFactory sessionFactory = new MockSessionFactory();
+ TestTenantBuilder testTenantBuilder = new TestTenantBuilder();
+ testTenantBuilder.createTenant(tenant)
+ .withSessionFactory(sessionFactory)
+ .withLocalSessionRepo(localRepo)
+ .withRemoteSessionRepo(remoteSessionRepo)
+ .withApplicationRepo(applicationRepo)
+ .build();
+ return new SessionActiveHandler(new Executor() {
+ @SuppressWarnings("NullableProblems")
+ @Override
+ public void execute(Runnable command) {
+ command.run();
+ }
+ }, AccessLog.voidAccessLog(), testTenantBuilder.createTenants(), HostProvisionerProvider.withProvisioner(hostProvisioner), Zone.defaultZone());
+ }
+
+ public static class MockProvisioner implements Provisioner {
+
+ boolean activated = false;
+ boolean removed = false;
+ boolean restarted = false;
+ ApplicationId lastApplicationId;
+ Collection<HostSpec> lastHosts;
+
+ @Override
+ public List<HostSpec> prepare(ApplicationId applicationId, ClusterSpec cluster, Capacity capacity, int groups, ProvisionLogger logger) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void activate(NestedTransaction transaction, ApplicationId application, Collection<HostSpec> hosts) {
+ transaction.commit();
+ activated = true;
+ lastApplicationId = application;
+ lastHosts = hosts;
+ }
+
+ @Override
+ public void removed(ApplicationId application) {
+ removed = true;
+ lastApplicationId = application;
+ }
+
+ @Override
+ public void restart(ApplicationId application, HostFilter filter) {
+ restarted = true;
+ lastApplicationId = application;
+ }
+
+ }
+
+ public static class FailingMockProvisioner extends MockProvisioner {
+
+ @Override
+ public void activate(NestedTransaction transaction, ApplicationId application, Collection<HostSpec> hosts) {
+ throw new IllegalArgumentException("Cannot activate application");
+ }
+
+ @Override
+ public void removed(ApplicationId application) {
+ throw new IllegalArgumentException("Cannot remove application");
+ }
+
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionContentHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionContentHandlerTest.java
new file mode 100644
index 00000000000..3ca2f2304cc
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionContentHandlerTest.java
@@ -0,0 +1,58 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.http.v2;
+
+import com.yahoo.config.model.application.provider.FilesApplicationPackage;
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.container.logging.AccessLog;
+import com.yahoo.jdisc.http.HttpRequest;
+import com.yahoo.vespa.config.server.http.SessionContentHandlerTestBase;
+import com.yahoo.vespa.config.server.http.SessionHandlerTest;
+import org.junit.Before;
+
+import java.io.InputStream;
+import java.util.concurrent.Executor;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class SessionContentHandlerTest extends SessionContentHandlerTestBase {
+ private static final TenantName tenant = TenantName.from("contenttest");
+ private SessionContentHandler handler = null;
+
+ @Before
+ public void setupHandler() throws Exception {
+ handler = createHandler();
+ pathPrefix = "/application/v2/tenant/" + tenant + "/session/";
+ baseUrl = "http://foo:1337/application/v2/tenant/" + tenant + "/session/1/content/";
+ }
+
+ protected HttpResponse doRequest(HttpRequest.Method method, String path) {
+ return doRequest(method, path, 1l);
+ }
+
+ protected HttpResponse doRequest(HttpRequest.Method method, String path, long sessionId) {
+ return handler.handle(SessionHandlerTest.createTestRequest(pathPrefix, method, Cmd.CONTENT, sessionId, path));
+ }
+
+ protected HttpResponse doRequest(HttpRequest.Method method, String path, InputStream data) {
+ return doRequest(method, path, 1l, data);
+ }
+
+ protected HttpResponse doRequest(HttpRequest.Method method, String path, long sessionId, InputStream data) {
+ return handler.handle(SessionHandlerTest.createTestRequest(pathPrefix, method, Cmd.CONTENT, sessionId, path, data));
+ }
+
+ private SessionContentHandler createHandler() throws Exception {
+ TestTenantBuilder testTenantBuilder = new TestTenantBuilder();
+ testTenantBuilder.createTenant(tenant).getLocalSessionRepo().addSession(new MockSession(1l, FilesApplicationPackage.fromFile(createTestApp())));
+ return new SessionContentHandler(new Executor() {
+ @SuppressWarnings("NullableProblems")
+ @Override
+ public void execute(Runnable command) {
+ command.run();
+ }
+ }, AccessLog.voidAccessLog(), testTenantBuilder.createTenants());
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandlerTest.java
new file mode 100644
index 00000000000..d31cdc1d1e1
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandlerTest.java
@@ -0,0 +1,135 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.http.v2;
+
+import com.yahoo.cloud.config.ConfigserverConfig;
+import com.yahoo.config.model.application.provider.FilesApplicationPackage;
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.container.jdisc.HttpRequest;
+import com.yahoo.container.logging.AccessLog;
+import com.yahoo.vespa.config.server.*;
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.vespa.config.server.application.MemoryApplicationRepo;
+import com.yahoo.vespa.config.server.http.SessionCreateHandlerTestBase;
+import com.yahoo.vespa.config.server.http.SessionHandlerTest;
+import com.yahoo.vespa.config.server.session.*;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.*;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+import static org.junit.Assert.*;
+
+import static com.yahoo.jdisc.http.HttpRequest.Method.*;
+
+/**
+ * @author musum
+ * @since 5.1
+ */
+public class SessionCreateHandlerTest extends SessionCreateHandlerTestBase {
+
+ private static final TenantName tenant = TenantName.from("test");
+
+ @Before
+ public void setupRepo() throws Exception {
+ applicationRepo = new MemoryApplicationRepo();
+ localSessionRepo = new LocalSessionRepo(applicationRepo);
+ pathPrefix = "/application/v2/tenant/" + tenant + "/session/";
+ createdMessage = " for tenant '" + tenant + "' created.\"";
+ tenantMessage = ",\"tenant\":\"test\"";
+ }
+
+ @Test
+ public void require_that_application_urls_can_be_given_as_from_parameter() throws Exception {
+ localSessionRepo.addSession(new SessionHandlerTest.MockSession(2l, FilesApplicationPackage.fromFile(testApp)));
+ ApplicationId fooId = new ApplicationId.Builder()
+ .tenant(tenant)
+ .applicationName("foo")
+ .instanceName("quux")
+ .build();
+ applicationRepo.createPutApplicationTransaction(fooId, 2).commit();
+ assertFromParameter("3", "http://myhost:40555/application/v2/tenant/" + tenant + "/application/foo/environment/test/region/baz/instance/quux");
+ localSessionRepo.addSession(new SessionHandlerTest.MockSession(5l, FilesApplicationPackage.fromFile(testApp)));
+ ApplicationId bioId = new ApplicationId.Builder()
+ .tenant(tenant)
+ .applicationName("foobio")
+ .instanceName("quux")
+ .build();
+ applicationRepo.createPutApplicationTransaction(bioId, 5).commit();
+ assertFromParameter("6", "http://myhost:40555/application/v2/tenant/" + tenant + "/application/foobio/environment/staging/region/baz/instance/quux");
+ }
+
+ @Test
+ public void require_that_from_parameter_must_be_valid() throws IOException {
+ assertIllegalFromParameter("active");
+ assertIllegalFromParameter("");
+ assertIllegalFromParameter("http://host:4013/application/v2/tenant/" + tenant + "/application/lol");
+ assertIllegalFromParameter("http://host:4013/application/v2/tenant/" + tenant + "/application/foo/environment/prod");
+ assertIllegalFromParameter("http://host:4013/application/v2/tenant/" + tenant + "/application/foo/environment/prod/region/baz");
+ assertIllegalFromParameter("http://host:4013/application/v2/tenant/" + tenant + "/application/foo/environment/prod/region/baz/instance");
+ }
+
+ @Override
+ public SessionCreateHandler createHandler() {
+ try {
+ return createHandler(new MockSessionFactory());
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail(e.getMessage());
+ }
+ return null;
+ }
+
+ @Override
+ public SessionCreateHandler createHandler(SessionFactory sessionFactory) {
+ try {
+ TestTenantBuilder testBuilder = new TestTenantBuilder();
+ testBuilder.createTenant(tenant).withSessionFactory(sessionFactory)
+ .withLocalSessionRepo(localSessionRepo)
+ .withApplicationRepo(applicationRepo);
+ return createHandler(testBuilder.createTenants());
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ SessionCreateHandler createHandler(Tenants tenants) {
+ return new SessionCreateHandler(new Executor() {
+ @SuppressWarnings("NullableProblems")
+ @Override
+ public void execute(Runnable command) {
+ command.run();
+ }
+ }, AccessLog.voidAccessLog(), tenants, new ConfigserverConfig(new ConfigserverConfig.Builder()));
+ }
+
+ @Override
+ public HttpRequest post() throws FileNotFoundException {
+ return post(null, postHeaders, new HashMap<String, String>());
+ }
+
+ @Override
+ public HttpRequest post(File file) throws FileNotFoundException {
+ return post(file, postHeaders, new HashMap<String, String>());
+ }
+
+ @Override
+ public HttpRequest post(File file, Map<String, String> headers, Map<String, String> parameters) throws FileNotFoundException {
+ HttpRequest request = HttpRequest.createTestRequest("http://" + hostname + ":" + port + "/application/v2/tenant/" + tenant + "/session",
+ POST,
+ file == null ? null : new FileInputStream(file),
+ parameters);
+ for (Map.Entry<String, String> entry : headers.entrySet()) {
+ request.getJDiscRequest().headers().put(entry.getKey(), entry.getValue());
+ }
+ return request;
+ }
+
+ @Override
+ public HttpRequest post(Map<String, String> parameters) throws FileNotFoundException {
+ return post(null, new HashMap<String, String>(), parameters);
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java
new file mode 100644
index 00000000000..0a5d4a1843c
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java
@@ -0,0 +1,246 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.http.v2;
+
+import com.google.common.collect.ImmutableMap;
+import com.yahoo.cloud.config.ConfigserverConfig;
+import com.yahoo.config.application.api.ApplicationFile;
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.config.model.api.ServiceInfo;
+import com.yahoo.config.model.test.MockApplicationPackage;
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.OutOfCapacityException;
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.container.logging.AccessLog;
+import com.yahoo.jdisc.http.HttpRequest;
+import com.yahoo.path.Path;
+import com.yahoo.slime.JsonDecoder;
+import com.yahoo.slime.Slime;
+import com.yahoo.transaction.Transaction;
+import com.yahoo.vespa.config.server.ApplicationSet;
+import com.yahoo.vespa.config.server.HostRegistry;
+import com.yahoo.vespa.config.server.application.ApplicationRepo;
+import com.yahoo.vespa.config.server.application.MemoryApplicationRepo;
+import com.yahoo.vespa.config.server.configchange.ConfigChangeActions;
+import com.yahoo.vespa.config.server.configchange.MockRefeedAction;
+import com.yahoo.vespa.config.server.configchange.MockRestartAction;
+import com.yahoo.vespa.config.server.http.*;
+import com.yahoo.vespa.config.server.session.*;
+import com.yahoo.vespa.curator.mock.MockCurator;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.Executor;
+
+import static com.yahoo.jdisc.Response.Status.OK;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author musum
+ *
+ * @since 5.1.14
+ */
+public class SessionPrepareHandlerTest extends SessionPrepareHandlerTestBase {
+ private static final TenantName tenant = TenantName.from("test");
+ private TestTenantBuilder builder;
+
+ @Before
+ public void setupRepo() throws Exception {
+ ApplicationRepo applicationRepo = new MemoryApplicationRepo();
+ curator = new MockCurator();
+ localRepo = new LocalSessionRepo(applicationRepo);
+ pathPrefix = "/application/v2/tenant/" + tenant + "/session/";
+ preparedMessage = " for tenant '" + tenant + "' prepared.\"";
+ tenantMessage = ",\"tenant\":\"" + tenant + "\"";
+ builder = new TestTenantBuilder();
+ }
+
+ @Test
+ public void require_that_tenant_is_in_response() throws Exception {
+ MockSession session = new MockSession(1, null);
+ localRepo.addSession(session);
+ HttpResponse response = createHandler(addTestTenant()).handle(SessionHandlerTest.createTestRequest(pathPrefix, HttpRequest.Method.PUT, Cmd.PREPARED, 1L));
+ assertNotNull(response);
+ assertThat(response.getStatus(), is(OK));
+ assertThat(session.getStatus(), is(Session.Status.PREPARE));
+ assertResponseContains(response, tenantMessage);
+ }
+
+ @Test
+ public void require_that_preparing_with_multiple_tenants_work() throws Exception {
+ ApplicationRepo applicationRepoDefault = new MemoryApplicationRepo();
+ LocalSessionRepo localRepoDefault = new LocalSessionRepo(applicationRepoDefault);
+ final TenantName tenantName = TenantName.defaultName();
+ addTenant(tenantName, localRepoDefault, new RemoteSessionRepo(), new SessionCreateHandlerTestBase.MockSessionFactory());
+ addTestTenant();
+ final SessionHandler handler = createHandler(builder);
+
+ long sessionId = 1;
+ // Deploy with default tenant
+ MockSession session = new MockSession(sessionId, null);
+ localRepoDefault.addSession(session);
+ pathPrefix = "/application/v2/tenant/default/session/";
+
+ HttpResponse response = handler.handle(SessionHandlerTest.createTestRequest(pathPrefix, HttpRequest.Method.PUT, Cmd.PREPARED, sessionId));
+ assertNotNull(response);
+ assertThat(SessionHandlerTest.getRenderedString(response), response.getStatus(), is(OK));
+ assertThat(session.getStatus(), is(Session.Status.PREPARE));
+
+ // Same session id, as this is for another tenant
+ session = new MockSession(sessionId, null);
+ localRepo.addSession(session);
+ String applicationName = "myapp";
+ pathPrefix = "/application/v2/tenant/" + tenant + "/session/" + sessionId + "/prepared?applicationName=" + applicationName;
+ response = handler.handle(SessionHandlerTest.createTestRequest(pathPrefix));
+ assertNotNull(response);
+ assertThat(SessionHandlerTest.getRenderedString(response), response.getStatus(), is(OK));
+ assertThat(session.getStatus(), is(Session.Status.PREPARE));
+
+ sessionId++;
+ session = new MockSession(sessionId, null);
+ localRepo.addSession(session);
+ pathPrefix = "/application/v2/tenant/" + tenant + "/session/" + sessionId + "/prepared?applicationName=" + applicationName + "&instance=quux";
+ response = handler.handle(SessionHandlerTest.createTestRequest(pathPrefix));
+ assertNotNull(response);
+ assertThat(SessionHandlerTest.getRenderedString(response), response.getStatus(), is(OK));
+ assertThat(session.getStatus(), is(Session.Status.PREPARE));
+ }
+
+ @Test
+ public void require_that_config_change_actions_are_in_response() throws Exception {
+ MockSession session = new MockSession(1, null);
+ localRepo.addSession(session);
+ HttpResponse response = createHandler(addTestTenant()).handle(SessionHandlerTest.createTestRequest(pathPrefix, HttpRequest.Method.PUT, Cmd.PREPARED, 1L));
+ assertResponseContains(response, "\"configChangeActions\":{\"restart\":[],\"refeed\":[]}");
+ }
+
+ @Test
+ public void require_that_config_change_actions_are_logged_if_existing() throws Exception {
+ List<ServiceInfo> services = Collections.singletonList(new ServiceInfo("serviceName", "serviceType", null,
+ ImmutableMap.of("clustername", "foo", "clustertype", "bar"), "configId", "hostName"));
+ ConfigChangeActions actions = new ConfigChangeActions(Arrays.asList(
+ new MockRestartAction("change", services),
+ new MockRefeedAction("change-id", false, "other change", services, "test")));
+ MockSession session = new MockSession(1, null, actions);
+ localRepo.addSession(session);
+ HttpResponse response = createHandler(addTestTenant()).handle(SessionHandlerTest.createTestRequest(pathPrefix, HttpRequest.Method.PUT, Cmd.PREPARED, 1L));
+ assertResponseContains(response, "Change(s) between active and new application that require restart:\\nIn cluster 'foo' of type 'bar");
+ assertResponseContains(response, "Change(s) between active and new application that may require re-feed:\\nchange-id: Consider removing data and re-feed document type 'test'");
+ }
+
+ @Test
+ public void require_that_config_change_actions_are_not_logged_if_not_existing() throws Exception {
+ MockSession session = new MockSession(1, null);
+ localRepo.addSession(session);
+ HttpResponse response = createHandler(addTestTenant()).handle(SessionHandlerTest.createTestRequest(pathPrefix, HttpRequest.Method.PUT, Cmd.PREPARED, 1L));
+ assertResponseNotContains(response, "Change(s) between active and new application that require restart");
+ assertResponseNotContains(response, "Change(s) between active and new application that require re-feed");
+ }
+
+ @Test
+ public void test_out_of_capacity_response() throws InterruptedException, IOException {
+ String message = "No nodes available";
+ SessionThrowingException session = new SessionThrowingException(new OutOfCapacityException(message));
+ localRepo.addSession(session);
+ HttpResponse response = createHandler(addTestTenant()).handle(SessionHandlerTest.createTestRequest(pathPrefix, HttpRequest.Method.PUT, Cmd.PREPARED, 1L));
+ assertEquals(400, response.getStatus());
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ response.render(baos);
+ Slime data = new Slime();
+ new JsonDecoder().decode(data, baos.toByteArray());
+ assertThat(data.get().field("error-code").asString(), is(HttpErrorResponse.errorCodes.OUT_OF_CAPACITY.name()));
+ assertThat(data.get().field("message").asString(), is(message));
+ }
+
+ @Override
+ public SessionHandler createHandler() throws Exception {
+ return createHandler(addTestTenant());
+ }
+
+ @Override
+ public SessionHandler createHandler(RemoteSessionRepo remoteSessionRepo) throws Exception {
+ return createHandler(addTenant(tenant, localRepo, remoteSessionRepo,
+ new SessionCreateHandlerTestBase.MockSessionFactory()));
+ }
+
+ private TestTenantBuilder addTestTenant() {
+ return addTenant(tenant, localRepo, new RemoteSessionRepo(),
+ new SessionCreateHandlerTestBase.MockSessionFactory());
+ }
+
+ static SessionHandler createHandler(TestTenantBuilder builder) {
+ return new SessionPrepareHandler(new Executor() {
+ @SuppressWarnings("NullableProblems")
+ @Override
+ public void execute(Runnable command) {
+ command.run();
+ }}, AccessLog.voidAccessLog(), builder.createTenants(), new ConfigserverConfig(new ConfigserverConfig.Builder()));
+ }
+
+ private TestTenantBuilder addTenant(TenantName tenantName,
+ LocalSessionRepo localSessionRepo,
+ RemoteSessionRepo remoteSessionRepo,
+ SessionFactory sessionFactory) {
+ builder.createTenant(tenantName).withSessionFactory(sessionFactory)
+ .withLocalSessionRepo(localSessionRepo)
+ .withRemoteSessionRepo(remoteSessionRepo)
+ .withApplicationRepo(new MemoryApplicationRepo());
+ return builder;
+ }
+
+ public static class SessionThrowingException extends LocalSession {
+ private final RuntimeException exception;
+
+ public SessionThrowingException(RuntimeException exception) {
+ super(TenantName.defaultName(), 1, null, new SessionContext(null, new MockSessionZKClient(MockApplicationPackage.createEmpty()), null, null, new HostRegistry<>(), null));
+ this.exception = exception;
+ }
+
+ @Override
+ public ConfigChangeActions prepare(DeployLogger logger, PrepareParams params, Optional<ApplicationSet> application, Path tenantPath) {
+ throw exception;
+ }
+
+ @Override
+ public Session.Status getStatus() {
+ return null;
+ }
+
+ @Override
+ public Transaction createDeactivateTransaction() {
+ return null;
+ }
+
+ @Override
+ public Transaction createActivateTransaction() {
+ return null;
+ }
+
+ @Override
+ public ApplicationFile getApplicationFile(Path relativePath, Mode mode) {
+ return null;
+ }
+
+ @Override
+ public ApplicationId getApplicationId() {
+ return null;
+ }
+
+ @Override
+ public long getCreateTime() {
+ return 0;
+ }
+
+ @Override
+ public void delete() { }
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TenantHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TenantHandlerTest.java
new file mode 100644
index 00000000000..106b675c2c7
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TenantHandlerTest.java
@@ -0,0 +1,115 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.http.v2;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.ApplicationName;
+import com.yahoo.config.provision.InstanceName;
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.vespa.config.server.*;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.yahoo.container.jdisc.HttpRequest;
+import com.yahoo.jdisc.http.HttpRequest.Method;
+import com.yahoo.vespa.config.server.http.BadRequestException;
+import com.yahoo.vespa.config.server.http.NotFoundException;
+
+public class TenantHandlerTest extends TenantTest {
+
+ private TenantHandler handler;
+ private final TenantName a = TenantName.from("a");
+
+ @Before
+ public void setup() throws Exception {
+ handler = new TenantHandler(testExecutor(), null, tenants);
+ }
+
+ @Test
+ public void testTenantCreate() throws Exception {
+ assertFalse(tenants.tenantsCopy().containsKey(a));
+ TenantCreateResponse response = (TenantCreateResponse) putSync(a,
+ HttpRequest.createTestRequest("http://deploy.example.yahoo.com:80/application/v2/tenant/a", Method.PUT));
+ assertResponseEquals(response, "{\"message\":\"Tenant a created.\"}");
+ }
+
+ @Test
+ public void testTenantCreateWithAllPossibleCharactersInName() throws Exception {
+ TenantName tenantName = TenantName.from("aB-9999_foo");
+ assertFalse(tenants.tenantsCopy().containsKey(tenantName));
+ TenantCreateResponse response = (TenantCreateResponse) putSync(a,
+ HttpRequest.createTestRequest("http://deploy.example.yahoo.com:80/application/v2/tenant/" + tenantName, Method.PUT));
+ assertResponseEquals(response, "{\"message\":\"Tenant " + tenantName + " created.\"}");
+ }
+
+ private HttpResponse putSync(TenantName name, HttpRequest testRequest) throws InterruptedException {
+ HttpResponse response = handler.handlePUT(testRequest);
+ return response;
+ }
+
+ @Test(expected=NotFoundException.class)
+ public void testGetNonExisting() throws Exception {
+ handler.handleGET(HttpRequest.createTestRequest("http://deploy.example.yahoo.com:80/application/v2/tenant/x", Method.GET));
+ }
+
+ @Test
+ public void testGetExisting() throws Exception {
+ tenants.createTenant(a);
+ TenantGetResponse response = (TenantGetResponse) handler.handleGET(HttpRequest.createTestRequest("http://deploy.example.yahoo.com:80/application/v2/tenant/a", Method.GET));
+ assertResponseEquals(response, "{\"message\":\"Tenant 'a' exists.\"}");
+ }
+
+ @Test(expected=BadRequestException.class)
+ public void testCreateExisting() throws Exception {
+ assertFalse(tenants.tenantsCopy().containsKey(a));
+ TenantCreateResponse response = (TenantCreateResponse) putSync(a, HttpRequest.createTestRequest("http://deploy.example.yahoo.com:80/application/v2/tenant/a", Method.PUT));
+ assertResponseEquals(response, "{\"message\":\"Tenant a created.\"}");
+ Tenant ta = tenants.tenantsCopy().get(a);
+ assertEquals(ta.getName(), a);
+ handler.handlePUT(HttpRequest.createTestRequest("http://deploy.example.yahoo.com:80/application/v2/tenant/a", Method.PUT));
+ }
+
+ @Test
+ public void testDelete() throws IOException, InterruptedException {
+ putSync(a, HttpRequest.createTestRequest("http://deploy.example.yahoo.com:80/application/v2/tenant/a", Method.PUT));
+ assertEquals(tenants.tenantsCopy().get(a).getName(), a);
+ TenantDeleteResponse delResp = (TenantDeleteResponse) handler.handleDELETE(HttpRequest.createTestRequest("http://deploy.example.yahoo.com:80/application/v2/tenant/a", Method.DELETE));
+ assertResponseEquals(delResp, "{\"message\":\"Tenant a deleted.\"}");
+ assertFalse(tenants.tenantsCopy().containsKey(a));
+ }
+
+ @Test
+ public void testDeleteTenantWithActiveApplications() throws Exception {
+ putSync(a, HttpRequest.createTestRequest("http://deploy.example.yahoo.com:80/application/v2/tenant/" + a, Method.PUT));
+ assertEquals(tenants.tenantsCopy().get(a).getName(), a);
+
+ final Tenant tenant = tenants.tenantsCopy().get(a);
+ final int sessionId = 1;
+ ApplicationId app = ApplicationId.from(a,
+ ApplicationName.from("foo"), InstanceName.defaultName());
+ ApplicationHandlerTest.addApplication(tenant, app, sessionId);
+
+ try {
+ handler.handleDELETE(HttpRequest.createTestRequest("http://deploy.example.yahoo.com:80/application/v2/tenant/" + a, Method.DELETE));
+ fail();
+ } catch (BadRequestException e) {
+ assertThat(e.getMessage(), is("Cannot delete tenant 'a', as it has active applications: [tenant 'a', application 'foo', instance 'default']"));
+ }
+ }
+
+ @Test(expected=NotFoundException.class)
+ public void testDeleteNonExisting() {
+ handler.handleDELETE(HttpRequest.createTestRequest("http://deploy.example.yahoo.com:80/application/v2/tenant/x", Method.DELETE));
+ }
+
+ @Test(expected=BadRequestException.class)
+ public void testIllegalNameSlashes() throws InterruptedException {
+ putSync(a, HttpRequest.createTestRequest("http://deploy.example.yahoo.com:80/application/v2/tenant/a/b", Method.PUT));
+ }
+
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TenantTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TenantTest.java
new file mode 100644
index 00000000000..930787361af
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TenantTest.java
@@ -0,0 +1,55 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.http.v2;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.concurrent.Executor;
+
+import com.yahoo.vespa.config.server.*;
+import com.yahoo.vespa.config.server.http.SessionResponse;
+import com.yahoo.vespa.config.server.monitoring.Metrics;
+import org.junit.After;
+import org.junit.Before;
+
+/**
+ * Supertype for tests in the multi tenant application API
+ *
+ * @author vegardh
+ *
+ */
+public class TenantTest extends TestWithCurator {
+
+ protected Tenants tenants;
+
+ @Before
+ public void setupTenants() throws Exception {
+ tenants = createTenants();
+ }
+
+ @After
+ public void closeTenants() throws IOException {
+ tenants.close();
+ }
+
+ protected Tenants createTenants() throws Exception {
+ return new Tenants(new TestComponentRegistry(curator), Metrics.createTestMetrics());
+ }
+
+ protected Executor testExecutor() {
+ return new Executor() {
+ @Override
+ public void execute(Runnable command) {
+ command.run();
+ }
+ };
+ }
+
+ protected void assertResponseEquals(SessionResponse response, String payload) throws IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ response.render(baos);
+ assertEquals(baos.toString("UTF-8"), payload);
+ }
+
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TestTenantBuilder.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TestTenantBuilder.java
new file mode 100644
index 00000000000..a128fa6c891
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TestTenantBuilder.java
@@ -0,0 +1,61 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.http.v2;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Collections2;
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.path.Path;
+import com.yahoo.vespa.config.server.*;
+import com.yahoo.vespa.config.server.application.MemoryApplicationRepo;
+import com.yahoo.vespa.config.server.http.SessionCreateHandlerTestBase;
+import com.yahoo.vespa.config.server.monitoring.Metrics;
+import com.yahoo.vespa.config.server.session.LocalSessionRepo;
+import com.yahoo.vespa.config.server.session.RemoteSessionRepo;
+import com.yahoo.vespa.curator.mock.MockCurator;
+
+import java.util.*;
+
+/**
+ * Test utility for creating tenants used for testing and setup wiring of tenant stuff.
+ *
+ * @author lulf
+ * @since 5.1
+ */
+public class TestTenantBuilder {
+
+ private GlobalComponentRegistry componentRegistry;
+ private Map<TenantName, TenantBuilder> tenantMap = new HashMap<>();
+
+ public TestTenantBuilder() throws Exception {
+ componentRegistry = new TestComponentRegistry(new MockCurator());
+ }
+
+ public TenantBuilder createTenant(TenantName tenantName) {
+ MemoryApplicationRepo applicationRepo = new MemoryApplicationRepo();
+ TenantBuilder builder = TenantBuilder.create(componentRegistry, tenantName, Path.createRoot().append(tenantName.value()))
+ .withSessionFactory(new SessionCreateHandlerTestBase.MockSessionFactory())
+ .withLocalSessionRepo(new LocalSessionRepo(applicationRepo))
+ .withRemoteSessionRepo(new RemoteSessionRepo())
+ .withApplicationRepo(applicationRepo);
+ tenantMap.put(tenantName, builder);
+ return builder;
+ }
+
+ public Map<TenantName, TenantBuilder> tenants() {
+ return Collections.unmodifiableMap(tenantMap);
+ }
+
+ public Tenants createTenants() {
+ Collection<Tenant> tenantList = Collections2.transform(tenantMap.values(), new Function<TenantBuilder, Tenant>() {
+ @Override
+ public Tenant apply(TenantBuilder builder) {
+ try {
+ return builder.build();
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Unable to build tenant", e);
+ }
+ }
+ });
+ return new Tenants(componentRegistry, Metrics.createTestMetrics(), tenantList);
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/model/LbServicesProducerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/model/LbServicesProducerTest.java
new file mode 100644
index 00000000000..d35f7abac5f
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/model/LbServicesProducerTest.java
@@ -0,0 +1,206 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.model;
+
+import com.yahoo.cloud.config.LbServicesConfig;
+import com.yahoo.config.application.api.ApplicationPackage;
+import com.yahoo.config.model.NullConfigModelRegistry;
+import com.yahoo.config.model.api.Model;
+import com.yahoo.config.model.deploy.DeployProperties;
+import com.yahoo.config.model.deploy.DeployState;
+import com.yahoo.config.model.test.MockApplicationPackage;
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.Environment;
+import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.Rotation;
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.config.provision.Version;
+import com.yahoo.config.provision.Zone;
+import com.yahoo.vespa.config.ConfigPayload;
+import com.yahoo.vespa.config.server.ServerCache;
+import com.yahoo.vespa.config.server.application.Application;
+import com.yahoo.vespa.config.server.monitoring.MetricUpdater;
+import com.yahoo.vespa.model.VespaModel;
+import org.junit.Test;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.util.*;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author lulf
+ * @since 5.26
+ */
+public class LbServicesProducerTest {
+ private static final String rotation1 = "rotation-1";
+ private static final String rotation2 = "rotation-2";
+ private static final String rotationString = rotation1 + "," + rotation2;
+ private static final Set<Rotation> rotations = Collections.singleton(new Rotation(rotationString));
+
+ @Test
+ public void testDeterministicGetConfig() throws IOException, SAXException {
+ Map<TenantName, Map<ApplicationId, Application>> testModel = createTestModel(new DeployState.Builder().rotations(rotations));
+ LbServicesConfig last = null;
+ for (int i = 0; i < 100; i++) {
+ testModel = randomizeTenant(testModel, i);
+ LbServicesConfig config = getLbServicesConfig(Zone.defaultZone(), testModel);
+ if (last != null) {
+ assertConfig(last, config);
+ }
+ last = config;
+ }
+ }
+
+ @Test
+ public void testConfigAliases() throws IOException, SAXException {
+ Map<TenantName, Map<ApplicationId, Application>> testModel = createTestModel(new DeployState.Builder());
+ LbServicesConfig conf = getLbServicesConfig(Zone.defaultZone(), testModel);
+ final LbServicesConfig.Tenants.Applications.Hosts.Services services = conf.tenants("foo").applications("foo:prod:default:default").hosts("foo.foo.yahoo.com").services("qrserver");
+ assertThat(services.servicealiases().size(), is(1));
+ assertThat(services.endpointaliases().size(), is(2));
+
+ assertThat(services.servicealiases(0), is("service1"));
+ assertThat(services.endpointaliases(0), is("foo1.bar1.com"));
+ assertThat(services.endpointaliases(1), is("foo2.bar2.com"));
+ }
+
+ @Test
+ public void testConfigActiveRotation() throws IOException, SAXException {
+ {
+ RegionName regionName = RegionName.from("us-east-1");
+ LbServicesConfig conf = createModelAndGetLbServicesConfig(regionName);
+ assertTrue(conf.tenants("foo").applications("foo:prod:" + regionName.value() + ":default").activeRotation());
+ }
+
+ {
+ RegionName regionName = RegionName.from("us-east-2");
+ LbServicesConfig conf = createModelAndGetLbServicesConfig(regionName);
+ assertFalse(conf.tenants("foo").applications("foo:prod:" + regionName.value() + ":default").activeRotation());
+ }
+ }
+
+ private LbServicesConfig createModelAndGetLbServicesConfig(RegionName regionName) throws IOException, SAXException {
+ final Zone zone = new Zone(Environment.prod, regionName);
+ Map<TenantName, Map<ApplicationId, Application>> testModel = createTestModel(new DeployState.Builder().
+ properties(new DeployProperties.Builder().zone(zone).build()));
+ return getLbServicesConfig(new Zone(Environment.prod, regionName), testModel);
+ }
+
+ private LbServicesConfig getLbServicesConfig(Zone zone, Map<TenantName, Map<ApplicationId, Application>> testModel) {
+ LbServicesProducer producer = new LbServicesProducer(testModel, zone);
+ LbServicesConfig.Builder builder = new LbServicesConfig.Builder();
+ producer.getConfig(builder);
+ return new LbServicesConfig(builder);
+ }
+
+ @Test
+ public void testConfigAliasesWithRotations() throws IOException, SAXException {
+ Map<TenantName, Map<ApplicationId, Application>> testModel = createTestModel(new DeployState.Builder().rotations(rotations));
+ RegionName regionName = RegionName.from("us-east-1");
+ LbServicesConfig conf = getLbServicesConfig(new Zone(Environment.prod, regionName), testModel);
+ final LbServicesConfig.Tenants.Applications.Hosts.Services services = conf.tenants("foo").applications("foo:prod:" + regionName.value() + ":default").hosts("foo.foo.yahoo.com").services("qrserver");
+ assertThat(services.servicealiases().size(), is(1));
+ assertThat(services.endpointaliases().size(), is(4));
+
+ assertThat(services.servicealiases(0), is("service1"));
+ assertThat(services.endpointaliases(0), is("foo1.bar1.com"));
+ assertThat(services.endpointaliases(1), is("foo2.bar2.com"));
+ assertThat(services.endpointaliases(2), is(rotation1));
+ assertThat(services.endpointaliases(3), is(rotation2));
+ }
+
+ private Map<TenantName, Map<ApplicationId, Application>> randomizeTenant(Map<TenantName, Map<ApplicationId, Application>> testModel, int seed) {
+ Map<TenantName, Map<ApplicationId, Application>> randomizedTenants = new LinkedHashMap<>();
+ List<TenantName> keys = new ArrayList<>(testModel.keySet());
+ Collections.shuffle(keys, new Random(seed));
+ for (TenantName key : keys) {
+ randomizedTenants.put(key, randomizeApplications(testModel.get(key), randomizedTenants.size()));
+ }
+ return randomizedTenants;
+ }
+
+ private Map<ApplicationId, Application> randomizeApplications(Map<ApplicationId, Application> applicationIdApplicationMap, int seed) {
+ Map<ApplicationId, Application> randomizedApplications = new LinkedHashMap<>();
+ List<ApplicationId> keys = new ArrayList<>(applicationIdApplicationMap.keySet());
+ Collections.shuffle(keys, new Random(seed));
+ for (ApplicationId key : keys) {
+ randomizedApplications.put(key, applicationIdApplicationMap.get(key));
+ }
+ return randomizedApplications;
+ }
+
+ private Map<TenantName, Map<ApplicationId, Application>> createTestModel(DeployState.Builder deployStateBuilder) throws IOException, SAXException {
+ Map<TenantName, Map<ApplicationId, Application>> tMap = new LinkedHashMap<>();
+ TenantName foo = TenantName.from("foo");
+ TenantName bar = TenantName.from("bar");
+ TenantName baz = TenantName.from("baz");
+ tMap.put(foo, createTestApplications(foo, deployStateBuilder));
+ tMap.put(bar, createTestApplications(bar, deployStateBuilder));
+ tMap.put(baz, createTestApplications(baz, deployStateBuilder));
+ return tMap;
+ }
+
+ private Map<ApplicationId, Application> createTestApplications(TenantName tenant, DeployState.Builder deploystateBuilder) throws IOException, SAXException {
+ Map<ApplicationId, Application> aMap = new LinkedHashMap<>();
+ ApplicationId fooApp = new ApplicationId.Builder().tenant(tenant).applicationName("foo").build();
+ ApplicationId barApp = new ApplicationId.Builder().tenant(tenant).applicationName("bar").build();
+ ApplicationId bazApp = new ApplicationId.Builder().tenant(tenant).applicationName("baz").build();
+ aMap.put(fooApp, createApplication(fooApp, deploystateBuilder));
+ aMap.put(barApp, createApplication(barApp, deploystateBuilder));
+ aMap.put(bazApp, createApplication(bazApp, deploystateBuilder));
+ return aMap;
+ }
+
+ private Application createApplication(ApplicationId appId, DeployState.Builder deploystateBuilder) throws IOException, SAXException {
+ return new Application(createVespaModel(createApplicationPackage(
+ appId.tenant() + "." + appId.application() + ".yahoo.com", appId.tenant().value() + "." + appId.application().value() + "2.yahoo.com"),
+ deploystateBuilder),
+ new ServerCache(),
+ 3l,
+ Version.fromIntValues(1, 2, 3),
+ MetricUpdater.createTestUpdater(),
+ appId);
+ }
+
+ private ApplicationPackage createApplicationPackage(String host1, String host2) {
+ String hosts = "<hosts><host name='" + host1 + "'><alias>node1</alias></host><host name='" + host2 + "'><alias>node2</alias></host></hosts>";
+ String services = "<services><admin version='2.0'><adminserver hostalias='node1' /><logserver hostalias='node1' /><slobroks><slobrok hostalias='node1' /><slobrok hostalias='node2' /></slobroks></admin>"
+ + "<jdisc id='mydisc' version='1.0'>" +
+ " <aliases>" +
+ " <endpoint-alias>foo2.bar2.com</endpoint-alias>" +
+ " <service-alias>service1</service-alias>" +
+ " <endpoint-alias>foo1.bar1.com</endpoint-alias>" +
+ " </aliases>" +
+ " <nodes>" +
+ " <node hostalias='node1' />" +
+ " </nodes>" +
+ " <search/>" +
+ "</jdisc>" +
+ "</services>";
+ String deploymentInfo ="<?xml version='1.0' encoding='UTF-8'?>" +
+ "<deployment version='1.0'>" +
+ " <test />" +
+ " <prod global-service-id='mydisc'>" +
+ " <region active='true'>us-east-1</region>" +
+ " <region active='false'>us-east-2</region>" +
+ " </prod>" +
+ "</deployment>";
+
+ return new MockApplicationPackage.Builder().withHosts(hosts).withServices(services).withDeploymentInfo(deploymentInfo).build();
+ }
+
+ private Model createVespaModel(ApplicationPackage applicationPackage, DeployState.Builder deployStateBuilder) throws IOException, SAXException {
+ return new VespaModel(new NullConfigModelRegistry(), deployStateBuilder.applicationPackage(applicationPackage).build());
+ }
+
+ private void assertConfig(LbServicesConfig expected, LbServicesConfig actual) {
+ assertFalse(expected.toString().isEmpty());
+ assertFalse(actual.toString().isEmpty());
+ assertThat(expected.toString(), is(actual.toString()));
+ assertThat(ConfigPayload.fromInstance(expected).toString(true), is(ConfigPayload.fromInstance(actual).toString(true)));
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/model/RoutingProducerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/model/RoutingProducerTest.java
new file mode 100755
index 00000000000..a8263cd361a
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/model/RoutingProducerTest.java
@@ -0,0 +1,109 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.model;
+
+import com.yahoo.cloud.config.RoutingConfig;
+import com.yahoo.config.application.api.ApplicationPackage;
+import com.yahoo.config.model.NullConfigModelRegistry;
+import com.yahoo.config.model.api.Model;
+import com.yahoo.config.model.deploy.DeployState;
+import com.yahoo.config.model.test.MockApplicationPackage;
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.config.provision.Version;
+import com.yahoo.vespa.config.server.ServerCache;
+import com.yahoo.vespa.config.server.application.Application;
+import com.yahoo.vespa.config.server.monitoring.MetricUpdater;
+import com.yahoo.vespa.model.VespaModel;
+import org.junit.Test;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author can
+ */
+public class RoutingProducerTest {
+ @Test
+ public void testNodesFromRoutingAppOnly() throws Exception {
+ Map<TenantName, Map<ApplicationId, Application>> testModel = createTestModel(new DeployState.Builder());
+ RoutingProducer producer = new RoutingProducer(testModel);
+ RoutingConfig.Builder builder = new RoutingConfig.Builder();
+ producer.getConfig(builder);
+ RoutingConfig config = new RoutingConfig(builder);
+ assertThat(config.hosts().size(), is(2));
+ assertThat(config.hosts(0), is("hosted-vespa.routing.yahoo.com"));
+ assertThat(config.hosts(1), is("hosted-vespa.routing2.yahoo.com"));
+ }
+
+ private Map<TenantName, Map<ApplicationId, Application>> createTestModel(DeployState.Builder deployStateBuilder) throws IOException, SAXException {
+ Map<TenantName, Map<ApplicationId, Application>> tMap = new LinkedHashMap<>();
+ TenantName foo = TenantName.from("foo");
+ TenantName bar = TenantName.from("bar");
+ TenantName routing = TenantName.from(ApplicationId.HOSTED_VESPA_TENANT.value());
+ tMap.put(foo, createTestApplications(foo, deployStateBuilder));
+ tMap.put(bar, createTestApplications(bar, deployStateBuilder));
+ tMap.put(routing, createTestApplications(routing, deployStateBuilder));
+ return tMap;
+ }
+
+ private Map<ApplicationId, Application> createTestApplications(TenantName tenant, DeployState.Builder deploystateBuilder) throws IOException, SAXException {
+ Map<ApplicationId, Application> aMap = new LinkedHashMap<>();
+ ApplicationId fooApp = new ApplicationId.Builder().tenant(tenant).applicationName("foo").build();
+ ApplicationId barApp = new ApplicationId.Builder().tenant(tenant).applicationName("bar").build();
+ ApplicationId routingApp = new ApplicationId.Builder().tenant(tenant).applicationName(ApplicationId.ROUTING_APPLICATION.value()).build();
+ aMap.put(fooApp, createApplication(fooApp, deploystateBuilder));
+ aMap.put(barApp, createApplication(barApp, deploystateBuilder));
+ aMap.put(routingApp, createApplication(routingApp, deploystateBuilder));
+ return aMap;
+ }
+
+ private Application createApplication(ApplicationId appId, DeployState.Builder deploystateBuilder) throws IOException, SAXException {
+ return new Application(createVespaModel(createApplicationPackage(
+ appId.tenant() + "." + appId.application() + ".yahoo.com", appId.tenant().value() + "." + appId.application().value() + "2.yahoo.com"),
+ deploystateBuilder),
+ new ServerCache(),
+ 3l,
+ Version.fromIntValues(1, 2, 3),
+ MetricUpdater.createTestUpdater(),
+ appId);
+ }
+
+ private ApplicationPackage createApplicationPackage(String host1, String host2) {
+ String hosts = "<hosts><host name='" + host1 + "'><alias>node1</alias></host><host name='" + host2 + "'><alias>node2</alias></host></hosts>";
+ String services = "<services><admin version='2.0'><adminserver hostalias='node1' /><logserver hostalias='node1' /><slobroks><slobrok hostalias='node1' /><slobrok hostalias='node2' /></slobroks></admin>"
+ + "<jdisc id='mydisc' version='1.0'>" +
+ " <aliases>" +
+ " <endpoint-alias>foo2.bar2.com</endpoint-alias>" +
+ " <service-alias>service1</service-alias>" +
+ " <endpoint-alias>foo1.bar1.com</endpoint-alias>" +
+ " </aliases>" +
+ " <nodes>" +
+ " <node hostalias='node1' />" +
+ " </nodes>" +
+ " <search/>" +
+ "</jdisc>" +
+ "</services>";
+ String deploymentInfo ="<?xml version='1.0' encoding='UTF-8'?>" +
+ "<deployment version='1.0'>" +
+ " <test />" +
+ " <prod global-service-id='mydisc'>" +
+ " <region active='true'>us-east</region>" +
+ " </prod>" +
+ "</deployment>";
+
+ return new MockApplicationPackage.Builder()
+ .withHosts(hosts)
+ .withServices(services)
+ .withDeploymentInfo(deploymentInfo)
+ .build();
+ }
+
+ private Model createVespaModel(ApplicationPackage applicationPackage, DeployState.Builder deployStateBuilder) throws IOException, SAXException {
+ return new VespaModel(new NullConfigModelRegistry(), deployStateBuilder.applicationPackage(applicationPackage).build());
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/model/TestModelFactory.java b/configserver/src/test/java/com/yahoo/vespa/config/server/model/TestModelFactory.java
new file mode 100644
index 00000000000..32d1b610194
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/model/TestModelFactory.java
@@ -0,0 +1,37 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.model;
+
+import com.yahoo.config.model.NullConfigModelRegistry;
+import com.yahoo.config.model.api.ModelContext;
+import com.yahoo.config.model.api.ModelCreateResult;
+import com.yahoo.config.provision.Version;
+import com.yahoo.vespa.model.VespaModelFactory;
+
+/**
+ * @author lulf
+ */
+public class TestModelFactory extends VespaModelFactory {
+ private final Version vespaVersion;
+ private ModelContext modelContext;
+
+ public TestModelFactory(Version vespaVersion) {
+ super(new NullConfigModelRegistry());
+ this.vespaVersion = vespaVersion;
+ }
+
+ // Needed for testing (to get hold of ModelContext)
+ @Override
+ public ModelCreateResult createAndValidateModel(ModelContext modelContext, boolean ignoreValidationErrors) {
+ this.modelContext = modelContext;
+ return super.createAndValidateModel(modelContext, ignoreValidationErrors);
+ }
+
+ @Override
+ public Version getVersion() {
+ return vespaVersion;
+ }
+
+ public ModelContext getModelContext() {
+ return modelContext;
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/modelconfigs/a.search-clustermusic-c0-r0-indexer4.cfg b/configserver/src/test/java/com/yahoo/vespa/config/server/modelconfigs/a.search-clustermusic-c0-r0-indexer4.cfg
new file mode 100644
index 00000000000..d3970ee48eb
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/modelconfigs/a.search-clustermusic-c0-r0-indexer4.cfg
@@ -0,0 +1,44 @@
+include: search/cluster.music
+include: search/cluster.music
+c 2
+storage[2]
+storage[0].feeder[1]
+storage[0].feeder[0] "test"
+storage[1].feeder[2]
+storage[1].feeder[0] "me"
+storage[1].feeder[1] now
+storage[1].id :parent:
+storage[1].id2 pjatt
+testref :parent:
+testref2 some/babbel
+config[1]
+config[0].role "rtx"
+#config[0].usewrapper false
+config[0].id search/cluster.music/rtx/0
+f[1]
+f[0].a "A"
+f[0].b "B"
+f[0].c "C"
+f[0].h "H"
+f[0].f "F"
+f[0].notindef "notindef"
+routingtable[1]
+routingtable[0].hop[3]
+routingtable[0].hop[0].name "docproc/cluster.music.indexing/chain.music.indexing"
+routingtable[0].hop[0].selector "docproc/cluster.music.indexing/*/chain.music.indexing"
+routingtable[0].hop[1].name "search/cluster.music"
+routingtable[0].hop[1].selector "search/cluster.music/[SearchColumn]/[SearchRow]/feed-destination"
+routingtable[0].hop[1].recipient[1]
+routingtable[0].hop[1].recipient[0] "search/cluster.music/c0/r0/feed-destination"
+routingtable[0].hop[2].selector "[DocumentRouteSelector]"
+routingtable[0].hop[2].name "indexing"
+routingtable[0].hop[2].notindef "not in def"
+routingtable[0].hop[2].recipient[1]
+routingtable[0].hop[2].recipient[0] "search/cluster.music"
+notindef "dfsd"
+nopenotindef[0] "boo"
+nadaindef[0].naw 98
+mode NOTINDEF
+rangecheck1 100
+rangecheck2 10000
+rangecheck3 20
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/modelconfigs/a.search-clustersports-c0-r0-indexer4.cfg b/configserver/src/test/java/com/yahoo/vespa/config/server/modelconfigs/a.search-clustersports-c0-r0-indexer4.cfg
new file mode 100644
index 00000000000..727a5052ed6
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/modelconfigs/a.search-clustersports-c0-r0-indexer4.cfg
@@ -0,0 +1,2 @@
+include: search/cluster.sports
+c 67
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/modelconfigs/a.vespamodel.cfg b/configserver/src/test/java/com/yahoo/vespa/config/server/modelconfigs/a.vespamodel.cfg
new file mode 100644
index 00000000000..f4996027f60
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/modelconfigs/a.vespamodel.cfg
@@ -0,0 +1 @@
+model vespa
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/modelconfigs/c.search-clustersports-c0-r0-indexer4.cfg b/configserver/src/test/java/com/yahoo/vespa/config/server/modelconfigs/c.search-clustersports-c0-r0-indexer4.cfg
new file mode 100644
index 00000000000..d75d76810f9
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/modelconfigs/c.search-clustersports-c0-r0-indexer4.cfg
@@ -0,0 +1,2 @@
+foo "bar"
+gaz -78
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/modelconfigs/compositeinclude.search-qrservers-0.cfg b/configserver/src/test/java/com/yahoo/vespa/config/server/modelconfigs/compositeinclude.search-qrservers-0.cfg
new file mode 100644
index 00000000000..7ccdb73eb9a
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/modelconfigs/compositeinclude.search-qrservers-0.cfg
@@ -0,0 +1,2 @@
+include: search/cluster.logical/*
+include: search/cluster.video/*
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/modelconfigs/recursiveinclude.search-clustermusic-c0-r0.cfg b/configserver/src/test/java/com/yahoo/vespa/config/server/modelconfigs/recursiveinclude.search-clustermusic-c0-r0.cfg
new file mode 100644
index 00000000000..5b07d3a2890
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/modelconfigs/recursiveinclude.search-clustermusic-c0-r0.cfg
@@ -0,0 +1 @@
+include: search/cluster.music
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/modelconfigs/spooler.clients-spooler-spooler.cfg b/configserver/src/test/java/com/yahoo/vespa/config/server/modelconfigs/spooler.clients-spooler-spooler.cfg
new file mode 100644
index 00000000000..038d655e83c
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/modelconfigs/spooler.clients-spooler-spooler.cfg
@@ -0,0 +1,13 @@
+directory /home/vespa/var/spool/vespa/
+keepsuccess false
+parsers[4]
+parsers[0].classname com.yahoo.vespaspooler.XMLFileParser
+parsers[0].parameters[0]
+parsers[1].classname com.yahoo.mail.vespa.spooler.MailFileParser
+parsers[1].parameters[0]
+parsers[2].classname com.yahoo.mail.vespa.spooler.UserDeleteParser
+parsers[2].parameters[0]
+parsers[3].classname com.yahoo.mail.vespa.spooler.VespaGrimParser
+parsers[3].parameters[1]
+parsers[3].parameters[0].key chunksize
+parsers[3].parameters[0].value 5
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/provision/StaticProvisionerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/provision/StaticProvisionerTest.java
new file mode 100644
index 00000000000..3831f94a77d
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/provision/StaticProvisionerTest.java
@@ -0,0 +1,60 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.provision;
+
+import com.yahoo.cloud.config.ModelConfig;
+import com.yahoo.config.application.api.ApplicationPackage;
+import com.yahoo.config.model.NullConfigModelRegistry;
+import com.yahoo.config.model.api.HostProvisioner;
+import com.yahoo.config.model.application.provider.FilesApplicationPackage;
+import com.yahoo.config.model.deploy.DeployProperties;
+import com.yahoo.config.model.deploy.DeployState;
+import com.yahoo.config.model.provision.InMemoryProvisioner;
+import com.yahoo.vespa.config.ConfigPayload;
+import com.yahoo.vespa.model.VespaModel;
+import org.junit.Test;
+import org.xml.sax.SAXException;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author lulf
+ */
+public class StaticProvisionerTest {
+ @Test
+ public void sameHostsAreProvisioned() throws IOException, SAXException {
+ ApplicationPackage app = FilesApplicationPackage.fromFile(new File("src/test/apps/hosted"));
+ InMemoryProvisioner inMemoryHostProvisioner = new InMemoryProvisioner(false, "host1.yahoo.com", "host2.yahoo.com", "host3.yahoo.com", "host4.yahoo.com");
+ VespaModel firstModel = createModel(app, inMemoryHostProvisioner);
+
+ StaticProvisioner staticProvisioner = new StaticProvisioner(firstModel.getProvisionInfo().get());
+ VespaModel secondModel = createModel(app, staticProvisioner);
+
+ assertModelConfig(firstModel, secondModel);
+ }
+
+ private void assertModelConfig(VespaModel firstModel, VespaModel secondModel) {
+ String firstConfig = getModelConfig(firstModel);
+ String secondConfig = getModelConfig(secondModel);
+ assertEquals(firstConfig, secondConfig);
+ }
+
+ private String getModelConfig(VespaModel model) {
+ return ConfigPayload.fromInstance(model.getConfig(ModelConfig.class, "")).toString();
+ }
+
+ private VespaModel createModel(ApplicationPackage app, HostProvisioner provisioner) throws IOException, SAXException {
+ DeployState deployState = new DeployState.Builder()
+ .applicationPackage(app)
+ .modelHostProvisioner(provisioner)
+ .properties(new DeployProperties.Builder()
+ .multitenant(true)
+ .hostedVespa(true)
+ .build())
+ .build();
+ return new VespaModel(new NullConfigModelRegistry(), deployState);
+ }
+
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/restapi/impl/StatusResourceTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/restapi/impl/StatusResourceTest.java
new file mode 100644
index 00000000000..333cac4fd48
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/restapi/impl/StatusResourceTest.java
@@ -0,0 +1,46 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.restapi.impl;
+
+import com.yahoo.cloud.config.ConfigserverConfig;
+import com.yahoo.vespa.config.server.TestComponentRegistry;
+import com.yahoo.vespa.config.server.restapi.resources.StatusInformation;
+import com.yahoo.vespa.defaults.Defaults;
+import org.junit.Test;
+
+import java.io.IOException;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class StatusResourceTest {
+ @Test
+ public void require_that_status_handler_responds_to_ping() throws IOException {
+ StatusResource handler = new StatusResource(null, null, null, null, null, null, null, new TestComponentRegistry());
+ assertNotNull(handler.getStatus().configserverConfig);
+ }
+
+ @Test
+ public void require_that_generated_config_is_converted() {
+ ConfigserverConfig orig = new ConfigserverConfig(new ConfigserverConfig.Builder());
+ StatusInformation.ConfigserverConfig conv = new StatusInformation.ConfigserverConfig(orig);
+ assertThat(conv.applicationDirectory, is(Defaults.getDefaults().underVespaHome(orig.applicationDirectory())));
+ assertThat(conv.configModelPluginDir.size(), is(orig.configModelPluginDir().size()));
+ assertThat(conv.zookeeeperserver.size(), is(orig.zookeeperserver().size()));
+ assertThat(conv.zookeeperBarrierTimeout, is(orig.zookeeper().barrierTimeout()));
+ assertThat(conv.configServerDBDir, is(Defaults.getDefaults().underVespaHome(orig.configServerDBDir())));
+ assertThat(conv.masterGeneration, is(orig.masterGeneration()));
+ assertThat(conv.maxgetconfigclients, is(orig.maxgetconfigclients()));
+ assertThat(conv.multitenant, is(orig.multitenant()));
+ assertThat(conv.numDelayedResponseThreads, is(orig.numDelayedResponseThreads()));
+ assertThat(conv.numthreads, is(orig.numthreads()));
+ assertThat(conv.payloadCompressionType, is(orig.payloadCompressionType()));
+ assertThat(conv.rpcport, is(orig.rpcport()));
+ assertThat(conv.sessionLifetime, is(orig.sessionLifetime()));
+ assertThat(conv.zookeepercfg, is(Defaults.getDefaults().underVespaHome(orig.zookeepercfg())));
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/sdconfigs/a.search-clustermusic.cfg b/configserver/src/test/java/com/yahoo/vespa/config/server/sdconfigs/a.search-clustermusic.cfg
new file mode 100644
index 00000000000..f3acd4cf8b9
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/sdconfigs/a.search-clustermusic.cfg
@@ -0,0 +1,5 @@
+asyncfetchocc 9
+d 3
+kanon -78.56
+
+partialsd "sd"
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/sdconfigs/a.search-clustersports.cfg b/configserver/src/test/java/com/yahoo/vespa/config/server/sdconfigs/a.search-clustersports.cfg
new file mode 100644
index 00000000000..5d8a01a18ea
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/sdconfigs/a.search-clustersports.cfg
@@ -0,0 +1,2 @@
+d 89
+search[1].feeder[1] "sportsfeeder1"
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/sdconfigs/b.search-clustersports.cfg b/configserver/src/test/java/com/yahoo/vespa/config/server/sdconfigs/b.search-clustersports.cfg
new file mode 100644
index 00000000000..f6c35df398d
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/sdconfigs/b.search-clustersports.cfg
@@ -0,0 +1 @@
+gaff -89
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/sdconfigs/compositeinclude.search-clusterlogical.cfg b/configserver/src/test/java/com/yahoo/vespa/config/server/sdconfigs/compositeinclude.search-clusterlogical.cfg
new file mode 100644
index 00000000000..c3d9b1e45a1
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/sdconfigs/compositeinclude.search-clusterlogical.cfg
@@ -0,0 +1,8 @@
+classes[1]
+classes[logical].id 1906788747
+classes[logical].name logical
+classes[logical].fields[2]
+classes[logical].fields[0].name sddocnameNAM
+classes[logical].fields[0].type longstring
+classes[logical].fields[1].name title
+classes[logical].fields[1].type longstringSTRIN
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/sdconfigs/compositeinclude.search-clustervideo.cfg b/configserver/src/test/java/com/yahoo/vespa/config/server/sdconfigs/compositeinclude.search-clustervideo.cfg
new file mode 100644
index 00000000000..12a21671b4a
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/sdconfigs/compositeinclude.search-clustervideo.cfg
@@ -0,0 +1,8 @@
+classes[1]
+classes[music].id 1906788746
+classes[music].name music
+classes[music].fields[2]
+classes[music].fields[0].name sddocnameNAME
+classes[music].fields[0].type longstring
+classes[music].fields[1].name title
+classes[music].fields[1].type longstringSTRING
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/sdconfigs/compositeinclude.search-part-clusterlogical.cfg b/configserver/src/test/java/com/yahoo/vespa/config/server/sdconfigs/compositeinclude.search-part-clusterlogical.cfg
new file mode 100644
index 00000000000..4001c59adbc
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/sdconfigs/compositeinclude.search-part-clusterlogical.cfg
@@ -0,0 +1,14 @@
+classes[0]
+classes[smallsum614540714].id 614540714
+classes[smallsum614540714].name smallsum
+classes[smallsum614540714].fields[5]
+classes[smallsum614540714].fields[0].name s_13
+classes[smallsum614540714].fields[0].type longstring
+classes[smallsum614540714].fields[1].name ranklog
+classes[smallsum614540714].fields[1].type longstring
+classes[smallsum614540714].fields[2].name rankfeatures
+classes[smallsum614540714].fields[2].type longstring
+classes[smallsum614540714].fields[3].name summaryfeatures
+classes[smallsum614540714].fields[3].type longstring
+classes[smallsum614540714].fields[4].name sddocname
+classes[smallsum614540714].fields[4].type longstring
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/sdconfigs/compositeinclude.search-part-clustervideo.cfg b/configserver/src/test/java/com/yahoo/vespa/config/server/sdconfigs/compositeinclude.search-part-clustervideo.cfg
new file mode 100644
index 00000000000..33d07b99ab6
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/sdconfigs/compositeinclude.search-part-clustervideo.cfg
@@ -0,0 +1,14 @@
+classes[0]
+classes[smallsum507688128].id 507688128
+classes[smallsum507688128].name smallsum
+classes[smallsum507688128].fields[5]
+classes[smallsum507688128].fields[0].name title
+classes[smallsum507688128].fields[0].type longstring
+classes[smallsum507688128].fields[1].name ranklog
+classes[smallsum507688128].fields[1].type longstring
+classes[smallsum507688128].fields[2].name rankfeatures
+classes[smallsum507688128].fields[2].type longstring
+classes[smallsum507688128].fields[3].name summaryfeatures
+classes[smallsum507688128].fields[3].type longstring
+classes[smallsum507688128].fields[4].name sddocname
+classes[smallsum507688128].fields[4].type longstring
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/sdconfigs/recursiveinclude.search-clustermusic-conf1.4.cfg b/configserver/src/test/java/com/yahoo/vespa/config/server/sdconfigs/recursiveinclude.search-clustermusic-conf1.4.cfg
new file mode 100644
index 00000000000..de9fbdd39f4
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/sdconfigs/recursiveinclude.search-clustermusic-conf1.4.cfg
@@ -0,0 +1,7 @@
+rec 56
+national 77
+ilscript[1]
+ilscript[music].name music
+ilscript[music].doctype music
+ilscript[music].content[1]
+ilscript[music].content[0] "input year | summary s_3 | tokenize \"stemming,normalizing\" { index f_3 | index f_4; };"
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/sdconfigs/recursiveinclude.search-clustermusic-conf2.4.cfg b/configserver/src/test/java/com/yahoo/vespa/config/server/sdconfigs/recursiveinclude.search-clustermusic-conf2.4.cfg
new file mode 100644
index 00000000000..e95f976a43a
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/sdconfigs/recursiveinclude.search-clustermusic-conf2.4.cfg
@@ -0,0 +1,7 @@
+ursive -50
+teatern 78
+ilscript[1]
+ilscript[father].name father
+ilscript[father].doctype father
+ilscript[father].content[6]
+ilscript[father].content[0] "input year | summary s_3 | tokenize \"stemming,normalizing\" { index f_3 | index f_5; };"
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/sdconfigs/recursiveinclude.search-clustermusic.cfg b/configserver/src/test/java/com/yahoo/vespa/config/server/sdconfigs/recursiveinclude.search-clustermusic.cfg
new file mode 100644
index 00000000000..cea943d5bc9
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/sdconfigs/recursiveinclude.search-clustermusic.cfg
@@ -0,0 +1,2 @@
+include: search/cluster.music/conf1.sd.derived
+include: search/cluster.music/conf2.sd.derived
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/DummyTransaction.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/DummyTransaction.java
new file mode 100644
index 00000000000..ea4455ab99f
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/DummyTransaction.java
@@ -0,0 +1,54 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.session;
+
+import com.yahoo.transaction.Transaction;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Dummy transaction implementation that only does stuff in memory and does not adhere to contract.
+ * @author lulf
+ */
+public class DummyTransaction implements Transaction {
+
+ private final List<Operation> operations = new ArrayList<>();
+
+ public interface RunnableOperation extends Operation, Runnable {
+ }
+
+ public DummyTransaction() { }
+
+ @Override
+ public Transaction add(Operation operation) {
+ this.operations.add(operation);
+ return this;
+ }
+
+ @Override
+ public Transaction add(List<Operation> operations) {
+ this.operations.addAll(operations);
+ return this;
+ }
+
+ @Override
+ public List<Operation> operations() { return new ArrayList<>(operations); }
+
+ @Override
+ public void prepare() { }
+
+ @Override
+ public void commit() {
+ for (Operation op : operations) {
+ ((RunnableOperation)op).run();
+ }
+ }
+
+ @Override
+ public void rollbackOrLog() {
+ throw new IllegalStateException("Unexpected rollback");
+ }
+
+ @Override
+ public void close() { }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionRepoTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionRepoTest.java
new file mode 100644
index 00000000000..84fce1c09fe
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionRepoTest.java
@@ -0,0 +1,127 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.session;
+
+import com.yahoo.config.model.application.provider.FilesApplicationPackage;
+import com.yahoo.path.Path;
+import com.yahoo.test.ManualClock;
+import com.yahoo.vespa.config.server.*;
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.vespa.config.server.application.MemoryApplicationRepo;
+import com.yahoo.vespa.config.server.deploy.TenantFileSystemDirs;
+import com.yahoo.io.IOUtils;
+import com.yahoo.vespa.config.server.http.SessionHandlerTest;
+import com.yahoo.vespa.config.server.zookeeper.SessionCounter;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.time.Duration;
+import java.time.Instant;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class LocalSessionRepoTest extends TestWithCurator {
+
+ private File testApp = new File("src/test/apps/app");
+ private LocalSessionRepo repo;
+ private ManualClock clock;
+ private static final TenantName tenantName = TenantName.defaultName();
+
+ @Before
+ public void setupSessions() throws Exception {
+ setupSessions(tenantName, true);
+ }
+
+ private void setupSessions(TenantName tenantName, boolean createInitialSessions) throws Exception {
+ GlobalComponentRegistry globalComponentRegistry = new TestComponentRegistry(curator);
+ TenantFileSystemDirs tenantFileSystemDirs = TenantFileSystemDirs.createTestDirs(tenantName);
+ if (createInitialSessions) {
+ IOUtils.copyDirectory(testApp, new File(tenantFileSystemDirs.path(), "1"));
+ IOUtils.copyDirectory(testApp, new File(tenantFileSystemDirs.path(), "2"));
+ IOUtils.copyDirectory(testApp, new File(tenantFileSystemDirs.path(), "3"));
+ }
+ clock = new ManualClock(Instant.ofEpochSecond(1));
+ LocalSessionLoader loader = new SessionFactoryImpl(globalComponentRegistry,
+ new SessionCounter(globalComponentRegistry.getCurator(),
+ Path.fromString("counter"),
+ Path.fromString("sessions")),
+ Path.createRoot(),
+ new MemoryApplicationRepo(),
+ tenantFileSystemDirs, new HostRegistry<>(),
+ tenantName);
+ repo = new LocalSessionRepo(tenantFileSystemDirs, loader, new MemoryApplicationRepo(), clock, 5);
+ }
+
+ @Test
+ public void require_that_sessions_can_be_loaded_from_disk() {
+ assertNotNull(repo.getSession(1l));
+ assertNotNull(repo.getSession(2l));
+ assertNotNull(repo.getSession(3l));
+ assertNull(repo.getSession(4l));
+ }
+
+ @Test
+ public void require_that_old_sessions_are_purged() {
+ clock.advance(Duration.ofSeconds(1));
+ assertNotNull(repo.getSession(1l));
+ assertNotNull(repo.getSession(2l));
+ assertNotNull(repo.getSession(3l));
+ clock.advance(Duration.ofSeconds(1));
+ assertNotNull(repo.getSession(1l));
+ assertNotNull(repo.getSession(2l));
+ assertNotNull(repo.getSession(3l));
+ clock.advance(Duration.ofSeconds(1));
+ addSession(4l, 6);
+ assertNotNull(repo.getSession(1l));
+ assertNotNull(repo.getSession(2l));
+ assertNotNull(repo.getSession(3l));
+ assertNotNull(repo.getSession(4l));
+ clock.advance(Duration.ofSeconds(1));
+ addSession(5l, 10);
+ assertNull(repo.getSession(1l));
+ assertNull(repo.getSession(2l));
+ assertNull(repo.getSession(3l));
+ }
+
+ @Test
+ public void require_that_all_sessions_are_deleted() {
+ repo.deleteAllSessions();
+ assertNull(repo.getSession(1l));
+ assertNull(repo.getSession(2l));
+ assertNull(repo.getSession(3l));
+ }
+
+ private void addSession(long sessionId, long createTime) {
+ repo.addSession(new SessionHandlerTest.MockSession(sessionId, FilesApplicationPackage.fromFile(testApp), createTime));
+ }
+
+ @Test
+ public void require_that_sessions_belong_to_a_tenant() {
+ // tenant is "default"
+ assertNotNull(repo.getSession(1l));
+ assertNotNull(repo.getSession(2l));
+ assertNotNull(repo.getSession(3l));
+ assertNull(repo.getSession(4l));
+
+ // tenant is "newTenant"
+ try {
+ setupSessions(TenantName.from("newTenant"), false);
+ } catch (Exception e) {
+ fail();
+ }
+ assertNull(repo.getSession(1l));
+
+ repo.addSession(new SessionHandlerTest.MockSession(1l, FilesApplicationPackage.fromFile(testApp)));
+ repo.addSession(new SessionHandlerTest.MockSession(2l, FilesApplicationPackage.fromFile(testApp)));
+ assertNotNull(repo.getSession(1l));
+ assertNotNull(repo.getSession(2l));
+ assertNull(repo.getSession(3l));
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionTest.java
new file mode 100644
index 00000000000..4f638d54d46
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionTest.java
@@ -0,0 +1,180 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.session;
+
+import com.google.common.io.Files;
+import com.yahoo.config.application.api.ApplicationFile;
+import com.yahoo.config.provision.*;
+import com.yahoo.path.Path;
+import com.yahoo.config.model.application.provider.*;
+import com.yahoo.slime.Slime;
+import com.yahoo.vespa.config.server.*;
+import com.yahoo.vespa.config.server.application.MemoryApplicationRepo;
+import com.yahoo.vespa.config.server.deploy.TenantFileSystemDirs;
+import com.yahoo.vespa.config.server.deploy.ZooKeeperClient;
+import com.yahoo.vespa.curator.Curator;
+import com.yahoo.vespa.curator.mock.MockCurator;
+import com.yahoo.vespa.config.server.zookeeper.ConfigCurator;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.util.*;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.*;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class LocalSessionTest {
+
+ private Path tenantPath = Path.createRoot();
+ private Curator curator;
+ private ConfigCurator configCurator;
+ private TenantFileSystemDirs tenantFileSystemDirs;
+ private SuperModelGenerationCounter superModelGenerationCounter;
+
+ @Before
+ public void setupTest() throws Exception {
+ curator = new MockCurator();
+ configCurator = ConfigCurator.create(curator);
+ superModelGenerationCounter = new SuperModelGenerationCounter(curator);
+ tenantFileSystemDirs = new TenantFileSystemDirs(Files.createTempDir(), TenantName.from("test_tenant"));
+ }
+
+ @Test
+ public void require_that_session_is_initialized() throws Exception {
+ LocalSession session = createSession(TenantName.defaultName(), 2);
+ assertThat(session.getSessionId(), is(2l));
+ session = createSession(TenantName.defaultName(), Long.MAX_VALUE);
+ assertThat(session.getSessionId(), is(Long.MAX_VALUE));
+ assertThat(session.getActiveSessionAtCreate(), is(0l));
+ }
+
+ @Test
+ public void require_that_session_status_is_updated() throws Exception {
+ LocalSession session = createSession(TenantName.defaultName(), 3);
+ assertThat(session.getStatus(), is(Session.Status.NEW));
+ doPrepare(session);
+ assertThat(session.getStatus(), is(Session.Status.PREPARE));
+ session.createActivateTransaction().commit();
+ assertThat(session.getStatus(), is(Session.Status.ACTIVATE));
+ }
+
+ @Test
+ public void require_that_marking_session_modified_changes_status_to_new() throws Exception {
+ LocalSession session = createSession(TenantName.defaultName(), 3);
+ doPrepare(session);
+ assertThat(session.getStatus(), is(Session.Status.PREPARE));
+ session.getApplicationFile(Path.createRoot(), LocalSession.Mode.READ);
+ assertThat(session.getStatus(), is(Session.Status.PREPARE));
+ session.getApplicationFile(Path.createRoot(), LocalSession.Mode.WRITE);
+ assertThat(session.getStatus(), is(Session.Status.NEW));
+ }
+
+ @Test
+ public void require_that_preparer_is_run() throws Exception {
+ SessionTest.MockSessionPreparer preparer = new SessionTest.MockSessionPreparer();
+ LocalSession session = createSession(TenantName.defaultName(), 3, preparer);
+ assertFalse(preparer.isPrepared);
+ doPrepare(session);
+ assertTrue(preparer.isPrepared);
+ assertThat(session.getStatus(), is(Session.Status.PREPARE));
+ }
+
+ @Test
+ public void require_that_session_status_can_be_deactivated() throws Exception {
+ SessionTest.MockSessionPreparer preparer = new SessionTest.MockSessionPreparer();
+ LocalSession session = createSession(TenantName.defaultName(), 3, preparer);
+ session.createDeactivateTransaction().commit();
+ assertThat(session.getStatus(), is(Session.Status.DEACTIVATE));
+ }
+
+ private File testApp = new File("src/test/apps/app");
+
+ @Test
+ public void require_that_application_file_can_be_fetched() throws Exception {
+ LocalSession session = createSession(TenantName.defaultName(), 3);
+ ApplicationFile f1 = session.getApplicationFile(Path.fromString("services.xml"), LocalSession.Mode.READ);
+ ApplicationFile f2 = session.getApplicationFile(Path.fromString("services2.xml"), LocalSession.Mode.READ);
+ assertTrue(f1.exists());
+ assertFalse(f2.exists());
+ }
+
+ @Test
+ public void require_that_session_can_be_deleted() throws Exception {
+ LocalSession session = createSession(TenantName.defaultName(), 3);
+ assertTrue(configCurator.exists("/3"));
+ assertTrue(new File(tenantFileSystemDirs.path(), "3").exists());
+ long gen = superModelGenerationCounter.get();
+ session.delete();
+ assertThat(superModelGenerationCounter.get(), is(gen + 1));
+ assertFalse(configCurator.exists("/3"));
+ assertFalse(new File(tenantFileSystemDirs.path(), "3").exists());
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void require_that_no_provision_info_throws_exception() throws Exception {
+ createSession(TenantName.defaultName(), 3).getProvisionInfo();
+ }
+
+ @Test
+ public void require_that_provision_info_can_be_read() throws Exception {
+ ProvisionInfo input = ProvisionInfo.withHosts(Collections.singleton(new HostSpec("myhost", Collections.<String>emptyList())));
+
+ LocalSession session = createSession(TenantName.defaultName(), 3, new SessionTest.MockSessionPreparer(), Optional.of(input));
+ ApplicationId origId = new ApplicationId.Builder()
+ .tenant("tenant")
+ .applicationName("foo").instanceName("quux").build();
+ doPrepare(session, new PrepareParams().applicationId(origId));
+ ProvisionInfo info = session.getProvisionInfo();
+ assertNotNull(info);
+ assertThat(info.getHosts().size(), is(1));
+ assertTrue(info.getHosts().contains(new HostSpec("myhost", Collections.emptyList())));
+ }
+
+ @Test
+ public void require_that_application_metadata_is_correct() throws Exception {
+ LocalSession session = createSession(TenantName.defaultName(), 3);
+ doPrepare(session, new PrepareParams());
+ assertThat(session.getMetaData().toString(), is("n/a, n/a, 0, 0, , 0"));
+ }
+
+ private LocalSession createSession(TenantName tenant, long sessionId) throws Exception {
+ SessionTest.MockSessionPreparer preparer = new SessionTest.MockSessionPreparer();
+ return createSession(tenant, sessionId, preparer);
+ }
+
+ private LocalSession createSession(TenantName tenant, long sessionId, SessionTest.MockSessionPreparer preparer) throws Exception {
+ return createSession(tenant, sessionId, preparer, Optional.<ProvisionInfo>empty());
+ }
+
+ private LocalSession createSession(TenantName tenant, long sessionId, SessionTest.MockSessionPreparer preparer, Optional<ProvisionInfo> provisionInfo) throws Exception {
+ Path appPath = Path.fromString("/" + sessionId);
+ SessionZooKeeperClient zkc = new MockSessionZKClient(curator, appPath, provisionInfo);
+ zkc.createWriteStatusTransaction(Session.Status.NEW).commit();
+ ZooKeeperClient zkClient = new ZooKeeperClient(configCurator, new BaseDeployLogger(), false, appPath);
+ if (provisionInfo.isPresent()) {
+ zkClient.feedProvisionInfos(Collections.singletonMap(Version.fromIntValues(0, 0, 0), provisionInfo.get()));
+ }
+ zkClient.feedZKFileRegistries(Collections.singletonMap(Version.fromIntValues(0, 0, 0), new MockFileRegistry()));
+ File sessionDir = new File(tenantFileSystemDirs.path(), String.valueOf(sessionId));
+ sessionDir.createNewFile();
+ return new LocalSession(tenant, sessionId, preparer, new SessionContext(FilesApplicationPackage.fromFile(testApp), zkc, sessionDir, new MemoryApplicationRepo(), new HostRegistry<>(), superModelGenerationCounter));
+ }
+
+ private void doPrepare(LocalSession session) {
+ doPrepare(session, new PrepareParams());
+ }
+
+ private void doPrepare(LocalSession session, PrepareParams params) {
+ session.prepare(getLogger(false), params, Optional.empty(), tenantPath);
+ }
+
+ DeployHandlerLogger getLogger(boolean verbose) {
+ return new DeployHandlerLogger(new Slime().get(), verbose,
+ new ApplicationId.Builder().tenant("testtenant").applicationName("testapp").build());
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/MockFileDistributionFactory.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/MockFileDistributionFactory.java
new file mode 100644
index 00000000000..0af74cc9312
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/MockFileDistributionFactory.java
@@ -0,0 +1,27 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.session;
+
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.vespa.config.server.filedistribution.FileDistributionProvider;
+import com.yahoo.vespa.config.server.filedistribution.MockFileDistributionProvider;
+import com.yahoo.vespa.curator.mock.MockCurator;
+
+import java.io.File;
+
+/**
+* @author lulf
+* @since 5.1
+*/
+public class MockFileDistributionFactory extends FileDistributionFactory {
+
+ public final MockFileDistributionProvider mockFileDistributionProvider = new MockFileDistributionProvider();
+
+ public MockFileDistributionFactory() {
+ super(new MockCurator(), "");
+ }
+
+ @Override
+ public FileDistributionProvider createProvider(File applicationFile, ApplicationId applicationId) {
+ return mockFileDistributionProvider;
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/MockSessionZKClient.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/MockSessionZKClient.java
new file mode 100644
index 00000000000..829c3f9008b
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/MockSessionZKClient.java
@@ -0,0 +1,67 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.session;
+
+import com.yahoo.config.application.api.ApplicationPackage;
+import com.yahoo.config.model.test.MockApplicationPackage;
+import com.yahoo.config.provision.ProvisionInfo;
+import com.yahoo.transaction.Transaction;
+import com.yahoo.path.Path;
+import com.yahoo.vespa.curator.Curator;
+import com.yahoo.vespa.curator.mock.MockCurator;
+
+import java.util.Optional;
+
+/**
+ * Overrides application package fetching, because this part is hard to do without feeding a full app.
+ *
+ * @author lulf
+ * @since 5.1
+ */
+public class MockSessionZKClient extends SessionZooKeeperClient {
+
+ private ApplicationPackage app = null;
+ private Optional<ProvisionInfo> info = null;
+ private Session.Status sessionStatus;
+
+ public MockSessionZKClient(Curator curator, Path rootPath) {
+ this(curator, rootPath, (ApplicationPackage)null);
+ }
+
+ public MockSessionZKClient(Curator curator, Path rootPath, Optional<ProvisionInfo> provisionInfo) {
+ this(curator, rootPath);
+ this.info = provisionInfo;
+ }
+
+ public MockSessionZKClient(Curator curator, Path rootPath, ApplicationPackage application) {
+ super(curator, rootPath);
+ this.app = application;
+ }
+
+ public MockSessionZKClient(ApplicationPackage app) {
+ super(new MockCurator(), Path.createRoot());
+ this.app = app;
+ }
+
+ @Override
+ public ApplicationPackage loadApplicationPackage() {
+ if (app != null) return app;
+ return new MockApplicationPackage.Builder().withEmptyServices().build();
+ }
+
+ @Override
+ ProvisionInfo getProvisionInfo() {
+ return info.orElseThrow(() -> new IllegalStateException("Trying to read provision info, but no provision info exists"));
+ }
+
+ @Override
+ public Transaction createWriteStatusTransaction(Session.Status status) {
+ return new DummyTransaction().add((DummyTransaction.RunnableOperation) () -> {
+ sessionStatus = status;
+ });
+ }
+
+ @Override
+ public Session.Status readStatus() {
+ return sessionStatus;
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/PrepareParamsTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/PrepareParamsTest.java
new file mode 100644
index 00000000000..9faba599e3a
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/PrepareParamsTest.java
@@ -0,0 +1,89 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.session;
+
+import com.yahoo.cloud.config.ConfigserverConfig;
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.Rotation;
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.config.provision.Version;
+import com.yahoo.container.jdisc.HttpRequest;
+
+import org.junit.Test;
+
+import java.util.Optional;
+import java.util.Set;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author musum
+ */
+public class PrepareParamsTest {
+
+ @Test
+ public void testCorrectParsing() {
+ PrepareParams prepareParams = createParams("http://foo:19071/application/v2/",
+ TenantName.defaultName());
+
+ assertThat(prepareParams.getApplicationId(), is(ApplicationId.defaultId()));
+ assertFalse(prepareParams.isDryRun());
+ assertFalse(prepareParams.ignoreValidationErrors());
+ assertThat(prepareParams.getVespaVersion(), is(Optional.<String>empty()));
+ assertTrue(prepareParams.getTimeoutBudget().hasTimeLeft());
+ assertThat(prepareParams.getRotations().size(), is(0));
+ }
+
+
+ static final String rotation = "rotation-042.vespa.a02.yahoodns.net";
+ static final String vespaVersion = "6.37.49";
+ static final String request = "http://foo:19071/application/v2/tenant/foo/application/bar?" +
+ PrepareParams.DRY_RUN_PARAM_NAME + "=true&" +
+ PrepareParams.IGNORE_VALIDATION_PARAM_NAME + "=false&" +
+ PrepareParams.APPLICATION_NAME_PARAM_NAME + "=baz&" +
+ PrepareParams.VESPA_VERSION_PARAM_NAME + "=" + vespaVersion + "&" +
+ PrepareParams.DOCKER_VESPA_IMAGE_VERSION_PARAM_NAME+ "=" + vespaVersion;
+
+ @Test
+ public void testCorrectParsingWithRotation() {
+ PrepareParams prepareParams = createParams(request + "&" +
+ PrepareParams.ROTATIONS_PARAM_NAME + "=" + rotation,
+ TenantName.from("foo"));
+
+ assertThat(prepareParams.getApplicationId().serializedForm(), is("foo:baz:default"));
+ assertTrue(prepareParams.isDryRun());
+ assertFalse(prepareParams.ignoreValidationErrors());
+ final Version expectedVersion = Version.fromString(vespaVersion);
+ assertThat(prepareParams.getVespaVersion().get(), is(expectedVersion));
+ assertTrue(prepareParams.getTimeoutBudget().hasTimeLeft());
+ final Set<Rotation> rotations = prepareParams.getRotations();
+ assertThat(rotations.size(), is(1));
+ assertThat(rotations, contains(equalTo(new Rotation(rotation))));
+ assertThat(prepareParams.getDockerVespaImageVersion().get(), is(expectedVersion));
+ }
+
+ @Test
+ public void testCorrectParsingWithSeveralRotations() {
+ final String rotationTwo = "rotation-043.vespa.a02.yahoodns.net";
+ final String twoRotations = rotation + "," + rotationTwo;
+ PrepareParams prepareParams = createParams(request + "&" +
+ PrepareParams.ROTATIONS_PARAM_NAME + "=" + twoRotations,
+ TenantName.from("foo"));
+ final Set<Rotation> rotations = prepareParams.getRotations();
+ assertThat(rotations, containsInAnyOrder(new Rotation(rotation), new Rotation(rotationTwo)));
+ }
+
+ // Create PrepareParams from a request (based on uri and tenant name)
+ private static PrepareParams createParams(String uri, TenantName tenantName) {
+ return PrepareParams.fromHttpRequest(
+ HttpRequest.createTestRequest(uri,
+ com.yahoo.jdisc.http.HttpRequest.Method.PUT),
+ tenantName,
+ new ConfigserverConfig(new ConfigserverConfig.Builder()));
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/RemoteSessionRepoTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/RemoteSessionRepoTest.java
new file mode 100644
index 00000000000..6d8f93f4f8f
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/RemoteSessionRepoTest.java
@@ -0,0 +1,161 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.session;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.*;
+
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.path.Path;
+import com.yahoo.text.Utf8;
+import com.yahoo.transaction.Transaction;
+import com.yahoo.vespa.config.server.*;
+
+import com.yahoo.vespa.config.server.application.ApplicationRepo;
+import com.yahoo.vespa.curator.Curator;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.yahoo.vespa.config.server.zookeeper.ConfigCurator;
+
+import java.time.Duration;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.function.LongPredicate;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class RemoteSessionRepoTest extends TestWithCurator {
+
+ private RemoteSessionRepo remoteSessionRepo;
+
+ @Before
+ public void setupFacade() throws Exception {
+ createSession(2l, false);
+ createSession(3l, false);
+ curator.create(Path.fromString("/applications"));
+ curator.create(Path.fromString("/sessions"));
+ Tenant tenant = TenantBuilder.create(new TestComponentRegistry(curator), TenantName.defaultName(), Path.createRoot()).build();
+ this.remoteSessionRepo = tenant.getRemoteSessionRepo();
+ }
+
+ private void createSession(long sessionId, boolean wait) {
+ createSession("", sessionId, wait);
+ }
+
+
+ private void createSession(String root, long sessionId, boolean wait) {
+ Path rootPath = Path.fromString(root).append("sessions");
+ curator.create(rootPath);
+ SessionZooKeeperClient zkc = new SessionZooKeeperClient(curator, rootPath.append(String.valueOf(sessionId)));
+ zkc.createNewSession(System.currentTimeMillis(), TimeUnit.MILLISECONDS);
+ if (wait) {
+ Curator.CompletionWaiter waiter = zkc.getUploadWaiter();
+ waiter.awaitCompletion(Duration.ofSeconds(120));
+ }
+ }
+
+ @Test
+ public void testInitialize() {
+ assertSessionExists(2l);
+ assertSessionExists(3l);
+ }
+
+ @Test
+ public void testCreateSession() throws Exception {
+ createSession(0l, true);
+ assertSessionExists(0l);
+ }
+
+ @Test
+ public void testSessionStateChange() throws Exception {
+ Path session = Path.fromString("/sessions/0");
+ createSession(0l, true);
+ assertSessionStatus(0l, Session.Status.NEW);
+ assertStatusChange(0l, Session.Status.PREPARE);
+ assertStatusChange(0l, Session.Status.ACTIVATE);
+
+ curator.delete(session);
+ assertSessionRemoved(0l);
+ assertNull(remoteSessionRepo.getSession(0l));
+ }
+
+ @Test
+ public void testBadApplicationRepoOnActivate() throws Exception {
+ ApplicationRepo applicationRepo = new FailingApplicationRepo();
+ curator.framework().create().forPath("/mytenant");
+ Tenant tenant = TenantBuilder.create(new TestComponentRegistry(curator), TenantName.from("mytenant"), Path.fromString("mytenant"))
+ .withApplicationRepo(applicationRepo)
+ .build();
+ remoteSessionRepo = tenant.getRemoteSessionRepo();
+ createSession("/mytenant", 2l, true);
+ assertThat(remoteSessionRepo.listSessions().size(), is(1));
+ }
+
+ private void assertStatusChange(long sessionId, Session.Status status) throws Exception {
+ Path statePath = Path.fromString("/sessions/" + sessionId).append(ConfigCurator.SESSIONSTATE_ZK_SUBPATH);
+ curator.create(statePath);
+ curatorFramework.setData().forPath(statePath.getAbsolute(), Utf8.toBytes(status.toString()));
+ System.out.println("Setting status " + status + " for " + sessionId);
+ assertSessionStatus(0l, status);
+ }
+
+ private void assertSessionRemoved(long sessionId) {
+ waitFor(p -> remoteSessionRepo.getSession(sessionId) == null, sessionId);
+ assertNull(remoteSessionRepo.getSession(sessionId));
+ }
+
+ private void assertSessionExists(long sessionId) {
+ assertSessionStatus(sessionId, Session.Status.NEW);
+ }
+
+ private void assertSessionStatus(long sessionId, Session.Status status) {
+ waitFor(p -> remoteSessionRepo.getSession(sessionId) != null &&
+ remoteSessionRepo.getSession(sessionId).getStatus() == status, sessionId);
+ assertNotNull(remoteSessionRepo.getSession(sessionId));
+ assertThat(remoteSessionRepo.getSession(sessionId).getStatus(), is(status));
+ }
+
+ private void waitFor(LongPredicate predicate, long sessionId) {
+ long endTime = System.currentTimeMillis() + 60_000;
+ boolean ok;
+ do {
+ ok = predicate.test(sessionId);
+ try {
+ Thread.sleep(10);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ } while (System.currentTimeMillis() < endTime && !ok);
+ }
+
+ private class FailingApplicationRepo implements ApplicationRepo {
+ @Override
+ public List<ApplicationId> listApplications() {
+ return Collections.singletonList(ApplicationId.defaultId());
+ }
+
+ @Override
+ public Transaction createPutApplicationTransaction(ApplicationId applicationId, long sessionId) {
+ return null;
+ }
+
+ @Override
+ public long getSessionIdForApplication(ApplicationId applicationId) {
+ throw new IllegalArgumentException("Bad id " + applicationId);
+ }
+
+ @Override
+ public void deleteApplication(ApplicationId applicationId) {
+
+ }
+
+ @Override
+ public void close() {
+
+ }
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/RemoteSessionTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/RemoteSessionTest.java
new file mode 100644
index 00000000000..2fc65eb77a8
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/RemoteSessionTest.java
@@ -0,0 +1,284 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.session;
+
+import com.google.common.io.Files;
+import com.yahoo.cloud.config.ConfigserverConfig;
+import com.yahoo.config.application.api.ApplicationPackage;
+import com.yahoo.config.model.NullConfigModelRegistry;
+import com.yahoo.config.model.api.*;
+import com.yahoo.config.model.deploy.DeployState;
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.config.model.test.MockApplicationPackage;
+import com.yahoo.config.provision.Version;
+import com.yahoo.path.Path;
+import com.yahoo.vespa.config.server.ApplicationSet;
+import com.yahoo.vespa.config.server.modelfactory.ModelFactoryRegistry;
+import com.yahoo.vespa.config.server.PathProvider;
+import com.yahoo.vespa.config.server.TestComponentRegistry;
+import com.yahoo.vespa.config.server.application.PermanentApplicationPackage;
+import com.yahoo.vespa.curator.mock.MockCurator;
+import com.yahoo.vespa.curator.Curator;
+import com.yahoo.vespa.model.VespaModel;
+import com.yahoo.vespa.model.VespaModelFactory;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class RemoteSessionTest {
+
+ private Curator curator;
+ private PathProvider pathProvider;
+
+ @Before
+ public void setupTest() throws Exception {
+ curator = new MockCurator();
+ pathProvider = new PathProvider(Path.createRoot());
+ }
+
+ @Test
+ public void require_that_session_is_initialized() {
+ Session session = createSession(2);
+ assertThat(session.getSessionId(), is(2l));
+ session = createSession(Long.MAX_VALUE);
+ assertThat(session.getSessionId(), is(Long.MAX_VALUE));
+ }
+
+ @Test
+ public void require_that_applications_are_loaded() throws IOException, SAXException {
+ RemoteSession session = createSession(3, Arrays.asList(new MockModelFactory(), new VespaModelFactory(new NullConfigModelRegistry())));
+ session.loadPrepared();
+ ApplicationSet applicationSet = session.ensureApplicationLoaded();
+ assertNotNull(applicationSet);
+ assertThat(applicationSet.getApplicationGeneration(), is(3l));
+ assertThat(applicationSet.getForVersionOrLatest(Optional.empty()).getName(), is("foo"));
+ assertNotNull(applicationSet.getForVersionOrLatest(Optional.empty()).getModel());
+ session.deactivate();
+
+ applicationSet = session.ensureApplicationLoaded();
+ assertNotNull(applicationSet);
+ assertThat(applicationSet.getApplicationGeneration(), is(3l));
+ assertThat(applicationSet.getForVersionOrLatest(Optional.empty()).getName(), is("foo"));
+ assertNotNull(applicationSet.getForVersionOrLatest(Optional.empty()).getModel());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void require_that_new_invalid_application_throws_exception() throws IOException, SAXException {
+ MockModelFactory failingFactory = new MockModelFactory();
+ failingFactory.vespaVersion = Version.fromIntValues(1, 2, 0);
+ failingFactory.throwOnLoad = true;
+
+ MockModelFactory okFactory = new MockModelFactory();
+ okFactory.vespaVersion = Version.fromIntValues(1, 1, 0);
+ okFactory.throwOnLoad = false;
+
+ RemoteSession session = createSession(3, Arrays.asList(okFactory, failingFactory));
+ session.loadPrepared();
+ }
+
+ @Test
+ public void require_that_application_incompatible_with_latestmajor_is_loaded_on_earlier_major() throws IOException, SAXException {
+ MockModelFactory okFactory1 = new MockModelFactory();
+ okFactory1.vespaVersion = Version.fromIntValues(1, 1, 0);
+ okFactory1.throwOnLoad = false;
+
+ MockModelFactory okFactory2 = new MockModelFactory();
+ okFactory2.vespaVersion = Version.fromIntValues(1, 2, 0);
+ okFactory2.throwOnLoad = false;
+
+ MockModelFactory failingFactory = new MockModelFactory();
+ failingFactory.vespaVersion = Version.fromIntValues(2, 0, 0);
+ failingFactory.throwOnLoad = true;
+
+ RemoteSession session = createSession(3, Arrays.asList(okFactory1, failingFactory, okFactory2));
+ session.loadPrepared();
+ }
+
+ @Test
+ public void require_that_old_invalid_application_does_not_throw_exception_if_skipped() throws IOException, SAXException {
+ MockModelFactory failingFactory = new MockModelFactory();
+ failingFactory.vespaVersion = Version.fromIntValues(1, 1, 0);
+ failingFactory.throwOnLoad = true;
+
+ MockModelFactory okFactory =
+ new MockModelFactory("<validation-overrides><allow until='2000-01-30'>skip-old-config-models</allow></validation-overrides>");
+ okFactory.vespaVersion = Version.fromIntValues(1, 2, 0);
+ okFactory.throwOnLoad = false;
+
+ RemoteSession session = createSession(3, Arrays.asList(okFactory, failingFactory));
+ session.loadPrepared();
+ }
+
+ @Test
+ public void require_that_old_invalid_application_does_not_throw_exception_if_skipped_also_across_major_versions() throws IOException, SAXException {
+ MockModelFactory failingFactory = new MockModelFactory();
+ failingFactory.vespaVersion = Version.fromIntValues(1, 0, 0);
+ failingFactory.throwOnLoad = true;
+
+ MockModelFactory okFactory =
+ new MockModelFactory("<validation-overrides><allow until='2000-01-30'>skip-old-config-models</allow></validation-overrides>");
+ okFactory.vespaVersion = Version.fromIntValues(2, 0, 0);
+ okFactory.throwOnLoad = false;
+
+ RemoteSession session = createSession(3, Arrays.asList(okFactory, failingFactory));
+ session.loadPrepared();
+ }
+
+ @Test
+ public void require_that_old_invalid_application_does_not_throw_exception_if_skipped_also_when_new_major_is_incompatible() throws IOException, SAXException {
+ MockModelFactory failingFactory = new MockModelFactory();
+ failingFactory.vespaVersion = Version.fromIntValues(1, 0, 0);
+ failingFactory.throwOnLoad = true;
+
+ MockModelFactory okFactory =
+ new MockModelFactory("<validation-overrides><allow until='2000-01-30'>skip-old-config-models</allow></validation-overrides>");
+ okFactory.vespaVersion = Version.fromIntValues(1, 1, 0);
+ okFactory.throwOnLoad = false;
+
+ MockModelFactory tooNewFactory =
+ new MockModelFactory("<validation-overrides><allow until='2000-01-30'>skip-old-config-models</allow></validation-overrides>");
+ tooNewFactory.vespaVersion = Version.fromIntValues(2, 0, 0);
+ tooNewFactory.throwOnLoad = true;
+
+ RemoteSession session = createSession(3, Arrays.asList(tooNewFactory, okFactory, failingFactory));
+ session.loadPrepared();
+ }
+
+ @Test
+ public void require_that_an_application_package_can_limit_to_one_major_version() throws IOException, SAXException {
+ ApplicationPackage application =
+ new MockApplicationPackage.Builder().withServices("<services major-version='2' version=\"1.0\"></services>").build();
+
+ MockModelFactory failingFactory = new MockModelFactory();
+ failingFactory.vespaVersion = Version.fromIntValues(3, 0, 0);
+ failingFactory.throwOnLoad = true;
+
+ MockModelFactory okFactory = new MockModelFactory();
+ okFactory.vespaVersion = Version.fromIntValues(2, 0, 0);
+ okFactory.throwOnLoad = false;
+
+ SessionZooKeeperClient zkc = new MockSessionZKClient(curator, pathProvider.getSessionDir(3), application);
+ RemoteSession session = createSession(3, zkc, Arrays.asList(okFactory, failingFactory));
+ session.loadPrepared();
+
+ // Does not cause an exception because model version 3 is skipped
+ }
+
+ @Test
+ public void require_that_session_status_is_updated() throws IOException, SAXException {
+ SessionZooKeeperClient zkc = new MockSessionZKClient(curator, pathProvider.getSessionDir(3));
+ RemoteSession session = createSession(3, zkc);
+ assertThat(session.getStatus(), is(Session.Status.NEW));
+ zkc.writeStatus(Session.Status.PREPARE);
+ assertThat(session.getStatus(), is(Session.Status.PREPARE));
+ }
+
+ @Test
+ public void require_that_permanent_app_is_used() {
+ Optional<PermanentApplicationPackage> permanentApp = Optional.of(new PermanentApplicationPackage(
+ new ConfigserverConfig(new ConfigserverConfig.Builder().applicationDirectory(Files.createTempDir().getAbsolutePath()))));
+ MockModelFactory mockModelFactory = new MockModelFactory();
+ try {
+ int sessionId = 3;
+ SessionZooKeeperClient zkc = new MockSessionZKClient(curator, pathProvider.getSessionDir(sessionId));
+ createSession(sessionId, zkc, Collections.singletonList(mockModelFactory), permanentApp).ensureApplicationLoaded();
+ } catch (Exception e) {
+ e.printStackTrace();
+ // ignore, we're not interested in deploy errors as long as the below state is OK.
+ }
+ assertNotNull(mockModelFactory.modelContext);
+ assertTrue(mockModelFactory.modelContext.permanentApplicationPackage().isPresent());
+ }
+
+ private RemoteSession createSession(long sessionId) {
+ return createSession(sessionId, Collections.singletonList(new VespaModelFactory(new NullConfigModelRegistry())));
+ }
+ private RemoteSession createSession(long sessionId, SessionZooKeeperClient zkc) {
+ return createSession(sessionId, zkc, Collections.singletonList(new VespaModelFactory(new NullConfigModelRegistry())));
+ }
+ private RemoteSession createSession(long sessionId, List<ModelFactory> modelFactories) {
+ SessionZooKeeperClient zkc = new MockSessionZKClient(curator, pathProvider.getSessionDir(sessionId));
+ return createSession(sessionId, zkc, modelFactories);
+ }
+
+ private RemoteSession createSession(long sessionId, SessionZooKeeperClient zkc, List<ModelFactory> modelFactories) {
+ return createSession(sessionId, zkc, modelFactories, Optional.empty());
+ }
+
+ private RemoteSession createSession(long sessionId, SessionZooKeeperClient zkc, List<ModelFactory> modelFactories, Optional<PermanentApplicationPackage> permanentApplicationPackage) {
+ zkc.writeStatus(Session.Status.NEW);
+ zkc.writeApplicationId(new ApplicationId.Builder().applicationName("foo").instanceName("bim").build());
+ return new RemoteSession(TenantName.from("default"), sessionId, new TestComponentRegistry(curator, new ModelFactoryRegistry(modelFactories), permanentApplicationPackage), zkc);
+ }
+
+ private class MockModelFactory implements ModelFactory {
+
+ public boolean throwOnLoad = false;
+ public ModelContext modelContext;
+ public Version vespaVersion = Version.fromIntValues(1, 2, 3);
+
+ /** The validation overrides of this, or null if none */
+ private final String validationOverrides;
+
+ public MockModelFactory() { this(null); }
+
+ public MockModelFactory(String validationOverrides) {
+ this.validationOverrides = validationOverrides;
+ }
+
+ @Override
+ public Version getVersion() {
+ return vespaVersion;
+ }
+
+ @Override
+ public Model createModel(ModelContext modelContext) {
+ if (throwOnLoad) {
+ throw new IllegalArgumentException("Foo");
+ }
+ this.modelContext = modelContext;
+ return loadModel();
+ }
+
+ public Model loadModel() {
+ try {
+ Instant now = LocalDate.parse("2000-01-01", DateTimeFormatter.ISO_DATE).atStartOfDay().atZone(ZoneOffset.UTC).toInstant();
+ ApplicationPackage application = new MockApplicationPackage.Builder().withEmptyHosts().withEmptyServices().withValidationOverrides(validationOverrides).build();
+ DeployState deployState = new DeployState.Builder().applicationPackage(application).now(now).build();
+ return new VespaModel(deployState);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public ModelCreateResult createAndValidateModel(ModelContext modelContext, boolean ignoreValidationErrors) {
+ if (throwOnLoad) {
+ throw new IllegalArgumentException("Foo");
+ }
+ this.modelContext = modelContext;
+ return new ModelCreateResult(loadModel(), new ArrayList<>());
+ }
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionFactoryTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionFactoryTest.java
new file mode 100644
index 00000000000..83ed65f03e4
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionFactoryTest.java
@@ -0,0 +1,79 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.session;
+
+import com.google.common.io.Files;
+import com.yahoo.config.application.api.ApplicationFile;
+import com.yahoo.config.model.application.provider.BaseDeployLogger;
+import com.yahoo.io.IOUtils;
+import com.yahoo.path.Path;
+import com.yahoo.vespa.config.server.*;
+import com.yahoo.vespa.config.server.http.SessionCreate;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class SessionFactoryTest extends TestWithTenant {
+ private SessionFactory factory;
+
+ @Before
+ public void setup_test() throws Exception {
+ factory = tenant.getSessionFactory();
+ }
+
+ @Test
+ public void require_that_session_can_be_created() throws IOException {
+ LocalSession session = getLocalSession();
+ assertNotNull(session);
+ assertThat(session.getSessionId(), is(2l));
+ assertTrue(session.getCreateTime() > 0);
+ }
+
+ @Test
+ public void require_that_application_name_is_set_in_application_package() throws IOException, JSONException {
+ LocalSession session = getLocalSession("book");
+ assertNotNull(session);
+ ApplicationFile meta = session.getApplicationFile(Path.createRoot().append(".applicationMetaData"), LocalSession.Mode.READ);
+ assertTrue(meta.exists());
+ JSONObject json = new JSONObject(IOUtils.readAll(meta.createReader()));
+ assertThat(json.getJSONObject("application").getString("name"), is("book"));
+ }
+
+ @Test
+ public void require_that_session_can_be_created_from_existing() throws IOException {
+ LocalSession session = getLocalSession();
+ assertNotNull(session);
+ assertThat(session.getSessionId(), is(2l));
+ LocalSession session2 = factory.createSessionFromExisting(session, new BaseDeployLogger(), TimeoutBudgetTest.day());
+ assertNotNull(session2);
+ assertThat(session2.getSessionId(), is(3l));
+ }
+
+ @Test(expected = RuntimeException.class)
+ public void require_that_invalid_app_dir_is_handled() throws IOException {
+ factory.createSession(new File("doesnotpointtoavaliddir"), "music", new BaseDeployLogger(), TimeoutBudgetTest.day());
+ }
+
+ private LocalSession getLocalSession() throws IOException {
+ return getLocalSession("music");
+ }
+
+ private LocalSession getLocalSession(String appName) throws IOException {
+ CompressedApplicationInputStream app = CompressedApplicationInputStream.createFromCompressedStream(new FileInputStream(CompressedApplicationInputStreamTest.createTarFile()), SessionCreate.APPLICATION_X_GZIP);
+ return factory.createSession(app.decompress(Files.createTempDir()), appName, new BaseDeployLogger(), TimeoutBudgetTest.day());
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionPreparerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionPreparerTest.java
new file mode 100644
index 00000000000..308e6aa29d2
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionPreparerTest.java
@@ -0,0 +1,286 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.session;
+
+import com.google.common.collect.ImmutableSet;
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.config.model.api.ConfigChangeAction;
+import com.yahoo.config.model.api.ModelContext;
+import com.yahoo.config.model.api.ModelCreateResult;
+import com.yahoo.config.model.api.ServiceInfo;
+import com.yahoo.config.model.application.provider.*;
+import com.yahoo.config.provision.ApplicationName;
+import com.yahoo.config.provision.InstanceName;
+import com.yahoo.config.provision.Rotation;
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.config.provision.Version;
+import com.yahoo.io.IOUtils;
+import com.yahoo.log.LogLevel;
+import com.yahoo.path.Path;
+import com.yahoo.slime.Slime;
+import com.yahoo.vespa.config.server.*;
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.vespa.config.server.application.MemoryApplicationRepo;
+import com.yahoo.vespa.config.server.application.PermanentApplicationPackage;
+import com.yahoo.vespa.config.server.configchange.MockRestartAction;
+import com.yahoo.vespa.config.server.configchange.RestartActions;
+import com.yahoo.vespa.config.server.http.InvalidApplicationException;
+import com.yahoo.vespa.config.server.model.TestModelFactory;
+import com.yahoo.vespa.config.server.modelfactory.ModelFactoryRegistry;
+import com.yahoo.vespa.config.server.provision.HostProvisionerProvider;
+import com.yahoo.vespa.config.server.zookeeper.ConfigCurator;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.xml.sax.SAXException;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.*;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.Matchers.contains;
+import static org.junit.Assert.*;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class SessionPreparerTest extends TestWithCurator {
+
+ private static final Path appPath = Path.createRoot().append("testapp");
+ private static final File testApp = new File("src/test/apps/app");
+ private static final File invalidTestApp = new File("src/test/apps/illegalApp");
+
+ private SessionPreparer preparer;
+ private TestComponentRegistry componentRegistry;
+ private MockFileDistributionFactory fileDistributionFactory;
+ private Path tenantPath = appPath;
+
+ @Rule
+ public TemporaryFolder folder = new TemporaryFolder();
+
+ @Before
+ public void setUp() throws Exception {
+ componentRegistry = new TestComponentRegistry(curator);
+ fileDistributionFactory = (MockFileDistributionFactory)componentRegistry.getFileDistributionFactory();
+ preparer = createPreparer();
+ }
+
+ private SessionPreparer createPreparer() {
+ return createPreparer(HostProvisionerProvider.empty());
+ }
+
+ private SessionPreparer createPreparer(HostProvisionerProvider hostProvisionerProvider) {
+ ModelFactoryRegistry modelFactoryRegistry = new ModelFactoryRegistry(Arrays.asList(
+ new TestModelFactory(Version.fromIntValues(1, 2, 3)),
+ new TestModelFactory(Version.fromIntValues(3, 2, 1))));
+ return createPreparer(modelFactoryRegistry, hostProvisionerProvider);
+ }
+
+ private SessionPreparer createPreparer(ModelFactoryRegistry modelFactoryRegistry,
+ HostProvisionerProvider hostProvisionerProvider) {
+ return new SessionPreparer(
+ modelFactoryRegistry,
+ componentRegistry.getFileDistributionFactory(),
+ hostProvisionerProvider,
+ new PermanentApplicationPackage(componentRegistry.getConfigserverConfig()),
+ componentRegistry.getConfigserverConfig(),
+ componentRegistry.getConfigDefinitionRepo(),
+ curator,
+ componentRegistry.getZone());
+ }
+
+ @Test(expected = InvalidApplicationException.class)
+ public void require_that_application_validation_exception_is_not_caught() throws IOException, SAXException {
+ FilesApplicationPackage app = getApplicationPackage(invalidTestApp);
+ preparer.prepare(getContext(app), getLogger(), new PrepareParams(), Optional.empty(), tenantPath);
+ }
+
+ @Test
+ public void require_that_application_validation_exception_is_ignored_if_forced() throws IOException, SAXException {
+ FilesApplicationPackage app = getApplicationPackage(invalidTestApp);
+ preparer.prepare(getContext(app), getLogger(), new PrepareParams().ignoreValidationErrors(true).timeoutBudget(TimeoutBudgetTest.day()), Optional.empty(), tenantPath);
+ }
+
+ @Test
+ public void require_that_zookeeper_is_not_written_to_if_dryrun() throws IOException {
+ preparer.prepare(getContext(getApplicationPackage(testApp)), getLogger(), new PrepareParams().dryRun(true).timeoutBudget(TimeoutBudgetTest.day()), Optional.empty(), tenantPath);
+ assertFalse(configCurator.exists(appPath.append(ConfigCurator.USERAPP_ZK_SUBPATH).append("services.xml").getAbsolute()));
+ }
+
+ @Test
+ public void require_that_filedistribution_is_ignored_on_dryrun() throws IOException {
+ preparer.prepare(getContext(getApplicationPackage(testApp)), getLogger(), new PrepareParams().dryRun(true).timeoutBudget(TimeoutBudgetTest.day()), Optional.empty(), tenantPath);
+ assertThat(fileDistributionFactory.mockFileDistributionProvider.getMockFileDBHandler().sendDeployedFilesCalled, is(0));
+ assertThat(fileDistributionFactory.mockFileDistributionProvider.getMockFileDBHandler().limitSendingOfDeployedFilesToCalled, is(0));
+ assertThat(fileDistributionFactory.mockFileDistributionProvider.getMockFileDBHandler().reloadDeployFileDistributorCalled, is(0));
+ }
+
+ @Test
+ public void require_that_application_is_prepared() throws Exception {
+ preparer.prepare(getContext(getApplicationPackage(testApp)), getLogger(), new PrepareParams(), Optional.empty(), tenantPath);
+ assertThat(fileDistributionFactory.mockFileDistributionProvider.getMockFileDBHandler().sendDeployedFilesCalled, is(2));
+ assertThat(fileDistributionFactory.mockFileDistributionProvider.getMockFileDBHandler().limitSendingOfDeployedFilesToCalled, is(2));
+ // Should be called only once no matter how many model versions are built
+ assertThat(fileDistributionFactory.mockFileDistributionProvider.getMockFileDBHandler().reloadDeployFileDistributorCalled, is(1));
+ assertTrue(configCurator.exists(appPath.append(ConfigCurator.USERAPP_ZK_SUBPATH).append("services.xml").getAbsolute()));
+ }
+
+ @Test
+ public void require_that_prepare_succeeds_if_newer_version_fails() throws IOException {
+ ModelFactoryRegistry modelFactoryRegistry = new ModelFactoryRegistry(Arrays.asList(
+ new TestModelFactory(Version.fromIntValues(1, 2, 3)),
+ new FailingModelFactory(Version.fromIntValues(3, 2, 1), new IllegalArgumentException("BOOHOO"))));
+ preparer = createPreparer(modelFactoryRegistry, HostProvisionerProvider.empty());
+ preparer.prepare(getContext(getApplicationPackage(testApp)), getLogger(), new PrepareParams(), Optional.empty(), tenantPath);
+ }
+
+ @Test(expected = InvalidApplicationException.class)
+ public void require_that_prepare_fails_if_older_version_fails() throws IOException {
+ ModelFactoryRegistry modelFactoryRegistry = new ModelFactoryRegistry(Arrays.asList(
+ new TestModelFactory(Version.fromIntValues(3, 2, 3)),
+ new FailingModelFactory(Version.fromIntValues(1, 2, 1), new IllegalArgumentException("BOOHOO"))));
+ preparer = createPreparer(modelFactoryRegistry, HostProvisionerProvider.empty());
+ preparer.prepare(getContext(getApplicationPackage(testApp)), getLogger(), new PrepareParams(), Optional.empty(), tenantPath);
+ }
+
+ @Test(expected = InvalidApplicationException.class)
+ public void require_exception_for_overlapping_host() throws IOException {
+ SessionContext ctx = getContext(getApplicationPackage(testApp));
+ ((HostRegistry<ApplicationId>)ctx.getHostValidator()).update(applicationId("foo"), Collections.singletonList("mytesthost"));
+ preparer.prepare(ctx, new BaseDeployLogger(), new PrepareParams(), Optional.empty(), tenantPath);
+ }
+
+ @Test
+ public void require_no_warning_for_overlapping_host_for_same_appid() throws IOException {
+ SessionContext ctx = getContext(getApplicationPackage(testApp));
+ ((HostRegistry<ApplicationId>)ctx.getHostValidator()).update(applicationId("default"), Collections.singletonList("mytesthost"));
+ final StringBuilder logged = new StringBuilder();
+ DeployLogger logger = (level, message) -> {
+ System.out.println(level + ": "+message);
+ if (level.equals(LogLevel.WARNING) && message.contains("The host mytesthost is already in use")) logged.append("ok");
+ };
+ preparer.prepare(ctx, logger, new PrepareParams(), Optional.empty(), tenantPath);
+ assertEquals(logged.toString(), "");
+ }
+
+ @Test
+ public void require_that_application_id_is_written_in_prepare() throws IOException {
+ TenantName tenant = TenantName.from("tenant");
+ ApplicationId origId = new ApplicationId.Builder()
+ .tenant(tenant)
+ .applicationName("foo").instanceName("quux").build();
+ PrepareParams params = new PrepareParams().applicationId(origId);
+ preparer.prepare(getContext(getApplicationPackage(testApp)), getLogger(), params, Optional.empty(), tenantPath);
+ SessionZooKeeperClient zkc = new SessionZooKeeperClient(curator, appPath);
+ assertTrue(configCurator.exists(appPath.append(SessionZooKeeperClient.APPLICATION_ID_PATH).getAbsolute()));
+ assertThat(zkc.readApplicationId(tenant), is(origId));
+ }
+
+ @Test
+ public void require_that_config_change_actions_are_collected_from_all_models() throws IOException {
+ ServiceInfo service = new ServiceInfo("serviceName", "serviceType", null, new HashMap<>(), "configId", "hostName");
+ ModelFactoryRegistry modelFactoryRegistry = new ModelFactoryRegistry(Arrays.asList(
+ new ConfigChangeActionsModelFactory(Version.fromIntValues(1, 2, 3),
+ new MockRestartAction("change", Arrays.asList(service))),
+ new ConfigChangeActionsModelFactory(Version.fromIntValues(1, 2, 4),
+ new MockRestartAction("other change", Arrays.asList(service)))));
+ preparer = createPreparer(modelFactoryRegistry, HostProvisionerProvider.empty());
+ List<RestartActions.Entry> actions =
+ preparer.prepare(getContext(getApplicationPackage(testApp)), getLogger(), new PrepareParams(), Optional.empty(), tenantPath).
+ getRestartActions().getEntries();
+ assertThat(actions.size(), is(1));
+ assertThat(actions.get(0).getMessages(), equalTo(ImmutableSet.of("change", "other change")));
+ }
+
+ private Set<Rotation> readRotationsFromZK(ApplicationId applicationId) {
+ return new RotationsCache(curator, tenantPath).readRotationsFromZooKeeper(applicationId);
+ }
+
+ @Test
+ public void require_that_rotations_are_written_in_prepare() throws IOException {
+ final String rotations = "mediasearch.msbe.global.vespa.yahooapis.com";
+ final ApplicationId applicationId = applicationId("test");
+ PrepareParams params = new PrepareParams().applicationId(applicationId).rotations(rotations);
+ File app = new File("src/test/resources/deploy/app");
+ preparer.prepare(getContext(getApplicationPackage(app)), getLogger(), params, Optional.empty(), tenantPath);
+ assertThat(readRotationsFromZK(applicationId), contains(new Rotation(rotations)));
+ }
+
+ @Test
+ public void require_that_rotations_are_read_from_zookeeper_and_used() throws IOException {
+ final Version vespaVersion = Version.fromIntValues(1, 2, 3);
+ final TestModelFactory modelFactory = new TestModelFactory(vespaVersion);
+ preparer = createPreparer(new ModelFactoryRegistry(Arrays.asList(modelFactory)),
+ HostProvisionerProvider.empty());
+
+ final String rotations = "foo.msbe.global.vespa.yahooapis.com";
+ final ApplicationId applicationId = applicationId("test");
+ new RotationsCache(curator, tenantPath).writeRotationsToZooKeeper(applicationId, Collections.singleton(new Rotation(rotations)));
+ final PrepareParams params = new PrepareParams().applicationId(applicationId);
+ final File app = new File("src/test/resources/deploy/app");
+ preparer.prepare(getContext(getApplicationPackage(app)), getLogger(), params, Optional.empty(), tenantPath);
+
+ // check that the rotation from zookeeper were used
+ final ModelContext modelContext = modelFactory.getModelContext();
+ final Set<Rotation> rotationSet = modelContext.properties().rotations();
+ assertThat(rotationSet, contains(new Rotation(rotations)));
+
+ // Check that the persisted value is still the same
+ assertThat(readRotationsFromZK(applicationId), contains(new Rotation(rotations)));
+ }
+
+ private SessionContext getContext(FilesApplicationPackage app) throws IOException {
+ return new SessionContext(app, new SessionZooKeeperClient(curator, appPath), app.getAppDir(), new MemoryApplicationRepo(), new HostRegistry<>(), new SuperModelGenerationCounter(curator));
+ }
+
+ private FilesApplicationPackage getApplicationPackage(File testFile) throws IOException {
+ File appDir = folder.newFolder();
+ IOUtils.copyDirectory(testFile, appDir);
+ return FilesApplicationPackage.fromFile(appDir);
+ }
+
+ DeployHandlerLogger getLogger() {
+ return getLogger(false);
+ }
+
+ DeployHandlerLogger getLogger(boolean verbose) {
+ return new DeployHandlerLogger(new Slime().get(), verbose,
+ new ApplicationId.Builder().tenant("testtenant").applicationName("testapp").build());
+ }
+
+ private static class FailingModelFactory extends TestModelFactory {
+ private final RuntimeException exception;
+ public FailingModelFactory(Version vespaVersion, RuntimeException exception) {
+ super(vespaVersion);
+ this.exception = exception;
+ }
+
+ @Override
+ public ModelCreateResult createAndValidateModel(ModelContext modelContext, boolean ignoreValidationErrors) {
+ throw exception;
+ }
+ }
+
+ private ApplicationId applicationId(String applicationName) {
+ return ApplicationId.from(TenantName.defaultName(),
+ ApplicationName.from(applicationName), InstanceName.defaultName());
+ }
+
+ private static class ConfigChangeActionsModelFactory extends TestModelFactory {
+ private final ConfigChangeAction action;
+ public ConfigChangeActionsModelFactory(Version vespaVersion, ConfigChangeAction action) {
+ super(vespaVersion);
+ this.action = action;
+ }
+
+ @Override
+ public ModelCreateResult createAndValidateModel(ModelContext modelContext, boolean ignoreValidationErrors) {
+ ModelCreateResult result = super.createAndValidateModel(modelContext, ignoreValidationErrors);
+ return new ModelCreateResult(result.getModel(), Arrays.asList(action));
+ }
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionRepoTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionRepoTest.java
new file mode 100644
index 00000000000..8549902faf0
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionRepoTest.java
@@ -0,0 +1,38 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.session;
+
+import org.junit.Test;
+
+import com.yahoo.config.provision.TenantName;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author musum
+ * @since 5.1.14
+ */
+public class SessionRepoTest {
+ @Test
+ public void require_that_sessionrepo_is_initialized() {
+ SessionRepo<TestSession> sessionRepo = new SessionRepo<>();
+ assertNull(sessionRepo.getSession(1L));
+ sessionRepo.addSession(new TestSession(1));
+ assertThat(sessionRepo.getSession(1L).getSessionId(), is(1l));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void require_that_adding_existing_session_fails() {
+ SessionRepo<TestSession> sessionRepo = new SessionRepo<>();
+ final TestSession session = new TestSession(1);
+ sessionRepo.addSession(session);
+ sessionRepo.addSession(session);
+ }
+
+ private class TestSession extends Session {
+ public TestSession(long sessionId) {
+ super(TenantName.from("default"), sessionId);
+ }
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionTest.java
new file mode 100644
index 00000000000..de64db1bbc9
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionTest.java
@@ -0,0 +1,33 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.session;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.path.Path;
+import com.yahoo.vespa.config.server.ApplicationSet;
+import com.yahoo.vespa.config.server.configchange.ConfigChangeActions;
+import com.yahoo.vespa.curator.mock.MockCurator;
+
+import java.util.ArrayList;
+import java.util.Optional;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class SessionTest {
+
+ public static class MockSessionPreparer extends SessionPreparer {
+ public boolean isPrepared = false;
+
+ public MockSessionPreparer() {
+ super(null, null, null, null, null, null, new MockCurator(), null);
+ }
+
+ @Override
+ public ConfigChangeActions prepare(SessionContext context, DeployLogger logger, PrepareParams params, Optional<ApplicationSet> currentActiveApplicationSet, Path tenantPath) {
+ isPrepared = true;
+ return new ConfigChangeActions(new ArrayList<>());
+ }
+ }
+
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClientTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClientTest.java
new file mode 100644
index 00000000000..4508d8c234f
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClientTest.java
@@ -0,0 +1,118 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.session;
+
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.path.Path;
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.text.Utf8;
+import com.yahoo.vespa.config.server.TestWithCurator;
+import com.yahoo.vespa.config.server.zookeeper.ConfigCurator;
+import org.junit.Test;
+
+import java.util.concurrent.TimeUnit;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class SessionZooKeeperClientTest extends TestWithCurator {
+
+ @Test
+ public void require_that_status_can_be_updated() {
+ SessionZooKeeperClient zkc = createSessionZKClient("1");
+ zkc.writeStatus(Session.Status.NEW);
+ assertThat(zkc.readStatus(), is(Session.Status.NEW));
+
+ zkc.writeStatus(Session.Status.PREPARE);
+ assertThat(zkc.readStatus(), is(Session.Status.PREPARE));
+
+ zkc.writeStatus(Session.Status.ACTIVATE);
+ assertThat(zkc.readStatus(), is(Session.Status.ACTIVATE));
+
+ zkc.writeStatus(Session.Status.DEACTIVATE);
+ assertThat(zkc.readStatus(), is(Session.Status.DEACTIVATE));
+ }
+
+ @Test
+ public void require_that_status_is_written_to_zk() {
+ SessionZooKeeperClient zkc = createSessionZKClient("2");
+ zkc.writeStatus(Session.Status.NEW);
+ String path = "/2" + ConfigCurator.SESSIONSTATE_ZK_SUBPATH;
+ assertTrue(configCurator.exists(path));
+ assertThat(configCurator.getData(path), is("NEW"));
+ }
+
+ @Test
+ public void require_that_status_is_read_from_zk() {
+ SessionZooKeeperClient zkc = createSessionZKClient("3");
+ curator.set(Path.fromString("3").append(ConfigCurator.SESSIONSTATE_ZK_SUBPATH), Utf8.toBytes("PREPARE"));
+ assertThat(zkc.readStatus(), is(Session.Status.PREPARE));
+ }
+
+ @Test
+ public void require_that_application_id_is_written_to_zk() {
+ ApplicationId id = new ApplicationId.Builder()
+ .tenant("tenant")
+ .applicationName("foo").instanceName("bim").build();
+ SessionZooKeeperClient zkc = createSessionZKClient("3");
+ zkc.writeApplicationId(id);
+ String path = "/3/" + SessionZooKeeperClient.APPLICATION_ID_PATH;
+ assertTrue(configCurator.exists(path));
+ assertThat(configCurator.getData(path), is("tenant:foo:bim"));
+ }
+
+ @Test
+ public void require_that_application_id_is_read_from_zk() {
+ ApplicationId id = new ApplicationId.Builder()
+ .tenant("tenant")
+ .applicationName("bar").instanceName("quux").build();
+ String idNoVersion = id.serializedForm();
+ assertApplicationIdParse("3", idNoVersion, idNoVersion);
+ }
+
+ @Test
+ public void require_that_default_name_is_returned_if_node_does_not_exist() {
+ assertThat(createSessionZKClient("3").readApplicationId(TenantName.defaultName()).application().value(), is("default"));
+ }
+
+ @Test
+ public void require_that_create_time_can_be_written_and_read() {
+ SessionZooKeeperClient zkc = createSessionZKClient("3");
+ curator.delete(Path.fromString("3"));
+ assertThat(zkc.readCreateTime(), is(0l));
+ zkc.createNewSession(123456l, TimeUnit.SECONDS);
+ assertThat(zkc.readCreateTime(), is(123456l));
+ }
+
+ @Test
+ public void require_that_create_time_has_correct_unit() {
+ SessionZooKeeperClient zkc = createSessionZKClient("3");
+ curator.delete(Path.fromString("3"));
+ assertThat(zkc.readCreateTime(), is(0l));
+ zkc.createNewSession(60, TimeUnit.MINUTES);
+ assertThat(zkc.readCreateTime(), is(3600l));
+ }
+
+ private void assertApplicationIdParse(String sessionId, String idString, String expectedIdString) {
+ SessionZooKeeperClient zkc = createSessionZKClient(sessionId);
+ String path = "/" + sessionId + "/" + SessionZooKeeperClient.APPLICATION_ID_PATH;
+ configCurator.putData(path, idString);
+ ApplicationId zkId = zkc.readApplicationId(TenantName.defaultName());
+ assertThat(zkId.serializedForm(), is(expectedIdString));
+ }
+
+ private SessionZooKeeperClient createSessionZKClient(String generation) {
+ return createSessionZKClient(generation, 100);
+ }
+
+ private SessionZooKeeperClient createSessionZKClient(String generation, long createTimeInMillis) {
+ SessionZooKeeperClient zkc = new SessionZooKeeperClient(curator, Path.fromString(generation));
+ zkc.createNewSession(createTimeInMillis, TimeUnit.MILLISECONDS);
+ return zkc;
+ }
+
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/userconfigs/a.cfg b/configserver/src/test/java/com/yahoo/vespa/config/server/userconfigs/a.cfg
new file mode 100644
index 00000000000..0bc17bae65e
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/userconfigs/a.cfg
@@ -0,0 +1,18 @@
+asyncfetchocc 10
+e 4
+search[2].feeder[1] "bazfeeder"
+search[1].feeder[0] "barfeeder1_1"
+search[1].feeder[3] "barfeeder2_1"
+onlyindef 45
+
+speciallog[0].filehandler.rotation "0 1 ..."
+
+rulebase[4]
+rulebase[0].name "cjk"
+rulebase[0].rules "# Use unicode equivalents in java source:\n#\n# 佳:\u4f73\n# 能:\u80fd\n# 索:\u7d22\n# 尼:\u5c3c\n# 惠:\u60e0\n# 普:\u666e\n\n@default\n\na索 -> 索a;\n\n[brand] -> brand:[brand];\n\n[brand] :- 索尼,惠普,佳能;\n"
+rulebase[1].name "common"
+rulebase[1].rules "## Some test rules\n\n# Spelling correction\nbahc -> bach;\n\n# Stopwords\nsomelongstopword -> ;\n[stopword] -> ;\n[stopword] :- someotherlongstopword, yetanotherstopword;\n\n# \n[song] by [artist] -> song:[song] artist:[artist];\n\n[song] :- together, imagine, tinseltown;\n[artist] :- youngbloods, beatles, zappa;\n\n# Negative\nvarious +> -kingz;\n\n\n"
+rulebase[2].name "egyik"
+rulebase[2].rules "@include(common.sr)\n@automata(/home/vespa/etc/vespa/fsa/stopwords.fsa)\n[stopwords] -> ;\n\n"
+rulebase[3].name "masik"
+rulebase[3].rules "@include(common.sr)\n[stopwords] :- etaoin, shrdlu;\n[stopwords] -> ;\n\n"
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/userconfigs/b.search#cluster.sports#c0#r0#indexer4.cfg b/configserver/src/test/java/com/yahoo/vespa/config/server/userconfigs/b.search#cluster.sports#c0#r0#indexer4.cfg
new file mode 100644
index 00000000000..88b50384058
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/userconfigs/b.search#cluster.sports#c0#r0#indexer4.cfg
@@ -0,0 +1 @@
+usercfgwithid 86
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/userconfigs/c.cfg b/configserver/src/test/java/com/yahoo/vespa/config/server/userconfigs/c.cfg
new file mode 100644
index 00000000000..b34c4ed311e
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/userconfigs/c.cfg
@@ -0,0 +1 @@
+foo "test"
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/userconfigs/d.cfg b/configserver/src/test/java/com/yahoo/vespa/config/server/userconfigs/d.cfg
new file mode 100644
index 00000000000..12c5b53de7d
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/userconfigs/d.cfg
@@ -0,0 +1 @@
+theint 34
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/userconfigs/spooler.cfg b/configserver/src/test/java/com/yahoo/vespa/config/server/userconfigs/spooler.cfg
new file mode 100644
index 00000000000..73ed41667f9
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/userconfigs/spooler.cfg
@@ -0,0 +1 @@
+keepsuccess true
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/version/VersionStateTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/version/VersionStateTest.java
new file mode 100644
index 00000000000..f517145fd91
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/version/VersionStateTest.java
@@ -0,0 +1,60 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.version;
+
+import com.yahoo.cloud.config.ConfigserverConfig;
+import com.yahoo.config.provision.Version;
+import com.yahoo.io.IOUtils;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author lulf
+ */
+public class VersionStateTest {
+
+ @Rule
+ public TemporaryFolder tempDir = new TemporaryFolder();
+
+ @Test
+ public void upgrade() throws IOException {
+ Version unknownVersion = Version.fromIntValues(0, 0, 0);
+ File versionFile = tempDir.newFile();
+ VersionState state = new VersionState(versionFile);
+ assertThat(state.storedVersion(), is(unknownVersion));
+ assertTrue(state.isUpgraded());
+ state.saveNewVersion();
+ assertFalse(state.isUpgraded());
+
+ IOUtils.writeFile(versionFile, "badversion", false);
+ assertThat(state.storedVersion(), is(unknownVersion));
+ assertTrue(state.isUpgraded());
+
+ IOUtils.writeFile(versionFile, "5.0.0", false);
+ assertThat(state.storedVersion(), is(Version.fromIntValues(5, 0, 0)));
+ assertTrue(state.isUpgraded());
+
+ state.saveNewVersion();
+ assertThat(state.currentVersion(), is(state.storedVersion()));
+ assertFalse(state.isUpgraded());
+ }
+
+ @Test
+ public void serverdbfile() throws IOException {
+ File dbDir = tempDir.newFolder();
+ VersionState state = new VersionState(new ConfigserverConfig(new ConfigserverConfig.Builder().configServerDBDir(dbDir.getAbsolutePath())));
+ state.saveNewVersion();
+ File versionFile = new File(dbDir, "vespa_version");
+ assertTrue(versionFile.exists());
+ Version stored = Version.fromString(IOUtils.readFile(versionFile));
+ assertThat(stored, is(state.currentVersion()));
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/zookeeper/ConfigCuratorTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/zookeeper/ConfigCuratorTest.java
new file mode 100644
index 00000000000..b370b148fe0
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/zookeeper/ConfigCuratorTest.java
@@ -0,0 +1,239 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.zookeeper;
+
+import com.yahoo.text.Utf8;
+import com.yahoo.vespa.curator.mock.MockCurator;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Tests the ZKFacade using a curator mock.
+ *
+ * @author <a href="musum@yahoo-inc.com">Harald Musum</a>
+ */
+public class ConfigCuratorTest {
+
+ private final String defKey1 = "attributes";
+
+ private final String payload1 = "attribute[5]\n" +
+ "attribute[0].name Popularity\n" +
+ "attribute[0].datatype string\n" +
+ "attribute[0].collectiontype single\n" +
+ "attribute[0].removeifzero false\n" +
+ "attribute[0].createifnonexistent false\n" +
+ "attribute[0].loadtype \"always\"\n" +
+ "attribute[0].uniqueonly false\n" +
+ "attribute[0].sparse false\n" +
+ "attribute[0].noupdate false\n" +
+ "attribute[0].fastsearch false\n" +
+ "attribute[0].fastaggregate false\n" +
+ "attribute[0].fastersearch false\n" +
+ "attribute[1].name atA\n" +
+ "attribute[1].datatype string\n" +
+ "attribute[1].collectiontype weightedset\n" +
+ "attribute[1].removeifzero false\n" +
+ "attribute[1].createifnonexistent false\n" +
+ "attribute[1].loadtype \"always\"\n" +
+ "attribute[1].uniqueonly false\n" +
+ "attribute[1].sparse false\n" +
+ "attribute[1].noupdate false\n" +
+ "attribute[1].fastsearch true\n" +
+ "attribute[1].fastaggregate false\n" +
+ "attribute[1].fastersearch false\n" +
+ "attribute[2].name default_fieldlength\n" +
+ "attribute[2].datatype uint32\n" +
+ "attribute[2].collectiontype single\n" +
+ "attribute[2].removeifzero false\n" +
+ "attribute[2].createifnonexistent false\n" +
+ "attribute[2].loadtype \"always\"\n" +
+ "attribute[2].uniqueonly false\n" +
+ "attribute[2].sparse false\n" +
+ "attribute[2].noupdate true\n" +
+ "attribute[2].fastsearch false\n" +
+ "attribute[2].fastaggregate false\n" +
+ "attribute[2].fastersearch false\n" +
+ "attribute[3].name default_literal_fieldlength\n" +
+ "attribute[3].datatype uint32\n" +
+ "attribute[3].collectiontype single\n" +
+ "attribute[3].removeifzero false\n" +
+ "attribute[3].createifnonexistent false\n" +
+ "attribute[3].loadtype \"always\"\n" +
+ "attribute[3].uniqueonly false\n" +
+ "attribute[3].sparse false\n" +
+ "attribute[3].noupdate true\n" +
+ "attribute[3].fastsearch false\n" +
+ "attribute[3].fastaggregate false\n" +
+ "attribute[3].fastersearch false\n" +
+ "attribute[4].name artist_fieldlength\n" +
+ "attribute[4].datatype uint32\n" +
+ "attribute[4].collectiontype single\n" +
+ "attribute[4].removeifzero false\n" +
+ "attribute[4].createifnonexistent false\n" +
+ "attribute[4].loadtype \"always\"\n" +
+ "attribute[4].uniqueonly false\n" +
+ "attribute[4].sparse false\n" +
+ "attribute[4].noupdate true\n" +
+ "attribute[4].fastsearch false\n" +
+ "attribute[4].fastaggregate false\n" +
+ "attribute[4].fastersearch false\n";
+
+ private final String payload3 = "attribute[5]\n" +
+ "attribute[0].name Popularity\n" +
+ "attribute[0].datatype String\n" +
+ "attribute[0].collectiontype single\n" +
+ "attribute[0].removeifzero false\n" +
+ "attribute[0].createifnonexistent false\n" +
+ "attribute[0].loadtype \"always\"\n" +
+ "attribute[0].uniqueonly false\n" +
+ "attribute[0].sparse false\n" +
+ "attribute[0].noupdate false\n" +
+ "attribute[0].fastsearch false\n" +
+ "attribute[0].fastaggregate false\n" +
+ "attribute[0].fastersearch false\n" +
+ "attribute[1].name atA\n" +
+ "attribute[1].datatype string\n" +
+ "attribute[1].collectiontype weightedset\n" +
+ "attribute[1].removeifzero false\n" +
+ "attribute[1].createifnonexistent false\n" +
+ "attribute[1].loadtype \"always\"\n" +
+ "attribute[1].uniqueonly false\n" +
+ "attribute[1].sparse false\n" +
+ "attribute[1].noupdate false\n" +
+ "attribute[1].fastsearch true\n" +
+ "attribute[1].fastaggregate false\n" +
+ "attribute[1].fastersearch false\n" +
+ "attribute[2].name default_fieldlength\n" +
+ "attribute[2].datatype uint32\n" +
+ "attribute[2].collectiontype single\n" +
+ "attribute[2].removeifzero false\n" +
+ "attribute[2].createifnonexistent false\n" +
+ "attribute[2].loadtype \"always\"\n" +
+ "attribute[2].uniqueonly false\n" +
+ "attribute[2].sparse false\n" +
+ "attribute[2].noupdate true\n" +
+ "attribute[2].fastsearch false\n" +
+ "attribute[2].fastaggregate false\n" +
+ "attribute[2].fastersearch false\n" +
+ "attribute[3].name default_literal_fieldlength\n" +
+ "attribute[3].datatype uint32\n" +
+ "attribute[3].collectiontype single\n" +
+ "attribute[3].removeifzero false\n" +
+ "attribute[3].createifnonexistent false\n" +
+ "attribute[3].loadtype \"always\"\n" +
+ "attribute[3].uniqueonly false\n" +
+ "attribute[3].sparse false\n" +
+ "attribute[3].noupdate true\n" +
+ "attribute[3].fastsearch false\n" +
+ "attribute[3].fastaggregate false\n" +
+ "attribute[3].fastersearch false\n" +
+ "attribute[4].name artist_fieldlength\n" +
+ "attribute[4].datatype uint32\n" +
+ "attribute[4].collectiontype single\n" +
+ "attribute[4].removeifzero false\n" +
+ "attribute[4].createifnonexistent false\n" +
+ "attribute[4].loadtype \"always\"\n" +
+ "attribute[4].uniqueonly false\n" +
+ "attribute[4].sparse false\n" +
+ "attribute[4].noupdate true\n" +
+ "attribute[4].fastsearch false\n" +
+ "attribute[4].fastaggregate false\n" +
+ "attribute[4].fastersearch false\n";
+
+ private void initAndClearZK(ConfigCurator zkIf) {
+ zkIf.initAndClear(ConfigCurator.DEFCONFIGS_ZK_SUBPATH);
+ zkIf.initAndClear(ConfigCurator.USERAPP_ZK_SUBPATH);
+ }
+
+ private ConfigCurator deployApp() {
+ ConfigCurator zkIf = getFacade();
+ initAndClearZK(zkIf);
+ zkIf.putData(ConfigCurator.DEFCONFIGS_ZK_SUBPATH, defKey1, payload1);
+ // zkIf.putData(ConfigCurator.USERCONFIGS_ZK_SUBPATH, cfgKey1, payload3);
+ String partitionsDef = "version=7\\n" +
+ "dataset[].id int\\n" +
+ "dataset[].partbits int default=6";
+ zkIf.putData(ConfigCurator.DEFCONFIGS_ZK_SUBPATH, "partitions", partitionsDef);
+ String partitionsUser = "dataset[0].partbits 8\\n";
+ // zkIf.putData(ConfigCurator.USERCONFIGS_ZK_SUBPATH, "partitions", partitionsUser);
+ return zkIf;
+ }
+
+ @Test
+ public void testZKInterface() {
+ ConfigCurator zkIf = getFacade();
+ zkIf.putData("", "test", "foo");
+ zkIf.putData("/test", "me", "bar");
+ zkIf.putData("", "test;me;now,then", "baz");
+ assertEquals(zkIf.getData("", "test"), "foo");
+ assertEquals(zkIf.getData("/test", "me"), "bar");
+ assertEquals(zkIf.getData("", "test;me;now,then"), "baz");
+ }
+
+ @Test
+ public void testWatcher() {
+ ConfigCurator zkIf = getFacade();
+
+ String data = zkIf.getData("/nothere");
+ assertNull(data);
+ zkIf.putData("", "/nothere", "foo");
+ assertEquals(zkIf.getData("/nothere"), "foo");
+
+ zkIf.putData("", "test", "foo");
+ data = zkIf.getData("/test");
+ assertEquals(data, "foo");
+ zkIf.putData("", "/test", "bar");
+ data = zkIf.getData("/test");
+ assertEquals(data, "bar");
+
+ zkIf.getChildren("/");
+ zkIf.putData("", "test2", "foo2");
+ }
+
+ private ConfigCurator getFacade() {
+ return ConfigCurator.create(new MockCurator());
+ }
+
+ @Test
+ public void testGetDeployedData() {
+ ConfigCurator zkIf = deployApp();
+ assertEquals(zkIf.getData(ConfigCurator.DEFCONFIGS_ZK_SUBPATH, defKey1), payload1);
+ }
+
+ @Test
+ public void testEmptyData() {
+ ConfigCurator zkIf = getFacade();
+ zkIf.createNode("/empty", "data");
+ assertEquals("", zkIf.getData("/empty", "data"));
+ }
+
+ @Test
+ public void testRecursiveDelete() {
+ ConfigCurator configCurator = getFacade();
+ configCurator.putData("/foo", Utf8.toBytes("sadsdfsdfsdfsdf"));
+ configCurator.putData("/foo/bar", Utf8.toBytes("dsfsdffds"));
+ configCurator.putData("/foo/baz",
+ Utf8.toBytes("sdf\u00F8l ksdfl skdflsk dflsdkfd welkr3k lkr e4kt4 54l4l353k l534klk3lk4l33k5l 353l4k l43k l4k"));
+ configCurator.putData("/foo/bar/dill", Utf8.toBytes("sdfsfe 23 42 3 3 2342"));
+ configCurator.putData("/foo", Utf8.toBytes("sdcfsdfsdf"));
+ configCurator.putData("/foo", Utf8.toBytes("sdcfsd sdfdffsdf"));
+ configCurator.deleteRecurse("/foo");
+ assertFalse(configCurator.exists("/foo"));
+ assertFalse(configCurator.exists("/foo/bar"));
+ assertFalse(configCurator.exists("/foo/bar/dill"));
+ assertFalse(configCurator.exists("/foo/bar/baz"));
+ try {
+ configCurator.getChildren("/foo");
+ fail("Got children from nonexisting ZK path");
+ } catch (RuntimeException e) {
+ assertTrue(e.getCause().getMessage().matches(".*NoNode.*"));
+ }
+ configCurator.deleteRecurse("/nonexisting");
+ }
+
+ @Test
+ public void testGetZkNodePath() {
+ assertEquals("foo,1", ConfigCurator.getZkNodePath(ConfigCurator.DEFCONFIGS_ZK_SUBPATH, "foo", "1", "a/b"));
+ }
+
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/zookeeper/InitializedCounterTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/zookeeper/InitializedCounterTest.java
new file mode 100644
index 00000000000..95d17156e63
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/zookeeper/InitializedCounterTest.java
@@ -0,0 +1,35 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.zookeeper;
+
+import com.yahoo.vespa.config.server.TestWithCurator;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class InitializedCounterTest extends TestWithCurator {
+
+ @Rule
+ public TemporaryFolder folder = new TemporaryFolder();
+
+ @Before
+ public void setupZK() {
+ configCurator.createNode("/sessions");
+ configCurator.createNode("/sessions/1");
+ configCurator.createNode("/sessions/2");
+ }
+
+ @Test
+ public void requireThatCounterIsInitializedFromNumberOfSessions() {
+ InitializedCounter counter = new InitializedCounter(curator, "/counter", "/sessions");
+ assertThat(counter.counter.get(), is(2l));
+ }
+
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/zookeeper/ZKApplicationFileTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/zookeeper/ZKApplicationFileTest.java
new file mode 100644
index 00000000000..6205ac09c4c
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/zookeeper/ZKApplicationFileTest.java
@@ -0,0 +1,37 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.zookeeper;
+
+import com.google.common.io.Files;
+import com.yahoo.config.application.api.ApplicationFile;
+import com.yahoo.config.application.api.ApplicationFileTest;
+import com.yahoo.path.Path;
+import com.yahoo.vespa.curator.mock.MockCurator;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class ZKApplicationFileTest extends ApplicationFileTest {
+
+ private void feed(ConfigCurator zk, File dirToFeed) throws IOException {
+ assertTrue(dirToFeed.isDirectory());
+ String appPath = "/0";
+ zk.feedZooKeeper(dirToFeed, appPath + ConfigCurator.USERAPP_ZK_SUBPATH, null, true);
+ zk.putData(appPath, ZKApplicationPackage.fileRegistryNode, "dummyfiles");
+ }
+
+ @Override
+ public ApplicationFile getApplicationFile(Path path) throws IOException{
+ ConfigCurator configCurator = ConfigCurator.create(new MockCurator());
+ File tmp = Files.createTempDir();
+ writeAppTo(tmp);
+ feed(configCurator, tmp);
+ return new ZKApplicationFile(path, new ZKLiveApp(configCurator, Path.fromString("/0")));
+ }
+
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/zookeeper/ZKApplicationPackageTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/zookeeper/ZKApplicationPackageTest.java
new file mode 100644
index 00000000000..217e2a04f9b
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/zookeeper/ZKApplicationPackageTest.java
@@ -0,0 +1,77 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.zookeeper;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.*;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.Reader;
+import java.util.Collections;
+import java.util.regex.Pattern;
+
+import com.yahoo.config.application.api.DeploymentSpec;
+import com.yahoo.config.model.deploy.DeployState;
+import com.yahoo.config.provision.HostSpec;
+import com.yahoo.config.provision.ProvisionInfo;
+import com.yahoo.config.provision.Version;
+import com.yahoo.path.Path;
+import com.yahoo.text.Utf8;
+import com.yahoo.vespa.config.server.TestWithCurator;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import com.yahoo.io.IOUtils;
+
+public class ZKApplicationPackageTest extends TestWithCurator {
+
+ private static final String APP = "src/test/apps/zkapp";
+ private static final ProvisionInfo provisionInfo = ProvisionInfo.withHosts(
+ Collections.singleton(new HostSpec("foo.yahoo.com", Collections.emptyList())));
+
+ @Rule
+ public TemporaryFolder tmpDir = new TemporaryFolder();
+
+ @Test
+ public void testBasicZKFeed() throws IOException {
+ feed(configCurator, new File(APP));
+ ZKApplicationPackage zkApp = new ZKApplicationPackage(configCurator, Path.fromString("/0"));
+ assertTrue(Pattern.compile(".*<slobroks>.*",Pattern.MULTILINE+Pattern.DOTALL).matcher(IOUtils.readAll(zkApp.getServices())).matches());
+ assertTrue(Pattern.compile(".*<alias>.*",Pattern.MULTILINE+Pattern.DOTALL).matcher(IOUtils.readAll(zkApp.getHosts())).matches());
+ assertTrue(Pattern.compile(".*<slobroks>.*",Pattern.MULTILINE+Pattern.DOTALL).matcher(IOUtils.readAll(zkApp.getFile(Path.fromString("services.xml")).createReader())).matches());
+ DeployState deployState = new DeployState.Builder().applicationPackage(zkApp).build();
+ assertEquals(deployState.getSearchDefinitions().size(), 5);
+ assertEquals(zkApp.searchDefinitionContents().size(), 5);
+ assertEquals(IOUtils.readAll(zkApp.getRankingExpression("foo.expression")), "foo()+1\n");
+ assertEquals(zkApp.getFiles(Path.fromString(""), "xml").size(), 3);
+ assertEquals(zkApp.getFileReference(Path.fromString("components/file.txt")).getAbsolutePath(), "/home/vespa/test/file.txt");
+ try (Reader foo = zkApp.getFile(Path.fromString("files/foo.json")).createReader()) {
+ assertEquals(IOUtils.readAll(foo), "foo : foo\n");
+ }
+ try (Reader bar = zkApp.getFile(Path.fromString("files/sub/bar.json")).createReader()) {
+ assertEquals(IOUtils.readAll(bar), "bar : bar\n");
+ }
+ assertTrue(zkApp.getFile(Path.createRoot()).exists());
+ assertTrue(zkApp.getFile(Path.createRoot()).isDirectory());
+ Version goodVersion = Version.fromIntValues(3, 0, 0);
+ assertTrue(zkApp.getFileRegistryMap().containsKey(goodVersion));
+ assertFalse(zkApp.getFileRegistryMap().containsKey(Version.fromIntValues(0, 0, 0)));
+ assertThat(zkApp.getFileRegistryMap().get(goodVersion).fileSourceHost(), is("dummyfiles"));
+ assertTrue(zkApp.getProvisionInfoMap().containsKey(goodVersion));
+ ProvisionInfo readInfo = zkApp.getProvisionInfoMap().get(goodVersion);
+ assertThat(Utf8.toString(readInfo.toJson()), is(Utf8.toString(provisionInfo.toJson())));
+ assertTrue(zkApp.getDeployment().isPresent());
+ assertThat(DeploymentSpec.fromXml(zkApp.getDeployment().get()).globalServiceId().get(), is("mydisc"));
+ }
+
+ private void feed(ConfigCurator zk, File dirToFeed) throws IOException {
+ assertTrue(dirToFeed.isDirectory());
+ zk.feedZooKeeper(dirToFeed, "/0" + ConfigCurator.USERAPP_ZK_SUBPATH, null, true);
+ String metaData = "{\"deploy\":{\"user\":\"foo\",\"from\":\"bar\",\"timestamp\":1},\"application\":{\"name\":\"foo\",\"checksum\":\"abc\",\"generation\":4,\"previousActiveGeneration\":3}}";
+ zk.putData("/0", ConfigCurator.META_ZK_PATH, metaData);
+ zk.putData("/0/" + ZKApplicationPackage.fileRegistryNode + "/3.0.0", "dummyfiles");
+ zk.putData("/0/" + ZKApplicationPackage.allocatedHostsNode + "/3.0.0", provisionInfo.toJson());
+ }
+
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/serviceview/ServiceModelTest.java b/configserver/src/test/java/com/yahoo/vespa/serviceview/ServiceModelTest.java
new file mode 100644
index 00000000000..cbaa8f36805
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/serviceview/ServiceModelTest.java
@@ -0,0 +1,109 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.serviceview;
+
+import static org.junit.Assert.*;
+
+import java.util.Arrays;
+
+import com.yahoo.vespa.defaults.Defaults;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.yahoo.vespa.serviceview.bindings.ApplicationView;
+import com.yahoo.vespa.serviceview.bindings.HostService;
+import com.yahoo.vespa.serviceview.bindings.ModelResponse;
+import com.yahoo.vespa.serviceview.bindings.ServicePort;
+import com.yahoo.vespa.serviceview.bindings.ServiceView;
+
+/**
+ * Functional tests for the programmatic view of cloud.config.model.
+ *
+ * @author <a href="mailto:steinar@yahoo-inc.com">Steinar Knutsen</a>
+ */
+public class ServiceModelTest {
+
+ ServiceModel model;
+
+ @Before
+ public void setUp() throws Exception {
+ ModelResponse model = syntheticModelResponse();
+ this.model = new ServiceModel(model);
+ }
+
+ static ModelResponse syntheticModelResponse() {
+ ModelResponse model = new ModelResponse();
+ HostService h = new HostService();
+ h.name = "vespa.yahoo.com";
+ com.yahoo.vespa.serviceview.bindings.Service service0 = new com.yahoo.vespa.serviceview.bindings.Service();
+ {
+ service0.clustername = "examplecluster";
+ service0.clustertype = "somethingservers";
+ service0.index = 1L;
+ service0.type = "something";
+ service0.name = "examplename";
+ service0.configid = "blblb/lbl.0";
+ ServicePort port = new ServicePort();
+ port.number = Defaults.getDefaults().vespaWebServicePort();
+ port.tags = "state http";
+ service0.ports = Arrays.asList(new ServicePort[] { port });
+ }
+ com.yahoo.vespa.serviceview.bindings.Service service1 = new com.yahoo.vespa.serviceview.bindings.Service();
+ {
+ service1.clustername = "examplecluster";
+ service1.clustertype = "somethingservers";
+ service1.index = 2L;
+ service1.type = "container-clustercontroller";
+ service1.name = "clustercontroller";
+ service1.configid = "clustercontroller/lbl.0";
+ ServicePort port = new ServicePort();
+ port.number = 4090;
+ port.tags = "state http";
+ service1.ports = Arrays.asList(new ServicePort[] { port });
+ }
+ com.yahoo.vespa.serviceview.bindings.Service service2 = new com.yahoo.vespa.serviceview.bindings.Service();
+ {
+ service2.clustername = "tralala";
+ service2.clustertype = "admin";
+ service2.index = 3L;
+ service2.type = "configserver";
+ service2.name = "configservername";
+ service2.configid = "clustercontroller/lbl.0";
+ ServicePort port = new ServicePort();
+ port.number = 5000;
+ port.tags = "state http";
+ service2.ports = Arrays.asList(new ServicePort[] { port });
+ }
+ h.services = Arrays.asList(new com.yahoo.vespa.serviceview.bindings.Service[] { service0, service1, service2 });
+ model.hosts = Arrays.asList(new HostService[] { h });
+ return model;
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ model = null;
+ }
+
+ @Test
+ public final void test() {
+ final String uriBase = "http://configserver:5000/";
+ ApplicationView x = model.showAllClusters(uriBase, "/tenant/default/application/default");
+ assertEquals(2, x.clusters.size());
+ String urlTracking = null;
+ for (com.yahoo.vespa.serviceview.bindings.ClusterView c : x.clusters) {
+ for (ServiceView s : c.services) {
+ if ("examplename".equals(s.serviceName)) {
+ assertEquals("something", s.serviceType);
+ urlTracking = s.url;
+ break;
+ }
+ }
+ }
+ assertNotNull(urlTracking);
+ final String serviceIdentifier = urlTracking.substring(urlTracking.indexOf("something"),
+ urlTracking.length() - "/state/v1/".length());
+ Service y = model.getService(serviceIdentifier);
+ assertEquals("examplename", y.name);
+ }
+
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/serviceview/StateResourceTest.java b/configserver/src/test/java/com/yahoo/vespa/serviceview/StateResourceTest.java
new file mode 100644
index 00000000000..0f577030984
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/serviceview/StateResourceTest.java
@@ -0,0 +1,96 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.serviceview;
+
+import static org.junit.Assert.*;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.client.Client;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.UriInfo;
+
+import com.yahoo.vespa.defaults.Defaults;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import com.yahoo.cloud.config.ConfigserverConfig;
+import com.yahoo.container.jaxrs.annotation.Component;
+import com.yahoo.vespa.serviceview.bindings.ApplicationView;
+import com.yahoo.vespa.serviceview.bindings.HealthClient;
+import com.yahoo.vespa.serviceview.bindings.ModelResponse;
+
+/**
+ * Functional test for {@link StateResource}.
+ *
+ * @author <a href="mailto:steinar@yahoo-inc.com">Steinar Knutsen</a>
+ */
+public class StateResourceTest {
+
+ private static final String EXTERNAL_BASE_URI = "http://someserver:8080/serviceview/";
+
+ private static class TestResource extends StateResource {
+ private static final String BASE_URI = "http://vespa.yahoo.com:8080/state/v1";
+
+ TestResource(@Component ConfigServerLocation configServer, @Context UriInfo ui) {
+ super(configServer, ui);
+ }
+
+ @Override
+ protected ModelResponse getModelConfig(String tenant, String application, String environment, String region, String instance) {
+ return ServiceModelTest.syntheticModelResponse();
+ }
+
+ @Override
+ protected HealthClient getHealthClient(String apiParams, Service s, int requestedPort, Client client) {
+ HealthClient healthClient = Mockito.mock(HealthClient.class);
+ HashMap<Object, Object> dummyHealthData = new HashMap<>();
+ HashMap<String, String> dummyLink = new HashMap<>();
+ dummyLink.put("url", BASE_URI);
+ dummyHealthData.put("resources", Arrays.asList(dummyLink));
+ Mockito.when(healthClient.getHealthInfo()).thenReturn(dummyHealthData);
+ return healthClient;
+ }
+ }
+
+ StateResource testResource;
+ ServiceModel correspondingModel;
+
+ @Before
+ public void setUp() throws Exception {
+ UriInfo base = Mockito.mock(UriInfo.class);
+ Mockito.when(base.getBaseUri()).thenReturn(new URI(EXTERNAL_BASE_URI));
+ ConfigServerLocation dummyLocation = new ConfigServerLocation(new ConfigserverConfig(new ConfigserverConfig.Builder()));
+ testResource = new TestResource(dummyLocation, base);
+ correspondingModel = new ServiceModel(ServiceModelTest.syntheticModelResponse());
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ testResource = null;
+ correspondingModel = null;
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Test
+ public final void test() {
+ Service s = correspondingModel.resolve("vespa.yahoo.com", 8080, null);
+ String api = "/state/v1";
+ HashMap boom = testResource.singleService("default", "default", "default", "default", "default", s.getIdentifier(8080), api);
+ assertEquals(EXTERNAL_BASE_URI + "v1/tenant/default/application/default/environment/default/region/default/instance/default/service/" + s.getIdentifier(8080) + api,
+ ((Map) ((List) boom.get("resources")).get(0)).get("url"));
+ }
+
+ @Test
+ public final void testLinkEquality() {
+ ApplicationView explicitParameters = testResource.getUserInfo("default", "default", "default", "default", "default");
+ ApplicationView implicitParameters = testResource.getDefaultUserInfo();
+ assertEquals(explicitParameters.clusters.get(0).services.get(0).url, implicitParameters.clusters.get(0).services.get(0).url);
+ }
+
+}