aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHarald Musum <musum@yahoo-inc.com>2016-09-07 22:09:38 +0200
committerGitHub <noreply@github.com>2016-09-07 22:09:38 +0200
commit17f9a3d99c95fa6a3468d8b65dbb38a62adc7765 (patch)
treea9b44ea6061bb97909089fffacb17b18c582b3bc
parent557c942a6cfee933140eef893889e2218897b26c (diff)
parent7b6814407f853a5e7e80bdff03c4350f0d4267bc (diff)
Merge pull request #584 from yahoo/bratseth/dont-validate-when-deploying-from-local
Bratseth/dont validate when deploying from local
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/model/api/ModelFactory.java4
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/VespaModelFactory.java60
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/application/validation/ValidationOverrideTest.java13
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java4
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/deploy/Deployment.java24
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandler.java5
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/PreparedModelsBuilder.java2
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSession.java8
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java5
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/tenant/Tenants.java2
-rw-r--r--configserver/src/test/apps/validationOverride/services.xml4
-rw-r--r--configserver/src/test/apps/validationOverride/validation-overrides.xml3
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployTester.java188
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/deploy/HostedDeployTest.java115
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/deploy/MockDeployer.java2
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/deploy/RedeployTest.java74
-rw-r--r--testutil/src/main/java/com/yahoo/test/ManualClock.java11
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();
+ }
+
}