diff options
author | jonmv <venstad@gmail.com> | 2024-01-11 15:04:33 +0100 |
---|---|---|
committer | jonmv <venstad@gmail.com> | 2024-01-11 15:04:33 +0100 |
commit | 1699dd01dd4d5dfd753cd3d3e6dcc7e58da77a5d (patch) | |
tree | 2141a68558f189358a5c32a9c9fc63c80ff63cb9 | |
parent | 697f451c9a60df5240dece9fd330b9d30a0eb637 (diff) |
Store config change actions on prepare, trigger on activate
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) { |