summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHåkon Hallingstad <hakon@yahooinc.com>2022-08-18 16:57:55 +0200
committerHåkon Hallingstad <hakon@yahooinc.com>2022-08-18 16:57:55 +0200
commitb679206de2431f11e52e6734abfcaefa40554037 (patch)
treebde9c884a71f552165d5554596c48bcd3f63fdf1
parent7a65553cd4efc3574cb1bd859b17008ecdf878d6 (diff)
Allow overriding wanted docker tag and vespa version
-rw-r--r--component/abi-spec.json1
-rw-r--r--component/src/main/java/com/yahoo/component/Version.java6
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/FetchVector.java4
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/Flags.java17
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/PermanentFlags.java30
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/UnboundStringFlag.java28
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesResponse.java31
7 files changed, 108 insertions, 9 deletions
diff --git a/component/abi-spec.json b/component/abi-spec.json
index d990a9077b4..cfbbdf4b306 100644
--- a/component/abi-spec.json
+++ b/component/abi-spec.json
@@ -138,6 +138,7 @@
"public void <init>(java.lang.String)",
"public void <init>(com.yahoo.text.Utf8Array)",
"public static com.yahoo.component.Version fromString(java.lang.String)",
+ "public com.yahoo.component.Version withQualifier(java.lang.String)",
"public java.lang.String toFullString()",
"public int getMajor()",
"public int getMinor()",
diff --git a/component/src/main/java/com/yahoo/component/Version.java b/component/src/main/java/com/yahoo/component/Version.java
index 1d4546c0c58..db8606d31fa 100644
--- a/component/src/main/java/com/yahoo/component/Version.java
+++ b/component/src/main/java/com/yahoo/component/Version.java
@@ -202,6 +202,12 @@ public final class Version implements Comparable<Version> {
return (versionString == null) ? emptyVersion :new Version(versionString);
}
+ public Version withQualifier(String qualifier) {
+ if (qualifier.indexOf('.') != -1)
+ throw new IllegalArgumentException("Qualifier cannot contain '.'");
+ return new Version(major, minor, micro, qualifier);
+ }
+
/**
* Must be called on construction after the component values are set
*
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/FetchVector.java b/flags/src/main/java/com/yahoo/vespa/flags/FetchVector.java
index 9f40013f627..c1877373ce2 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/FetchVector.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/FetchVector.java
@@ -20,7 +20,7 @@ public class FetchVector {
* Note: If this enum is changed, you must also change {@link DimensionHelper}.
*/
public enum Dimension {
- /** A legal value for TenantName, e.g. vespa-team */
+ /** Value from TenantName::value, e.g. vespa-team */
TENANT_ID,
/** Value from ApplicationId::serializedForm of the form tenant:applicationName:instance. */
@@ -29,7 +29,7 @@ public class FetchVector {
/** Node type from com.yahoo.config.provision.NodeType::name, e.g. tenant, host, confighost, controller, etc. */
NODE_TYPE,
- /** Cluster type from com.yahoo.config.provision.ClusterSpec.Type::value, e.g. content, container, admin */
+ /** Cluster type from com.yahoo.config.provision.ClusterSpec.Type::name, e.g. content, container, admin */
CLUSTER_TYPE,
/** Cluster ID from com.yahoo.config.provision.ClusterSpec.Id::value, e.g. cluster-controllers, logserver. */
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
index e349165b855..9552e1a961d 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
@@ -11,6 +11,7 @@ import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Optional;
import java.util.TreeMap;
+import java.util.function.Predicate;
import static com.yahoo.vespa.flags.FetchVector.Dimension.APPLICATION_ID;
import static com.yahoo.vespa.flags.FetchVector.Dimension.CONSOLE_USER_EMAIL;
@@ -490,7 +491,19 @@ public class Flags {
public static UnboundStringFlag defineStringFlag(String flagId, String defaultValue, List<String> owners,
String createdAt, String expiresAt, String description,
String modificationEffect, FetchVector.Dimension... dimensions) {
- return define(UnboundStringFlag::new, flagId, defaultValue, owners, createdAt, expiresAt, description, modificationEffect, dimensions);
+ return defineStringFlag(flagId, defaultValue, owners,
+ createdAt, expiresAt, description,
+ modificationEffect, value -> true,
+ dimensions);
+ }
+
+ /** WARNING: public for testing: All flags should be defined in {@link Flags}. */
+ public static UnboundStringFlag defineStringFlag(String flagId, String defaultValue, List<String> owners,
+ String createdAt, String expiresAt, String description,
+ String modificationEffect, Predicate<String> validator,
+ FetchVector.Dimension... dimensions) {
+ return define((i, d, v) -> new UnboundStringFlag(i, d, v, validator),
+ flagId, defaultValue, owners, createdAt, expiresAt, description, modificationEffect, dimensions);
}
/** WARNING: public for testing: All flags should be defined in {@link Flags}. */
@@ -532,7 +545,7 @@ public class Flags {
@FunctionalInterface
private interface TypedUnboundFlagFactory<T, U extends UnboundFlag<?, ?, ?>> {
- U create(FlagId id, T defaultVale, FetchVector defaultFetchVector);
+ U create(FlagId id, T defaultValue, FetchVector defaultFetchVector);
}
/**
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/PermanentFlags.java b/flags/src/main/java/com/yahoo/vespa/flags/PermanentFlags.java
index b5a292a554d..0003d5e42c7 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/PermanentFlags.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/PermanentFlags.java
@@ -9,6 +9,8 @@ import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
+import java.util.function.Predicate;
+import java.util.regex.Pattern;
import static com.yahoo.vespa.flags.FetchVector.Dimension.APPLICATION_ID;
import static com.yahoo.vespa.flags.FetchVector.Dimension.CLUSTER_ID;
@@ -17,6 +19,7 @@ import static com.yahoo.vespa.flags.FetchVector.Dimension.CONSOLE_USER_EMAIL;
import static com.yahoo.vespa.flags.FetchVector.Dimension.HOSTNAME;
import static com.yahoo.vespa.flags.FetchVector.Dimension.NODE_TYPE;
import static com.yahoo.vespa.flags.FetchVector.Dimension.TENANT_ID;
+import static com.yahoo.vespa.flags.FetchVector.Dimension.VESPA_VERSION;
import static com.yahoo.vespa.flags.FetchVector.Dimension.ZONE_ID;
/**
@@ -113,6 +116,28 @@ public class PermanentFlags {
"Takes effect on next deployment from controller",
ZONE_ID, APPLICATION_ID);
+ private static final String VERSION_QUALIFIER_REGEX = "[a-zA-Z0-9_-]+";
+ private static final Pattern QUALIFIER_PATTERN = Pattern.compile("^" + VERSION_QUALIFIER_REGEX + "$");
+ private static final Pattern VERSION_PATTERN = Pattern.compile("^\\d\\.\\d\\.\\d(\\." + VERSION_QUALIFIER_REGEX + ")?$");
+
+ public static final UnboundStringFlag WANTED_DOCKER_TAG = defineStringFlag(
+ "wanted-docker-tag", "",
+ "If non-empty the flag value overrides the docker image tag of the wantedDockerImage of the node object. " +
+ "If the flag value contains '.', it must specify a valid Vespa version like '8.83.42'. " +
+ "Otherwise a '.' + the flag value will be appended.",
+ "Takes effect on the next host admin tick. The upgrade to the new wanted docker image is orchestrated.",
+ value -> value.isEmpty() || QUALIFIER_PATTERN.matcher(value).find() || VERSION_PATTERN.matcher(value).find(),
+ HOSTNAME, NODE_TYPE, TENANT_ID, APPLICATION_ID, CLUSTER_TYPE, CLUSTER_ID, VESPA_VERSION);
+
+ public static final UnboundStringFlag WANTED_VESPA_VERSION = defineStringFlag(
+ "wanted-vespa-version", "",
+ "If non-empty the flag value overrides the wantedVespaVersion of the node object." +
+ "If the flag value contains '.', it must specify a valid Vespa version like '8.83.42'. " +
+ "Otherwise a '.' + the flag value will be appended.",
+ "Takes effect on the next host admin tick. The upgrade to the new wanted docker image is orchestrated.",
+ value -> value.isEmpty() || QUALIFIER_PATTERN.matcher(value).find() || VERSION_PATTERN.matcher(value).find(),
+ HOSTNAME, NODE_TYPE, TENANT_ID, APPLICATION_ID, CLUSTER_TYPE, CLUSTER_ID, VESPA_VERSION);
+
public static final UnboundStringFlag ZOOKEEPER_SERVER_VERSION = defineStringFlag(
"zookeeper-server-version", "3.7.1", // Note: Nodes running Vespa 7 have 3.7.1 as the only available version
"ZooKeeper server version, a jar file zookeeper-server-<ZOOKEEPER_SERVER_VERSION>-jar-with-dependencies.jar must exist",
@@ -280,6 +305,11 @@ public class PermanentFlags {
return Flags.defineStringFlag(flagId, defaultValue, OWNERS, toString(CREATED_AT), toString(EXPIRES_AT), description, modificationEffect, dimensions);
}
+ private static UnboundStringFlag defineStringFlag(
+ String flagId, String defaultValue, String description, String modificationEffect, Predicate<String> validator, FetchVector.Dimension... dimensions) {
+ return Flags.defineStringFlag(flagId, defaultValue, OWNERS, toString(CREATED_AT), toString(EXPIRES_AT), description, modificationEffect, validator, dimensions);
+ }
+
private static UnboundIntFlag defineIntFlag(
String flagId, int defaultValue, String description, String modificationEffect, FetchVector.Dimension... dimensions) {
return Flags.defineIntFlag(flagId, defaultValue, OWNERS, toString(CREATED_AT), toString(EXPIRES_AT), description, modificationEffect, dimensions);
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/UnboundStringFlag.java b/flags/src/main/java/com/yahoo/vespa/flags/UnboundStringFlag.java
index f96be55e2eb..9c69e917fa6 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/UnboundStringFlag.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/UnboundStringFlag.java
@@ -4,6 +4,10 @@ package com.yahoo.vespa.flags;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.TextNode;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
/**
* @author hakonhall
*/
@@ -13,8 +17,26 @@ public class UnboundStringFlag extends UnboundFlagImpl<String, StringFlag, Unbou
}
public UnboundStringFlag(FlagId id, String defaultValue, FetchVector defaultFetchVector) {
- super(id, defaultValue, defaultFetchVector,
- new SimpleFlagSerializer<>(TextNode::new, JsonNode::isTextual, JsonNode::asText),
- UnboundStringFlag::new, StringFlag::new);
+ this(id, defaultValue, defaultFetchVector,
+ new SimpleFlagSerializer<>(TextNode::new, JsonNode::isTextual, JsonNode::asText));
+ }
+
+ public UnboundStringFlag(FlagId id, String defaultValue, Predicate<String> validator) {
+ this(id, defaultValue, new FetchVector(), validator);
+ }
+
+ public UnboundStringFlag(FlagId id, String defaultValue, FetchVector fetchVector, Predicate<String> validator) {
+ this(id, defaultValue, fetchVector,
+ new SimpleFlagSerializer<>(stringValue -> {
+ if (!validator.test(stringValue))
+ throw new IllegalArgumentException("Invalid value: '" + stringValue + "'");
+ return new TextNode(stringValue);
+ },
+ JsonNode::isTextual, JsonNode::asText));
+ }
+
+ public UnboundStringFlag(FlagId id, String defaultValue, FetchVector defaultFetchVector,
+ FlagSerializer<String> serializer) {
+ super(id, defaultValue, defaultFetchVector, serializer, UnboundStringFlag::new, StringFlag::new);
}
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesResponse.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesResponse.java
index 87a9735f91e..2ecf73a645b 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesResponse.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesResponse.java
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.provision.restapi;
+import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ClusterMembership;
import com.yahoo.config.provision.DockerImage;
@@ -10,10 +11,14 @@ import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.restapi.SlimeJsonResponse;
import com.yahoo.slime.Cursor;
import com.yahoo.vespa.applicationmodel.HostName;
+import com.yahoo.vespa.flags.FetchVector;
+import com.yahoo.vespa.flags.PermanentFlags;
+import com.yahoo.vespa.flags.StringFlag;
import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.NodeList;
import com.yahoo.vespa.hosted.provision.NodeRepository;
import com.yahoo.vespa.hosted.provision.node.Address;
+import com.yahoo.vespa.hosted.provision.node.Allocation;
import com.yahoo.vespa.hosted.provision.node.History;
import com.yahoo.vespa.hosted.provision.node.TrustStoreItem;
import com.yahoo.vespa.hosted.provision.node.filter.NodeFilter;
@@ -47,6 +52,8 @@ class NodesResponse extends SlimeJsonResponse {
private final boolean recursive;
private final Function<HostName, Optional<HostInfo>> orchestrator;
private final NodeRepository nodeRepository;
+ private final StringFlag wantedDockerTagFlag;
+ private final StringFlag wantedVespaVersionFlag;
public NodesResponse(ResponseType responseType, HttpRequest request,
Orchestrator orchestrator, NodeRepository nodeRepository) {
@@ -56,6 +63,8 @@ class NodesResponse extends SlimeJsonResponse {
this.recursive = request.getBooleanProperty("recursive");
this.orchestrator = orchestrator.getHostResolver();
this.nodeRepository = nodeRepository;
+ this.wantedDockerTagFlag = PermanentFlags.WANTED_DOCKER_TAG.bindTo(nodeRepository.flagSource());
+ this.wantedVespaVersionFlag = PermanentFlags.WANTED_VESPA_VERSION.bindTo(nodeRepository.flagSource());
Cursor root = slime.setObject();
switch (responseType) {
@@ -146,8 +155,8 @@ class NodesResponse extends SlimeJsonResponse {
toSlime(allocation.membership(), object.setObject("membership"));
object.setLong("restartGeneration", allocation.restartGeneration().wanted());
object.setLong("currentRestartGeneration", allocation.restartGeneration().current());
- object.setString("wantedDockerImage", nodeRepository.containerImages().get(node).withTag(allocation.membership().cluster().vespaVersion()).asString());
- object.setString("wantedVespaVersion", allocation.membership().cluster().vespaVersion().toFullString());
+ object.setString("wantedDockerImage", nodeRepository.containerImages().get(node).withTag(resolveVersionFlag(wantedDockerTagFlag, node, allocation)).asString());
+ object.setString("wantedVespaVersion", resolveVersionFlag(wantedVespaVersionFlag, node, allocation).toFullString());
NodeResourcesSerializer.toSlime(allocation.requestedResources(), object.setObject("requestedResources"));
allocation.networkPorts().ifPresent(ports -> NetworkPortsSerializer.toSlime(ports, object.setArray("networkPorts")));
orchestrator.apply(new HostName(node.hostname()))
@@ -189,6 +198,24 @@ class NodesResponse extends SlimeJsonResponse {
node.cloudAccount().ifPresent(cloudAccount -> object.setString("cloudAccount", cloudAccount.value()));
}
+ private Version resolveVersionFlag(StringFlag flag, Node node, Allocation allocation) {
+ String value = flag
+ .with(FetchVector.Dimension.HOSTNAME, node.hostname())
+ .with(FetchVector.Dimension.NODE_TYPE, node.type().name())
+ .with(FetchVector.Dimension.TENANT_ID, allocation.owner().tenant().value())
+ .with(FetchVector.Dimension.APPLICATION_ID, allocation.owner().serializedForm())
+ .with(FetchVector.Dimension.CLUSTER_TYPE, allocation.membership().cluster().type().name())
+ .with(FetchVector.Dimension.CLUSTER_ID, allocation.membership().cluster().id().value())
+ .with(FetchVector.Dimension.VESPA_VERSION, allocation.membership().cluster().vespaVersion().toFullString())
+ .value();
+
+ return value.isEmpty() ?
+ allocation.membership().cluster().vespaVersion() :
+ value.indexOf('.') == -1 ?
+ allocation.membership().cluster().vespaVersion().withQualifier(value) :
+ new Version(value);
+ }
+
private void toSlime(ApplicationId id, Cursor object) {
object.setString("tenant", id.tenant().value());
object.setString("application", id.application().value());