From 5f620f817dd9778b5fc45e433368c1bb4d6e9f45 Mon Sep 17 00:00:00 2001 From: Harald Musum Date: Wed, 11 Mar 2020 12:28:21 +0100 Subject: Add support for using a Docker image repository when deploying an application Preliminary support only, not completed --- .../vespa/config/server/ApplicationRepository.java | 4 +- .../vespa/config/server/deploy/Deployment.java | 38 ++++++------ .../config/server/deploy/ModelContextImpl.java | 7 +++ .../modelfactory/ActivatedModelsBuilder.java | 2 + .../config/server/modelfactory/ModelsBuilder.java | 14 +++-- .../server/modelfactory/PreparedModelsBuilder.java | 6 +- .../vespa/config/server/session/LocalSession.java | 6 ++ .../vespa/config/server/session/PrepareParams.java | 22 ++++++- .../vespa/config/server/session/RemoteSession.java | 1 + .../config/server/session/SessionFactoryImpl.java | 1 + .../config/server/session/SessionPreparer.java | 15 ++++- .../server/session/SessionZooKeeperClient.java | 16 +++++ .../vespa/config/server/ModelContextImplTest.java | 10 +++- .../vespa/config/server/deploy/DeployTester.java | 16 +++++ .../config/server/deploy/HostedDeployTest.java | 14 +++++ .../config/server/http/SessionHandlerTest.java | 29 +++++++-- .../server/http/v2/SessionPrepareHandlerTest.java | 68 +++++++++++----------- 17 files changed, 196 insertions(+), 73 deletions(-) (limited to 'configserver') diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java b/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java index d3f37eb320e..b10726cd73b 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java @@ -314,7 +314,9 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye tenant.getLocalSessionRepo().addSession(newSession); return Optional.of(Deployment.unprepared(newSession, this, hostProvisioner, tenant, timeout, clock, - false /* don't validate as this is already deployed */, newSession.getVespaVersion(), + false /* don't validate as this is already deployed */, + newSession.getDockerImageRepository(), + newSession.getVespaVersion(), bootstrap)); } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/Deployment.java b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/Deployment.java index 89d7c349d6b..9a46ce099f8 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/Deployment.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/Deployment.java @@ -6,8 +6,6 @@ import com.yahoo.config.application.api.DeployLogger; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.HostFilter; import com.yahoo.config.provision.Provisioner; -import com.yahoo.config.provision.zone.ZoneId; -import com.yahoo.jdisc.Metric; import com.yahoo.log.LogLevel; import com.yahoo.transaction.NestedTransaction; import com.yahoo.transaction.Transaction; @@ -25,7 +23,6 @@ import com.yahoo.vespa.curator.Lock; import java.time.Clock; import java.time.Duration; -import java.time.Instant; import java.util.Optional; import java.util.logging.Logger; @@ -49,7 +46,10 @@ public class Deployment implements com.yahoo.config.provision.Deployment { private final Duration timeout; private final Clock clock; private final DeployLogger logger = new SilentDeployLogger(); - + + /** The repository part of docker image this application should run on. Version is separate from image repo */ + Optional dockerImageRepository; + /** The Vespa version this application should run on */ private final Version version; @@ -65,8 +65,8 @@ public class Deployment implements com.yahoo.config.provision.Deployment { private Deployment(LocalSession session, ApplicationRepository applicationRepository, Optional hostProvisioner, Tenant tenant, - Duration timeout, Clock clock, boolean prepared, boolean validate, Version version, - boolean isBootstrap) { + Duration timeout, Clock clock, boolean prepared, boolean validate, + Optional dockerImageRepository, Version version, boolean isBootstrap) { this.session = session; this.applicationRepository = applicationRepository; this.hostProvisioner = hostProvisioner; @@ -75,23 +75,26 @@ public class Deployment implements com.yahoo.config.provision.Deployment { this.clock = clock; this.prepared = prepared; this.validate = validate; + this.dockerImageRepository = dockerImageRepository; this.version = version; this.isBootstrap = isBootstrap; } public static Deployment unprepared(LocalSession session, ApplicationRepository applicationRepository, Optional hostProvisioner, Tenant tenant, - Duration timeout, Clock clock, boolean validate, Version version, + Duration timeout, Clock clock, boolean validate, + Optional dockerImageRepository, Version version, boolean isBootstrap) { return new Deployment(session, applicationRepository, hostProvisioner, tenant, - timeout, clock, false, validate, version, isBootstrap); + timeout, clock, false, validate, dockerImageRepository, version, isBootstrap); } public static Deployment prepared(LocalSession session, ApplicationRepository applicationRepository, Optional hostProvisioner, Tenant tenant, Duration timeout, Clock clock, boolean isBootstrap) { return new Deployment(session, applicationRepository, hostProvisioner, tenant, - timeout, clock, true, true, session.getVespaVersion(), isBootstrap); + timeout, clock, true, true, session.getDockerImageRepository(), + session.getVespaVersion(), isBootstrap); } public void setIgnoreSessionStaleFailure(boolean ignoreSessionStaleFailure) { @@ -105,16 +108,13 @@ public class Deployment implements com.yahoo.config.provision.Deployment { try (ActionTimer timer = applicationRepository.timerFor(session.getApplicationId(), "deployment.prepareMillis")) { TimeoutBudget timeoutBudget = new TimeoutBudget(clock, timeout); - session.prepare(logger, - new PrepareParams.Builder().applicationId(session.getApplicationId()) - .timeoutBudget(timeoutBudget) - .ignoreValidationErrors(!validate) - .vespaVersion(version.toString()) - .isBootstrap(isBootstrap) - .build(), - Optional.empty(), - tenant.getPath(), - clock.instant()); + PrepareParams.Builder params = new PrepareParams.Builder().applicationId(session.getApplicationId()) + .timeoutBudget(timeoutBudget) + .ignoreValidationErrors(!validate) + .vespaVersion(version.toString()) + .isBootstrap(isBootstrap); + dockerImageRepository.ifPresent(params::dockerImageRepository); + session.prepare(logger, params.build(), Optional.empty(), tenant.getPath(), clock.instant()); this.prepared = true; } } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java index db90d3f9976..55a1482cde8 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java @@ -43,6 +43,8 @@ public class ModelContextImpl implements ModelContext { private final ModelContext.Properties properties; private final Optional appDir; + private final Optional wantedDockerImageRepository; + /** The version of Vespa we are building a model for */ private final Version modelVespaVersion; @@ -64,6 +66,7 @@ public class ModelContextImpl implements ModelContext { Optional hostProvisioner, ModelContext.Properties properties, Optional appDir, + Optional wantedDockerImageRepository, Version modelVespaVersion, Version wantedNodeVespaVersion) { this.applicationPackage = applicationPackage; @@ -75,6 +78,7 @@ public class ModelContextImpl implements ModelContext { this.hostProvisioner = hostProvisioner; this.properties = properties; this.appDir = appDir; + this.wantedDockerImageRepository = wantedDockerImageRepository; this.modelVespaVersion = modelVespaVersion; this.wantedNodeVespaVersion = wantedNodeVespaVersion; } @@ -111,6 +115,9 @@ public class ModelContextImpl implements ModelContext { @Override public Optional appDir() { return appDir; } + @Override + public Optional wantedDockerImageRepository() { return wantedDockerImageRepository; } + @Override public Version modelVespaVersion() { return modelVespaVersion; } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ActivatedModelsBuilder.java b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ActivatedModelsBuilder.java index a2fc2bfd6a0..70faf3ff36f 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ActivatedModelsBuilder.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ActivatedModelsBuilder.java @@ -83,6 +83,7 @@ public class ActivatedModelsBuilder extends ModelsBuilder { protected Application buildModelVersion(ModelFactory modelFactory, ApplicationPackage applicationPackage, ApplicationId applicationId, + Optional wantedDockerImageRepository, Version wantedNodeVespaVersion, Optional ignored, // Ignored since we have this in the app package for activated models Instant now) { @@ -99,6 +100,7 @@ public class ActivatedModelsBuilder extends ModelsBuilder { createStaticProvisioner(applicationPackage.getAllocatedHosts(), modelContextProperties), modelContextProperties, Optional.empty(), + wantedDockerImageRepository, modelFactory.version(), wantedNodeVespaVersion); MetricUpdater applicationMetricUpdater = metrics.getOrCreateMetricUpdater(Metrics.createDimensions(applicationId)); diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ModelsBuilder.java b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ModelsBuilder.java index 2de64ed145c..5257fc6b72e 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ModelsBuilder.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ModelsBuilder.java @@ -72,6 +72,7 @@ public abstract class ModelsBuilder { * and assigns to this SettableOptional such that it can be used after this method returns */ public List buildModels(ApplicationId applicationId, + Optional dockerImageRepository, Version wantedNodeVespaVersion, ApplicationPackage applicationPackage, SettableOptional allocatedHosts, @@ -104,8 +105,9 @@ public abstract class ModelsBuilder { int majorVersion = majorVersions.get(i); try { allApplicationModels.addAll(buildModelVersions(keepMajorVersion(majorVersion, versions), - applicationId, wantedNodeVespaVersion, applicationPackage, - allocatedHosts, now, buildLatestModelForThisMajor, majorVersion)); + applicationId, dockerImageRepository, wantedNodeVespaVersion, + applicationPackage, allocatedHosts, now, + buildLatestModelForThisMajor, majorVersion)); buildLatestModelForThisMajor = false; // We have successfully built latest model version, do it only for this major } catch (OutOfCapacityException | ApplicationLockException | TransientException e) { @@ -146,6 +148,7 @@ public abstract class ModelsBuilder { // versions is the set of versions for one particular major version private List buildModelVersions(Set versions, ApplicationId applicationId, + Optional wantedDockerImageRepository, Version wantedNodeVespaVersion, ApplicationPackage applicationPackage, SettableOptional allocatedHosts, @@ -160,6 +163,7 @@ public abstract class ModelsBuilder { MODELRESULT latestModelVersion = buildModelVersion(modelFactoryRegistry.getFactory(latest.get()), applicationPackage, applicationId, + wantedDockerImageRepository, wantedNodeVespaVersion, allocatedHosts.asOptional(), now); @@ -182,6 +186,7 @@ public abstract class ModelsBuilder { modelVersion = buildModelVersion(modelFactoryRegistry.getFactory(version), applicationPackage, applicationId, + wantedDockerImageRepository, wantedNodeVespaVersion, allocatedHosts.asOptional(), now); @@ -236,9 +241,8 @@ public abstract class ModelsBuilder { } protected abstract MODELRESULT buildModelVersion(ModelFactory modelFactory, ApplicationPackage applicationPackage, - ApplicationId applicationId, - Version wantedNodeVespaVersion, - Optional allocatedHosts, + ApplicationId applicationId, Optional dockerImageRepository, + Version wantedNodeVespaVersion, Optional allocatedHosts, Instant now); /** diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/PreparedModelsBuilder.java b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/PreparedModelsBuilder.java index 9cacb78e53c..598cec325b6 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/PreparedModelsBuilder.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/PreparedModelsBuilder.java @@ -82,8 +82,9 @@ public class PreparedModelsBuilder extends ModelsBuilder wantedDockerImageRepository, + Version wantedNodeVespaVersion, Optional allocatedHosts, Instant now) { Version modelVersion = modelFactory.version(); @@ -101,6 +102,7 @@ public class PreparedModelsBuilder extends ModelsBuilder { zooKeeperClient.writeVespaVersion(version); } + public void setDockerImageRepository(Optional dockerImageRepository) { + zooKeeperClient.writeDockerImageRepository(dockerImageRepository); + } + public enum Mode { READ, WRITE } @@ -148,6 +152,8 @@ public class LocalSession extends Session implements Comparable { public ApplicationId getApplicationId() { return zooKeeperClient.readApplicationId(); } + public Optional getDockerImageRepository() { return zooKeeperClient.readDockerImageRepository(); } + public Version getVespaVersion() { return zooKeeperClient.readVespaVersion(); } public AllocatedHosts getAllocatedHosts() { diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/PrepareParams.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/PrepareParams.java index 49f59773bca..e217bb39b39 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/PrepareParams.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/PrepareParams.java @@ -35,6 +35,7 @@ public final class PrepareParams { static final String CONTAINER_ENDPOINTS_PARAM_NAME = "containerEndpoints"; static final String TLS_SECRETS_KEY_NAME_PARAM_NAME = "tlsSecretsKeyName"; static final String ENDPOINT_CERTIFICATE_METADATA_PARAM_NAME = "endpointCertificateMetadata"; + static final String DOCKER_IMAGE_REPOSITORY = "dockerImageRepository"; private final ApplicationId applicationId; private final TimeoutBudget timeoutBudget; @@ -46,11 +47,13 @@ public final class PrepareParams { private final List containerEndpoints; private final Optional tlsSecretsKeyName; private final Optional endpointCertificateMetadata; + private final Optional dockerImageRepository; private PrepareParams(ApplicationId applicationId, TimeoutBudget timeoutBudget, boolean ignoreValidationErrors, boolean dryRun, boolean verbose, boolean isBootstrap, Optional vespaVersion, List containerEndpoints, Optional tlsSecretsKeyName, - Optional endpointCertificateMetadata) { + Optional endpointCertificateMetadata, + Optional dockerImageRepository) { this.timeoutBudget = timeoutBudget; this.applicationId = applicationId; this.ignoreValidationErrors = ignoreValidationErrors; @@ -61,6 +64,7 @@ public final class PrepareParams { this.containerEndpoints = containerEndpoints; this.tlsSecretsKeyName = tlsSecretsKeyName; this.endpointCertificateMetadata = endpointCertificateMetadata; + this.dockerImageRepository = dockerImageRepository; } public static class Builder { @@ -75,6 +79,7 @@ public final class PrepareParams { private List containerEndpoints = List.of(); private Optional tlsSecretsKeyName = Optional.empty(); private Optional endpointCertificateMetadata = Optional.empty(); + private Optional dockerImageRepository = Optional.empty(); public Builder() { } @@ -142,9 +147,16 @@ public final class PrepareParams { return this; } + public Builder dockerImageRepository(String dockerImageRepository) { + if (dockerImageRepository == null) return this; + this.dockerImageRepository = Optional.of(dockerImageRepository); + return this; + } + public PrepareParams build() { return new PrepareParams(applicationId, timeoutBudget, ignoreValidationErrors, dryRun, - verbose, isBootstrap, vespaVersion, containerEndpoints, tlsSecretsKeyName, endpointCertificateMetadata); + verbose, isBootstrap, vespaVersion, containerEndpoints, tlsSecretsKeyName, + endpointCertificateMetadata, dockerImageRepository); } } @@ -159,6 +171,7 @@ public final class PrepareParams { .containerEndpoints(request.getProperty(CONTAINER_ENDPOINTS_PARAM_NAME)) .tlsSecretsKeyName(request.getProperty(TLS_SECRETS_KEY_NAME_PARAM_NAME)) .endpointCertificateMetadata(request.getProperty(ENDPOINT_CERTIFICATE_METADATA_PARAM_NAME)) + .dockerImageRepository(request.getProperty(DOCKER_IMAGE_REPOSITORY)) .build(); } @@ -219,4 +232,9 @@ public final class PrepareParams { public Optional endpointCertificateMetadata() { return endpointCertificateMetadata; } + + public Optional dockerImageRepository() { + return dockerImageRepository; + } + } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSession.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSession.java index 26f437920ad..39c15474f0e 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSession.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSession.java @@ -63,6 +63,7 @@ public class RemoteSession extends Session { Optional allocatedHosts = applicationPackage.getAllocatedHosts(); return ApplicationSet.fromList(applicationLoader.buildModels(zooKeeperClient.readApplicationId(), + zooKeeperClient.readDockerImageRepository(), zooKeeperClient.readVespaVersion(), applicationPackage, new SettableOptional<>(allocatedHosts), diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionFactoryImpl.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionFactoryImpl.java index cb1f6519f57..87dead8eed1 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionFactoryImpl.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionFactoryImpl.java @@ -131,6 +131,7 @@ public class SessionFactoryImpl implements SessionFactory, LocalSessionLoader { LocalSession session = create(existingApp, existingApplicationId, activeSessionId, internalRedeploy, timeoutBudget); session.setApplicationId(existingApplicationId); session.setVespaVersion(existingSession.getVespaVersion()); + session.setDockerImageRepository(existingSession.getDockerImageRepository()); return session; } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java index 0115876ded9..d2c7fe3718e 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java @@ -114,6 +114,7 @@ public class SessionPreparer { try { AllocatedHosts allocatedHosts = preparation.buildModels(now); preparation.makeResult(allocatedHosts); + System.out.println("Dry run: " + params.isDryRun()); if ( ! params.isDryRun()) { preparation.writeStateZK(); preparation.writeEndpointCertificateMetadataZK(); @@ -139,8 +140,11 @@ public class SessionPreparer { final Path tenantPath; final ApplicationId applicationId; + /** The repository part of docker image to be used for this deployment */ + final Optional dockerImageRepository; + /** The version of Vespa the application to be prepared specifies for its nodes */ - final com.yahoo.component.Version vespaVersion; + final Version vespaVersion; final ContainerEndpointsCache containerEndpoints; final Set endpointsSet; @@ -165,6 +169,7 @@ public class SessionPreparer { this.tenantPath = tenantPath; this.applicationId = params.getApplicationId(); + this.dockerImageRepository = params.dockerImageRepository(); this.vespaVersion = params.vespaVersion().orElse(Vtag.currentVersion); this.containerEndpoints = new ContainerEndpointsCache(tenantPath, curator); this.endpointCertificateMetadataStore = new EndpointCertificateMetadataStore(curator, tenantPath); @@ -223,7 +228,7 @@ public class SessionPreparer { AllocatedHosts buildModels(Instant now) { SettableOptional allocatedHosts = new SettableOptional<>(); - this.modelResultList = preparedModelsBuilder.buildModels(applicationId, vespaVersion, + this.modelResultList = preparedModelsBuilder.buildModels(applicationId, dockerImageRepository, vespaVersion, applicationPackage, allocatedHosts, now); checkTimeout("build models"); return allocatedHosts.get(); @@ -239,6 +244,7 @@ public class SessionPreparer { writeStateToZooKeeper(context.getSessionZooKeeperClient(), applicationPackage, applicationId, + dockerImageRepository, vespaVersion, logger, prepareResult.getFileRegistries(), @@ -281,15 +287,18 @@ public class SessionPreparer { private void writeStateToZooKeeper(SessionZooKeeperClient zooKeeperClient, ApplicationPackage applicationPackage, ApplicationId applicationId, - com.yahoo.component.Version vespaVersion, + Optional dockerImageRepository, + Version vespaVersion, DeployLogger deployLogger, Map fileRegistryMap, AllocatedHosts allocatedHosts) { + System.out.println("DEBUG DEBUG"); ZooKeeperDeployer zkDeployer = zooKeeperClient.createDeployer(deployLogger); try { zkDeployer.deploy(applicationPackage, fileRegistryMap, allocatedHosts); zooKeeperClient.writeApplicationId(applicationId); zooKeeperClient.writeVespaVersion(vespaVersion); + zooKeeperClient.writeDockerImageRepository(dockerImageRepository); } catch (RuntimeException | IOException e) { zkDeployer.cleanup(); throw new RuntimeException("Error preparing session", e); diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClient.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClient.java index 727d4e6d7bc..36415512491 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClient.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClient.java @@ -41,6 +41,7 @@ public class SessionZooKeeperClient { static final String APPLICATION_ID_PATH = "applicationId"; private static final String VERSION_PATH = "version"; private static final String CREATE_TIME_PATH = "createTime"; + private static final String DOCKER_IMAGE_REPOSITORY_PATH = "dockerImageRepository"; private final Curator curator; private final ConfigCurator configCurator; private final Path sessionPath; @@ -165,6 +166,10 @@ public class SessionZooKeeperClient { return sessionPath.append(VERSION_PATH).getAbsolute(); } + private String dockerImageRepositoryPath() { + return sessionPath.append(DOCKER_IMAGE_REPOSITORY_PATH).getAbsolute(); + } + public void writeVespaVersion(Version version) { configCurator.putData(versionPath(), version.toString()); } @@ -174,6 +179,17 @@ public class SessionZooKeeperClient { return new Version(configCurator.getData(versionPath())); } + public Optional readDockerImageRepository() { + if ( ! configCurator.exists(dockerImageRepositoryPath())) return Optional.empty(); + String dockerImageRepository = configCurator.getData(dockerImageRepositoryPath()); + return dockerImageRepository.isEmpty() ? Optional.empty() : Optional.of(dockerImageRepository); + } + + public void writeDockerImageRepository(Optional dockerImageRepository) { + System.out.println("Writing docker image repo"); + dockerImageRepository.ifPresent(repo -> configCurator.putData(dockerImageRepositoryPath(), repo)); + } + // in seconds public long readCreateTime() { String path = getCreateTimePath(); 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 index 370e0bd3c0e..339c676000b 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/ModelContextImplTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/ModelContextImplTest.java @@ -20,6 +20,7 @@ import java.util.Set; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; @@ -60,8 +61,9 @@ public class ModelContextImplTest { flagSource, null), Optional.empty(), - new Version(6), - new Version(6)); + Optional.empty(), + new Version(7), + new Version(8)); assertTrue(context.applicationPackage() instanceof MockApplicationPackage); assertFalse(context.hostProvisioner().isPresent()); assertFalse(context.permanentApplicationPackage().isPresent()); @@ -75,6 +77,10 @@ public class ModelContextImplTest { assertFalse(context.properties().hostedVespa()); assertThat(context.properties().endpoints(), equalTo(endpoints)); assertThat(context.properties().isFirstTimeDeployment(), equalTo(false)); + + assertEquals(Optional.empty(), context.wantedDockerImageRepository()); + assertEquals(new Version(7), context.modelVespaVersion()); + assertEquals(new Version(8), context.wantedNodeVespaVersion()); } } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployTester.java b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployTester.java index 32b704dd551..85f07da0325 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployTester.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployTester.java @@ -216,12 +216,28 @@ public class DeployTester { return deployApp(applicationPath, vespaVersion, Instant.now()); } + + /** + * Do the initial "deploy" with the existing API-less code as the deploy API doesn't support first deploys yet. + */ + public PrepareResult deployApp(String applicationPath, String vespaVersion, String dockerImageRepository) { + return deployApp(applicationPath, vespaVersion, Instant.now(), dockerImageRepository); + } + /** * Do the initial "deploy" with the existing API-less code as the deploy API doesn't support first deploys yet. */ public PrepareResult deployApp(String applicationPath, String vespaVersion, Instant now) { + return deployApp(applicationPath, vespaVersion, now, null); + } + + /** + * Do the initial "deploy" with the existing API-less code as the deploy API doesn't support first deploys yet. + */ + public PrepareResult deployApp(String applicationPath, String vespaVersion, Instant now, String dockerImageRepository) { PrepareParams.Builder paramsBuilder = new PrepareParams.Builder() .applicationId(applicationId) + .dockerImageRepository(dockerImageRepository) .timeoutBudget(new TimeoutBudget(clock, Duration.ofSeconds(60))); if (vespaVersion != null) paramsBuilder.vespaVersion(vespaVersion); 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 index dd0c4eaf342..5498d4b0315 100644 --- 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 @@ -86,6 +86,20 @@ public class HostedDeployTest { assertTrue(tester.applicationRepository().getActiveSession(appId).getMetaData().isInternalRedeploy()); } + @Test + public void testDeployWithWantedDockerImageRepository() throws IOException { + CountingModelFactory modelFactory = createHostedModelFactory(Version.fromString("4.5.6"), Clock.systemUTC()); + DeployTester tester = new DeployTester(List.of(modelFactory), createConfigserverConfig()); + String dockerImageRepository = "docker.foo.com:4443/bar/baz"; + tester.deployApp("src/test/apps/hosted/", "4.5.6", dockerImageRepository); + + Optional deployment = tester.redeployFromLocalActive(tester.applicationId()); + assertTrue(deployment.isPresent()); + deployment.get().activate(); + assertEquals("4.5.6", ((Deployment) deployment.get()).session().getVespaVersion().toString()); + assertEquals(dockerImageRepository, ((Deployment) deployment.get()).session().getDockerImageRepository().get()); + } + @Test public void testDeployMultipleVersions() throws IOException { List modelFactories = List.of(createHostedModelFactory(Version.fromString("6.1.0")), 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 index 9a326a18dd5..70bba674778 100644 --- 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 @@ -42,6 +42,7 @@ import java.nio.charset.StandardCharsets; import java.time.Instant; import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.Optional; /** @@ -55,15 +56,27 @@ public class SessionHandlerTest { 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, InputStream data, Map properties) { + return HttpRequest.createTestRequest("http://" + hostname + ":" + port + path + "/" + id + "/" + + cmd.toString() + subPath, method, data, properties); + } + + 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, 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) { + public static HttpRequest createTestRequest(String path, com.yahoo.jdisc.http.HttpRequest.Method method, + Cmd cmd, Long id) { return createTestRequest(path, method, cmd, id, ""); } @@ -88,6 +101,7 @@ public class SessionHandlerTest { private ConfigChangeActions actions = new ConfigChangeActions(); private long createTime = System.currentTimeMillis() / 1000; private ApplicationId applicationId; + private Optional dockerImageRepository; public MockSession(long id, ApplicationPackage app) { this(id, app, new InMemoryFlagSource()); @@ -115,6 +129,7 @@ public class SessionHandlerTest { @Override public ConfigChangeActions prepare(DeployLogger logger, PrepareParams params, Optional application, Path tenantPath, Instant now) { status = Session.Status.PREPARE; + this.dockerImageRepository = params.dockerImageRepository(); if (doVerboseLogging) { logger.log(LogLevel.DEBUG, "debuglog"); } @@ -158,6 +173,10 @@ public class SessionHandlerTest { @Override public void delete(NestedTransaction transaction) { } + @Override + public Optional getDockerImageRepository() { + return dockerImageRepository; + } } public enum Cmd { 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 index 028f5f9eb8c..11cbdb03ccf 100644 --- 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 @@ -44,6 +44,7 @@ import java.time.Instant; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Optional; import static com.yahoo.jdisc.Response.Status.BAD_REQUEST; @@ -90,7 +91,7 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest { @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, HttpRequest.Method.PUT, Cmd.PREPARED, 9999L)); + HttpResponse response = request(HttpRequest.Method.PUT, 9999L); assertHttpStatusCodeErrorCodeAndMessage(response, NOT_FOUND, HttpErrorResponse.errorCodes.NOT_FOUND, "Session 9999 was not found"); } @@ -120,8 +121,7 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest { 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, HttpRequest.Method.PUT, Cmd.PREPARED, 1L)); + HttpResponse response = request(HttpRequest.Method.PUT, 1L); assertThat(session.getStatus(), is(Session.Status.PREPARE)); assertNotNull(response); assertThat(response.getStatus(), is(OK)); @@ -155,13 +155,11 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest { public void require_get_response_activate_url_on_ok() throws Exception { MockSession session = new MockSession(1, null); localRepo.addSession(session); - SessionHandler sessHandler = createHandler(); - sessHandler.handle(SessionHandlerTest.createTestRequest(pathPrefix, HttpRequest.Method.PUT, Cmd.PREPARED, 1L)); + request(HttpRequest.Method.PUT, 1L); session.setStatus(Session.Status.PREPARE); SessionZooKeeperClient zooKeeperClient = createSessionZooKeeperClient(session); zooKeeperClient.writeStatus(Session.Status.PREPARE); - HttpResponse getResponse = sessHandler.handle( - SessionHandlerTest.createTestRequest(pathPrefix, HttpRequest.Method.GET, Cmd.PREPARED, 1L)); + HttpResponse getResponse = request(HttpRequest.Method.GET, 1L); assertResponseContains(getResponse, "\"activate\":\"http://foo:1337" + pathPrefix + "1/active\",\"message\":\"Session 1" + preparedMessage); } @@ -170,19 +168,16 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest { public void require_get_response_error_on_not_prepared() throws Exception { MockSession session = new MockSession(1, null); localRepo.addSession(session); - SessionHandler sessHandler = createHandler(); session.setStatus(Session.Status.NEW); SessionZooKeeperClient zooKeeperClient = createSessionZooKeeperClient(session); zooKeeperClient.writeStatus(Session.Status.NEW); - HttpResponse getResponse = sessHandler.handle( - SessionHandlerTest.createTestRequest(pathPrefix, HttpRequest.Method.GET, Cmd.PREPARED, 1L)); + HttpResponse getResponse = request(HttpRequest.Method.GET, 1L); 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, HttpRequest.Method.GET, Cmd.PREPARED, 1L)); + getResponse = request(HttpRequest.Method.GET, 1L); assertHttpStatusCodeErrorCodeAndMessage(getResponse, BAD_REQUEST, HttpErrorResponse.errorCodes.BAD_REQUEST, "Session is active: 1"); @@ -193,9 +188,7 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest { MockSession session = new MockSession(1, null); localRepo.addSession(session); session.setStatus(Session.Status.ACTIVATE); - SessionHandler sessionHandler = createHandler(); - HttpResponse putResponse = sessionHandler.handle( - SessionHandlerTest.createTestRequest(pathPrefix, HttpRequest.Method.PUT, Cmd.PREPARED, 1L)); + HttpResponse putResponse = request(HttpRequest.Method.PUT, 1L); assertHttpStatusCodeErrorCodeAndMessage(putResponse, BAD_REQUEST, HttpErrorResponse.errorCodes.BAD_REQUEST, "Session is active: 1"); @@ -205,9 +198,7 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest { 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(); - HttpResponse getResponse = sessHandler.handle( - SessionHandlerTest.createTestRequest(pathPrefix, HttpRequest.Method.GET, Cmd.PREPARED, 9999L)); + HttpResponse getResponse = request(HttpRequest.Method.GET, 9999L); assertHttpStatusCodeErrorCodeAndMessage(getResponse, NOT_FOUND, HttpErrorResponse.errorCodes.NOT_FOUND, "Session 9999 was not found"); @@ -217,8 +208,7 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest { public void require_that_tenant_is_in_response() throws Exception { MockSession session = new MockSession(1, null); localRepo.addSession(session); - HttpResponse response = createHandler().handle( - SessionHandlerTest.createTestRequest(pathPrefix, HttpRequest.Method.PUT, Cmd.PREPARED, 1L)); + HttpResponse response = request(HttpRequest.Method.PUT, 1L); assertNotNull(response); assertThat(response.getStatus(), is(OK)); assertThat(session.getStatus(), is(Session.Status.PREPARE)); @@ -242,8 +232,7 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest { localRepoDefault.addSession(session); pathPrefix = "/application/v2/tenant/" + defaultTenant + "/session/"; - HttpResponse response = handler.handle( - SessionHandlerTest.createTestRequest(pathPrefix, HttpRequest.Method.PUT, Cmd.PREPARED, sessionId)); + HttpResponse response = request(HttpRequest.Method.PUT, sessionId); assertNotNull(response); assertThat(SessionHandlerTest.getRenderedString(response), response.getStatus(), is(OK)); assertThat(session.getStatus(), is(Session.Status.PREPARE)); @@ -274,8 +263,7 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest { public void require_that_config_change_actions_are_in_response() throws Exception { MockSession session = new MockSession(1, null); localRepo.addSession(session); - HttpResponse response = createHandler().handle( - SessionHandlerTest.createTestRequest(pathPrefix, HttpRequest.Method.PUT, Cmd.PREPARED, 1L)); + HttpResponse response = request(HttpRequest.Method.PUT, 1L); assertResponseContains(response, "\"configChangeActions\":{\"restart\":[],\"refeed\":[]}"); } @@ -289,8 +277,7 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest { new MockRefeedAction("change-id", false, "other change", services, "test"))); MockSession session = new MockSession(1, null, actions); localRepo.addSession(session); - HttpResponse response = createHandler().handle( - SessionHandlerTest.createTestRequest(pathPrefix, HttpRequest.Method.PUT, Cmd.PREPARED, 1L)); + HttpResponse response = request(HttpRequest.Method.PUT, 1L); assertResponseContains(response, "Change(s) between active and new application that require restart:\\nIn cluster 'foo' of type 'bar"); assertResponseContains(response, @@ -301,8 +288,7 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest { 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().handle( - SessionHandlerTest.createTestRequest(pathPrefix, HttpRequest.Method.PUT, Cmd.PREPARED, 1L)); + HttpResponse response = request(HttpRequest.Method.PUT, 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"); } @@ -312,8 +298,7 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest { String message = "Internal error"; SessionThrowingException session = new SessionThrowingException(new OutOfCapacityException(message)); localRepo.addSession(session); - HttpResponse response = createHandler() - .handle(SessionHandlerTest.createTestRequest(pathPrefix, HttpRequest.Method.PUT, Cmd.PREPARED, 1L)); + HttpResponse response = request(HttpRequest.Method.PUT, 1L); assertEquals(400, response.getStatus()); Slime data = getData(response); assertThat(data.get().field("error-code").asString(), is(HttpErrorResponse.errorCodes.OUT_OF_CAPACITY.name())); @@ -325,8 +310,7 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest { String message = "No nodes available"; SessionThrowingException session = new SessionThrowingException(new NullPointerException(message)); localRepo.addSession(session); - HttpResponse response = createHandler() - .handle(SessionHandlerTest.createTestRequest(pathPrefix, HttpRequest.Method.PUT, Cmd.PREPARED, 1L)); + HttpResponse response = request(HttpRequest.Method.PUT, 1L); assertEquals(500, response.getStatus()); Slime data = getData(response); assertThat(data.get().field("error-code").asString(), is(HttpErrorResponse.errorCodes.INTERNAL_SERVER_ERROR.name())); @@ -339,14 +323,22 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest { SessionThrowingException session = new SessionThrowingException(new ApplicationLockException(new UncheckedTimeoutException(message))); localRepo.addSession(session); - HttpResponse response = createHandler() - .handle(SessionHandlerTest.createTestRequest(pathPrefix, HttpRequest.Method.PUT, Cmd.PREPARED, 1L)); + HttpResponse response = request(HttpRequest.Method.PUT, 1L); assertEquals(500, response.getStatus()); Slime data = getData(response); assertThat(data.get().field("error-code").asString(), is(HttpErrorResponse.errorCodes.APPLICATION_LOCK_FAILURE.name())); assertThat(data.get().field("message").asString(), is(message)); } + @Test + public void test_docker_image_repository() { + MockSession session = new MockSession(1, null); + localRepo.addSession(session); + String dockerImageRepository = "https://foo.bar.com:4443/baz"; + request(HttpRequest.Method.PUT, 1L, Map.of("dockerImageRepository", dockerImageRepository)); + assertEquals(dockerImageRepository, localRepo.getSession(1).getDockerImageRepository().get()); + } + private Slime getData(HttpResponse response) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); response.render(baos); @@ -374,6 +366,14 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest { } + private HttpResponse request(HttpRequest.Method put, long l) { + return request(put, l, Map.of()); + } + + private HttpResponse request(HttpRequest.Method put, long l, Map requestParameters) { + return createHandler().handle(SessionHandlerTest.createTestRequest(pathPrefix, put, Cmd.PREPARED, l, "", null, requestParameters)); + } + public static class SessionThrowingException extends LocalSession { private final RuntimeException exception; -- cgit v1.2.3