diff options
author | Harald Musum <musum@verizonmedia.com> | 2020-06-01 13:12:28 +0200 |
---|---|---|
committer | Harald Musum <musum@verizonmedia.com> | 2020-06-01 13:12:28 +0200 |
commit | 4d032b49de56a86df6bac48ff40f2572100d833a (patch) | |
tree | 2002b5ec3e699b0be6c5daeb6d4315c9e3446779 /configserver | |
parent | f9d72e54b506f2ab81e9c8805a6461964bf04b86 (diff) |
Move two more tests and remove one that has been ignored for a long time
Diffstat (limited to 'configserver')
3 files changed, 630 insertions, 54 deletions
diff --git a/configserver/ApplicationRepositoryTest.java b/configserver/ApplicationRepositoryTest.java new file mode 100644 index 00000000000..5771303ec4c --- /dev/null +++ b/configserver/ApplicationRepositoryTest.java @@ -0,0 +1,604 @@ +// Copyright 2018 Yahoo Holdings. 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.application.api.ApplicationMetaData; +import com.yahoo.config.model.api.ApplicationRoles; +import com.yahoo.config.model.application.provider.BaseDeployLogger; +import com.yahoo.config.provision.AllocatedHosts; +import com.yahoo.config.provision.ApplicationId; +import com.yahoo.config.provision.ApplicationName; +import com.yahoo.config.provision.Deployment; +import com.yahoo.config.provision.HostSpec; +import com.yahoo.config.provision.InstanceName; +import com.yahoo.config.provision.NetworkPorts; +import com.yahoo.config.provision.Provisioner; +import com.yahoo.config.provision.TenantName; +import com.yahoo.container.jdisc.HttpResponse; +import com.yahoo.docproc.jdisc.metric.NullMetric; +import com.yahoo.io.IOUtils; +import com.yahoo.jdisc.Metric; +import com.yahoo.test.ManualClock; +import com.yahoo.text.Utf8; +import com.yahoo.vespa.config.server.application.OrchestratorMock; +import com.yahoo.vespa.config.server.deploy.DeployTester; +import com.yahoo.vespa.config.server.http.InternalServerException; +import com.yahoo.vespa.config.server.http.SessionHandlerTest; +import com.yahoo.vespa.config.server.http.v2.PrepareResult; +import com.yahoo.vespa.config.server.session.LocalSession; +import com.yahoo.vespa.config.server.session.LocalSessionRepo; +import com.yahoo.vespa.config.server.session.PrepareParams; +import com.yahoo.vespa.config.server.session.RemoteSession; +import com.yahoo.vespa.config.server.session.Session; +import com.yahoo.vespa.config.server.session.SilentDeployLogger; +import com.yahoo.vespa.config.server.tenant.ApplicationRolesStore; +import com.yahoo.vespa.config.server.tenant.Tenant; +import com.yahoo.vespa.config.server.tenant.TenantRepository; +import com.yahoo.vespa.curator.Curator; +import com.yahoo.vespa.curator.mock.MockCurator; +import org.hamcrest.core.Is; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.io.IOException; +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +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; + +/** + * @author hmusum + */ +public class ApplicationRepositoryTest { + + private final static File testApp = new File("src/test/apps/app"); + private final static File testAppJdiscOnly = new File("src/test/apps/app-jdisc-only"); + private final static File testAppJdiscOnlyRestart = new File("src/test/apps/app-jdisc-only-restart"); + private final static File testAppLogServerWithContainer = new File("src/test/apps/app-logserver-with-container"); + + private final static TenantName tenant1 = TenantName.from("test1"); + private final static TenantName tenant2 = TenantName.from("test2"); + private final static TenantName tenant3 = TenantName.from("test3"); + private final static Clock clock = Clock.systemUTC(); + + private ApplicationRepository applicationRepository; + private TenantRepository tenantRepository; + private SessionHandlerTest.MockProvisioner provisioner; + private OrchestratorMock orchestrator; + private TimeoutBudget timeoutBudget; + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Rule + public ExpectedException exceptionRule = ExpectedException.none(); + + @Before + public void setup() { + Curator curator = new MockCurator(); + tenantRepository = new TenantRepository(new TestComponentRegistry.Builder() + .curator(curator) + .build()); + tenantRepository.addTenant(TenantRepository.HOSTED_VESPA_TENANT); + tenantRepository.addTenant(tenant1); + tenantRepository.addTenant(tenant2); + tenantRepository.addTenant(tenant3); + orchestrator = new OrchestratorMock(); + provisioner = new SessionHandlerTest.MockProvisioner(); + applicationRepository = new ApplicationRepository(tenantRepository, + provisioner, + orchestrator, + clock); + timeoutBudget = new TimeoutBudget(clock, Duration.ofSeconds(60)); + } + + @Test + public void prepareAndActivate() { + PrepareResult result = prepareAndActivate(testApp); + assertTrue(result.configChangeActions().getRefeedActions().isEmpty()); + assertTrue(result.configChangeActions().getRestartActions().isEmpty()); + + TenantName tenantName = applicationId().tenant(); + Tenant tenant = tenantRepository.getTenant(tenantName); + LocalSession session = tenant.getLocalSessionRepo().getSession(tenant.getApplicationRepo() + .requireActiveSessionOf(applicationId())); + session.getAllocatedHosts(); + } + + @Test + public void prepareAndActivateWithRestart() { + prepareAndActivate(testAppJdiscOnly); + PrepareResult result = prepareAndActivate(testAppJdiscOnlyRestart); + assertTrue(result.configChangeActions().getRefeedActions().isEmpty()); + assertFalse(result.configChangeActions().getRestartActions().isEmpty()); + } + + @Test + public void createAndPrepareAndActivate() { + PrepareResult result = deployApp(testApp); + assertTrue(result.configChangeActions().getRefeedActions().isEmpty()); + assertTrue(result.configChangeActions().getRestartActions().isEmpty()); + } + + @Test + public void createFromActiveSession() { + PrepareResult result = deployApp(testApp); + long sessionId = applicationRepository.createSessionFromExisting(applicationId(), + new SilentDeployLogger(), + false, + timeoutBudget); + long originalSessionId = result.sessionId(); + ApplicationMetaData originalApplicationMetaData = getApplicationMetaData(applicationId(), originalSessionId); + ApplicationMetaData applicationMetaData = getApplicationMetaData(applicationId(), sessionId); + + assertNotEquals(sessionId, originalSessionId); + assertEquals(originalApplicationMetaData.getApplicationId(), applicationMetaData.getApplicationId()); + assertEquals(originalApplicationMetaData.getGeneration().longValue(), applicationMetaData.getPreviousActiveGeneration()); + assertNotEquals(originalApplicationMetaData.getGeneration(), applicationMetaData.getGeneration()); + assertEquals(originalApplicationMetaData.getDeployedByUser(), applicationMetaData.getDeployedByUser()); + } + + @Test + public void testSuspension() { + deployApp(testApp); + assertFalse(applicationRepository.isSuspended(applicationId())); + orchestrator.suspend(applicationId()); + assertTrue(applicationRepository.isSuspended(applicationId())); + } + + @Test + public void getLogs() { + applicationRepository = createApplicationRepository(); + deployApp(testAppLogServerWithContainer); + HttpResponse response = applicationRepository.getLogs(applicationId(), Optional.empty(), ""); + assertEquals(200, response.getStatus()); + } + + @Test + public void getLogsForHostname() { + applicationRepository = createApplicationRepository(); + ApplicationId applicationId = ApplicationId.from("hosted-vespa", "tenant-host", "default"); + deployApp(testAppLogServerWithContainer, new PrepareParams.Builder().applicationId(applicationId).build()); + HttpResponse response = applicationRepository.getLogs(applicationId, Optional.of("localhost"), ""); + assertEquals(200, response.getStatus()); + } + + @Test(expected = IllegalArgumentException.class) + public void refuseToGetLogsFromHostnameNotInApplication() { + applicationRepository = createApplicationRepository(); + deployApp(testAppLogServerWithContainer); + HttpResponse response = applicationRepository.getLogs(applicationId(), Optional.of("host123.fake.yahoo.com"), ""); + assertEquals(200, response.getStatus()); + } + + @Test + public void deleteUnusedTenants() { + // Set clock to epoch plus hour, as mock curator will always return epoch as creation time + Instant now = ManualClock.at("1970-01-01T01:00:00"); + + // 3 tenants exist, tenant1 and tenant2 has applications deployed: + deployApp(testApp); + deployApp(testApp, new PrepareParams.Builder().applicationId(applicationId(tenant2)).build()); + + // Should not be deleted, not old enough + Duration ttlForUnusedTenant = Duration.ofHours(1); + assertTrue(applicationRepository.deleteUnusedTenants(ttlForUnusedTenant, now).isEmpty()); + // Should be deleted + ttlForUnusedTenant = Duration.ofMillis(1); + assertEquals(tenant3, applicationRepository.deleteUnusedTenants(ttlForUnusedTenant, now).iterator().next()); + + // Delete app used by tenant1, tenant2 still has an application + applicationRepository.delete(applicationId()); + Set<TenantName> tenantsDeleted = applicationRepository.deleteUnusedTenants(Duration.ofMillis(1), now); + assertTrue(tenantsDeleted.contains(tenant1)); + assertFalse(tenantsDeleted.contains(tenant2)); + } + + @Test + public void deleteUnusedFileReferences() throws IOException { + File fileReferencesDir = temporaryFolder.newFolder(); + + // Add file reference that is not in use and should be deleted (older than 14 days) + File filereferenceDir = createFilereferenceOnDisk(new File(fileReferencesDir, "foo"), Instant.now().minus(Duration.ofDays(15))); + // Add file reference that is not in use, but should not be deleted (not older than 14 days) + File filereferenceDir2 = createFilereferenceOnDisk(new File(fileReferencesDir, "baz"), Instant.now()); + + tenantRepository.addTenant(tenant1); + Provisioner provisioner = new SessionHandlerTest.MockProvisioner(); + applicationRepository = new ApplicationRepository(tenantRepository, provisioner, orchestrator, clock); + timeoutBudget = new TimeoutBudget(clock, Duration.ofSeconds(60)); + + // TODO: Deploy an app with a bundle or file that will be a file reference, too much missing in test setup to get this working now + PrepareParams prepareParams = new PrepareParams.Builder().applicationId(applicationId()).ignoreValidationErrors(true).build(); + deployApp(new File("src/test/apps/app"), prepareParams); + + Set<String> toBeDeleted = applicationRepository.deleteUnusedFiledistributionReferences(fileReferencesDir, Duration.ofHours(48)); + assertEquals(Collections.singleton("foo"), toBeDeleted); + assertFalse(filereferenceDir.exists()); + assertTrue(filereferenceDir2.exists()); + } + + private File createFilereferenceOnDisk(File filereferenceDir, Instant lastModifiedTime) { + assertTrue(filereferenceDir.mkdir()); + File bar = new File(filereferenceDir, "file"); + IOUtils.writeFile(bar, Utf8.toBytes("test")); + assertTrue(filereferenceDir.setLastModified(lastModifiedTime.toEpochMilli())); + return filereferenceDir; + } + + @Test + public void delete() { + { + PrepareResult result = deployApp(testApp); + long sessionId = result.sessionId(); + Tenant tenant = tenantRepository.getTenant(applicationId().tenant()); + LocalSession applicationData = tenant.getLocalSessionRepo().getSession(sessionId); + assertNotNull(applicationData); + assertNotNull(applicationData.getApplicationId()); + assertNotNull(tenant.getRemoteSessionRepo().getSession(sessionId)); + assertNotNull(applicationRepository.getActiveSession(applicationId())); + + // Delete app and verify that it has been deleted from repos and provisioner + assertTrue(applicationRepository.delete(applicationId())); + assertNull(applicationRepository.getActiveSession(applicationId())); + assertNull(tenant.getLocalSessionRepo().getSession(sessionId)); + assertNull(tenant.getRemoteSessionRepo().getSession(sessionId)); + assertTrue(provisioner.removed); + assertEquals(tenant.getName(), provisioner.lastApplicationId.tenant()); + assertEquals(applicationId(), provisioner.lastApplicationId); + + assertFalse(applicationRepository.delete(applicationId())); + } + + { + deployApp(testApp); + assertTrue(applicationRepository.delete(applicationId())); + deployApp(testApp); + + // Deploy another app (with id fooId) + ApplicationId fooId = applicationId(tenant2); + PrepareParams prepareParams2 = new PrepareParams.Builder().applicationId(fooId).build(); + deployApp(testApp, prepareParams2); + assertNotNull(applicationRepository.getActiveSession(fooId)); + + // Delete app with id fooId, should not affect original app + assertTrue(applicationRepository.delete(fooId)); + assertEquals(fooId, provisioner.lastApplicationId); + assertNotNull(applicationRepository.getActiveSession(applicationId())); + + assertTrue(applicationRepository.delete(applicationId())); + } + + { + PrepareResult prepareResult = deployApp(testApp); + try { + applicationRepository.delete(applicationId(), Duration.ZERO); + fail("Should have gotten an exception"); + } catch (InternalServerException e) { + assertEquals("test1.testapp was not deleted (waited PT0S), session " + prepareResult.sessionId(), e.getMessage()); + } + + // No active session or remote session (deleted in step above), but an exception was thrown above + // A new delete should cleanup and be successful + RemoteSession activeSession = applicationRepository.getActiveSession(applicationId()); + assertNull(activeSession); + Tenant tenant = tenantRepository.getTenant(applicationId().tenant()); + assertNull(tenant.getRemoteSessionRepo().getSession(prepareResult.sessionId())); + + assertTrue(applicationRepository.delete(applicationId())); + } + } + + @Test + public void testDeletingInactiveSessions() throws IOException { + ManualClock clock = new ManualClock(Instant.now()); + ConfigserverConfig configserverConfig = + new ConfigserverConfig(new ConfigserverConfig.Builder() + .configServerDBDir(temporaryFolder.newFolder("serverdb").getAbsolutePath()) + .configDefinitionsDir(temporaryFolder.newFolder("configdefinitions").getAbsolutePath()) + .sessionLifetime(60)); + DeployTester tester = new DeployTester(configserverConfig, clock); + tester.deployApp("src/test/apps/app", clock.instant()); // session 2 (numbering starts at 2) + + clock.advance(Duration.ofSeconds(10)); + Optional<Deployment> deployment2 = tester.redeployFromLocalActive(); + + assertTrue(deployment2.isPresent()); + deployment2.get().activate(); // session 3 + long activeSessionId = tester.tenant().getApplicationRepo().requireActiveSessionOf(tester.applicationId()); + + clock.advance(Duration.ofSeconds(10)); + Optional<com.yahoo.config.provision.Deployment> deployment3 = tester.redeployFromLocalActive(); + assertTrue(deployment3.isPresent()); + deployment3.get().prepare(); // session 4 (not activated) + + LocalSession deployment3session = ((com.yahoo.vespa.config.server.deploy.Deployment) deployment3.get()).session(); + assertNotEquals(activeSessionId, deployment3session); + // No change to active session id + assertEquals(activeSessionId, tester.tenant().getApplicationRepo().requireActiveSessionOf(tester.applicationId())); + LocalSessionRepo localSessionRepo = tester.tenant().getLocalSessionRepo(); + assertEquals(3, localSessionRepo.getSessions().size()); + + clock.advance(Duration.ofHours(1)); // longer than session lifetime + + // All sessions except 3 should be removed after the call to deleteExpiredLocalSessions + tester.applicationRepository().deleteExpiredLocalSessions(); + Collection<LocalSession> sessions = localSessionRepo.getSessions(); + assertEquals(1, sessions.size()); + ArrayList<LocalSession> localSessions = new ArrayList<>(sessions); + LocalSession localSession = localSessions.get(0); + assertEquals(3, localSession.getSessionId()); + + // There should be no expired remote sessions in the common case + assertEquals(0, tester.applicationRepository().deleteExpiredRemoteSessions(clock, Duration.ofSeconds(0))); + + // Deploy, but do not activate + Optional<com.yahoo.config.provision.Deployment> deployment4 = tester.redeployFromLocalActive(); + assertTrue(deployment4.isPresent()); + deployment4.get().prepare(); // session 5 (not activated) + + assertEquals(2, localSessionRepo.getSessions().size()); + localSessionRepo.deleteSession(localSession); + assertEquals(1, localSessionRepo.getSessions().size()); + + // Check that trying to expire when there are no active sessions works + tester.applicationRepository().deleteExpiredLocalSessions(); + } + + @Test + public void testMetrics() { + MockMetric actual = new MockMetric(); + applicationRepository = new ApplicationRepository(tenantRepository, + provisioner, + orchestrator, + new ConfigserverConfig(new ConfigserverConfig.Builder()), + new MockLogRetriever(), + new ManualClock(), + new MockTesterClient(), + actual); + deployApp(testAppLogServerWithContainer); + Map<String, ?> context = Map.of("applicationId", "test1.testapp.default", + "tenantName", "test1", + "app", "testapp.default", + "zone", "prod.default"); + MockMetric expected = new MockMetric(); + expected.set("deployment.prepareMillis", 0L, expected.createContext(context)); + expected.set("deployment.activateMillis", 0L, expected.createContext(context)); + assertEquals(expected.values, actual.values); + } + + @Test + public void deletesApplicationRoles() { + var tenant = tenantRepository.getTenant(tenant1); + var applicationId = applicationId(tenant1); + var prepareParams = new PrepareParams.Builder().applicationId(applicationId) + .applicationRoles(ApplicationRoles.fromString("hostRole","containerRole")).build(); + deployApp(testApp, prepareParams); + var approlesStore = new ApplicationRolesStore(tenant.getCurator(), tenant.getPath()); + var appRoles = approlesStore.readApplicationRoles(applicationId); + + // App roles present after deploy + assertTrue(appRoles.isPresent()); + assertEquals("hostRole", appRoles.get().applicationHostRole()); + assertEquals("containerRole", appRoles.get().applicationContainerRole()); + + assertTrue(applicationRepository.delete(applicationId)); + + // App roles deleted on application delete + assertTrue(approlesStore.readApplicationRoles(applicationId).isEmpty()); + } + + @Test + public void require_that_provision_info_can_be_read() { + prepareAndActivate(testAppJdiscOnly); + + TenantName tenantName = applicationId().tenant(); + Tenant tenant = tenantRepository.getTenant(tenantName); + LocalSession session = tenant.getLocalSessionRepo().getSession(tenant.getApplicationRepo().requireActiveSessionOf(applicationId())); + + List<NetworkPorts.Allocation> list = new ArrayList<>(); + list.add(new NetworkPorts.Allocation(8080, "container", "container/container.0", "http")); + list.add(new NetworkPorts.Allocation(19070, "configserver", "admin/configservers/configserver.0", "rpc")); + list.add(new NetworkPorts.Allocation(19071, "configserver", "admin/configservers/configserver.0", "http")); + list.add(new NetworkPorts.Allocation(19080, "logserver", "admin/logserver", "rpc")); + list.add(new NetworkPorts.Allocation(19081, "logserver", "admin/logserver", "unused/1")); + list.add(new NetworkPorts.Allocation(19082, "logserver", "admin/logserver", "unused/2")); + list.add(new NetworkPorts.Allocation(19083, "logserver", "admin/logserver", "unused/3")); + list.add(new NetworkPorts.Allocation(19089, "logd", "hosts/mytesthost/logd", "http")); + list.add(new NetworkPorts.Allocation(19090, "configproxy", "hosts/mytesthost/configproxy", "rpc")); + list.add(new NetworkPorts.Allocation(19092, "metricsproxy-container", "admin/metrics/mytesthost", "http")); + list.add(new NetworkPorts.Allocation(19093, "metricsproxy-container", "admin/metrics/mytesthost", "http/1")); + list.add(new NetworkPorts.Allocation(19094, "metricsproxy-container", "admin/metrics/mytesthost", "rpc/admin")); + list.add(new NetworkPorts.Allocation(19095, "metricsproxy-container", "admin/metrics/mytesthost", "rpc/metrics")); + list.add(new NetworkPorts.Allocation(19097, "config-sentinel", "hosts/mytesthost/sentinel", "rpc")); + list.add(new NetworkPorts.Allocation(19098, "config-sentinel", "hosts/mytesthost/sentinel", "http")); + list.add(new NetworkPorts.Allocation(19099, "slobrok", "admin/slobrok.0", "rpc")); + list.add(new NetworkPorts.Allocation(19100, "container", "container/container.0", "http/1")); + list.add(new NetworkPorts.Allocation(19101, "container", "container/container.0", "messaging")); + list.add(new NetworkPorts.Allocation(19102, "container", "container/container.0", "rpc/admin")); + list.add(new NetworkPorts.Allocation(19103, "slobrok", "admin/slobrok.0", "http")); + + AllocatedHosts info = session.getAllocatedHosts(); + assertNotNull(info); + assertThat(info.getHosts().size(), is(1)); + assertTrue(info.getHosts().contains(new HostSpec("mytesthost", + Collections.emptyList(), + Optional.empty()))); + Optional<NetworkPorts> portsCopy = info.getHosts().iterator().next().networkPorts(); + assertTrue(portsCopy.isPresent()); + assertThat(portsCopy.get().allocations(), is(list)); + } + + @Test + public void testActivationOfUnpreparedSession() { + // Needed so we can test that the original active session is still active after a failed activation + PrepareResult result = deployApp(testApp); + long firstSession = result.sessionId(); + + TimeoutBudget timeoutBudget = new TimeoutBudget(clock, Duration.ofSeconds(10)); + long sessionId = applicationRepository.createSession(applicationId(), timeoutBudget, testAppJdiscOnly); + exceptionRule.expect(IllegalStateException.class); + exceptionRule.expectMessage(containsString("tenant:test1 Session 3 is not prepared")); + applicationRepository.activate(tenantRepository.getTenant(tenant1), sessionId, timeoutBudget, false); + + RemoteSession activeSession = applicationRepository.getActiveSession(applicationId()); + assertEquals(firstSession, activeSession.getSessionId()); + assertEquals(Session.Status.ACTIVATE, activeSession.getStatus()); + } + + @Test + public void testActivationTimesOut() { + // Needed so we can test that the original active session is still active after a failed activation + PrepareResult result = deployApp(testAppJdiscOnly); + long firstSession = result.sessionId(); + + long sessionId = applicationRepository.createSession(applicationId(), timeoutBudget, testAppJdiscOnly); + applicationRepository.prepare(tenantRepository.getTenant(tenant1), sessionId, prepareParams(), clock.instant()); + exceptionRule.expect(RuntimeException.class); + exceptionRule.expectMessage(containsString("Timeout exceeded when trying to activate 'test1.testapp'")); + applicationRepository.activate(tenantRepository.getTenant(tenant1), sessionId, new TimeoutBudget(clock, Duration.ofSeconds(0)), false); + + RemoteSession activeSession = applicationRepository.getActiveSession(applicationId()); + assertEquals(firstSession, activeSession.getSessionId()); + assertEquals(Session.Status.ACTIVATE, activeSession.getStatus()); + } + + @Test + public void testActivationOfSessionCreatedFromNoLongerActiveSessionFails() { + TimeoutBudget timeoutBudget = new TimeoutBudget(clock, Duration.ofSeconds(10)); + + PrepareResult result1 = deployApp(testAppJdiscOnly); + result1.sessionId(); + + long sessionId2 = applicationRepository.createSessionFromExisting(applicationId(), + new BaseDeployLogger(), + false, + timeoutBudget); + // Deploy and activate another session + PrepareResult result2 = deployApp(testAppJdiscOnly); + result2.sessionId(); + + applicationRepository.prepare(tenantRepository.getTenant(tenant1), sessionId2, prepareParams(), clock.instant()); + exceptionRule.expect(ActivationConflictException.class); + exceptionRule.expectMessage(containsString("tenant:test1 app:testapp:default Cannot activate session 3 because the currently active session (4) has changed since session 3 was created (was 2 at creation time)")); + applicationRepository.activate(tenantRepository.getTenant(tenant1), sessionId2, timeoutBudget, false); + } + + @Test + public void testAlreadyActivatedSession() { + PrepareResult result = deployApp(testAppJdiscOnly); + long sessionId = result.sessionId(); + + exceptionRule.expect(IllegalStateException.class); + exceptionRule.expectMessage(containsString("tenant:test1 app:testapp:default Session 2 is already active")); + applicationRepository.activate(tenantRepository.getTenant(tenant1), sessionId, timeoutBudget, false); + } + + @Test + public void testThatPreviousSessionIsDeactivated() { + deployApp(testAppJdiscOnly); + Session firstSession = applicationRepository.getActiveSession(applicationId()); + + deployApp(testAppJdiscOnly); + + assertEquals(Session.Status.DEACTIVATE, firstSession.getStatus()); + } + + private ApplicationRepository createApplicationRepository() { + return new ApplicationRepository(tenantRepository, + provisioner, + orchestrator, + new ConfigserverConfig(new ConfigserverConfig.Builder()), + new MockLogRetriever(), + clock, + new MockTesterClient(), + new NullMetric()); + } + + private PrepareResult prepareAndActivate(File application) { + return applicationRepository.deploy(application, prepareParams(), false, Instant.now()); + } + + private PrepareResult deployApp(File applicationPackage) { + return deployApp(applicationPackage, prepareParams()); + } + + private PrepareResult deployApp(File applicationPackage, PrepareParams prepareParams) { + return applicationRepository.deploy(applicationPackage, prepareParams); + } + + private PrepareParams prepareParams() { + return new PrepareParams.Builder().applicationId(applicationId()).build(); + } + + private ApplicationId applicationId() { + return ApplicationId.from(tenant1, ApplicationName.from("testapp"), InstanceName.defaultName()); + } + + private ApplicationId applicationId(TenantName tenantName) { + return ApplicationId.from(tenantName, ApplicationName.from("testapp"), InstanceName.defaultName()); + } + + private ApplicationMetaData getApplicationMetaData(ApplicationId applicationId, long sessionId) { + Tenant tenant = tenantRepository.getTenant(applicationId.tenant()); + return applicationRepository.getMetadataFromLocalSession(tenant, sessionId); + } + + + /** Stores all added or set values for each metric and context. */ + static class MockMetric implements Metric { + + final Map<String, Map<Map<String, ?>, Number>> values = new HashMap<>(); + + @Override + public void set(String key, Number val, Metric.Context ctx) { + values.putIfAbsent(key, new HashMap<>()); + values.get(key).put(((Context) ctx).point, val); + } + + @Override + public void add(String key, Number val, Metric.Context ctx) { + throw new UnsupportedOperationException(); + } + + @Override + public Context createContext(Map<String, ?> properties) { + return new Context(properties); + } + + + private static class Context implements Metric.Context { + + private final Map<String, ?> point; + + public Context(Map<String, ?> point) { + this.point = Map.copyOf(point); + } + + } + + } + +} diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java index 900e0e8d8a5..5771303ec4c 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java @@ -36,6 +36,7 @@ import com.yahoo.vespa.config.server.tenant.Tenant; import com.yahoo.vespa.config.server.tenant.TenantRepository; import com.yahoo.vespa.curator.Curator; import com.yahoo.vespa.curator.mock.MockCurator; +import org.hamcrest.core.Is; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -485,7 +486,7 @@ public class ApplicationRepositoryTest { } @Test - public void testActivationofSessionCreatedFromNoLongerActiveSessionFails() { + public void testActivationOfSessionCreatedFromNoLongerActiveSessionFails() { TimeoutBudget timeoutBudget = new TimeoutBudget(clock, Duration.ofSeconds(10)); PrepareResult result1 = deployApp(testAppJdiscOnly); @@ -505,6 +506,26 @@ public class ApplicationRepositoryTest { applicationRepository.activate(tenantRepository.getTenant(tenant1), sessionId2, timeoutBudget, false); } + @Test + public void testAlreadyActivatedSession() { + PrepareResult result = deployApp(testAppJdiscOnly); + long sessionId = result.sessionId(); + + exceptionRule.expect(IllegalStateException.class); + exceptionRule.expectMessage(containsString("tenant:test1 app:testapp:default Session 2 is already active")); + applicationRepository.activate(tenantRepository.getTenant(tenant1), sessionId, timeoutBudget, false); + } + + @Test + public void testThatPreviousSessionIsDeactivated() { + deployApp(testAppJdiscOnly); + Session firstSession = applicationRepository.getActiveSession(applicationId()); + + deployApp(testAppJdiscOnly); + + assertEquals(Session.Status.DEACTIVATE, firstSession.getStatus()); + } + private ApplicationRepository createApplicationRepository() { return new ApplicationRepository(tenantRepository, provisioner, 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 index 175f0337d41..2d3590ee520 100644 --- 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 @@ -43,7 +43,6 @@ import com.yahoo.vespa.curator.mock.MockCurator; import com.yahoo.vespa.model.VespaModelFactory; import org.hamcrest.core.Is; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -55,18 +54,15 @@ import java.util.Collections; import java.util.Optional; import java.util.Set; -import static com.yahoo.jdisc.Response.Status.BAD_REQUEST; import static com.yahoo.jdisc.Response.Status.METHOD_NOT_ALLOWED; import static com.yahoo.jdisc.Response.Status.NOT_FOUND; import static com.yahoo.jdisc.Response.Status.OK; -import static com.yahoo.vespa.config.server.http.SessionHandlerTest.createTestRequest; import static com.yahoo.vespa.config.server.http.SessionHandlerTest.Cmd; -import static com.yahoo.vespa.config.server.http.SessionHandlerTest.FailingMockProvisioner; +import static com.yahoo.vespa.config.server.http.SessionHandlerTest.createTestRequest; import static com.yahoo.vespa.config.server.http.SessionHandlerTest.getRenderedString; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; @@ -110,13 +106,6 @@ public class SessionActiveHandlerTest { } @Test - public void testThatPreviousSessionIsDeactivated() throws Exception { - RemoteSession firstSession = activateAndAssertOK(90, 0); - activateAndAssertOK(91, 90); - assertThat(firstSession.getStatus(), Is.is(Session.Status.DEACTIVATE)); - } - - @Test public void testForceActivationWithActivationInBetween() throws Exception { activateAndAssertOK(90, 0); activateAndAssertOK(92, 89, "?force=true"); @@ -129,15 +118,6 @@ public class SessionActiveHandlerTest { } @Test - public void testAlreadyActivatedSession() throws Exception { - activateAndAssertOK(1, 0); - HttpResponse response = handler.handle(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")); - } - - @Test public void testActivation() throws Exception { activateAndAssertOK(1, 0); } @@ -149,15 +129,6 @@ public class SessionActiveHandlerTest { testUnsupportedMethod(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, BAD_REQUEST, HttpErrorResponse.errorCodes.BAD_REQUEST, "Cannot activate application"); - assertFalse(hostProvisioner.activated); - } - private RemoteSession createRemoteSession(long sessionId, Session.Status status, SessionZooKeeperClient zkClient) throws IOException { zkClient.writeStatus(status); ZooKeeperClient zkC = new ZooKeeperClient(componentRegistry.getConfigCurator(), new BaseDeployLogger(), false, @@ -188,18 +159,6 @@ public class SessionActiveHandlerTest { return activateRequest; } - private void activateAndAssertErrorPut(long sessionId, long previousSessionId, - int statusCode, HttpErrorResponse.errorCodes errorCode, String expectedError) throws Exception { - ActivateRequest activateRequest = new ActivateRequest(sessionId, previousSessionId, ""); - activateRequest.invoke(); - HttpResponse actResponse = activateRequest.getActResponse(); - RemoteSession session = activateRequest.getSession(); - assertThat(actResponse.getStatus(), Is.is(statusCode)); - String message = getRenderedString(actResponse); - assertThat(message, Is.is("{\"error-code\":\"" + errorCode.name() + "\",\"message\":\"" + expectedError + "\"}")); - assertThat(session.getStatus(), Is.is(Session.Status.PREPARE)); - } - private void testUnsupportedMethod(com.yahoo.container.jdisc.HttpRequest request) throws Exception { HttpResponse response = handler.handle(request); HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(response, @@ -255,7 +214,7 @@ public class SessionActiveHandlerTest { return metaData; } - ActivateRequest invoke() throws Exception { + void invoke() throws Exception { SessionZooKeeperClient zkClient = new MockSessionZKClient(curator, tenantName, sessionId, Optional.of(AllocatedHosts.withHosts(Set.of(new HostSpec("bar", Collections.emptyList(), Optional.empty()))))); @@ -264,13 +223,11 @@ public class SessionActiveHandlerTest { tenantRepository.getTenant(tenantName).getApplicationRepo().createApplication(deployData.getApplicationId()); metaData = localRepo.getSession(sessionId).getMetaData(); actResponse = handler.handle(createTestRequest(pathPrefix, HttpRequest.Method.PUT, Cmd.ACTIVE, sessionId, subPath)); - return this; } } - private RemoteSession activateAndAssertOK(long sessionId, long previousSessionId) throws Exception { - ActivateRequest activateRequest = activateAndAssertOKPut(sessionId, previousSessionId, ""); - return activateRequest.getSession(); + private void activateAndAssertOK(long sessionId, long previousSessionId) throws Exception { + activateAndAssertOKPut(sessionId, previousSessionId, ""); } private void activateAndAssertOK(long sessionId, long previousSessionId, String subPath) throws Exception { @@ -290,12 +247,6 @@ public class SessionActiveHandlerTest { assertThat(hostProvisioner.lastHosts.size(), is(1)); } - private void activateAndAssertError(long sessionId, long previousSessionId, int statusCode, HttpErrorResponse.errorCodes errorCode, String expectedError) throws Exception { - hostProvisioner.activated = false; - activateAndAssertErrorPut(sessionId, previousSessionId, statusCode, errorCode, expectedError); - assertFalse(hostProvisioner.activated); - } - private void writeApplicationId(SessionZooKeeperClient zkc, ApplicationId id) { zkc.writeApplicationId(id); } |