summaryrefslogtreecommitdiffstats
path: root/configserver
diff options
context:
space:
mode:
authorjonmv <venstad@gmail.com>2024-01-11 15:04:33 +0100
committerjonmv <venstad@gmail.com>2024-01-11 15:04:33 +0100
commit1699dd01dd4d5dfd753cd3d3e6dcc7e58da77a5d (patch)
tree2141a68558f189358a5c32a9c9fc63c80ff63cb9 /configserver
parent697f451c9a60df5240dece9fd330b9d30a0eb637 (diff)
Store config change actions on prepare, trigger on activate
Diffstat (limited to 'configserver')
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/deploy/Deployment.java65
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/response/SessionPrepareAndActivateResponse.java16
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/ActivationTriggers.java37
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/ActivationTriggersSerializer.java60
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/PrepareParams.java12
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/Session.java4
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionData.java9
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java7
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java1
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionSerializer.java9
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClient.java11
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/session/ActivationTriggersSerializerTest.java27
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClientTest.java18
13 files changed, 220 insertions, 56 deletions
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 ae1215fd5aa..f10bea5da6e 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
@@ -23,6 +23,9 @@ import com.yahoo.vespa.config.server.application.ConfigNotConvergedException;
import com.yahoo.vespa.config.server.configchange.ConfigChangeActions;
import com.yahoo.vespa.config.server.configchange.ReindexActions;
import com.yahoo.vespa.config.server.configchange.RestartActions;
+import com.yahoo.vespa.config.server.session.ActivationTriggers;
+import com.yahoo.vespa.config.server.session.ActivationTriggers.NodeRestart;
+import com.yahoo.vespa.config.server.session.ActivationTriggers.Reindexing;
import com.yahoo.vespa.config.server.session.PrepareParams;
import com.yahoo.vespa.config.server.session.Session;
import com.yahoo.vespa.config.server.session.SessionRepository;
@@ -31,6 +34,7 @@ import com.yahoo.yolean.concurrent.Memoized;
import java.time.Clock;
import java.time.Duration;
+import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
@@ -41,6 +45,7 @@ import java.util.stream.Collectors;
import static com.yahoo.vespa.config.server.application.ConfigConvergenceChecker.ServiceListResponse;
import static com.yahoo.vespa.config.server.session.Session.Status.DELETE;
+import static java.util.stream.Collectors.toSet;
/**
* The process of deploying an application.
@@ -63,14 +68,12 @@ public class Deployment implements com.yahoo.config.provision.Deployment {
private final Tenant tenant;
private final DeployLogger deployLogger;
private final Clock clock;
- private final boolean internalRedeploy;
private boolean prepared;
private ConfigChangeActions configChangeActions;
private Deployment(Session session, ApplicationRepository applicationRepository, Supplier<PrepareParams> params,
- Optional<Provisioner> provisioner, Tenant tenant, DeployLogger deployLogger, Clock clock,
- boolean internalRedeploy, boolean prepared) {
+ Optional<Provisioner> provisioner, Tenant tenant, DeployLogger deployLogger, Clock clock, boolean prepared) {
this.session = session;
this.applicationRepository = applicationRepository;
this.params = params;
@@ -78,27 +81,26 @@ public class Deployment implements com.yahoo.config.provision.Deployment {
this.tenant = tenant;
this.deployLogger = deployLogger;
this.clock = clock;
- this.internalRedeploy = internalRedeploy;
this.prepared = prepared;
}
public static Deployment unprepared(Session session, ApplicationRepository applicationRepository,
Optional<Provisioner> provisioner, Tenant tenant, PrepareParams params, DeployLogger logger, Clock clock) {
- return new Deployment(session, applicationRepository, () -> params, provisioner, tenant, logger, clock, false, false);
+ return new Deployment(session, applicationRepository, () -> params, provisioner, tenant, logger, clock, false);
}
public static Deployment unprepared(Session session, ApplicationRepository applicationRepository,
Optional<Provisioner> provisioner, Tenant tenant, DeployLogger logger,
Duration timeout, Clock clock, boolean validate, boolean isBootstrap) {
- Supplier<PrepareParams> params = createPrepareParams(clock, timeout, session, isBootstrap, !validate, false, true);
- return new Deployment(session, applicationRepository, params, provisioner, tenant, logger, clock, true, false);
+ Supplier<PrepareParams> params = createPrepareParams(clock, timeout, session, true, isBootstrap, !validate, false, true);
+ return new Deployment(session, applicationRepository, params, provisioner, tenant, logger, clock, false);
}
public static Deployment prepared(Session session, ApplicationRepository applicationRepository,
Optional<Provisioner> provisioner, Tenant tenant, DeployLogger logger,
Duration timeout, Clock clock, boolean isBootstrap, boolean force) {
- Supplier<PrepareParams> params = createPrepareParams(clock, timeout, session, isBootstrap, false, force, false);
- return new Deployment(session, applicationRepository, params, provisioner, tenant, logger, clock, false, true);
+ Supplier<PrepareParams> params = createPrepareParams(clock, timeout, session, false, isBootstrap, false, force, false);
+ return new Deployment(session, applicationRepository, params, provisioner, tenant, logger, clock, true);
}
/** Prepares this. This does nothing if this is already prepared */
@@ -106,9 +108,8 @@ public class Deployment implements com.yahoo.config.provision.Deployment {
public void prepare() {
if (prepared) return;
- PrepareParams params = this.params.get();
- try (ActionTimer timer = applicationRepository.timerFor(params.getApplicationId(), ConfigServerMetrics.DEPLOYMENT_PREPARE_MILLIS.baseName())) {
- this.configChangeActions = sessionRepository().prepareLocalSession(session, deployLogger, params, clock.instant());
+ try (ActionTimer timer = applicationRepository.timerFor(params.get().getApplicationId(), ConfigServerMetrics.DEPLOYMENT_PREPARE_MILLIS.baseName())) {
+ this.configChangeActions = sessionRepository().prepareLocalSession(session, deployLogger, params.get(), clock.instant());
this.prepared = true;
} catch (Exception e) {
log.log(Level.FINE, "Preparing session " + session.getSessionId() + " failed, deleting it");
@@ -124,18 +125,17 @@ public class Deployment implements com.yahoo.config.provision.Deployment {
validateSessionStatus(session);
- PrepareParams params = this.params.get();
- waitForResourcesOrTimeout(params, session, provisioner);
+ waitForResourcesOrTimeout(params.get(), session, provisioner);
ApplicationId applicationId = session.getApplicationId();
try (ActionTimer timer = applicationRepository.timerFor(applicationId, ConfigServerMetrics.DEPLOYMENT_ACTIVATE_MILLIS.baseName())) {
- TimeoutBudget timeoutBudget = params.getTimeoutBudget();
+ TimeoutBudget timeoutBudget = params.get().getTimeoutBudget();
timeoutBudget.assertNotTimedOut(() -> "Timeout exceeded when trying to activate '" + applicationId + "'");
- Activation activation = applicationRepository.activate(session, applicationId, tenant, params.force());
+ Activation activation = applicationRepository.activate(session, applicationId, tenant, params.get().force());
waitForActivation(applicationId, timeoutBudget, activation);
restartServicesIfNeeded(applicationId);
- storeReindexing(applicationId, session.getMetaData().getGeneration());
+ storeReindexing(applicationId);
return session.getMetaData().getGeneration();
}
@@ -166,19 +166,22 @@ public class Deployment implements com.yahoo.config.provision.Deployment {
}
private void restartServicesIfNeeded(ApplicationId applicationId) {
- if (provisioner.isEmpty() || configChangeActions == null) return;
+ if (provisioner.isEmpty()) return;
- RestartActions restartActions = configChangeActions.getRestartActions().useForInternalRestart(internalRedeploy);
- if (restartActions.isEmpty()) return;
+ Set<String> nodesToRestart = session.getActivationTriggers().nodeRestarts().stream().map(NodeRestart::hostname).collect(toSet());
+ if (nodesToRestart.isEmpty()) return;
- Set<String> hostnames = restartActions.hostnames();
- waitForConfigToConverge(applicationId, hostnames);
+ // TODO: replace this with a maintainer that waits for active config >= this session's config generation,
+ // and let config convergence waiter in controller also check _pending_ restarts, maintained by that.
+ // Here, we'll then instead hand these restarts over to that maintainer, with our session id.
+ waitForConfigToConverge(applicationId, nodesToRestart);
- provisioner.get().restart(applicationId, HostFilter.from(hostnames));
- deployLogger.log(Level.INFO, String.format("Scheduled service restart of %d nodes: %s",
- hostnames.size(), hostnames.stream().sorted().collect(Collectors.joining(", "))));
+ provisioner.get().restart(applicationId, HostFilter.from(nodesToRestart));
+ String restartsMessage = String.format("Scheduled service restart of %d nodes: %s",
+ nodesToRestart.size(), nodesToRestart.stream().sorted().collect(Collectors.joining(", ")));
+ deployLogger.log(Level.INFO, restartsMessage);
log.info(String.format("%sScheduled service restart of %d nodes: %s",
- session.logPre(), hostnames.size(), restartActions.format()));
+ session.logPre(), nodesToRestart.size(), restartsMessage));
this.configChangeActions = configChangeActions.withRestartActions(new RestartActions());
}
@@ -222,11 +225,10 @@ public class Deployment implements com.yahoo.config.provision.Deployment {
.collect(Collectors.joining(", "));
}
- private void storeReindexing(ApplicationId applicationId, long requiredSession) {
+ private void storeReindexing(ApplicationId applicationId) {
applicationRepository.modifyReindexing(applicationId, reindexing -> {
- if (configChangeActions != null)
- for (ReindexActions.Entry entry : configChangeActions.getReindexActions().getEntries())
- reindexing = reindexing.withPending(entry.getClusterName(), entry.getDocumentType(), requiredSession);
+ for (Reindexing entry : session.getActivationTriggers().reindexings())
+ reindexing = reindexing.withPending(entry.clusterId(), entry.documentType(), session.getSessionId());
return reindexing;
});
@@ -272,7 +274,7 @@ public class Deployment implements com.yahoo.config.provision.Deployment {
* @param force whether activation of this model should be forced
*/
private static Supplier<PrepareParams> createPrepareParams(
- Clock clock, Duration timeout, Session session,
+ Clock clock, Duration timeout, Session session, boolean isInternalRedeployment,
boolean isBootstrap, boolean ignoreValidationErrors, boolean force, boolean waitForResourcesInPrepare) {
// Use supplier because we shouldn't/can't create this before validateSessionStatus() for prepared deployments,
@@ -286,6 +288,7 @@ public class Deployment implements com.yahoo.config.provision.Deployment {
.timeoutBudget(timeoutBudget)
.ignoreValidationErrors(ignoreValidationErrors)
.isBootstrap(isBootstrap)
+ .isInternalRedeployment(isInternalRedeployment)
.force(force)
.waitForResourcesInPrepare(waitForResourcesInPrepare)
.tenantSecretStores(session.getTenantSecretStores())
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/response/SessionPrepareAndActivateResponse.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/response/SessionPrepareAndActivateResponse.java
index a794b57aa89..1e6f7dfe45e 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/response/SessionPrepareAndActivateResponse.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/response/SessionPrepareAndActivateResponse.java
@@ -24,15 +24,17 @@ public class SessionPrepareAndActivateResponse extends SlimeJsonResponse {
String message = "Session " + result.sessionId() + " for tenant '" + tenantName.value() + "' prepared and activated.";
Cursor root = slime.get();
- root.setString("tenant", tenantName.value());
root.setString("session-id", Long.toString(result.sessionId()));
- root.setString("url", "http://" + request.getHost() + ":" + request.getPort() +
- "/application/v2/tenant/" + tenantName +
- "/application/" + applicationId.application().value() +
- "/environment/" + zone.environment().value() +
- "/region/" + zone.region().value() +
- "/instance/" + applicationId.instance().value());
root.setString("message", message);
+
+ // TODO: remove unused fields, but add whether activation was successful.
+ root.setString("tenant", tenantName.value());
+ root.setString("url", "http://" + request.getHost() + ":" + request.getPort() +
+ "/application/v2/tenant/" + tenantName +
+ "/application/" + applicationId.application().value() +
+ "/environment/" + zone.environment().value() +
+ "/region/" + zone.region().value() +
+ "/instance/" + applicationId.instance().value());
new ConfigChangeActionsSlimeConverter(result.configChangeActions()).toSlime(root);
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/ActivationTriggers.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/ActivationTriggers.java
new file mode 100644
index 00000000000..191f936c333
--- /dev/null
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/ActivationTriggers.java
@@ -0,0 +1,37 @@
+package com.yahoo.vespa.config.server.session;
+
+import com.yahoo.vespa.config.server.configchange.ConfigChangeActions;
+
+import java.util.List;
+
+/**
+ * Events that should trigger upon activation of a session.
+ * A prepared session is always prepared with a specific origin session, unless it is the first session in a zone,
+ * and activation of a session fails if the current session changes before the prepared session is activated.
+ * Therefore, the trigger events computed upon preparation of a session will always be valid when a session is activated,
+ * and can be stored as part of the session state.
+ * This is needed to properly handle activation of sessions on different servers than the preparing ones.
+ *
+ * @author jonmv
+ */
+public record ActivationTriggers(List<NodeRestart> nodeRestarts, List<Reindexing> reindexings) {
+
+ private static final ActivationTriggers empty = new ActivationTriggers(List.of(), List.of());
+
+ public record NodeRestart(String hostname) { }
+ public record Reindexing(String clusterId, String documentType) { }
+
+ public static ActivationTriggers empty() { return empty; }
+
+ public static ActivationTriggers from(ConfigChangeActions configChangeActions, boolean isInternalRedeployment) {
+ return new ActivationTriggers(configChangeActions.getRestartActions()
+ .useForInternalRestart(isInternalRedeployment)
+ .hostnames().stream()
+ .map(NodeRestart::new)
+ .toList(),
+ configChangeActions.getReindexActions().getEntries().stream()
+ .map(entry -> new Reindexing(entry.getClusterName(), entry.getDocumentType()))
+ .toList());
+ }
+
+}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/ActivationTriggersSerializer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/ActivationTriggersSerializer.java
new file mode 100644
index 00000000000..5f67de0ccca
--- /dev/null
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/ActivationTriggersSerializer.java
@@ -0,0 +1,60 @@
+package com.yahoo.vespa.config.server.session;
+
+import com.yahoo.slime.Cursor;
+import com.yahoo.slime.Slime;
+import com.yahoo.slime.SlimeUtils;
+import com.yahoo.vespa.config.server.session.ActivationTriggers.NodeRestart;
+import com.yahoo.vespa.config.server.session.ActivationTriggers.Reindexing;
+
+import java.util.List;
+
+import static com.yahoo.yolean.Exceptions.uncheck;
+
+/**
+ * @author jonmv
+ */
+public class ActivationTriggersSerializer {
+
+ static final String NODE_RESTARTS = "nodeRestarts";
+ static final String REINDEXINGS = "reindexings";
+ static final String CLUSTER_NAME = "clusterName";
+ static final String DOCUMENT_TYPE = "documentType";
+
+ public static byte[] toJson(ActivationTriggers triggers) {
+ Slime root = new Slime();
+ toSlime(triggers, root.setObject());
+ return uncheck(() -> SlimeUtils.toJsonBytes(root));
+ }
+
+ public static ActivationTriggers fromJson(byte[] json) {
+ return fromSlime(SlimeUtils.jsonToSlime(json).get());
+ }
+
+ public static void toSlime(ActivationTriggers triggers, Cursor object) {
+ Cursor nodeRestarts = object.setArray(NODE_RESTARTS);
+ for (NodeRestart nodeRestart : triggers.nodeRestarts())
+ nodeRestarts.addString(nodeRestart.hostname());
+
+ Cursor reindexings = object.setArray(REINDEXINGS);
+ for (Reindexing reindexing : triggers.reindexings()) {
+ Cursor entry = reindexings.addObject();
+ entry.setString(CLUSTER_NAME, reindexing.clusterId());
+ entry.setString(DOCUMENT_TYPE, reindexing.documentType());
+ }
+ }
+
+ public static ActivationTriggers fromSlime(Cursor object) {
+ if ( ! object.valid())
+ return ActivationTriggers.empty();
+
+ List<NodeRestart> nodeRestarts = SlimeUtils.entriesStream(object.field(NODE_RESTARTS))
+ .map(entry -> new NodeRestart(entry.asString()))
+ .toList();
+ List<Reindexing> reindexings = SlimeUtils.entriesStream(object.field(REINDEXINGS))
+ .map(entry -> new Reindexing(entry.field(CLUSTER_NAME).asString(),
+ entry.field(DOCUMENT_TYPE).asString()))
+ .toList();
+ return new ActivationTriggers(nodeRestarts, reindexings);
+ }
+
+}
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 19f227f8dc8..ec24cc17284 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
@@ -65,6 +65,7 @@ public final class PrepareParams {
private final boolean dryRun;
private final boolean verbose;
private final boolean isBootstrap;
+ private final boolean isInternalRedeployment;
private final boolean force;
private final boolean waitForResourcesInPrepare;
private final Optional<Version> vespaVersion;
@@ -84,6 +85,7 @@ public final class PrepareParams {
boolean dryRun,
boolean verbose,
boolean isBootstrap,
+ boolean isInternalRedeployment,
Optional<Version> vespaVersion,
List<ContainerEndpoint> containerEndpoints,
Optional<EndpointCertificateMetadata> endpointCertificateMetadata,
@@ -102,6 +104,7 @@ public final class PrepareParams {
this.dryRun = dryRun;
this.verbose = verbose;
this.isBootstrap = isBootstrap;
+ this.isInternalRedeployment = isInternalRedeployment;
this.vespaVersion = vespaVersion;
this.containerEndpoints = containerEndpoints;
this.endpointCertificateMetadata = endpointCertificateMetadata;
@@ -124,6 +127,7 @@ public final class PrepareParams {
private boolean isBootstrap = false;
private boolean force = false;
private boolean waitForResourcesInPrepare = false;
+ private boolean isInternalRedeployment = false;
private ApplicationId applicationId = null;
private TimeoutBudget timeoutBudget = new TimeoutBudget(Clock.systemUTC(), Duration.ofSeconds(60));
private Optional<Version> vespaVersion = Optional.empty();
@@ -164,6 +168,11 @@ public final class PrepareParams {
return this;
}
+ public Builder isInternalRedeployment(boolean isInternalRedeployment) {
+ this.isInternalRedeployment = isInternalRedeployment;
+ return this;
+ }
+
public Builder timeoutBudget(TimeoutBudget timeoutBudget) {
this.timeoutBudget = timeoutBudget;
return this;
@@ -285,6 +294,7 @@ public final class PrepareParams {
dryRun,
verbose,
isBootstrap,
+ isInternalRedeployment,
vespaVersion,
containerEndpoints,
endpointCertificateMetadata,
@@ -424,6 +434,8 @@ public final class PrepareParams {
public boolean isBootstrap() { return isBootstrap; }
+ public boolean isInternalRedeployment() { return isInternalRedeployment; }
+
public boolean force() { return force; }
public boolean waitForResourcesInPrepare() { return waitForResourcesInPrepare; }
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/Session.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/Session.java
index 37ebfa12e7e..39025aa8374 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/Session.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/Session.java
@@ -158,6 +158,10 @@ public abstract class Session implements Comparable<Session> {
return sessionZooKeeperClient.readDataplaneTokens();
}
+ public ActivationTriggers getActivationTriggers() {
+ return sessionZooKeeperClient.readActivationTriggers();
+ }
+
public SessionZooKeeperClient getSessionZooKeeperClient() { return sessionZooKeeperClient; }
private Transaction createSetStatusTransaction(Status status) {
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionData.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionData.java
index 6c27eefc055..1757998882e 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionData.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionData.java
@@ -41,7 +41,8 @@ public record SessionData(ApplicationId applicationId,
List<TenantSecretStore> tenantSecretStores,
List<X509Certificate> operatorCertificates,
Optional<CloudAccount> cloudAccount,
- List<DataplaneToken> dataplaneTokens) {
+ List<DataplaneToken> dataplaneTokens,
+ ActivationTriggers activationTriggers) {
// NOTE: Any state added here MUST also be propagated in com.yahoo.vespa.config.server.deploy.Deployment.prepare()
static final String APPLICATION_ID_PATH = "applicationId";
@@ -56,6 +57,7 @@ public record SessionData(ApplicationId applicationId,
static final String CLOUD_ACCOUNT_PATH = "cloudAccount";
static final String DATAPLANE_TOKENS_PATH = "dataplaneTokens";
static final String SESSION_DATA_PATH = "sessionData";
+ static final String ACTIVATION_TRIGGERS_PATH = "activationTriggers";
public byte[] toJson() {
try {
@@ -87,6 +89,8 @@ public record SessionData(ApplicationId applicationId,
Cursor dataplaneTokensArray = object.setArray(DATAPLANE_TOKENS_PATH);
DataplaneTokenSerializer.toSlime(dataplaneTokens, dataplaneTokensArray);
+
+ ActivationTriggersSerializer.toSlime(activationTriggers, object.setObject(ACTIVATION_TRIGGERS_PATH));
}
static SessionData fromSlime(Slime slime) {
@@ -103,7 +107,8 @@ public record SessionData(ApplicationId applicationId,
TenantSecretStoreSerializer.listFromSlime(cursor.field(TENANT_SECRET_STORES_PATH)),
OperatorCertificateSerializer.fromSlime(cursor.field(OPERATOR_CERTIFICATES_PATH)),
optionalString(cursor.field(CLOUD_ACCOUNT_PATH)).map(CloudAccount::from),
- DataplaneTokenSerializer.fromSlime(cursor.field(DATAPLANE_TOKENS_PATH)));
+ DataplaneTokenSerializer.fromSlime(cursor.field(DATAPLANE_TOKENS_PATH)),
+ ActivationTriggersSerializer.fromSlime(cursor.field(ACTIVATION_TRIGGERS_PATH)));
}
}
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 5c049c61c1a..87d3c8ce093 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
@@ -358,7 +358,8 @@ public class SessionPreparer {
params.tenantSecretStores(),
params.operatorCertificates(),
params.cloudAccount(),
- params.dataplaneTokens());
+ params.dataplaneTokens(),
+ ActivationTriggers.from(prepareResult.getConfigChangeActions(), params.isInternalRedeployment()));
checkTimeout("write state to zookeeper");
}
@@ -401,7 +402,8 @@ public class SessionPreparer {
List<TenantSecretStore> tenantSecretStores,
List<X509Certificate> operatorCertificates,
Optional<CloudAccount> cloudAccount,
- List<DataplaneToken> dataplaneTokens) {
+ List<DataplaneToken> dataplaneTokens,
+ ActivationTriggers activationTriggers) {
var zooKeeperDeplyer = new ZooKeeperDeployer(curator, deployLogger, applicationId, zooKeeperClient.sessionId());
try {
zooKeeperDeplyer.deploy(applicationPackage, fileRegistryMap, allocatedHosts);
@@ -417,6 +419,7 @@ public class SessionPreparer {
operatorCertificates,
cloudAccount,
dataplaneTokens,
+ activationTriggers,
writeSessionData);
} catch (RuntimeException | IOException e) {
zooKeeperDeplyer.cleanup();
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java
index 47b8215b52d..8da58de26b8 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java
@@ -606,6 +606,7 @@ public class SessionRepository {
existingSession.getOperatorCertificates(),
existingSession.getCloudAccount(),
existingSession.getDataplaneTokens(),
+ existingSession.getActivationTriggers(),
writeSessionData);
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionSerializer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionSerializer.java
index f71476c5770..438db91721f 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionSerializer.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionSerializer.java
@@ -33,7 +33,7 @@ public class SessionSerializer {
Instant created, Optional<FileReference> fileReference, Optional<DockerImage> dockerImageRepository,
Version vespaVersion, Optional<AthenzDomain> athenzDomain, Optional<Quota> quota,
List<TenantSecretStore> tenantSecretStores, List<X509Certificate> operatorCertificates,
- Optional<CloudAccount> cloudAccount, List<DataplaneToken> dataplaneTokens,
+ Optional<CloudAccount> cloudAccount, List<DataplaneToken> dataplaneTokens, ActivationTriggers activationTriggers,
BooleanFlag writeSessionData) {
zooKeeperClient.writeApplicationId(applicationId);
zooKeeperClient.writeApplicationPackageReference(fileReference);
@@ -45,6 +45,7 @@ public class SessionSerializer {
zooKeeperClient.writeOperatorCertificates(operatorCertificates);
zooKeeperClient.writeCloudAccount(cloudAccount);
zooKeeperClient.writeDataplaneTokens(dataplaneTokens);
+ zooKeeperClient.writeActivationTriggers(activationTriggers);
if (writeSessionData.value())
zooKeeperClient.writeSessionData(new SessionData(applicationId,
fileReference,
@@ -56,7 +57,8 @@ public class SessionSerializer {
tenantSecretStores,
operatorCertificates,
cloudAccount,
- dataplaneTokens));
+ dataplaneTokens,
+ activationTriggers));
}
SessionData read(SessionZooKeeperClient zooKeeperClient, BooleanFlag readSessionData) {
@@ -82,7 +84,8 @@ public class SessionSerializer {
zooKeeperClient.readTenantSecretStores(),
zooKeeperClient.readOperatorCertificates(),
zooKeeperClient.readCloudAccount(),
- zooKeeperClient.readDataplaneTokens());
+ zooKeeperClient.readDataplaneTokens(),
+ zooKeeperClient.readActivationTriggers());
}
}
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 cbd6e956d1d..bcffecf28ea 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
@@ -42,6 +42,7 @@ import java.util.List;
import java.util.Optional;
import java.util.logging.Level;
+import static com.yahoo.vespa.config.server.session.SessionData.ACTIVATION_TRIGGERS_PATH;
import static com.yahoo.vespa.config.server.session.SessionData.APPLICATION_ID_PATH;
import static com.yahoo.vespa.config.server.session.SessionData.APPLICATION_PACKAGE_REFERENCE_PATH;
import static com.yahoo.vespa.config.server.session.SessionData.ATHENZ_DOMAIN;
@@ -353,6 +354,16 @@ public class SessionZooKeeperClient {
.orElse(List.of());
}
+ public void writeActivationTriggers(ActivationTriggers activationTriggers) {
+ curator.set(sessionPath.append(ACTIVATION_TRIGGERS_PATH), ActivationTriggersSerializer.toJson(activationTriggers));
+ }
+
+ public ActivationTriggers readActivationTriggers() {
+ return curator.getData(sessionPath.append(ACTIVATION_TRIGGERS_PATH))
+ .map(ActivationTriggersSerializer::fromJson)
+ .orElse(ActivationTriggers.empty());
+ }
+
/**
* Create necessary paths atomically for a new session.
*
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/ActivationTriggersSerializerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/ActivationTriggersSerializerTest.java
new file mode 100644
index 00000000000..97f079d9ee3
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/ActivationTriggersSerializerTest.java
@@ -0,0 +1,27 @@
+package com.yahoo.vespa.config.server.session;
+
+import com.yahoo.vespa.config.server.session.ActivationTriggers.NodeRestart;
+import com.yahoo.vespa.config.server.session.ActivationTriggers.Reindexing;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+
+/**
+ * @author jonmv
+ */
+class ActivationTriggersSerializerTest {
+
+ @Test
+ void testSerialization() {
+ ActivationTriggers triggers = new ActivationTriggers(List.of(new NodeRestart("node1"),
+ new NodeRestart("node2")),
+ List.of(new Reindexing("cluster1", "type1"),
+ new Reindexing("cluster1", "type2"),
+ new Reindexing("cluster2", "type1")));
+ assertEquals(triggers, ActivationTriggersSerializer.fromJson(ActivationTriggersSerializer.toJson(triggers)));
+ }
+
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClientTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClientTest.java
index 7fcaae05224..a7892bd4c56 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClientTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClientTest.java
@@ -14,9 +14,7 @@ import com.yahoo.vespa.config.server.tenant.TenantRepository;
import com.yahoo.vespa.curator.Curator;
import com.yahoo.vespa.curator.mock.MockCurator;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
-import org.junit.rules.ExpectedException;
import java.time.Instant;
import java.util.List;
@@ -27,6 +25,7 @@ import static com.yahoo.vespa.config.server.session.SessionData.SESSION_DATA_PAT
import static com.yahoo.vespa.config.server.zookeeper.ZKApplication.SESSIONSTATE_ZK_SUBPATH;
import static java.math.BigDecimal.valueOf;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
/**
@@ -38,10 +37,6 @@ public class SessionZooKeeperClientTest {
private Curator curator;
- @SuppressWarnings("deprecation")
- @Rule
- public ExpectedException expectedException = ExpectedException.none();
-
@Before
public void setup() {
curator = new MockCurator();
@@ -107,9 +102,9 @@ public class SessionZooKeeperClientTest {
int sessionId = 3;
SessionZooKeeperClient zkc = createSessionZKClient(sessionId);
- expectedException.expect(IllegalArgumentException.class);
- expectedException.expectMessage("Cannot write application id 'someOtherTenant.foo.bim' for tenant 'default'");
- zkc.writeApplicationId(id);
+ assertEquals("Cannot write application id 'someOtherTenant.foo.bim' for tenant 'default'",
+ assertThrows(IllegalArgumentException.class,
+ () -> zkc.writeApplicationId(id)).getMessage());
}
@Test
@@ -176,12 +171,13 @@ public class SessionZooKeeperClientTest {
List.of(),
List.of(),
Optional.empty(),
- List.of()));
+ List.of(),
+ ActivationTriggers.empty()));
Path path = sessionPath(sessionId).append(SESSION_DATA_PATH);
assertTrue(curator.exists(path));
String data = Utf8.toString(curator.getData(path).get());
assertTrue(data.contains("{\"applicationId\":\"default:default:default\",\"applicationPackageReference\":\"foo\",\"version\":\"8.195.1\",\"createTime\":"));
- assertTrue(data.contains(",\"tenantSecretStores\":[],\"operatorCertificates\":[],\"dataplaneTokens\":[]}"));
+ assertTrue(data.contains(",\"tenantSecretStores\":[],\"operatorCertificates\":[],\"dataplaneTokens\":[],\"activationTriggers\":{\"nodeRestarts\":[],\"reindexings\":[]}"));
}
private void assertApplicationIdParse(long sessionId, String idString, String expectedIdString) {