diff options
author | Harald Musum <musum@yahoo-inc.com> | 2016-09-07 22:09:38 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-09-07 22:09:38 +0200 |
commit | 17f9a3d99c95fa6a3468d8b65dbb38a62adc7765 (patch) | |
tree | a9b44ea6061bb97909089fffacb17b18c582b3bc | |
parent | 557c942a6cfee933140eef893889e2218897b26c (diff) | |
parent | 7b6814407f853a5e7e80bdff03c4350f0d4267bc (diff) |
Merge pull request #584 from yahoo/bratseth/dont-validate-when-deploying-from-local
Bratseth/dont validate when deploying from local
17 files changed, 364 insertions, 160 deletions
diff --git a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelFactory.java b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelFactory.java index a6f7b8096ea..a61001cc9d9 100644 --- a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelFactory.java +++ b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelFactory.java @@ -18,8 +18,8 @@ public interface ModelFactory { /** * Creates an instance of a {@link Model}. The resulting instance will be used to serve config. No model - * validation will be done, calling this method presupposes that {@link #createAndValidateModel} has already - * been called. + * validation will be done, calling this method assumes that {@link #createAndValidateModel} has already + * been called at some point for this model. * * @param modelContext An instance of {@link ModelContext}, containing dependencies for creating a {@link Model}. * @return a {@link Model} instance. diff --git a/config-model/src/main/java/com/yahoo/vespa/model/VespaModelFactory.java b/config-model/src/main/java/com/yahoo/vespa/model/VespaModelFactory.java index 0dccc2c9750..9b234435ce2 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/VespaModelFactory.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/VespaModelFactory.java @@ -30,6 +30,7 @@ import org.xml.sax.SAXException; import java.io.IOException; import java.io.Reader; +import java.time.Clock; import java.util.ArrayList; import java.util.List; import java.util.logging.Logger; @@ -44,6 +45,7 @@ public class VespaModelFactory implements ModelFactory { private static final Logger log = Logger.getLogger(VespaModelFactory.class.getName()); private final ConfigModelRegistry configModelRegistry; private final Zone zone; + private final Clock clock; @Inject public VespaModelFactory(ComponentRegistry<ConfigModelPlugin> pluginRegistry, Zone zone) { @@ -55,9 +57,13 @@ public class VespaModelFactory implements ModelFactory { } this.configModelRegistry = new MapConfigModelRegistry(modelBuilders); this.zone = zone; + this.clock = Clock.systemUTC(); } public VespaModelFactory(ConfigModelRegistry configModelRegistry) { + this(configModelRegistry, Clock.systemUTC()); + } + public VespaModelFactory(ConfigModelRegistry configModelRegistry, Clock clock) { if (configModelRegistry == null) { this.configModelRegistry = new NullConfigModelRegistry(); log.info("Will not load config models from plugins, as no registry is available"); @@ -65,6 +71,7 @@ public class VespaModelFactory implements ModelFactory { this.configModelRegistry = configModelRegistry; } this.zone = Zone.defaultZone(); + this.clock = clock; } @Override @@ -77,6 +84,31 @@ public class VespaModelFactory implements ModelFactory { return buildModel(createDeployState(modelContext)); } + @Override + public ModelCreateResult createAndValidateModel(ModelContext modelContext, boolean ignoreValidationErrors) { + if (modelContext.appDir().isPresent()) { + ApplicationPackageXmlFilesValidator validator = + ApplicationPackageXmlFilesValidator.createDefaultXMLValidator(modelContext.appDir().get(), + modelContext.deployLogger(), + modelContext.vespaVersion()); + try { + validator.checkApplication(); + ApplicationPackageXmlFilesValidator.checkIncludedDirs(modelContext.applicationPackage()); + } catch (IllegalArgumentException e) { + rethrowUnlessIgnoreErrors(e, ignoreValidationErrors); + } catch (Exception e) { + throw new RuntimeException(e); + } + + } else { + validateXML(modelContext.applicationPackage(), modelContext.deployLogger(), ignoreValidationErrors); + } + DeployState deployState = createDeployState(modelContext); + VespaModel model = buildModel(deployState); + List<ConfigChangeAction> changeActions = validateModel(model, deployState, ignoreValidationErrors); + return new ModelCreateResult(model, changeActions); + } + private VespaModel buildModel(DeployState deployState) { try { return new VespaModel(configModelRegistry, deployState); @@ -95,7 +127,8 @@ public class VespaModelFactory implements ModelFactory { .properties(createDeployProperties(modelContext.properties())) .modelHostProvisioner(createHostProvisioner(modelContext)) .rotations(modelContext.properties().rotations()) - .zone(zone); + .zone(zone) + .now(clock.instant()); modelContext.previousModel().ifPresent(builder::previousModel); return builder.build(); } @@ -140,31 +173,6 @@ public class VespaModelFactory implements ModelFactory { return modelContext.properties().hostedVespa() && id.isHostedVespaRoutingApplication(); } - @Override - public ModelCreateResult createAndValidateModel(ModelContext modelContext, boolean ignoreValidationErrors) { - if (modelContext.appDir().isPresent()) { - ApplicationPackageXmlFilesValidator validator = - ApplicationPackageXmlFilesValidator.createDefaultXMLValidator(modelContext.appDir().get(), - modelContext.deployLogger(), - modelContext.vespaVersion()); - try { - validator.checkApplication(); - ApplicationPackageXmlFilesValidator.checkIncludedDirs(modelContext.applicationPackage()); - } catch (IllegalArgumentException e) { - rethrowUnlessIgnoreErrors(e, ignoreValidationErrors); - } catch (Exception e) { - throw new RuntimeException(e); - } - - } else { - validateXML(modelContext.applicationPackage(), modelContext.deployLogger(), ignoreValidationErrors); - } - DeployState deployState = createDeployState(modelContext); - VespaModel model = buildModel(deployState); - List<ConfigChangeAction> changeActions = validateModel(model, deployState, ignoreValidationErrors); - return new ModelCreateResult(model, changeActions); - } - private void validateXML(ApplicationPackage applicationPackage, DeployLogger deployLogger, boolean ignoreValidationErrors) { try { applicationPackage.validateXML(deployLogger); diff --git a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/ValidationOverrideTest.java b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/ValidationOverrideTest.java index 3354e9320fc..57e3cfa9dfa 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/ValidationOverrideTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/ValidationOverrideTest.java @@ -1,6 +1,7 @@ // Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.model.application.validation; +import com.yahoo.test.ManualClock; import com.yahoo.vespa.model.application.validation.xml.ValidationOverridesXMLReader; import org.junit.Test; import org.xml.sax.SAXException; @@ -32,7 +33,7 @@ public class ValidationOverrideTest { { ValidationOverrides overrides = new ValidationOverridesXMLReader().read(Optional.of(new StringReader(validationOverrides)), - at("2000-01-01T23:59:00")); + ManualClock.at("2000-01-01T23:59:00")); assertOverridden("indexing-change", overrides); assertOverridden("indexing-mode-change", overrides); assertNotOverridden("field-type-change", overrides); @@ -40,7 +41,7 @@ public class ValidationOverrideTest { { ValidationOverrides overrides = new ValidationOverridesXMLReader().read(Optional.of(new StringReader(validationOverrides)), - at("2000-01-02T00:00:00")); + ManualClock.at("2000-01-02T00:00:00")); assertNotOverridden("indexing-change", overrides); assertOverridden("indexing-mode-change", overrides); assertNotOverridden("field-type-change", overrides); @@ -48,7 +49,7 @@ public class ValidationOverrideTest { { ValidationOverrides overrides = new ValidationOverridesXMLReader().read(Optional.of(new StringReader(validationOverrides)), - at("2000-01-04T00:00:00")); + ManualClock.at("2000-01-04T00:00:00")); assertNotOverridden("indexing-change", overrides); assertNotOverridden("indexing-mode-change", overrides); assertNotOverridden("field-type-change", overrides); @@ -65,7 +66,7 @@ public class ValidationOverrideTest { try { new ValidationOverridesXMLReader().read(Optional.of(new StringReader(validationOverrides)), - at("2000-01-01T23:59:00")); + ManualClock.at("2000-01-01T23:59:00")); fail("Expected validation interval override validation validation failure"); } catch (IllegalArgumentException e) { @@ -75,10 +76,6 @@ public class ValidationOverrideTest { } } - private Instant at(String utcIsoTime) { - return LocalDateTime.parse(utcIsoTime, DateTimeFormatter.ISO_DATE_TIME).atZone(ZoneOffset.UTC).toInstant(); - } - private void assertOverridden(String validationId, ValidationOverrides overrides) { overrides.invalid(ValidationId.from(validationId).get(), "message"); // should not throw exception } 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 c32fef58b01..6ed1ddf6c7e 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 @@ -74,7 +74,9 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye configserverConfig, hostProvisioner, new ActivateLock(curator, tenant.getPath()), - timeout, clock)); + timeout, + clock, + /* already deployed, validate: */ false)); } public Deployment deployFromPreparedSession(LocalSession session, ActivateLock lock, LocalSessionRepo localSessionRepo, Duration timeout) { 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 925b442ea3a..ab82055ee53 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 @@ -51,13 +51,16 @@ public class Deployment implements com.yahoo.config.provision.Deployment { private final DeployLogger logger = new SilentDeployLogger(); private boolean prepared = false; + + /** Whether this model should be validated (only takes effect if prepared=false) */ + private boolean validate; private boolean ignoreLockFailure = false; private boolean ignoreSessionStaleFailure = false; private Deployment(LocalSession session, LocalSessionRepo localSessionRepo, Path tenantPath, ConfigserverConfig configserverConfig, Optional<Provisioner> hostProvisioner, ActivateLock activateLock, - Duration timeout, Clock clock, boolean prepared) { + Duration timeout, Clock clock, boolean prepared, boolean validate) { this.session = session; this.localSessionRepo = localSessionRepo; this.tenantPath = tenantPath; @@ -67,20 +70,21 @@ public class Deployment implements com.yahoo.config.provision.Deployment { this.timeout = timeout; this.clock = clock; this.prepared = prepared; + this.validate = validate; } public static Deployment unprepared(LocalSession session, LocalSessionRepo localSessionRepo, Path tenantPath, ConfigserverConfig configserverConfig, Optional<Provisioner> hostProvisioner, ActivateLock activateLock, - Duration timeout, Clock clock) { + Duration timeout, Clock clock, boolean validate) { return new Deployment(session, localSessionRepo, tenantPath, configserverConfig, hostProvisioner, activateLock, - timeout, clock, false); + timeout, clock, false, validate); } public static Deployment prepared(LocalSession session, LocalSessionRepo localSessionRepo, Optional<Provisioner> hostProvisioner, ActivateLock activateLock, Duration timeout, Clock clock) { return new Deployment(session, localSessionRepo, null, null, hostProvisioner, activateLock, - timeout, clock, true); + timeout, clock, true, true); } public Deployment setIgnoreLockFailure(boolean ignoreLockFailure) { @@ -100,7 +104,7 @@ public class Deployment implements com.yahoo.config.provision.Deployment { TimeoutBudget timeoutBudget = new TimeoutBudget(clock, timeout); session.prepare(logger, /** Assumes that session has already set application id, see {@link com.yahoo.vespa.config.server.session.SessionFactoryImpl}. */ - new PrepareParams(configserverConfig).applicationId(session.getApplicationId()).timeoutBudget(timeoutBudget), + new PrepareParams(configserverConfig).applicationId(session.getApplicationId()).timeoutBudget(timeoutBudget).ignoreValidationErrors( ! validate), Optional.empty(), tenantPath); this.prepared = true; @@ -173,12 +177,13 @@ public class Deployment implements com.yahoo.config.provision.Deployment { private void checkIfActiveHasChanged(LocalSession session, LocalSession currentActiveSession, boolean ignoreStaleSessionFailure) { long activeSessionAtCreate = session.getActiveSessionAtCreate(); - log.log(LogLevel.DEBUG, currentActiveSession.logPre()+"active session id at create time=" + activeSessionAtCreate); + log.log(LogLevel.DEBUG, currentActiveSession.logPre() + "active session id at create time=" + activeSessionAtCreate); if (activeSessionAtCreate == 0) return; // No active session at create long sessionId = session.getSessionId(); long currentActiveSessionSessionId = currentActiveSession.getSessionId(); - log.log(LogLevel.DEBUG, currentActiveSession.logPre()+"sessionId=" + sessionId + ", current active session=" + currentActiveSessionSessionId); + log.log(LogLevel.DEBUG, currentActiveSession.logPre() + "sessionId=" + sessionId + + ", current active session=" + currentActiveSessionSessionId); if (currentActiveSession.isNewerThan(activeSessionAtCreate) && currentActiveSessionSessionId != sessionId) { String errMsg = currentActiveSession.logPre()+"Cannot activate session " + @@ -186,7 +191,7 @@ public class Deployment implements com.yahoo.config.provision.Deployment { currentActiveSessionSessionId + ") has changed since session " + sessionId + " was created (was " + activeSessionAtCreate + " at creation time)"; if (ignoreStaleSessionFailure) { - log.warning(errMsg+ " (Continuing because of force.)"); + log.warning(errMsg + " (Continuing because of force.)"); } else { throw new IllegalStateException(errMsg); } @@ -198,7 +203,8 @@ public class Deployment implements com.yahoo.config.provision.Deployment { private void checkIfActiveIsNewerThanSessionToBeActivated(long sessionId, long currentActiveSessionId) { if (sessionId < currentActiveSessionId) { throw new IllegalArgumentException("It is not possible to activate session " + sessionId + - ", because it is older than current active session (" + currentActiveSessionId + ")"); + ", because it is older than current active session (" + + currentActiveSessionId + ")"); } } } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandler.java index 6e3f4ea246f..2221c862102 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandler.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandler.java @@ -67,7 +67,10 @@ public class SessionPrepareHandler extends SessionHandler { // An app id currently using only the name ApplicationId appId = prepParams.getApplicationId(); DeployLogger logger = createLogger(rawDeployLog, verbose, appId); - ConfigChangeActions actions = session.prepare(logger, prepParams, getCurrentActiveApplicationSet(tenantContext, appId), tenantContext.getPath()); + ConfigChangeActions actions = session.prepare(logger, + prepParams, + getCurrentActiveApplicationSet(tenantContext, appId), + tenantContext.getPath()); logConfigChangeActions(actions, logger); log.log(LogLevel.INFO, Tenants.logPre(appId)+"Session "+session.getSessionId()+" prepared successfully. "); return new SessionPrepareResponse(rawDeployLog, tenantContext, request, session, actions); 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 70333ce83a4..cacd53cf945 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 @@ -164,7 +164,7 @@ public class PreparedModelsBuilder extends ModelsBuilder<PreparedModelsBuilder.P Optional.of(version)); log.log(LogLevel.DEBUG, "Running createAndValidateModel for Vespa version " + version); - ModelCreateResult result = modelFactory.createAndValidateModel(modelContext, params.ignoreValidationErrors()); + ModelCreateResult result = modelFactory.createAndValidateModel(modelContext, params.ignoreValidationErrors()); validateModelHosts(context.getHostValidator(), applicationId, result.getModel()); log.log(LogLevel.DEBUG, "Done building model for Vespa version " + version); return new PreparedModelsBuilder.PreparedModelResult(version, result.getModel(), fileDistributionProvider, result.getConfigChangeActions()); diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSession.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSession.java index 8ea92d2eb8d..48a2674f45d 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSession.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSession.java @@ -60,9 +60,13 @@ public class LocalSession extends Session implements Comparable<LocalSession> { this.superModelGenerationCounter = sessionContext.getSuperModelGenerationCounter(); } - public ConfigChangeActions prepare(DeployLogger logger, PrepareParams params, Optional<ApplicationSet> currentActiveApplicationSet, Path tenantPath) { + public ConfigChangeActions prepare(DeployLogger logger, + PrepareParams params, + Optional<ApplicationSet> currentActiveApplicationSet, + Path tenantPath) { Curator.CompletionWaiter waiter = zooKeeperClient.createPrepareWaiter(); - ConfigChangeActions actions = sessionPreparer.prepare(sessionContext, logger, params, currentActiveApplicationSet, tenantPath); + ConfigChangeActions actions = sessionPreparer.prepare(sessionContext, logger, params, + currentActiveApplicationSet, tenantPath); setPrepared(); waiter.awaitCompletion(params.getTimeoutBudget().timeLeft()); return actions; 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 747b38f554f..b10865f257b 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 @@ -82,11 +82,10 @@ public class SessionPreparer { * @param params parameters controlling behaviour of prepare. * @param currentActiveApplicationSet Set of currently active applications. * @param tenantPath Zookeeper path for the tenant for this session - * @return The config change actions that must be done to handle the activation of the models prepared. + * @return the config change actions that must be done to handle the activation of the models prepared. */ public ConfigChangeActions prepare(SessionContext context, DeployLogger logger, PrepareParams params, - Optional<ApplicationSet> currentActiveApplicationSet, Path tenantPath) - { + Optional<ApplicationSet> currentActiveApplicationSet, Path tenantPath) { Preparation prep = new Preparation(context, logger, params, currentActiveApplicationSet, tenantPath); prep.preprocess(); try { diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/Tenants.java b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/Tenants.java index 6ff16c0c0fa..8264de0df01 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/Tenants.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/Tenants.java @@ -336,7 +336,7 @@ public class Tenants implements ConnectionStateListener, PathChildrenCacheListen } public void redeployApplications(Deployer deployer) { - final int totalNumberOfApplications = tenantsCopy().values().stream() + int totalNumberOfApplications = tenantsCopy().values().stream() .mapToInt(tenant -> tenant.getApplicationRepo().listApplications().size()).sum(); int applicationsRedeployed = 0; for (Tenant tenant : tenantsCopy().values()) { diff --git a/configserver/src/test/apps/validationOverride/services.xml b/configserver/src/test/apps/validationOverride/services.xml new file mode 100644 index 00000000000..c6779bf311e --- /dev/null +++ b/configserver/src/test/apps/validationOverride/services.xml @@ -0,0 +1,4 @@ +<jdisc version="1.0"> + <search/> + <nodes count="2"/> +</jdisc>
\ No newline at end of file diff --git a/configserver/src/test/apps/validationOverride/validation-overrides.xml b/configserver/src/test/apps/validationOverride/validation-overrides.xml new file mode 100644 index 00000000000..1f209d21c94 --- /dev/null +++ b/configserver/src/test/apps/validationOverride/validation-overrides.xml @@ -0,0 +1,3 @@ +<validation-overrides> + <allow until="2016-10-10">skip-old-config-models</allow> +</validation-overrides>
\ No newline at end of file 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 new file mode 100644 index 00000000000..30a3eec47fd --- /dev/null +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployTester.java @@ -0,0 +1,188 @@ +package com.yahoo.vespa.config.server.deploy; + +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.HostProvisioner; +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.model.deploy.DeployState; +import com.yahoo.config.model.provision.InMemoryProvisioner; +import com.yahoo.config.model.test.MockApplicationPackage; +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.config.provision.Version; +import com.yahoo.path.Path; +import com.yahoo.transaction.NestedTransaction; +import com.yahoo.vespa.config.server.ApplicationRepository; +import com.yahoo.vespa.config.server.TestComponentRegistry; +import com.yahoo.vespa.config.server.TimeoutBudget; +import com.yahoo.vespa.config.server.modelfactory.ModelFactoryRegistry; +import com.yahoo.vespa.config.server.modelfactory.ModelResult; +import com.yahoo.vespa.config.server.monitoring.Metrics; +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 com.yahoo.vespa.config.server.tenant.Tenant; +import com.yahoo.vespa.config.server.tenant.Tenants; +import com.yahoo.vespa.config.server.zookeeper.ConfigCurator; +import com.yahoo.vespa.curator.Curator; +import com.yahoo.vespa.curator.mock.MockCurator; +import com.yahoo.vespa.model.VespaModel; +import com.yahoo.vespa.model.VespaModelFactory; +import org.apache.curator.framework.CuratorFramework; +import org.junit.Before; + +import java.io.File; +import java.io.IOException; +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +/** + * @author bratseth + */ +public class DeployTester { + + private final Curator curator; + private final Tenants tenants; + private final Path tenantPath = Path.createRoot().append("testapp"); + private final File testApp; + + private ApplicationId id; + + public DeployTester(String appPath) { + this(appPath, Collections.singletonList(createDefaultModelFactory(Clock.systemUTC()))); + } + + public DeployTester(String appPath, List<ModelFactory> modelFactories) { + try { + this.curator = new MockCurator(); + this.testApp = new File(appPath); + ModelFactoryRegistry modelFactoryRegistry = new ModelFactoryRegistry(modelFactories); + this.tenants = new Tenants(new TestComponentRegistry(curator, modelFactoryRegistry), Metrics.createTestMetrics()); + } + catch (Exception e) { + throw new IllegalArgumentException(e); + } + } + + public Tenant tenant() { return tenants.defaultTenant(); } + + /** Create the model factory which will be used in production */ + public static ModelFactory createDefaultModelFactory(Clock clock) { return new VespaModelFactory(new NullConfigModelRegistry(), clock); } + + /** Create a model factory which always fails validation */ + public static ModelFactory createFailingModelFactory(Version version) { return new FailingModelFactory(version); } + + /** + * Do the initial "deploy" with the existing API-less code as the deploy API doesn't support first deploys yet. + */ + public ApplicationId deployApp(String appName) 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(appName), 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); + this.id = id; + return id; + } + + public ApplicationId applicationId() { return id; } + + public Optional<com.yahoo.config.provision.Deployment> redeployFromLocalActive() { + return redeployFromLocalActive(id); + } + + public Optional<com.yahoo.config.provision.Deployment> redeployFromLocalActive(ApplicationId id) { + ApplicationRepository applicationRepository = new ApplicationRepository(tenants, HostProvisionerProvider.withProvisioner(createHostProvisioner()), + new ConfigserverConfig(new ConfigserverConfig.Builder()), curator); + + Optional<com.yahoo.config.provision.Deployment> deployment = applicationRepository.deployFromLocalActive(id, Duration.ofSeconds(60)); + return deployment; + } + + 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 remove(NestedTransaction transaction, ApplicationId application) { + // noop + } + + @Override + public void restart(ApplicationId application, HostFilter filter) { + // noop + } + + } + + private static class FailingModelFactory implements ModelFactory { + + private final Version version; + + public FailingModelFactory(Version version) { + this.version = version; + } + + @Override + public Version getVersion() { return version; } + + @Override + public Model createModel(ModelContext modelContext) { + try { + Instant now = LocalDate.parse("2000-01-01", DateTimeFormatter.ISO_DATE).atStartOfDay().atZone(ZoneOffset.UTC).toInstant(); + ApplicationPackage application = new MockApplicationPackage.Builder().withEmptyHosts().withEmptyServices().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 ( ! ignoreValidationErrors) + throw new IllegalArgumentException("Validation fails"); + return new ModelCreateResult(createModel(modelContext), Collections.emptyList()); + } + + } + +} 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 12f69ec1dc6..25efbc77a17 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 @@ -1,105 +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.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.ApplicationRepository; -import com.yahoo.vespa.config.server.tenant.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 com.yahoo.config.model.api.ModelFactory; +import com.yahoo.config.provision.Version; +import com.yahoo.test.ManualClock; 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.ArrayList; import java.util.List; import java.util.Optional; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; /** * @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; +public class HostedDeployTest { @Test public void testRedeploy() throws InterruptedException, IOException { - ApplicationId id = deployApp(); - - ApplicationRepository applicationRepository = new ApplicationRepository(tenants, HostProvisionerProvider.withProvisioner(createHostProvisioner()), - new ConfigserverConfig(new ConfigserverConfig.Builder()), curator); + DeployTester tester = new DeployTester("src/test/apps/hosted/"); + tester.deployApp("myApp"); - Optional<com.yahoo.config.provision.Deployment> deployment = applicationRepository.deployFromLocalActive(id, Duration.ofSeconds(60)); + Optional<com.yahoo.config.provision.Deployment> deployment = tester.redeployFromLocalActive(); 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; + @Test + public void testRedeployAfterExpiredValidationOverride() throws InterruptedException, IOException { + // Old version of model fails, but application disables loading old models until 2016-10-10, so deployment works + ManualClock clock = new ManualClock("2016-10-09T00:00:00"); + List<ModelFactory> modelFactories = new ArrayList<>(); + modelFactories.add(DeployTester.createDefaultModelFactory(clock)); + modelFactories.add(DeployTester.createFailingModelFactory(Version.fromIntValues(1, 0, 0))); // older than default + DeployTester tester = new DeployTester("src/test/apps/validationOverride/", modelFactories); + tester.deployApp("myApp"); + + // Redeployment from local active works + { + Optional<com.yahoo.config.provision.Deployment> deployment = tester.redeployFromLocalActive(); + assertTrue(deployment.isPresent()); + deployment.get().prepare(); + deployment.get().activate(); } - @Override - public List<HostSpec> prepare(ApplicationId applicationId, ClusterSpec cluster, Capacity capacity, int groups, ProvisionLogger logger) { - return hostProvisioner.prepare(cluster, capacity, groups, logger); - } + clock.advance(Duration.ofDays(2)); // validation override expires - @Override - public void activate(NestedTransaction transaction, ApplicationId application, Collection<HostSpec> hosts) { - // noop + // Redeployment from local active also works after the validation override expires + { + Optional<com.yahoo.config.provision.Deployment> deployment = tester.redeployFromLocalActive(); + assertTrue(deployment.isPresent()); + deployment.get().prepare(); + deployment.get().activate(); } - - @Override - public void remove(NestedTransaction transaction, ApplicationId application) { - // noop + + // However, redeployment from the outside fails after this date + { + try { + tester.deployApp("myApp"); + fail("Expected redeployment to fail"); + } + catch (Exception expected) { + // success + } } - - @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 index 0dd01404109..a2b8439b570 100644 --- 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 @@ -11,6 +11,7 @@ import java.util.Optional; * @author lulf */ public class MockDeployer implements com.yahoo.config.provision.Deployer { + public ApplicationId lastDeployed; @Override @@ -18,4 +19,5 @@ public class MockDeployer implements com.yahoo.config.provision.Deployer { 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 index b06069d98af..ebfcca5c6a7 100644 --- 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 @@ -2,24 +2,50 @@ package com.yahoo.vespa.config.server.deploy; 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.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.model.deploy.DeployState; +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.path.Path; +import com.yahoo.test.ManualClock; import com.yahoo.vespa.config.server.ApplicationRepository; +import com.yahoo.vespa.config.server.TestComponentRegistry; +import com.yahoo.vespa.config.server.TestWithCurator; +import com.yahoo.vespa.config.server.modelfactory.ModelFactoryRegistry; +import com.yahoo.vespa.config.server.monitoring.Metrics; +import com.yahoo.vespa.config.server.tenant.Tenant; +import com.yahoo.vespa.config.server.tenant.Tenants; import com.yahoo.vespa.config.server.tenant.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 com.yahoo.vespa.model.VespaModel; +import com.yahoo.vespa.model.VespaModelFactory; +import org.junit.Before; import org.junit.Test; import java.io.File; import java.io.IOException; import java.time.Clock; import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Optional; import static org.junit.Assert.assertEquals; @@ -31,53 +57,35 @@ import static org.junit.Assert.assertTrue; * * @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; +public class RedeployTest { @Test public void testRedeploy() throws InterruptedException, IOException { - ApplicationId id = deployApp(); - - ApplicationRepository applicationRepository = new ApplicationRepository(tenants, HostProvisionerProvider.empty(), - new ConfigserverConfig(new ConfigserverConfig.Builder()), curator); + DeployTester tester = new DeployTester("src/test/apps/app"); + tester.deployApp("myapp"); + Optional<com.yahoo.config.provision.Deployment> deployment = tester.redeployFromLocalActive(); - Optional<com.yahoo.config.provision.Deployment> deployment = applicationRepository.deployFromLocalActive(id, Duration.ofSeconds(60)); assertTrue(deployment.isPresent()); - long activeSessionIdBefore = tenant.getLocalSessionRepo().getActiveSession(id).getSessionId(); - assertEquals(id, tenant.getLocalSessionRepo().getSession(activeSessionIdBefore).getApplicationId()); + long activeSessionIdBefore = tester.tenant().getLocalSessionRepo().getActiveSession(tester.applicationId()).getSessionId(); + assertEquals(tester.applicationId(), tester.tenant().getLocalSessionRepo().getSession(activeSessionIdBefore).getApplicationId()); deployment.get().prepare(); deployment.get().activate(); - long activeSessionIdAfter = tenant.getLocalSessionRepo().getActiveSession(id).getSessionId(); + long activeSessionIdAfter = tester.tenant().getLocalSessionRepo().getActiveSession(tester.applicationId()).getSessionId(); assertEquals(activeSessionIdAfter, activeSessionIdBefore + 1); - assertEquals(id, tenant.getLocalSessionRepo().getSession(activeSessionIdAfter).getApplicationId()); + assertEquals(tester.applicationId(), tester.tenant().getLocalSessionRepo().getSession(activeSessionIdAfter).getApplicationId()); } - /** No deploYMENT is done because there isn't a local active session. */ + /** No deployment is done because there is no local active session. */ @Test public void testNoRedeploy() { + List<ModelFactory> modelFactories = new ArrayList<>(); + modelFactories.add(DeployTester.createDefaultModelFactory(Clock.systemUTC())); + modelFactories.add(DeployTester.createFailingModelFactory(Version.fromIntValues(1, 0, 0))); + DeployTester tester = new DeployTester("ignored/app/path", modelFactories); ApplicationId id = ApplicationId.from(TenantName.from("default"), ApplicationName.from("default"), InstanceName.from("default")); - - ApplicationRepository applicationRepository = new ApplicationRepository(tenants, HostProvisionerProvider.empty(), - new ConfigserverConfig(new ConfigserverConfig.Builder()), curator); - - assertFalse(applicationRepository.deployFromLocalActive(id, Duration.ofSeconds(60)).isPresent()); + assertFalse(tester.redeployFromLocalActive(id).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/testutil/src/main/java/com/yahoo/test/ManualClock.java b/testutil/src/main/java/com/yahoo/test/ManualClock.java index b8325b3e3fc..1ffc7aa77da 100644 --- a/testutil/src/main/java/com/yahoo/test/ManualClock.java +++ b/testutil/src/main/java/com/yahoo/test/ManualClock.java @@ -3,7 +3,10 @@ package com.yahoo.test; import java.time.Clock; import java.time.Instant; +import java.time.LocalDateTime; import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; import java.time.temporal.TemporalAmount; /** A clock which initially has the time of its creation but can only be advanced by calling advance */ @@ -13,6 +16,10 @@ public class ManualClock extends Clock { public ManualClock() {} + public ManualClock(String utcIsoTime) { + this(at(utcIsoTime)); + } + public ManualClock(Instant currentTime) { this.currentTime = currentTime; } @@ -33,4 +40,8 @@ public class ManualClock extends Clock { @Override public long millis() { return currentTime.toEpochMilli(); } + public static Instant at(String utcIsoTime) { + return LocalDateTime.parse(utcIsoTime, DateTimeFormatter.ISO_DATE_TIME).atZone(ZoneOffset.UTC).toInstant(); + } + } |