summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--application-preprocessor/src/main/java/com/yahoo/application/preprocessor/ApplicationPreprocessor.java68
-rw-r--r--application-preprocessor/src/test/java/com/yahoo/application/preprocessor/ApplicationPreprocessorTest.java11
-rw-r--r--config-application-package/src/main/java/com/yahoo/config/application/OverrideProcessor.java76
-rw-r--r--config-application-package/src/main/java/com/yahoo/config/application/XmlPreProcessor.java45
-rw-r--r--config-application-package/src/main/java/com/yahoo/config/model/application/provider/DeployData.java20
-rw-r--r--config-application-package/src/main/java/com/yahoo/config/model/application/provider/FilesApplicationPackage.java6
-rw-r--r--config-application-package/src/test/java/com/yahoo/config/application/HostedOverrideProcessorTagsTest.java124
-rw-r--r--config-application-package/src/test/java/com/yahoo/config/application/HostedOverrideProcessorTest.java86
-rw-r--r--config-application-package/src/test/java/com/yahoo/config/application/MultiOverrideProcessorTest.java5
-rw-r--r--config-application-package/src/test/java/com/yahoo/config/application/OverrideProcessorTest.java13
-rw-r--r--config-application-package/src/test/java/com/yahoo/config/application/XmlPreprocessorTest.java47
-rw-r--r--config-model-api/abi-spec.json5
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/application/api/ApplicationMetaData.java34
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentInstanceSpec.java6
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentSpec.java2
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/application/api/xml/DeploymentSpecXmlReader.java38
-rw-r--r--config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecTest.java24
-rw-r--r--config-model/src/main/java/com/yahoo/config/model/test/MockApplicationPackage.java2
-rw-r--r--config-model/src/main/resources/schema/deployment.rnc1
-rw-r--r--config-model/src/test/java/com/yahoo/config/model/ApplicationDeployTest.java13
-rw-r--r--config-model/src/test/schema-test-files/deployment-with-instances.xml2
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/Tags.java61
-rw-r--r--config-provisioning/src/test/java/com/yahoo/config/provision/TagsTest.java52
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java16
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandler.java11
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/PrepareParams.java61
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/Session.java9
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java19
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepository.java27
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionZooKeeperClient.java16
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java3
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClientTest.java3
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java2
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java9
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/session/PrepareParamsTest.java15
-rw-r--r--container-search/src/main/java/com/yahoo/search/handler/Json2SingleLevelMap.java16
-rw-r--r--container-search/src/main/java/com/yahoo/search/querytransform/NGramSearcher.java12
-rw-r--r--container-search/src/main/java/com/yahoo/search/searchchain/AsyncExecution.java2
-rw-r--r--container-search/src/test/java/com/yahoo/search/test/QueryTestCase.java2
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/DeploymentData.java8
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java9
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java52
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Instance.java25
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java5
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackage.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java14
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java7
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java3
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java3
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/DeploymentQuotaCalculatorTest.java5
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificatesTest.java5
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java3
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java31
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/athenz/AthenzApiTest.java5
-rw-r--r--vespa-maven-plugin/src/main/java/ai/vespa/hosted/plugin/AbstractVespaMojo.java3
-rw-r--r--vespa-maven-plugin/src/main/java/ai/vespa/hosted/plugin/EffectiveServicesMojo.java8
-rw-r--r--vespa-maven-plugin/src/test/java/ai/vespa/hosted/plugin/EffectiveServicesMojoTest.java7
57 files changed, 913 insertions, 248 deletions
diff --git a/application-preprocessor/src/main/java/com/yahoo/application/preprocessor/ApplicationPreprocessor.java b/application-preprocessor/src/main/java/com/yahoo/application/preprocessor/ApplicationPreprocessor.java
index 081e9ad6036..9696e9c9848 100644
--- a/application-preprocessor/src/main/java/com/yahoo/application/preprocessor/ApplicationPreprocessor.java
+++ b/application-preprocessor/src/main/java/com/yahoo/application/preprocessor/ApplicationPreprocessor.java
@@ -3,14 +3,21 @@ package com.yahoo.application.preprocessor;
import com.yahoo.config.application.api.ApplicationPackage;
import com.yahoo.config.model.application.provider.BaseDeployLogger;
+import com.yahoo.config.model.application.provider.DeployData;
import com.yahoo.config.model.application.provider.FilesApplicationPackage;
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.Environment;
+import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.Tags;
+import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.Zone;
import com.yahoo.yolean.Exceptions;
import java.io.File;
import java.io.IOException;
import java.util.Optional;
+import java.util.Set;
/**
* Main entry for preprocessing an application package.
@@ -21,36 +28,75 @@ public class ApplicationPreprocessor {
private final File applicationDir;
private final Optional<File> outputDir;
+ private final Optional<InstanceName> instance;
private final Optional<Environment> environment;
private final Optional<RegionName> region;
+ private final Tags tags;
- public ApplicationPreprocessor(File applicationDir, Optional<File> outputDir, Optional<Environment> environment, Optional<RegionName> region) {
+ public ApplicationPreprocessor(File applicationDir,
+ Optional<File> outputDir,
+ Optional<InstanceName> instance,
+ Optional<Environment> environment,
+ Optional<RegionName> region,
+ Tags tags) {
this.applicationDir = applicationDir;
this.outputDir = outputDir;
+ this.instance = instance;
this.environment = environment;
this.region = region;
+ this.tags = tags;
}
public void run() throws IOException {
FilesApplicationPackage.Builder applicationPackageBuilder = new FilesApplicationPackage.Builder(applicationDir);
outputDir.ifPresent(applicationPackageBuilder::preprocessedDir);
- ApplicationPackage preprocessed = applicationPackageBuilder.build().preprocess(
- new Zone(environment.orElse(Environment.defaultEnvironment()), region.orElse(RegionName.defaultName())),
- new BaseDeployLogger());
+ applicationPackageBuilder.deployData(new DeployData(applicationDir.getAbsolutePath(),
+ ApplicationId.from(TenantName.defaultName(),
+ ApplicationName.defaultName(),
+ instance.orElse(InstanceName.defaultName())),
+ tags,
+ System.currentTimeMillis(),
+ false,
+ 0L,
+ -1));
+
+ ApplicationPackage preprocessed = applicationPackageBuilder.build().preprocess(new Zone(environment.orElse(Environment.defaultEnvironment()),
+ region.orElse(RegionName.defaultName())),
+ new BaseDeployLogger());
preprocessed.validateXML();
}
public static void main(String[] args) {
- int argCount = args.length;
- if (argCount < 1) {
- System.out.println("Usage: vespa-application-preprocessor <application package path> [environment] [region] [output path]");
+ if (args.length < 1) {
+ System.out.println("Usage: vespa-application-preprocessor <application package path> [instance] [environment] [region] [tag] [output path]");
System.exit(1);
}
File applicationDir = new File(args[0]);
- Optional<Environment> environment = (argCount > 1) ? Optional.of(Environment.valueOf(args[1])) : Optional.empty();
- Optional<RegionName> region = (argCount > 2) ? Optional.of(RegionName.from(args[2])) : Optional.empty();
- Optional<File> outputDir = (argCount > 3) ? Optional.of(new File(args[3])) : Optional.empty();
- ApplicationPreprocessor preprocessor = new ApplicationPreprocessor(applicationDir, outputDir, environment, region);
+ Optional<InstanceName> instance;
+ Optional<Environment> environment;
+ Optional<RegionName> region;
+ Tags tags;
+ Optional<File> outputDir;
+ if (args.length <= 4) { // Legacy: No instance and tags
+ instance = Optional.empty();
+ environment = args.length > 1 ? Optional.of(Environment.valueOf(args[1])) : Optional.empty();
+ region = args.length > 2 ? Optional.of(RegionName.from(args[2])) : Optional.empty();
+ tags = Tags.empty();
+ outputDir = args.length > 3 ? Optional.of(new File(args[3])) : Optional.empty();
+ }
+ else {
+ instance = Optional.of(InstanceName.from(args[1]));
+ environment = Optional.of(Environment.valueOf(args[2]));
+ region = Optional.of(RegionName.from(args[3]));
+ tags = Tags.fromString(args[4]);
+ outputDir = args.length > 5 ? Optional.of(new File(args[5])) : Optional.empty();
+ }
+ ApplicationPreprocessor preprocessor = new ApplicationPreprocessor(applicationDir,
+ outputDir,
+ instance,
+ environment,
+ region,
+ tags);
try {
preprocessor.run();
System.out.println("Application preprocessed successfully and written to " +
diff --git a/application-preprocessor/src/test/java/com/yahoo/application/preprocessor/ApplicationPreprocessorTest.java b/application-preprocessor/src/test/java/com/yahoo/application/preprocessor/ApplicationPreprocessorTest.java
index 0e5d8fb2784..4de4312c099 100644
--- a/application-preprocessor/src/test/java/com/yahoo/application/preprocessor/ApplicationPreprocessorTest.java
+++ b/application-preprocessor/src/test/java/com/yahoo/application/preprocessor/ApplicationPreprocessorTest.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.application.preprocessor;
+import com.yahoo.config.provision.Tags;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.xml.sax.SAXException;
@@ -18,11 +19,13 @@ public class ApplicationPreprocessorTest {
// Basic test just to check that instantiation and run() works. Unit testing is in config-application-package
@Test
- void basic() throws ParserConfigurationException, TransformerException, SAXException, IOException {
+ void basic() throws IOException {
ApplicationPreprocessor preprocessor = new ApplicationPreprocessor(new File("src/test/resources/simple"),
- Optional.of(newFolder(outputDir, "basic")),
- Optional.empty(),
- Optional.empty());
+ Optional.of(newFolder(outputDir, "basic")),
+ Optional.empty(),
+ Optional.empty(),
+ Optional.empty(),
+ Tags.empty());
preprocessor.run();
}
diff --git a/config-application-package/src/main/java/com/yahoo/config/application/OverrideProcessor.java b/config-application-package/src/main/java/com/yahoo/config/application/OverrideProcessor.java
index d7efca3b723..21bb193ef93 100644
--- a/config-application-package/src/main/java/com/yahoo/config/application/OverrideProcessor.java
+++ b/config-application-package/src/main/java/com/yahoo/config/application/OverrideProcessor.java
@@ -4,6 +4,7 @@ package com.yahoo.config.application;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.Tags;
import com.yahoo.text.XML;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
@@ -23,7 +24,7 @@ import java.util.logging.Logger;
import java.util.stream.Collectors;
/**
- * Handles overrides in a XML document according to the rules defined for multi environment application packages.
+ * Handles overrides in a XML document according to the rules defined for multi-environment application packages.
*
* Rules:
*
@@ -41,16 +42,19 @@ class OverrideProcessor implements PreProcessor {
private final InstanceName instance;
private final Environment environment;
private final RegionName region;
+ private final Tags tags;
private static final String ID_ATTRIBUTE = "id";
private static final String INSTANCE_ATTRIBUTE = "instance";
private static final String ENVIRONMENT_ATTRIBUTE = "environment";
private static final String REGION_ATTRIBUTE = "region";
+ private static final String TAGS_ATTRIBUTE = "tags";
- public OverrideProcessor(InstanceName instance, Environment environment, RegionName region) {
+ public OverrideProcessor(InstanceName instance, Environment environment, RegionName region, Tags tags) {
this.instance = instance;
this.environment = environment;
this.region = region;
+ this.tags = tags;
}
public Document process(Document input) throws TransformerException {
@@ -80,6 +84,7 @@ class OverrideProcessor implements PreProcessor {
child.removeAttributeNS(XmlPreProcessor.deployNamespaceUri, INSTANCE_ATTRIBUTE);
child.removeAttributeNS(XmlPreProcessor.deployNamespaceUri, ENVIRONMENT_ATTRIBUTE);
child.removeAttributeNS(XmlPreProcessor.deployNamespaceUri, REGION_ATTRIBUTE);
+ child.removeAttributeNS(XmlPreProcessor.deployNamespaceUri, TAGS_ATTRIBUTE);
}
}
@@ -87,13 +92,16 @@ class OverrideProcessor implements PreProcessor {
Set<InstanceName> instances = context.instances;
Set<Environment> environments = context.environments;
Set<RegionName> regions = context.regions;
+ Tags tags = context.tags;
if (instances.isEmpty())
instances = getInstances(parent);
if (environments.isEmpty())
environments = getEnvironments(parent);
if (regions.isEmpty())
regions = getRegions(parent);
- return Context.create(instances, environments, regions);
+ if (tags.isEmpty())
+ tags = getTags(parent);
+ return Context.create(instances, environments, regions, tags);
}
/**
@@ -128,17 +136,21 @@ class OverrideProcessor implements PreProcessor {
throw new IllegalArgumentException("Regions in child (" + regions +
") are not a subset of those of the parent (" + context.regions + ") at " + child);
}
+
+ Tags tags = getTags(child);
+ if ( ! tags.isEmpty() && ! context.tags.isEmpty() && ! context.tags.containsAll(tags)) {
+ throw new IllegalArgumentException("Tags in child (" + environments +
+ ") are not a subset of those of the parent (" + context.tags + ") at " + child);
+ }
}
}
- /**
- * Prune elements that are not matching our environment and region
- */
+ /** Prune elements that are not matching our environment and region. */
private void pruneNonMatching(Element parent, List<Element> children) {
Iterator<Element> elemIt = children.iterator();
while (elemIt.hasNext()) {
Element child = elemIt.next();
- if ( ! matches(getInstances(child), getEnvironments(child), getRegions(child))) {
+ if ( ! matches(getInstances(child), getEnvironments(child), getRegions(child), getTags(child))) {
parent.removeChild(child);
elemIt.remove();
}
@@ -147,7 +159,8 @@ class OverrideProcessor implements PreProcessor {
private boolean matches(Set<InstanceName> elementInstances,
Set<Environment> elementEnvironments,
- Set<RegionName> elementRegions) {
+ Set<RegionName> elementRegions,
+ Tags elementTags) {
if ( ! elementInstances.isEmpty()) { // match instance
if ( ! elementInstances.contains(instance)) return false;
}
@@ -164,12 +177,14 @@ class OverrideProcessor implements PreProcessor {
if ( ! environment.isMultiRegion() && elementEnvironments.isEmpty() ) return false;
}
+ if ( ! elementTags.isEmpty()) { // match tags
+ if ( ! elementTags.intersects(tags)) return false;
+ }
+
return true;
}
- /**
- * Find the most specific element and remove all others.
- */
+ /** Find the most specific element and remove all others. */
private void retainMostSpecific(Element parent, List<Element> children, Context context) {
// Keep track of elements with highest number of matches (might be more than one element with same tag, need a list)
List<Element> bestMatches = new ArrayList<>();
@@ -205,12 +220,15 @@ class OverrideProcessor implements PreProcessor {
Set<InstanceName> elementInstances = hasInstance(child) ? getInstances(child) : context.instances;
Set<Environment> elementEnvironments = hasEnvironment(child) ? getEnvironments(child) : context.environments;
Set<RegionName> elementRegions = hasRegion(child) ? getRegions(child) : context.regions;
+ Tags elementTags = hasTag(child) ? getTags(child) : context.tags;
if ( ! elementInstances.isEmpty() && elementInstances.contains(instance))
currentMatch++;
if ( ! elementEnvironments.isEmpty() && elementEnvironments.contains(environment))
currentMatch++;
if ( ! elementRegions.isEmpty() && elementRegions.contains(region))
currentMatch++;
+ if ( elementTags.intersects(tags))
+ currentMatch++;
return currentMatch;
}
@@ -233,16 +251,14 @@ class OverrideProcessor implements PreProcessor {
return false;
}
- /**
- * Retains all elements where at least one element is overridden. Removes non-overridden elements from map.
- */
+ /** Retains all elements where at least one element is overridden. Removes non-overridden elements from map. */
private void retainOverriddenElements(Map<String, List<Element>> elementsByTagName) {
Iterator<Map.Entry<String, List<Element>>> it = elementsByTagName.entrySet().iterator();
while (it.hasNext()) {
List<Element> elements = it.next().getValue();
boolean hasOverrides = false;
for (Element element : elements) {
- if (hasEnvironment(element) || hasRegion(element)) {
+ if (hasInstance(element) || hasEnvironment(element) || hasRegion(element) || hasTag(element)) {
hasOverrides = true;
}
}
@@ -264,24 +280,34 @@ class OverrideProcessor implements PreProcessor {
return element.hasAttributeNS(XmlPreProcessor.deployNamespaceUri, ENVIRONMENT_ATTRIBUTE);
}
+ private boolean hasTag(Element element) {
+ return element.hasAttributeNS(XmlPreProcessor.deployNamespaceUri, TAGS_ATTRIBUTE);
+ }
+
private Set<InstanceName> getInstances(Element element) {
String instance = element.getAttributeNS(XmlPreProcessor.deployNamespaceUri, INSTANCE_ATTRIBUTE);
- if (instance == null || instance.isEmpty()) return Collections.emptySet();
+ if (instance == null || instance.isEmpty()) return Set.of();
return Arrays.stream(instance.split(" ")).map(InstanceName::from).collect(Collectors.toSet());
}
private Set<Environment> getEnvironments(Element element) {
String env = element.getAttributeNS(XmlPreProcessor.deployNamespaceUri, ENVIRONMENT_ATTRIBUTE);
- if (env == null || env.isEmpty()) return Collections.emptySet();
+ if (env == null || env.isEmpty()) return Set.of();
return Arrays.stream(env.split(" ")).map(Environment::from).collect(Collectors.toSet());
}
private Set<RegionName> getRegions(Element element) {
String reg = element.getAttributeNS(XmlPreProcessor.deployNamespaceUri, REGION_ATTRIBUTE);
- if (reg == null || reg.isEmpty()) return Collections.emptySet();
+ if (reg == null || reg.isEmpty()) return Set.of();
return Arrays.stream(reg.split(" ")).map(RegionName::from).collect(Collectors.toSet());
}
+ private Tags getTags(Element element) {
+ String env = element.getAttributeNS(XmlPreProcessor.deployNamespaceUri, TAGS_ATTRIBUTE);
+ if (env == null || env.isEmpty()) return Tags.empty();
+ return Tags.fromString(env);
+ }
+
private Map<String, List<Element>> elementsByTagNameAndId(List<Element> children) {
Map<String, List<Element>> elementsByTagName = new LinkedHashMap<>();
// Index by tag name
@@ -336,21 +362,27 @@ class OverrideProcessor implements PreProcessor {
final Set<InstanceName> instances;
final Set<Environment> environments;
final Set<RegionName> regions;
+ final Tags tags;
- private Context(Set<InstanceName> instances, Set<Environment> environments, Set<RegionName> regions) {
+ private Context(Set<InstanceName> instances,
+ Set<Environment> environments,
+ Set<RegionName> regions,
+ Tags tags) {
this.instances = Set.copyOf(instances);
this.environments = Set.copyOf(environments);
this.regions = Set.copyOf(regions);
+ this.tags = tags;
}
static Context empty() {
- return new Context(Set.of(), Set.of(), Set.of());
+ return new Context(Set.of(), Set.of(), Set.of(), Tags.empty());
}
public static Context create(Set<InstanceName> instances,
Set<Environment> environments,
- Set<RegionName> regions) {
- return new Context(instances, environments, regions);
+ Set<RegionName> regions,
+ Tags tags) {
+ return new Context(instances, environments, regions, tags);
}
}
diff --git a/config-application-package/src/main/java/com/yahoo/config/application/XmlPreProcessor.java b/config-application-package/src/main/java/com/yahoo/config/application/XmlPreProcessor.java
index ba68894c9f9..42333ea7662 100644
--- a/config-application-package/src/main/java/com/yahoo/config/application/XmlPreProcessor.java
+++ b/config-application-package/src/main/java/com/yahoo/config/application/XmlPreProcessor.java
@@ -5,6 +5,7 @@ import com.yahoo.config.application.FileSystemWrapper.FileWrapper;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.Tags;
import com.yahoo.text.XML;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
@@ -19,6 +20,7 @@ import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
+import java.util.Set;
/**
* A preprocessor for services.xml files that handles deploy:environment, deploy:region, preprocess:properties, preprocess:include
@@ -38,22 +40,53 @@ public class XmlPreProcessor {
private final InstanceName instance;
private final Environment environment;
private final RegionName region;
+ private final Tags tags;
private final List<PreProcessor> chain;
- public XmlPreProcessor(File applicationDir, File xmlInput, InstanceName instance, Environment environment, RegionName region) throws IOException {
- this(applicationDir, new FileReader(xmlInput), instance, environment, region);
+ // TODO: Remove after November 2022
+ public XmlPreProcessor(File applicationDir,
+ File xmlInput,
+ InstanceName instance,
+ Environment environment,
+ RegionName region) throws IOException {
+ this(applicationDir, new FileReader(xmlInput), instance, environment, region, Tags.empty());
}
- public XmlPreProcessor(File applicationDir, Reader xmlInput, InstanceName instance, Environment environment, RegionName region) {
- this(FileSystemWrapper.getDefault(applicationDir.toPath()).wrap(applicationDir.toPath()), xmlInput, instance, environment, region);
+ public XmlPreProcessor(File applicationDir,
+ File xmlInput,
+ InstanceName instance,
+ Environment environment,
+ RegionName region,
+ Tags tags) throws IOException {
+ this(applicationDir, new FileReader(xmlInput), instance, environment, region, tags);
}
- public XmlPreProcessor(FileWrapper applicationDir, Reader xmlInput, InstanceName instance, Environment environment, RegionName region) {
+ public XmlPreProcessor(File applicationDir,
+ Reader xmlInput,
+ InstanceName instance,
+ Environment environment,
+ RegionName region,
+ Tags tags) {
+ this(FileSystemWrapper.getDefault(applicationDir.toPath()).wrap(applicationDir.toPath()),
+ xmlInput,
+ instance,
+ environment,
+ region,
+ tags);
+ }
+
+ public XmlPreProcessor(FileWrapper applicationDir,
+ Reader xmlInput,
+ InstanceName instance,
+ Environment environment,
+ RegionName region,
+ Tags tags) {
this.applicationDir = applicationDir;
this.xmlInput = xmlInput;
this.instance = instance;
this.environment = environment;
this.region = region;
+ this.tags = tags;
this.chain = setupChain();
}
@@ -73,7 +106,7 @@ public class XmlPreProcessor {
private List<PreProcessor> setupChain() {
List<PreProcessor> chain = new ArrayList<>();
chain.add(new IncludeProcessor(applicationDir));
- chain.add(new OverrideProcessor(instance, environment, region));
+ chain.add(new OverrideProcessor(instance, environment, region, tags));
chain.add(new PropertiesProcessor());
chain.add(new ValidationProcessor()); // must be last in chain
return chain;
diff --git a/config-application-package/src/main/java/com/yahoo/config/model/application/provider/DeployData.java b/config-application-package/src/main/java/com/yahoo/config/model/application/provider/DeployData.java
index 279af646a8c..c3e9b99f562 100644
--- a/config-application-package/src/main/java/com/yahoo/config/model/application/provider/DeployData.java
+++ b/config-application-package/src/main/java/com/yahoo/config/model/application/provider/DeployData.java
@@ -2,6 +2,9 @@
package com.yahoo.config.model.application.provider;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.Tags;
+
+import java.util.Set;
/**
* Data generated or computed during deployment
@@ -12,6 +15,8 @@ public class DeployData {
private final ApplicationId applicationId;
+ private final Tags tags;
+
/** The absolute path to the directory holding the application */
private final String deployedFromDir;
@@ -25,25 +30,16 @@ public class DeployData {
private final long generation;
private final long currentlyActiveGeneration;
- // TODO: Remove when oldest version in use is 8.13
- public DeployData(String ignored,
- String deployedFromDir,
- ApplicationId applicationId,
- Long deployTimestamp,
- boolean internalRedeploy,
- Long generation,
- long currentlyActiveGeneration) {
- this(deployedFromDir, applicationId, deployTimestamp, internalRedeploy, generation, currentlyActiveGeneration);
- }
-
public DeployData(String deployedFromDir,
ApplicationId applicationId,
+ Tags tags,
Long deployTimestamp,
boolean internalRedeploy,
Long generation,
long currentlyActiveGeneration) {
this.deployedFromDir = deployedFromDir;
this.applicationId = applicationId;
+ this.tags = tags;
this.deployTimestamp = deployTimestamp;
this.internalRedeploy = internalRedeploy;
this.generation = generation;
@@ -62,4 +58,6 @@ public class DeployData {
public ApplicationId getApplicationId() { return applicationId; }
+ public Tags getTags() { return tags; }
+
}
diff --git a/config-application-package/src/main/java/com/yahoo/config/model/application/provider/FilesApplicationPackage.java b/config-application-package/src/main/java/com/yahoo/config/model/application/provider/FilesApplicationPackage.java
index 7b483d0603c..e61ea01a99a 100644
--- a/config-application-package/src/main/java/com/yahoo/config/model/application/provider/FilesApplicationPackage.java
+++ b/config-application-package/src/main/java/com/yahoo/config/model/application/provider/FilesApplicationPackage.java
@@ -17,6 +17,7 @@ import com.yahoo.config.model.application.AbstractApplicationPackage;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.InstanceName;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.Zone;
import com.yahoo.io.HexDump;
@@ -138,6 +139,7 @@ public class FilesApplicationPackage extends AbstractApplicationPackage {
deployData.getDeployTimestamp(),
deployData.isInternalRedeploy(),
deployData.getApplicationId(),
+ deployData.getTags(),
computeCheckSum(appDir),
deployData.getGeneration(),
deployData.getCurrentlyActiveGeneration());
@@ -484,6 +486,7 @@ public class FilesApplicationPackage extends AbstractApplicationPackage {
ApplicationId.from(TenantName.defaultName(),
ApplicationName.from(originalAppDir),
InstanceName.defaultName()),
+ Tags.empty(),
"",
0L,
0L);
@@ -583,7 +586,8 @@ public class FilesApplicationPackage extends AbstractApplicationPackage {
inputXml,
metaData.getApplicationId().instance(),
zone.environment(),
- zone.region())
+ zone.region(),
+ metaData.getTags())
.run();
try (FileOutputStream outputStream = new FileOutputStream(destination)) {
diff --git a/config-application-package/src/test/java/com/yahoo/config/application/HostedOverrideProcessorTagsTest.java b/config-application-package/src/test/java/com/yahoo/config/application/HostedOverrideProcessorTagsTest.java
new file mode 100644
index 00000000000..8d7431d33b6
--- /dev/null
+++ b/config-application-package/src/test/java/com/yahoo/config/application/HostedOverrideProcessorTagsTest.java
@@ -0,0 +1,124 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.application;
+
+import com.yahoo.config.provision.Environment;
+import com.yahoo.config.provision.InstanceName;
+import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.Tags;
+import org.custommonkey.xmlunit.XMLUnit;
+import org.junit.Test;
+import org.w3c.dom.Document;
+
+import javax.xml.transform.TransformerException;
+import java.io.StringReader;
+
+/**
+ * @author bratseth
+ */
+public class HostedOverrideProcessorTagsTest {
+
+ static {
+ XMLUnit.setIgnoreWhitespace(true);
+ }
+
+ private static final String input =
+ "<?xml version='1.0' encoding='UTF-8' standalone='no'?>" +
+ "<services xmlns:deploy='vespa' xmlns:preprocess='?' version='1.0'>" +
+ " <container id='foo' version='1.0'>" +
+ " <nodes count='5' deploy:tags='a' deploy:environment='perf'/>" +
+ " <nodes count='10' deploy:tags='a b'/>" +
+ " <nodes count='20' deploy:tags='c'/>" +
+ " <search deploy:tags='b'/>" +
+ " <document-api deploy:tags='d'/>" +
+ " </container>" +
+ "</services>";
+
+ @Test
+ public void testParsingTagAPerf() throws TransformerException {
+ String expected =
+ "<?xml version='1.0' encoding='UTF-8' standalone='no'?>" +
+ "<services xmlns:deploy='vespa' xmlns:preprocess='?' version='1.0'>" +
+ " <container id='foo' version='1.0'>" +
+ " <nodes count='5' required='true'/>" +
+ " </container>" +
+ "</services>";
+ assertOverride(InstanceName.defaultName(),
+ Environment.perf,
+ RegionName.defaultName(),
+ Tags.fromString("a"),
+ expected);
+ }
+
+ @Test
+ public void testParsingTagAProd() throws TransformerException {
+ String expected =
+ "<?xml version='1.0' encoding='UTF-8' standalone='no'?>" +
+ "<services xmlns:deploy='vespa' xmlns:preprocess='?' version='1.0'>" +
+ " <container id='foo' version='1.0'>" +
+ " <nodes count='10' required='true'/>" +
+ " </container>" +
+ "</services>";
+ assertOverride(InstanceName.defaultName(),
+ Environment.prod,
+ RegionName.defaultName(),
+ Tags.fromString("a"),
+ expected);
+ }
+
+ @Test
+ public void testParsingTagB() throws TransformerException {
+ String expected =
+ "<?xml version='1.0' encoding='UTF-8' standalone='no'?>" +
+ "<services xmlns:deploy='vespa' xmlns:preprocess='?' version='1.0'>" +
+ " <container id='foo' version='1.0'>" +
+ " <nodes count='10' required='true'/>" +
+ " <search/>" +
+ " </container>" +
+ "</services>";
+ assertOverride(InstanceName.defaultName(),
+ Environment.prod,
+ RegionName.defaultName(),
+ Tags.fromString("b"),
+ expected);
+ }
+
+ @Test
+ public void testParsingTagC() throws TransformerException {
+ String expected =
+ "<?xml version='1.0' encoding='UTF-8' standalone='no'?>" +
+ "<services xmlns:deploy='vespa' xmlns:preprocess='?' version='1.0'>" +
+ " <container id='foo' version='1.0'>" +
+ " <nodes count='20' required='true'/>" +
+ " </container>" +
+ "</services>";
+ assertOverride(InstanceName.defaultName(),
+ Environment.prod,
+ RegionName.defaultName(),
+ Tags.fromString("c"),
+ expected);
+ }
+
+ @Test
+ public void testParsingTagCAndD() throws TransformerException {
+ String expected =
+ "<?xml version='1.0' encoding='UTF-8' standalone='no'?>" +
+ "<services xmlns:deploy='vespa' xmlns:preprocess='?' version='1.0'>" +
+ " <container id='foo' version='1.0'>" +
+ " <nodes count='20' required='true'/>" +
+ " <document-api/>" +
+ " </container>" +
+ "</services>";
+ assertOverride(InstanceName.defaultName(),
+ Environment.prod,
+ RegionName.defaultName(),
+ Tags.fromString("c d"),
+ expected);
+ }
+
+ private void assertOverride(InstanceName instance, Environment environment, RegionName region, Tags tags, String expected) throws TransformerException {
+ Document inputDoc = Xml.getDocument(new StringReader(input));
+ Document newDoc = new OverrideProcessor(instance, environment, region, tags).process(inputDoc);
+ TestBase.assertDocument(expected, newDoc);
+ }
+
+}
diff --git a/config-application-package/src/test/java/com/yahoo/config/application/HostedOverrideProcessorTest.java b/config-application-package/src/test/java/com/yahoo/config/application/HostedOverrideProcessorTest.java
index 1a4dab01930..451c7a3c217 100644
--- a/config-application-package/src/test/java/com/yahoo/config/application/HostedOverrideProcessorTest.java
+++ b/config-application-package/src/test/java/com/yahoo/config/application/HostedOverrideProcessorTest.java
@@ -4,6 +4,7 @@ package com.yahoo.config.application;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.Tags;
import org.custommonkey.xmlunit.XMLUnit;
import org.junit.Test;
import org.w3c.dom.Document;
@@ -14,6 +15,7 @@ import javax.xml.stream.XMLStreamException;
import javax.xml.transform.TransformerException;
import java.io.IOException;
import java.io.StringReader;
+import java.util.List;
/**
* @author bratseth
@@ -48,7 +50,11 @@ public class HostedOverrideProcessorTest {
" <nodes count='1'/>" +
" </container>" +
"</services>";
- assertOverride(Environment.test, RegionName.defaultName(), expected);
+ assertOverride(InstanceName.defaultName(),
+ Environment.test,
+ RegionName.defaultName(),
+ Tags.empty(),
+ expected);
}
@Test
@@ -60,7 +66,27 @@ public class HostedOverrideProcessorTest {
" <nodes count='4' required='true'/>" +
" </container>" +
"</services>";
- assertOverride(Environment.from("prod"), RegionName.from("us-west"), expected);
+ assertOverride(InstanceName.defaultName(),
+ Environment.from("prod"),
+ RegionName.from("us-west"),
+ Tags.empty(),
+ expected);
+ }
+
+ @Test
+ public void testParsingSpecificTag() throws TransformerException {
+ String expected =
+ "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>" +
+ "<services xmlns:deploy=\"vespa\" xmlns:preprocess=\"?\" version=\"1.0\">" +
+ " <container id=\"foo\" version=\"1.0\">" +
+ " <nodes count='4' required='true'/>" +
+ " </container>" +
+ "</services>";
+ assertOverride(InstanceName.defaultName(),
+ Environment.from("prod"),
+ RegionName.from("us-west"),
+ Tags.empty(),
+ expected);
}
@Test
@@ -72,7 +98,11 @@ public class HostedOverrideProcessorTest {
" <nodes count='1' required='true'/>" +
" </container>" +
"</services>";
- assertOverride(InstanceName.from("myinstance"), Environment.from("prod"), RegionName.from("us-west"), expected);
+ assertOverride(InstanceName.from("myinstance"),
+ Environment.from("prod"),
+ RegionName.from("us-west"),
+ Tags.empty(),
+ expected);
}
@Test
@@ -84,7 +114,11 @@ public class HostedOverrideProcessorTest {
" <nodes count='5' flavor='v-8-8-100' required='true'/>" +
" </container>" +
"</services>";
- assertOverride(Environment.from("prod"), RegionName.from("us-east-3"), expected);
+ assertOverride(InstanceName.defaultName(),
+ Environment.from("prod"),
+ RegionName.from("us-east-3"),
+ Tags.empty(),
+ expected);
}
@Test
@@ -96,7 +130,11 @@ public class HostedOverrideProcessorTest {
" <nodes count='3' required='true'/>" +
" </container>" +
"</services>";
- assertOverride(Environment.from("perf"), RegionName.from("us-east-3"), expected);
+ assertOverride(InstanceName.defaultName(),
+ Environment.from("perf"),
+ RegionName.from("us-east-3"),
+ Tags.empty(),
+ expected);
}
@Test
@@ -108,7 +146,11 @@ public class HostedOverrideProcessorTest {
" <nodes count='3' flavor='v-4-8-100' required='true'/>" +
" </container>" +
"</services>";
- assertOverride(Environment.valueOf("prod"), RegionName.from("unknown"), expected);
+ assertOverride(InstanceName.defaultName(),
+ Environment.valueOf("prod"),
+ RegionName.from("unknown"),
+ Tags.empty(),
+ expected);
}
@Test
@@ -120,7 +162,11 @@ public class HostedOverrideProcessorTest {
" <nodes count='3' flavor='v-4-8-100' required='true'/>" +
" </container>" +
"</services>";
- assertOverride(Environment.from("prod"), RegionName.defaultName(), expected);
+ assertOverride(InstanceName.defaultName(),
+ Environment.from("prod"),
+ RegionName.defaultName(),
+ Tags.empty(),
+ expected);
}
@Test
@@ -132,7 +178,11 @@ public class HostedOverrideProcessorTest {
" <nodes count='1'/>" +
" </container>" +
"</services>";
- assertOverride(Environment.from("dev"), RegionName.defaultName(), expected);
+ assertOverride(InstanceName.defaultName(),
+ Environment.from("dev"),
+ RegionName.defaultName(),
+ Tags.empty(),
+ expected);
}
@Test
@@ -144,7 +194,11 @@ public class HostedOverrideProcessorTest {
" <nodes count='1'/>" +
" </container>" +
"</services>";
- assertOverride(Environment.from("test"), RegionName.from("us-west"), expected);
+ assertOverride(InstanceName.defaultName(),
+ Environment.from("test"),
+ RegionName.from("us-west"),
+ Tags.empty(),
+ expected);
}
@Test
@@ -156,16 +210,16 @@ public class HostedOverrideProcessorTest {
" <nodes count='2' required='true'/>" +
" </container>" +
"</services>";
- assertOverride(Environment.from("staging"), RegionName.from("us-west"), expected);
- }
-
- private void assertOverride(Environment environment, RegionName region, String expected) throws TransformerException {
- assertOverride(InstanceName.from("default"), environment, region, expected);
+ assertOverride(InstanceName.defaultName(),
+ Environment.from("staging"),
+ RegionName.from("us-west"),
+ Tags.empty(),
+ expected);
}
- private void assertOverride(InstanceName instance, Environment environment, RegionName region, String expected) throws TransformerException {
+ private void assertOverride(InstanceName instance, Environment environment, RegionName region, Tags tags, String expected) throws TransformerException {
Document inputDoc = Xml.getDocument(new StringReader(input));
- Document newDoc = new OverrideProcessor(instance, environment, region).process(inputDoc);
+ Document newDoc = new OverrideProcessor(instance, environment, region, tags).process(inputDoc);
TestBase.assertDocument(expected, newDoc);
}
diff --git a/config-application-package/src/test/java/com/yahoo/config/application/MultiOverrideProcessorTest.java b/config-application-package/src/test/java/com/yahoo/config/application/MultiOverrideProcessorTest.java
index 44bcb12957a..debde6c1438 100644
--- a/config-application-package/src/test/java/com/yahoo/config/application/MultiOverrideProcessorTest.java
+++ b/config-application-package/src/test/java/com/yahoo/config/application/MultiOverrideProcessorTest.java
@@ -4,6 +4,7 @@ package com.yahoo.config.application;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.Tags;
import org.custommonkey.xmlunit.XMLUnit;
import org.junit.Test;
import org.w3c.dom.Document;
@@ -124,13 +125,13 @@ public class MultiOverrideProcessorTest {
private void assertOverride(Environment environment, RegionName region, String expected) throws TransformerException {
Document inputDoc = Xml.getDocument(new StringReader(input));
- Document newDoc = new OverrideProcessor(InstanceName.from("default"), environment, region).process(inputDoc);
+ Document newDoc = new OverrideProcessor(InstanceName.from("default"), environment, region, Tags.empty()).process(inputDoc);
TestBase.assertDocument(expected, newDoc);
}
private void assertOverrideWithIds(Environment environment, RegionName region, String expected) throws TransformerException {
Document inputDoc = Xml.getDocument(new StringReader(inputWithIds));
- Document newDoc = new OverrideProcessor(InstanceName.from("default"), environment, region).process(inputDoc);
+ Document newDoc = new OverrideProcessor(InstanceName.from("default"), environment, region, Tags.empty()).process(inputDoc);
TestBase.assertDocument(expected, newDoc);
}
diff --git a/config-application-package/src/test/java/com/yahoo/config/application/OverrideProcessorTest.java b/config-application-package/src/test/java/com/yahoo/config/application/OverrideProcessorTest.java
index e9ee7c97876..150999390d8 100644
--- a/config-application-package/src/test/java/com/yahoo/config/application/OverrideProcessorTest.java
+++ b/config-application-package/src/test/java/com/yahoo/config/application/OverrideProcessorTest.java
@@ -4,6 +4,7 @@ package com.yahoo.config.application;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.Tags;
import org.custommonkey.xmlunit.XMLUnit;
import org.junit.Test;
import org.w3c.dom.Document;
@@ -332,7 +333,10 @@ public class OverrideProcessorTest {
" </admin>" +
"</services>";
Document inputDoc = Xml.getDocument(new StringReader(in));
- new OverrideProcessor(InstanceName.from("default"), Environment.from("prod"), RegionName.from("us-west")).process(inputDoc);
+ new OverrideProcessor(InstanceName.from("default"),
+ Environment.from("prod"),
+ RegionName.from("us-west"),
+ Tags.empty()).process(inputDoc);
}
@Test(expected = IllegalArgumentException.class)
@@ -344,7 +348,10 @@ public class OverrideProcessorTest {
" </admin>" +
"</services>";
Document inputDoc = Xml.getDocument(new StringReader(in));
- new OverrideProcessor(InstanceName.from("default"), Environment.defaultEnvironment(), RegionName.from("us-west")).process(inputDoc);
+ new OverrideProcessor(InstanceName.from("default"),
+ Environment.defaultEnvironment(),
+ RegionName.from("us-west"),
+ Tags.empty()).process(inputDoc);
}
@Test
@@ -399,7 +406,7 @@ public class OverrideProcessorTest {
private void assertOverride(String input, Environment environment, RegionName region, String expected) throws TransformerException {
Document inputDoc = Xml.getDocument(new StringReader(input));
- Document newDoc = new OverrideProcessor(InstanceName.from("default"), environment, region).process(inputDoc);
+ Document newDoc = new OverrideProcessor(InstanceName.from("default"), environment, region, Tags.empty()).process(inputDoc);
TestBase.assertDocument(expected, newDoc);
}
diff --git a/config-application-package/src/test/java/com/yahoo/config/application/XmlPreprocessorTest.java b/config-application-package/src/test/java/com/yahoo/config/application/XmlPreprocessorTest.java
index 92c2c2a820f..0da94b69e58 100644
--- a/config-application-package/src/test/java/com/yahoo/config/application/XmlPreprocessorTest.java
+++ b/config-application-package/src/test/java/com/yahoo/config/application/XmlPreprocessorTest.java
@@ -4,11 +4,13 @@ package com.yahoo.config.application;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.Tags;
import org.junit.Test;
import org.w3c.dom.Document;
import java.io.File;
import java.io.StringReader;
+import java.util.Set;
/**
* @author hmusum
@@ -44,7 +46,13 @@ public class XmlPreprocessorTest {
" </nodes>\n" +
" </container>\n" +
"</services>";
- TestBase.assertDocument(expectedDev, new XmlPreProcessor(appDir, services, InstanceName.defaultName(), Environment.dev, RegionName.defaultName()).run());
+ TestBase.assertDocument(expectedDev,
+ new XmlPreProcessor(appDir,
+ services,
+ InstanceName.defaultName(),
+ Environment.dev,
+ RegionName.defaultName(),
+ Tags.empty()).run());
String expectedStaging =
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>" +
@@ -70,7 +78,13 @@ public class XmlPreprocessorTest {
" </nodes>\n" +
" </container>\n" +
"</services>";
- TestBase.assertDocument(expectedStaging, new XmlPreProcessor(appDir, services, InstanceName.defaultName(), Environment.staging, RegionName.defaultName()).run());
+ TestBase.assertDocument(expectedStaging,
+ new XmlPreProcessor(appDir,
+ services,
+ InstanceName.defaultName(),
+ Environment.staging,
+ RegionName.defaultName(),
+ Tags.empty()).run());
String expectedUsWest =
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>" +
@@ -104,7 +118,13 @@ public class XmlPreprocessorTest {
" </nodes>\n" +
" </container>\n" +
"</services>";
- TestBase.assertDocument(expectedUsWest, new XmlPreProcessor(appDir, services, InstanceName.defaultName(), Environment.prod, RegionName.from("us-west")).run());
+ TestBase.assertDocument(expectedUsWest,
+ new XmlPreProcessor(appDir,
+ services,
+ InstanceName.defaultName(),
+ Environment.prod,
+ RegionName.from("us-west"),
+ Tags.empty()).run());
String expectedUsEastAndCentral =
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>" +
@@ -138,9 +158,19 @@ public class XmlPreprocessorTest {
" </container>\n" +
"</services>";
TestBase.assertDocument(expectedUsEastAndCentral,
- new XmlPreProcessor(appDir, services, InstanceName.defaultName(), Environment.prod, RegionName.from("us-east")).run());
+ new XmlPreProcessor(appDir,
+ services,
+ InstanceName.defaultName(),
+ Environment.prod,
+ RegionName.from("us-east"),
+ Tags.empty()).run());
TestBase.assertDocument(expectedUsEastAndCentral,
- new XmlPreProcessor(appDir, services, InstanceName.defaultName(), Environment.prod, RegionName.from("us-central")).run());
+ new XmlPreProcessor(appDir,
+ services,
+ InstanceName.defaultName(),
+ Environment.prod,
+ RegionName.from("us-central"),
+ Tags.empty()).run());
}
@Test
@@ -184,7 +214,12 @@ public class XmlPreprocessorTest {
" <adminserver hostalias=\"node0\"/>" +
" </admin>" +
"</services>";
- Document docDev = (new XmlPreProcessor(appDir, new StringReader(input), InstanceName.defaultName(), Environment.prod, RegionName.defaultName()).run());
+ Document docDev = (new XmlPreProcessor(appDir,
+ new StringReader(input),
+ InstanceName.defaultName(),
+ Environment.prod,
+ RegionName.defaultName(),
+ Tags.empty()).run());
TestBase.assertDocument(expectedProd, docDev);
}
diff --git a/config-model-api/abi-spec.json b/config-model-api/abi-spec.json
index 7e17cd0f600..4f66ded727b 100644
--- a/config-model-api/abi-spec.json
+++ b/config-model-api/abi-spec.json
@@ -71,11 +71,13 @@
"public"
],
"methods": [
+ "public void <init>(java.lang.String, java.lang.Long, boolean, com.yahoo.config.provision.ApplicationId, com.yahoo.config.provision.Tags, java.lang.String, java.lang.Long, long)",
"public void <init>(java.lang.String, java.lang.Long, boolean, com.yahoo.config.provision.ApplicationId, java.lang.String, java.lang.Long, long)",
"public void <init>(java.lang.String, java.lang.String, java.lang.Long, boolean, com.yahoo.config.provision.ApplicationId, java.lang.String, java.lang.Long, long)",
"public java.lang.String getDeployedByUser()",
"public java.lang.String getDeployPath()",
"public com.yahoo.config.provision.ApplicationId getApplicationId()",
+ "public com.yahoo.config.provision.Tags getTags()",
"public java.lang.Long getDeployTimestamp()",
"public java.lang.Long getGeneration()",
"public boolean isInternalRedeploy()",
@@ -194,8 +196,9 @@
"public"
],
"methods": [
- "public void <init>(com.yahoo.config.provision.InstanceName, java.util.List, com.yahoo.config.application.api.DeploymentSpec$UpgradePolicy, com.yahoo.config.application.api.DeploymentSpec$RevisionTarget, com.yahoo.config.application.api.DeploymentSpec$RevisionChange, com.yahoo.config.application.api.DeploymentSpec$UpgradeRollout, int, int, int, java.util.List, java.util.Optional, java.util.Optional, java.util.Optional, com.yahoo.config.application.api.Notifications, java.util.List, java.time.Instant)",
+ "public void <init>(com.yahoo.config.provision.InstanceName, com.yahoo.config.provision.Tags, java.util.List, com.yahoo.config.application.api.DeploymentSpec$UpgradePolicy, com.yahoo.config.application.api.DeploymentSpec$RevisionTarget, com.yahoo.config.application.api.DeploymentSpec$RevisionChange, com.yahoo.config.application.api.DeploymentSpec$UpgradeRollout, int, int, int, java.util.List, java.util.Optional, java.util.Optional, java.util.Optional, com.yahoo.config.application.api.Notifications, java.util.List, java.time.Instant)",
"public com.yahoo.config.provision.InstanceName name()",
+ "public com.yahoo.config.provision.Tags tags()",
"public com.yahoo.config.application.api.DeploymentSpec$UpgradePolicy upgradePolicy()",
"public com.yahoo.config.application.api.DeploymentSpec$RevisionTarget revisionTarget()",
"public com.yahoo.config.application.api.DeploymentSpec$RevisionChange revisionChange()",
diff --git a/config-model-api/src/main/java/com/yahoo/config/application/api/ApplicationMetaData.java b/config-model-api/src/main/java/com/yahoo/config/application/api/ApplicationMetaData.java
index a23afd994f2..c830c2baa1f 100644
--- a/config-model-api/src/main/java/com/yahoo/config/application/api/ApplicationMetaData.java
+++ b/config-model-api/src/main/java/com/yahoo/config/application/api/ApplicationMetaData.java
@@ -2,6 +2,7 @@
package com.yahoo.config.application.api;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.Tags;
import com.yahoo.slime.Cursor;
import com.yahoo.slime.Inspector;
import com.yahoo.slime.Slime;
@@ -9,6 +10,9 @@ import com.yahoo.slime.SlimeUtils;
import com.yahoo.text.Utf8;
import java.io.IOException;
+import java.util.Arrays;
+import java.util.Set;
+import java.util.stream.Collectors;
/**
* Metadata about an application package.
@@ -21,32 +25,40 @@ public class ApplicationMetaData {
private final long deployTimestamp;
private final boolean internalRedeploy;
private final ApplicationId applicationId;
+ private final Tags tags;
private final String checksum;
private final long generation;
private final long previousActiveGeneration;
public ApplicationMetaData(String deployedFromDir, Long deployTimestamp, boolean internalRedeploy,
- ApplicationId applicationId, String checksum, Long generation, long previousActiveGeneration) {
- this("unknown", deployedFromDir, deployTimestamp, internalRedeploy, applicationId, checksum, generation, previousActiveGeneration);
- }
-
- @Deprecated
- // TODO: Remove in Vespa 9
- public ApplicationMetaData(String ignored, String deployedFromDir, Long deployTimestamp, boolean internalRedeploy,
- ApplicationId applicationId, String checksum, Long generation, long previousActiveGeneration) {
+ ApplicationId applicationId, Tags tags,
+ String checksum, Long generation, long previousActiveGeneration) {
this.deployedFromDir = deployedFromDir;
this.deployTimestamp = deployTimestamp;
this.internalRedeploy = internalRedeploy;
this.applicationId = applicationId;
+ this.tags = tags;
this.checksum = checksum;
this.generation = generation;
this.previousActiveGeneration = previousActiveGeneration;
}
+ @Deprecated // TODO: Remove on Vespa 9
+ public ApplicationMetaData(String deployedFromDir, Long deployTimestamp, boolean internalRedeploy,
+ ApplicationId applicationId, String checksum, Long generation, long previousActiveGeneration) {
+ this(deployedFromDir, deployTimestamp, internalRedeploy, applicationId, Tags.empty(), checksum, generation, previousActiveGeneration);
+ }
+
+ @Deprecated // TODO: Remove on Vespa 9
+ public ApplicationMetaData(String ignored, String deployedFromDir, Long deployTimestamp, boolean internalRedeploy,
+ ApplicationId applicationId, String checksum, Long generation, long previousActiveGeneration) {
+ this(deployedFromDir, deployTimestamp, internalRedeploy, applicationId, Tags.empty(), checksum, generation, previousActiveGeneration);
+ }
+
/**
* Gets the user who deployed the application.
*
- * @return user name for the user who ran "deploy-application"
+ * @return username of the user who ran "deploy-application"
*/
@Deprecated // TODO: Remove in Vespa 9
public String getDeployedByUser() { return "unknown"; }
@@ -61,6 +73,8 @@ public class ApplicationMetaData {
public ApplicationId getApplicationId() { return applicationId; }
+ public Tags getTags() { return tags; }
+
/**
* Gets the time the application was deployed.
* Will return null if a problem occurred while getting metadata.
@@ -103,6 +117,7 @@ public class ApplicationMetaData {
deploy.field("timestamp").asLong(),
booleanField("internalRedeploy", false, deploy),
ApplicationId.fromSerializedForm(app.field("id").asString()),
+ Tags.fromString(deploy.field("tags").asString()),
app.field("checksum").asString(),
app.field("generation").asLong(),
app.field("previousActiveGeneration").asLong());
@@ -118,6 +133,7 @@ public class ApplicationMetaData {
deploy.setString("from", deployedFromDir);
deploy.setLong("timestamp", deployTimestamp);
deploy.setBool("internalRedeploy", internalRedeploy);
+ deploy.setString("tags", tags.asString());
Cursor app = meta.setObject("application");
app.setString("id", applicationId.serializedForm());
app.setString("checksum", checksum);
diff --git a/config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentInstanceSpec.java b/config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentInstanceSpec.java
index cd20b5b8910..01a20cc158d 100644
--- a/config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentInstanceSpec.java
+++ b/config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentInstanceSpec.java
@@ -6,6 +6,7 @@ import com.yahoo.config.provision.CloudAccount;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.Tags;
import java.time.Duration;
import java.time.Instant;
@@ -40,6 +41,7 @@ public class DeploymentInstanceSpec extends DeploymentSpec.Steps {
/** The name of the instance this step deploys */
private final InstanceName name;
+ private final Tags tags;
private final DeploymentSpec.UpgradePolicy upgradePolicy;
private final DeploymentSpec.RevisionTarget revisionTarget;
private final DeploymentSpec.RevisionChange revisionChange;
@@ -55,6 +57,7 @@ public class DeploymentInstanceSpec extends DeploymentSpec.Steps {
private final List<Endpoint> endpoints;
public DeploymentInstanceSpec(InstanceName name,
+ Tags tags,
List<DeploymentSpec.Step> steps,
DeploymentSpec.UpgradePolicy upgradePolicy,
DeploymentSpec.RevisionTarget revisionTarget,
@@ -70,6 +73,7 @@ public class DeploymentInstanceSpec extends DeploymentSpec.Steps {
Instant now) {
super(steps);
this.name = Objects.requireNonNull(name);
+ this.tags = Objects.requireNonNull(tags);
this.upgradePolicy = Objects.requireNonNull(upgradePolicy);
Objects.requireNonNull(revisionTarget);
Objects.requireNonNull(revisionChange);
@@ -94,6 +98,8 @@ public class DeploymentInstanceSpec extends DeploymentSpec.Steps {
public InstanceName name() { return name; }
+ public Tags tags() { return tags; }
+
/**
* Throws an IllegalArgumentException if any production deployment or test is declared multiple times,
* or if any production test is declared not after its corresponding deployment.
diff --git a/config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentSpec.java b/config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentSpec.java
index b85150356e3..2df55ffce95 100644
--- a/config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentSpec.java
+++ b/config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentSpec.java
@@ -194,7 +194,7 @@ public class DeploymentSpec {
/** Returns the instance names declared in this */
public List<InstanceName> instanceNames() {
- return instances().stream().map(DeploymentInstanceSpec::name).collect(Collectors.toUnmodifiableList());
+ return instances().stream().map(DeploymentInstanceSpec::name).toList();
}
/** Returns the step descendants of this which are instances */
diff --git a/config-model-api/src/main/java/com/yahoo/config/application/api/xml/DeploymentSpecXmlReader.java b/config-model-api/src/main/java/com/yahoo/config/application/api/xml/DeploymentSpecXmlReader.java
index 83fba75325e..77894a8cf1f 100644
--- a/config-model-api/src/main/java/com/yahoo/config/application/api/xml/DeploymentSpecXmlReader.java
+++ b/config-model-api/src/main/java/com/yahoo/config/application/api/xml/DeploymentSpecXmlReader.java
@@ -25,6 +25,7 @@ import com.yahoo.config.provision.CloudAccount;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.Tags;
import com.yahoo.io.IOUtils;
import com.yahoo.text.XML;
import org.w3c.dom.Element;
@@ -55,6 +56,7 @@ public class DeploymentSpecXmlReader {
private static final String deploymentTag = "deployment";
private static final String instanceTag = "instance";
+ private static final String tagsTag = "tags";
private static final String testTag = "test";
private static final String stagingTag = "staging";
private static final String devTag = "dev";
@@ -155,48 +157,50 @@ public class DeploymentSpecXmlReader {
* Reads the content of an (implicit or explicit) instance tag producing an instances step
*
* @param instanceNameString a comma-separated list of the names of the instances this is for
- * @param instanceTag the element having the content of this instance
+ * @param instanceElement the element having the content of this instance
* @param parentTag the parent of instanceTag (or the same, if this instance is implicitly defined, which means instanceTag is the root)
* @return the instances specified, one for each instance name element
*/
private List<DeploymentInstanceSpec> readInstanceContent(String instanceNameString,
- Element instanceTag,
+ Element instanceElement,
Map<String, String> prodAttributes,
Element parentTag) {
if (instanceNameString.isBlank())
illegal("<instance> attribute 'id' must be specified, and not be blank");
// If this is an absolutely empty instance, or the implicit "default" instance but without content, ignore it
- if (XML.getChildren(instanceTag).isEmpty() && (instanceTag.getAttributes().getLength() == 0 || instanceTag == parentTag))
+ if (XML.getChildren(instanceElement).isEmpty() && (instanceElement.getAttributes().getLength() == 0 || instanceElement == parentTag))
return List.of();
if (validate)
- validateTagOrder(instanceTag);
+ validateTagOrder(instanceElement);
// Values where the parent may provide a default
- DeploymentSpec.UpgradePolicy upgradePolicy = getWithFallback(instanceTag, parentTag, upgradeTag, "policy", this::readUpgradePolicy, UpgradePolicy.defaultPolicy);
- DeploymentSpec.RevisionTarget revisionTarget = getWithFallback(instanceTag, parentTag, upgradeTag, "revision-target", this::readRevisionTarget, RevisionTarget.latest);
- DeploymentSpec.RevisionChange revisionChange = getWithFallback(instanceTag, parentTag, upgradeTag, "revision-change", this::readRevisionChange, RevisionChange.whenFailing);
- DeploymentSpec.UpgradeRollout upgradeRollout = getWithFallback(instanceTag, parentTag, upgradeTag, "rollout", this::readUpgradeRollout, UpgradeRollout.separate);
- int minRisk = getWithFallback(instanceTag, parentTag, upgradeTag, "min-risk", Integer::parseInt, 0);
- int maxRisk = getWithFallback(instanceTag, parentTag, upgradeTag, "max-risk", Integer::parseInt, 0);
- int maxIdleHours = getWithFallback(instanceTag, parentTag, upgradeTag, "max-idle-hours", Integer::parseInt, 8);
- List<DeploymentSpec.ChangeBlocker> changeBlockers = readChangeBlockers(instanceTag, parentTag);
- Optional<AthenzService> athenzService = mostSpecificAttribute(instanceTag, athenzServiceAttribute).map(AthenzService::from);
- Optional<CloudAccount> cloudAccount = mostSpecificAttribute(instanceTag, cloudAccountAttribute).map(CloudAccount::new);
- Notifications notifications = readNotifications(instanceTag, parentTag);
+ DeploymentSpec.UpgradePolicy upgradePolicy = getWithFallback(instanceElement, parentTag, upgradeTag, "policy", this::readUpgradePolicy, UpgradePolicy.defaultPolicy);
+ DeploymentSpec.RevisionTarget revisionTarget = getWithFallback(instanceElement, parentTag, upgradeTag, "revision-target", this::readRevisionTarget, RevisionTarget.latest);
+ DeploymentSpec.RevisionChange revisionChange = getWithFallback(instanceElement, parentTag, upgradeTag, "revision-change", this::readRevisionChange, RevisionChange.whenFailing);
+ DeploymentSpec.UpgradeRollout upgradeRollout = getWithFallback(instanceElement, parentTag, upgradeTag, "rollout", this::readUpgradeRollout, UpgradeRollout.separate);
+ int minRisk = getWithFallback(instanceElement, parentTag, upgradeTag, "min-risk", Integer::parseInt, 0);
+ int maxRisk = getWithFallback(instanceElement, parentTag, upgradeTag, "max-risk", Integer::parseInt, 0);
+ int maxIdleHours = getWithFallback(instanceElement, parentTag, upgradeTag, "max-idle-hours", Integer::parseInt, 8);
+ List<DeploymentSpec.ChangeBlocker> changeBlockers = readChangeBlockers(instanceElement, parentTag);
+ Optional<AthenzService> athenzService = mostSpecificAttribute(instanceElement, athenzServiceAttribute).map(AthenzService::from);
+ Optional<CloudAccount> cloudAccount = mostSpecificAttribute(instanceElement, cloudAccountAttribute).map(CloudAccount::new);
+ Notifications notifications = readNotifications(instanceElement, parentTag);
// Values where there is no default
+ Tags tags = XML.attribute(tagsTag, instanceElement).map(value -> Tags.fromString(value)).orElse(Tags.empty());
List<Step> steps = new ArrayList<>();
- for (Element instanceChild : XML.getChildren(instanceTag))
+ for (Element instanceChild : XML.getChildren(instanceElement))
steps.addAll(readNonInstanceSteps(instanceChild, prodAttributes, instanceChild));
- List<Endpoint> endpoints = readEndpoints(instanceTag, Optional.of(instanceNameString), steps);
+ List<Endpoint> endpoints = readEndpoints(instanceElement, Optional.of(instanceNameString), steps);
// Build and return instances with these values
Instant now = clock.instant();
return Arrays.stream(instanceNameString.split(","))
.map(name -> name.trim())
.map(name -> new DeploymentInstanceSpec(InstanceName.from(name),
+ tags,
steps,
upgradePolicy,
revisionTarget,
diff --git a/config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecTest.java b/config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecTest.java
index 87b0f709125..2e51e283eb9 100644
--- a/config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecTest.java
+++ b/config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecTest.java
@@ -7,6 +7,7 @@ import com.yahoo.config.provision.CloudAccount;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.Tags;
import com.yahoo.test.ManualClock;
import org.junit.Test;
@@ -133,6 +134,29 @@ public class DeploymentSpecTest {
}
@Test
+ public void specWithTags() {
+ StringReader r = new StringReader(
+ "<deployment version='1.0'>" +
+ " <instance id='a' tags='tag1 tag2'>" +
+ " <prod>" +
+ " <region active='false'>us-east1</region>" +
+ " <region active='true'>us-west1</region>" +
+ " </prod>" +
+ " </instance>" +
+ " <instance id='b' tags='tag3'>" +
+ " <prod>" +
+ " <region active='false'>us-east1</region>" +
+ " <region active='true'>us-west1</region>" +
+ " </prod>" +
+ " </instance>" +
+ "</deployment>"
+ );
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ assertEquals(Tags.fromString("tag1 tag2"), spec.requireInstance("a").tags());
+ assertEquals(Tags.fromString("tag3"), spec.requireInstance("b").tags());
+ }
+
+ @Test
public void maximalProductionSpec() {
StringReader r = new StringReader(
"<deployment version='1.0'>" +
diff --git a/config-model/src/main/java/com/yahoo/config/model/test/MockApplicationPackage.java b/config-model/src/main/java/com/yahoo/config/model/test/MockApplicationPackage.java
index 9ee279c68d3..2d98c8b35c0 100644
--- a/config-model/src/main/java/com/yahoo/config/model/test/MockApplicationPackage.java
+++ b/config-model/src/main/java/com/yahoo/config/model/test/MockApplicationPackage.java
@@ -10,6 +10,7 @@ import com.yahoo.config.application.api.UnparsedConfigDefinition;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.InstanceName;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.TenantName;
import com.yahoo.io.IOUtils;
import com.yahoo.io.reader.NamedReader;
@@ -85,6 +86,7 @@ public class MockApplicationPackage implements ApplicationPackage {
ApplicationId.from(TenantName.defaultName(),
ApplicationName.from(APPLICATION_NAME),
InstanceName.defaultName()),
+ Tags.empty(),
"checksum",
APPLICATION_GENERATION,
0L);
diff --git a/config-model/src/main/resources/schema/deployment.rnc b/config-model/src/main/resources/schema/deployment.rnc
index 9723e531bd2..bf94ee3b750 100644
--- a/config-model/src/main/resources/schema/deployment.rnc
+++ b/config-model/src/main/resources/schema/deployment.rnc
@@ -35,6 +35,7 @@ PrimitiveStep =
Instance = element instance {
attribute id { xsd:string } &
+ attribute tags { xsd:string }? &
attribute athenz-service { xsd:string }? &
attribute cloud-account { xsd:string }? &
StepExceptInstance
diff --git a/config-model/src/test/java/com/yahoo/config/model/ApplicationDeployTest.java b/config-model/src/test/java/com/yahoo/config/model/ApplicationDeployTest.java
index c1dd62316db..6507341670f 100644
--- a/config-model/src/test/java/com/yahoo/config/model/ApplicationDeployTest.java
+++ b/config-model/src/test/java/com/yahoo/config/model/ApplicationDeployTest.java
@@ -10,6 +10,7 @@ import com.yahoo.config.model.application.provider.DeployData;
import com.yahoo.config.model.application.provider.FilesApplicationPackage;
import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.Tags;
import com.yahoo.document.DataType;
import com.yahoo.io.IOUtils;
import com.yahoo.path.Path;
@@ -225,17 +226,19 @@ public class ApplicationDeployTest {
IOUtils.copyDirectory(new File(appPkg), tmp);
ApplicationId applicationId = ApplicationId.from("tenant1", "application1", "instance1");
DeployData deployData = new DeployData("bar",
- applicationId,
- 13L,
- false,
- 1337L,
- 3L);
+ applicationId,
+ Tags.fromString("tag1 tag2"),
+ 13L,
+ false,
+ 1337L,
+ 3L);
FilesApplicationPackage app = FilesApplicationPackage.fromFileWithDeployData(tmp, deployData);
app.writeMetaData();
FilesApplicationPackage newApp = FilesApplicationPackage.fromFileWithDeployData(tmp, deployData);
ApplicationMetaData meta = newApp.getMetaData();
assertEquals("bar", meta.getDeployPath());
assertEquals(applicationId, meta.getApplicationId());
+ assertEquals(Tags.fromString("tag1 tag2"), meta.getTags());
assertEquals(13L, (long) meta.getDeployTimestamp());
assertEquals(1337L, (long) meta.getGeneration());
assertEquals(3L, meta.getPreviousActiveGeneration());
diff --git a/config-model/src/test/schema-test-files/deployment-with-instances.xml b/config-model/src/test/schema-test-files/deployment-with-instances.xml
index 39771ca9d41..f37ff9f6cc6 100644
--- a/config-model/src/test/schema-test-files/deployment-with-instances.xml
+++ b/config-model/src/test/schema-test-files/deployment-with-instances.xml
@@ -35,7 +35,7 @@
<delay hours='2'/>
<parallel>
- <instance id="three">
+ <instance id="three" tags="a b">
<test/>
<staging/>
</instance>
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/Tags.java b/config-provisioning/src/main/java/com/yahoo/config/provision/Tags.java
new file mode 100644
index 00000000000..007db2fd171
--- /dev/null
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/Tags.java
@@ -0,0 +1,61 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.provision;
+
+import java.util.Set;
+
+/**
+ * A deployment may have a list of tags associated with it. Config files may have variants for these tags similar
+ * to how they may have variants for instance and zone.
+ *
+ * @author bratseth
+ */
+public class Tags {
+
+ private final Set<String> tags;
+
+ public Tags(Set<String> tags) {
+ this.tags = Set.copyOf(tags);
+ }
+
+ public boolean contains(String tag) {
+ return tags.contains(tag);
+ }
+
+ public boolean intersects(Tags other) {
+ return this.tags.stream().anyMatch(other::contains);
+ }
+
+ public boolean isEmpty() { return tags.isEmpty(); }
+
+ public boolean containsAll(Tags other) { return tags.containsAll(other.tags); }
+
+ /** Returns this as a space-separated string which can be used to recreate this by calling fromString(). */
+ public String asString() { return String.join(" ", tags); }
+
+ @Override
+ public String toString() {
+ return asString();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) return true;
+ if (other == null || other.getClass() != getClass()) return false;
+ return tags.equals(((Tags)other).tags);
+ }
+
+ @Override
+ public int hashCode() {
+ return tags.hashCode();
+ }
+
+ public static Tags empty() { return new Tags(Set.of()); }
+
+ /**
+ * Creates this from a space-separated string or null. */
+ public static Tags fromString(String tagsString) {
+ if (tagsString == null || tagsString.isBlank()) return empty();
+ return new Tags(Set.of(tagsString.trim().split(" ")));
+ }
+
+}
diff --git a/config-provisioning/src/test/java/com/yahoo/config/provision/TagsTest.java b/config-provisioning/src/test/java/com/yahoo/config/provision/TagsTest.java
new file mode 100644
index 00000000000..1f2b2737619
--- /dev/null
+++ b/config-provisioning/src/test/java/com/yahoo/config/provision/TagsTest.java
@@ -0,0 +1,52 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.provision;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * @author bratseth
+ */
+public class TagsTest {
+
+ @Test
+ public void testEmpty() {
+ assertEquals(Tags.empty(), Tags.fromString(null));
+ assertEquals(Tags.empty(), Tags.fromString(""));
+ assertEquals(Tags.empty(), Tags.fromString(" "));
+ }
+
+ @Test
+ public void testSerialization() {
+ Tags tags = new Tags(Set.of("a", "tag2", "3"));
+ assertEquals(tags, Tags.fromString(tags.toString())); // Required by automatic serialization
+ assertEquals(tags, Tags.fromString(tags.asString())); // Required by automatic serialization
+ }
+
+ @Test
+ public void testContains() {
+ Tags tags = new Tags(Set.of("a", "tag2", "3"));
+ assertTrue(tags.contains("a"));
+ assertTrue(tags.contains("tag2"));
+ assertTrue(tags.contains("3"));
+ assertFalse(tags.contains("other"));
+
+ Tags subTags = new Tags(Set.of("a", "3"));
+ assertTrue(tags.containsAll(subTags));
+ assertFalse(subTags.containsAll(tags));
+ }
+
+ @Test
+ public void testIntersects() {
+ Tags tags1 = new Tags(Set.of("a", "tag2", "3"));
+ Tags tags2 = new Tags(Set.of("a", "tag3"));
+ assertTrue(tags1.intersects(tags2));
+ assertTrue(tags2.intersects(tags1));
+ }
+
+}
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 f832c504526..589dfa775ae 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
@@ -23,6 +23,7 @@ import com.yahoo.config.provision.InfraDeployer;
import com.yahoo.config.provision.Provisioner;
import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.Zone;
import com.yahoo.config.provision.exception.ActivationConflictException;
@@ -357,8 +358,11 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
}
private PrepareResult deploy(File applicationDir, PrepareParams prepareParams, DeployHandlerLogger logger) {
- ApplicationId applicationId = prepareParams.getApplicationId();
- long sessionId = createSession(applicationId, prepareParams.getTimeoutBudget(), applicationDir, logger);
+ long sessionId = createSession(prepareParams.getApplicationId(),
+ prepareParams.tags(),
+ prepareParams.getTimeoutBudget(),
+ applicationDir,
+ logger);
Deployment deployment = prepare(sessionId, prepareParams, logger);
if ( ! prepareParams.isDryRun())
@@ -821,21 +825,21 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
return sessionRepository.createSessionFromExisting(fromSession, internalRedeploy, timeoutBudget, deployLogger).getSessionId();
}
- public long createSession(ApplicationId applicationId, TimeoutBudget timeoutBudget, InputStream in,
+ public long createSession(ApplicationId applicationId, Tags tags, TimeoutBudget timeoutBudget, InputStream in,
String contentType, DeployLogger logger) {
File tempDir = uncheck(() -> Files.createTempDirectory("deploy")).toFile();
long sessionId;
try {
- sessionId = createSession(applicationId, timeoutBudget, decompressApplication(in, contentType, tempDir), logger);
+ sessionId = createSession(applicationId, tags, timeoutBudget, decompressApplication(in, contentType, tempDir), logger);
} finally {
cleanupTempDirectory(tempDir, logger);
}
return sessionId;
}
- public long createSession(ApplicationId applicationId, TimeoutBudget timeoutBudget, File applicationDirectory, DeployLogger deployLogger) {
+ public long createSession(ApplicationId applicationId, Tags tags, TimeoutBudget timeoutBudget, File applicationDirectory, DeployLogger deployLogger) {
SessionRepository sessionRepository = getTenant(applicationId).getSessionRepository();
- Session session = sessionRepository.createSessionFromApplicationPackage(applicationDirectory, applicationId, timeoutBudget, deployLogger);
+ Session session = sessionRepository.createSessionFromApplicationPackage(applicationDirectory, applicationId, tags, timeoutBudget, deployLogger);
return session.getSessionId();
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandler.java
index f29b05b66af..71702e2926c 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandler.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandler.java
@@ -6,6 +6,7 @@ import com.yahoo.cloud.config.ConfigserverConfig;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.InstanceName;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.TenantName;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
@@ -46,7 +47,7 @@ public class SessionCreateHandler extends SessionHandler {
@Override
protected HttpResponse handlePOST(HttpRequest request) {
- final TenantName tenantName = Utils.getTenantNameFromSessionRequest(request);
+ TenantName tenantName = Utils.getTenantNameFromSessionRequest(request);
Utils.checkThatTenantExists(applicationRepository.tenantRepository(), tenantName);
TimeoutBudget timeoutBudget = SessionHandler.getTimeoutBudget(request, zookeeperBarrierTimeout);
boolean verbose = request.getBooleanProperty("verbose");
@@ -62,8 +63,12 @@ public class SessionCreateHandler extends SessionHandler {
logger = DeployHandlerLogger.forTenant(tenantName, verbose);
// TODO: Avoid using application id here at all
ApplicationId applicationId = ApplicationId.from(tenantName, ApplicationName.defaultName(), InstanceName.defaultName());
- sessionId = applicationRepository.createSession(applicationId, timeoutBudget, request.getData(),
- request.getHeader(ApplicationApiHandler.contentTypeHeader), logger);
+ sessionId = applicationRepository.createSession(applicationId,
+ Tags.empty(),
+ timeoutBudget,
+ request.getData(),
+ request.getHeader(ApplicationApiHandler.contentTypeHeader),
+ logger);
}
return new SessionCreateResponse(logger.slime(), tenantName, request.getHost(), request.getPort(), sessionId);
}
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 175b6f6457f..ec673377af9 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
@@ -10,6 +10,7 @@ import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.AthenzDomain;
import com.yahoo.config.provision.CloudAccount;
import com.yahoo.config.provision.DockerImage;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.TenantName;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.security.X509CertificateUtils;
@@ -41,6 +42,7 @@ public final class PrepareParams {
static final String APPLICATION_NAME_PARAM_NAME = "applicationName";
static final String INSTANCE_PARAM_NAME = "instance";
+ static final String TAGS_PARAM_NAME = "tags";
static final String IGNORE_VALIDATION_PARAM_NAME = "ignoreValidationErrors";
static final String DRY_RUN_PARAM_NAME = "dryRun";
static final String VERBOSE_PARAM_NAME = "verbose";
@@ -57,6 +59,7 @@ public final class PrepareParams {
static final String CLOUD_ACCOUNT = "cloudAccount";
private final ApplicationId applicationId;
+ private final Tags tags;
private final TimeoutBudget timeoutBudget;
private final boolean ignoreValidationErrors;
private final boolean dryRun;
@@ -74,16 +77,27 @@ public final class PrepareParams {
private final List<X509Certificate> operatorCertificates;
private final Optional<CloudAccount> cloudAccount;
- private PrepareParams(ApplicationId applicationId, TimeoutBudget timeoutBudget, boolean ignoreValidationErrors,
- boolean dryRun, boolean verbose, boolean isBootstrap, Optional<Version> vespaVersion,
+ private PrepareParams(ApplicationId applicationId,
+ Tags tags,
+ TimeoutBudget timeoutBudget,
+ boolean ignoreValidationErrors,
+ boolean dryRun,
+ boolean verbose,
+ boolean isBootstrap,
+ Optional<Version> vespaVersion,
List<ContainerEndpoint> containerEndpoints,
Optional<EndpointCertificateMetadata> endpointCertificateMetadata,
- Optional<DockerImage> dockerImageRepository, Optional<AthenzDomain> athenzDomain,
- Optional<Quota> quota, List<TenantSecretStore> tenantSecretStores,
- boolean force, boolean waitForResourcesInPrepare, List<X509Certificate> operatorCertificates,
+ Optional<DockerImage> dockerImageRepository,
+ Optional<AthenzDomain> athenzDomain,
+ Optional<Quota> quota,
+ List<TenantSecretStore> tenantSecretStores,
+ boolean force,
+ boolean waitForResourcesInPrepare,
+ List<X509Certificate> operatorCertificates,
Optional<CloudAccount> cloudAccount) {
this.timeoutBudget = timeoutBudget;
this.applicationId = Objects.requireNonNull(applicationId);
+ this.tags = tags;
this.ignoreValidationErrors = ignoreValidationErrors;
this.dryRun = dryRun;
this.verbose = verbose;
@@ -110,6 +124,7 @@ public final class PrepareParams {
private boolean force = false;
private boolean waitForResourcesInPrepare = false;
private ApplicationId applicationId = null;
+ private Tags tags = Tags.empty();
private TimeoutBudget timeoutBudget = new TimeoutBudget(Clock.systemUTC(), Duration.ofSeconds(60));
private Optional<Version> vespaVersion = Optional.empty();
private List<ContainerEndpoint> containerEndpoints = null;
@@ -128,6 +143,11 @@ public final class PrepareParams {
return this;
}
+ public Builder tags(Tags tags) {
+ this.tags = tags;
+ return this;
+ }
+
public Builder ignoreValidationErrors(boolean ignoreValidationErrors) {
this.ignoreValidationErrors = ignoreValidationErrors;
return this;
@@ -258,11 +278,24 @@ public final class PrepareParams {
}
public PrepareParams build() {
- return new PrepareParams(applicationId, timeoutBudget, ignoreValidationErrors, dryRun,
- verbose, isBootstrap, vespaVersion, containerEndpoints,
- endpointCertificateMetadata, dockerImageRepository, athenzDomain,
- quota, tenantSecretStores, force, waitForResourcesInPrepare,
- operatorCertificates, cloudAccount);
+ return new PrepareParams(applicationId,
+ tags,
+ timeoutBudget,
+ ignoreValidationErrors,
+ dryRun,
+ verbose,
+ isBootstrap,
+ vespaVersion,
+ containerEndpoints,
+ endpointCertificateMetadata,
+ dockerImageRepository,
+ athenzDomain,
+ quota,
+ tenantSecretStores,
+ force,
+ waitForResourcesInPrepare,
+ operatorCertificates,
+ cloudAccount);
}
}
@@ -273,6 +306,7 @@ public final class PrepareParams {
.verbose(request.getBooleanProperty(VERBOSE_PARAM_NAME))
.timeoutBudget(SessionHandler.getTimeoutBudget(request, barrierTimeout))
.applicationId(createApplicationId(request, tenant))
+ .tags(Tags.fromString(request.getProperty(TAGS_PARAM_NAME)))
.vespaVersion(request.getProperty(VESPA_VERSION_PARAM_NAME))
.containerEndpoints(request.getProperty(CONTAINER_ENDPOINTS_PARAM_NAME))
.endpointCertificateMetadata(request.getProperty(ENDPOINT_CERTIFICATE_METADATA_PARAM_NAME))
@@ -295,6 +329,7 @@ public final class PrepareParams {
.verbose(booleanValue(params, VERBOSE_PARAM_NAME))
.timeoutBudget(SessionHandler.getTimeoutBudget(getTimeout(params, barrierTimeout)))
.applicationId(createApplicationId(params, tenant))
+ .tags(Tags.fromString(params.field(TAGS_PARAM_NAME).asString()))
.vespaVersion(SlimeUtils.optionalString(params.field(VESPA_VERSION_PARAM_NAME)).orElse(null))
.containerEndpointList(deserialize(params.field(CONTAINER_ENDPOINTS_PARAM_NAME), ContainerEndpointSerializer::endpointListFromSlime, List.of()))
.endpointCertificateMetadata(deserialize(params.field(ENDPOINT_CERTIFICATE_METADATA_PARAM_NAME), EndpointCertificateMetadataSerializer::fromSlime))
@@ -367,9 +402,9 @@ public final class PrepareParams {
return applicationId.application().value();
}
- public ApplicationId getApplicationId() {
- return applicationId;
- }
+ public ApplicationId getApplicationId() { return applicationId; }
+
+ public Tags tags() { return tags; }
/** Returns the Vespa version the nodes running the prepared system should have, or empty to use the system version */
public Optional<Version> vespaVersion() { return vespaVersion; }
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 82faeae01e8..e4bbe120c11 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
@@ -12,6 +12,7 @@ import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.AthenzDomain;
import com.yahoo.config.provision.CloudAccount;
import com.yahoo.config.provision.DockerImage;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.TenantName;
import com.yahoo.path.Path;
import com.yahoo.transaction.Transaction;
@@ -119,6 +120,10 @@ public abstract class Session implements Comparable<Session> {
sessionZooKeeperClient.writeApplicationId(applicationId);
}
+ public void setTags(Tags tags) {
+ sessionZooKeeperClient.writeTags(tags);
+ }
+
void setApplicationPackageReference(FileReference applicationPackageReference) {
sessionZooKeeperClient.writeApplicationPackageReference(Optional.ofNullable(applicationPackageReference));
}
@@ -153,6 +158,10 @@ public abstract class Session implements Comparable<Session> {
.orElseThrow(() -> new RuntimeException("Unable to read application id for session " + sessionId));
}
+ public Tags getTags() {
+ return sessionZooKeeperClient.readTags();
+ }
+
/** Returns application id read from ZooKeeper. Will return Optional.empty() if not found */
public Optional<ApplicationId> getOptionalApplicationId() {
try {
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 71df5a8829e..8d023cac88a 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
@@ -24,6 +24,7 @@ import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.AthenzDomain;
import com.yahoo.config.provision.CloudAccount;
import com.yahoo.config.provision.DockerImage;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.Zone;
import com.yahoo.container.jdisc.secretstore.SecretStore;
import com.yahoo.net.HostName;
@@ -157,6 +158,7 @@ public class SessionPreparer {
final PrepareParams params;
final ApplicationId applicationId;
+ final Tags tags;
/** The repository part of docker image to be used for this deployment */
final Optional<DockerImage> dockerImageRepository;
@@ -188,6 +190,7 @@ public class SessionPreparer {
this.applicationPackage = applicationPackage;
this.sessionZooKeeperClient = sessionZooKeeperClient;
this.applicationId = params.getApplicationId();
+ this.tags = params.tags();
this.dockerImageRepository = params.dockerImageRepository();
this.vespaVersion = params.vespaVersion().orElse(Vtag.currentVersion);
this.containerEndpointsCache = new ContainerEndpointsCache(tenantPath, curator);
@@ -224,7 +227,7 @@ public class SessionPreparer {
if (! timeoutBudget.hasTimeLeft(step)) {
String used = timeoutBudget.timesUsed();
throw new UncheckedTimeoutException("prepare timed out " + used + " after " + step +
- " step (timeout " + timeoutBudget.timeout() + "): " + applicationId);
+ " step (timeout " + timeoutBudget.timeout() + "): " + applicationId);
}
}
@@ -248,7 +251,7 @@ public class SessionPreparer {
this.preprocessedApplicationPackage = applicationPackage.preprocess(zone, logger);
} catch (IOException | RuntimeException e) {
throw new IllegalArgumentException("Error preprocessing application package for " + applicationId +
- ", session " + sessionZooKeeperClient.sessionId(), e);
+ ", session " + sessionZooKeeperClient.sessionId(), e);
}
checkTimeout("preprocess");
}
@@ -318,10 +321,11 @@ public class SessionPreparer {
void vespaPreprocess(File appDir, File inputXml, ApplicationMetaData metaData) {
try {
new XmlPreProcessor(appDir,
- inputXml,
- metaData.getApplicationId().instance(),
- zone.environment(),
- zone.region())
+ inputXml,
+ metaData.getApplicationId().instance(),
+ zone.environment(),
+ zone.region(),
+ metaData.getTags())
.run();
} catch (ParserConfigurationException | IOException | SAXException | TransformerException e) {
throw new RuntimeException(e);
@@ -346,6 +350,7 @@ public class SessionPreparer {
writeStateToZooKeeper(sessionZooKeeperClient,
preprocessedApplicationPackage,
applicationId,
+ tags,
filereference,
dockerImageRepository,
vespaVersion,
@@ -387,6 +392,7 @@ public class SessionPreparer {
private void writeStateToZooKeeper(SessionZooKeeperClient zooKeeperClient,
ApplicationPackage applicationPackage,
ApplicationId applicationId,
+ Tags tags,
FileReference fileReference,
Optional<DockerImage> dockerImageRepository,
Version vespaVersion,
@@ -403,6 +409,7 @@ public class SessionPreparer {
zkDeployer.deploy(applicationPackage, fileRegistryMap, allocatedHosts);
// Note: When changing the below you need to also change similar calls in SessionRepository.createSessionFromExisting()
zooKeeperClient.writeApplicationId(applicationId);
+ zooKeeperClient.writeTags(tags);
zooKeeperClient.writeApplicationPackageReference(Optional.of(fileReference));
zooKeeperClient.writeVespaVersion(vespaVersion);
zooKeeperClient.writeDockerImageRepository(dockerImageRepository);
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 9e7af5a44a3..e686b17a1ae 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
@@ -13,6 +13,7 @@ import com.yahoo.config.model.api.ConfigDefinitionRepo;
import com.yahoo.config.model.application.provider.DeployData;
import com.yahoo.config.model.application.provider.FilesApplicationPackage;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.Zone;
import com.yahoo.container.jdisc.secretstore.SecretStore;
@@ -282,10 +283,17 @@ public class SessionRepository {
TimeoutBudget timeoutBudget,
DeployLogger deployLogger) {
ApplicationId existingApplicationId = existingSession.getApplicationId();
+ Tags existingTags = existingSession.getTags();
File existingApp = getSessionAppDir(existingSession.getSessionId());
- LocalSession session = createSessionFromApplication(existingApp, existingApplicationId, internalRedeploy, timeoutBudget, deployLogger);
+ LocalSession session = createSessionFromApplication(existingApp,
+ existingApplicationId,
+ existingTags,
+ internalRedeploy,
+ timeoutBudget,
+ deployLogger);
// Note: Setters below need to be kept in sync with calls in SessionPreparer.writeStateToZooKeeper()
session.setApplicationId(existingApplicationId);
+ session.setTags(existingTags);
session.setApplicationPackageReference(existingSession.getApplicationPackageReference());
session.setVespaVersion(existingSession.getVespaVersion());
session.setDockerImageRepository(existingSession.getDockerImageRepository());
@@ -306,19 +314,20 @@ public class SessionRepository {
*/
public LocalSession createSessionFromApplicationPackage(File applicationDirectory,
ApplicationId applicationId,
+ Tags tags,
TimeoutBudget timeoutBudget,
DeployLogger deployLogger) {
applicationRepo.createApplication(applicationId);
- return createSessionFromApplication(applicationDirectory, applicationId, false, timeoutBudget, deployLogger);
+ return createSessionFromApplication(applicationDirectory, applicationId, tags, false, timeoutBudget, deployLogger);
}
/**
* Creates a local session based on a remote session and the distributed application package.
* Does not wait for session being created on other servers.
*/
- private void createLocalSession(File applicationFile, ApplicationId applicationId, long sessionId) {
+ private void createLocalSession(File applicationFile, ApplicationId applicationId, Tags tags, long sessionId) {
try {
- ApplicationPackage applicationPackage = createApplicationPackage(applicationFile, applicationId, sessionId, false, Optional.empty());
+ ApplicationPackage applicationPackage = createApplicationPackage(applicationFile, applicationId, tags, sessionId, false, Optional.empty());
createLocalSession(sessionId, applicationPackage);
} catch (Exception e) {
throw new RuntimeException("Error creating session " + sessionId, e);
@@ -706,12 +715,13 @@ public class SessionRepository {
private ApplicationPackage createApplication(File userDir,
File configApplicationDir,
ApplicationId applicationId,
+ Tags tags,
long sessionId,
Optional<Long> currentlyActiveSessionId,
boolean internalRedeploy,
Optional<DeployLogger> deployLogger) {
long deployTimestamp = System.currentTimeMillis();
- DeployData deployData = new DeployData(userDir.getAbsolutePath(), applicationId, deployTimestamp, internalRedeploy,
+ DeployData deployData = new DeployData(userDir.getAbsolutePath(), applicationId, tags, deployTimestamp, internalRedeploy,
sessionId, currentlyActiveSessionId.orElse(nonExistingActiveSessionId));
FilesApplicationPackage app = FilesApplicationPackage.fromFileWithDeployData(configApplicationDir, deployData);
validateFileExtensions(applicationId, deployLogger, app);
@@ -739,13 +749,14 @@ public class SessionRepository {
private LocalSession createSessionFromApplication(File applicationDirectory,
ApplicationId applicationId,
+ Tags tags,
boolean internalRedeploy,
TimeoutBudget timeoutBudget,
DeployLogger deployLogger) {
long sessionId = getNextSessionId();
try {
ensureSessionPathDoesNotExist(sessionId);
- ApplicationPackage app = createApplicationPackage(applicationDirectory, applicationId, sessionId, internalRedeploy, Optional.of(deployLogger));
+ ApplicationPackage app = createApplicationPackage(applicationDirectory, applicationId, tags, sessionId, internalRedeploy, Optional.of(deployLogger));
log.log(Level.FINE, () -> TenantRepository.logPre(tenantName) + "Creating session " + sessionId + " in ZooKeeper");
SessionZooKeeperClient sessionZKClient = createSessionZooKeeperClient(sessionId);
sessionZKClient.createNewSession(clock.instant());
@@ -761,6 +772,7 @@ public class SessionRepository {
private ApplicationPackage createApplicationPackage(File applicationDirectory,
ApplicationId applicationId,
+ Tags tags,
long sessionId,
boolean internalRedeploy,
Optional<DeployLogger> deployLogger) throws IOException {
@@ -773,6 +785,7 @@ public class SessionRepository {
ApplicationPackage applicationPackage = createApplication(applicationDirectory,
userApplicationDir,
applicationId,
+ tags,
sessionId,
activeSessionId,
internalRedeploy,
@@ -884,7 +897,7 @@ public class SessionRepository {
ApplicationId applicationId = sessionZKClient.readApplicationId()
.orElseThrow(() -> new RuntimeException("Could not find application id for session " + sessionId));
log.log(Level.FINE, () -> "Creating local session for tenant '" + tenantName + "' with session id " + sessionId);
- createLocalSession(sessionDir, applicationId, sessionId);
+ createLocalSession(sessionDir, applicationId, sessionZKClient.readTags(), sessionId);
}
}
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 988d13b1978..9218b03af1e 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
@@ -14,6 +14,7 @@ import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.AthenzDomain;
import com.yahoo.config.provision.CloudAccount;
import com.yahoo.config.provision.DockerImage;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.TenantName;
import com.yahoo.path.Path;
import com.yahoo.slime.SlimeUtils;
@@ -57,6 +58,7 @@ public class SessionZooKeeperClient {
// 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";
+ static final String TAGS_PATH = "tags";
static final String APPLICATION_PACKAGE_REFERENCE_PATH = "applicationPackageReference";
private static final String VERSION_PATH = "version";
private static final String CREATE_TIME_PATH = "createTime";
@@ -171,6 +173,20 @@ public class SessionZooKeeperClient {
return curator.getData(applicationIdPath()).map(d -> ApplicationId.fromSerializedForm(Utf8.toString(d)));
}
+ private Path tagsPath() {
+ return sessionPath.append(TAGS_PATH);
+ }
+
+ public void writeTags(Tags tags) {
+ curator.set(tagsPath(), Utf8.toBytes(tags.asString()));
+ }
+
+ public Tags readTags() {
+ Optional<byte[]> data = curator.getData(tagsPath());
+ if (data.isEmpty()) return Tags.empty();
+ return Tags.fromString(Utf8.toString(data.get()));
+ }
+
void writeApplicationPackageReference(Optional<FileReference> applicationPackageReference) {
applicationPackageReference.ifPresent(
reference -> curator.set(applicationPackageReferencePath(), Utf8.toBytes(reference.value())));
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java
index 4f7be104b9c..4f14d57d748 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java
@@ -17,6 +17,7 @@ import com.yahoo.config.provision.HostFilter;
import com.yahoo.config.provision.HostSpec;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.NetworkPorts;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.exception.ActivationConflictException;
import com.yahoo.container.jdisc.HttpResponse;
@@ -849,7 +850,7 @@ public class ApplicationRepositoryTest {
}
private long createSession(ApplicationId applicationId, TimeoutBudget timeoutBudget, File app) {
- return applicationRepository.createSession(applicationId, timeoutBudget, app, new BaseDeployLogger());
+ return applicationRepository.createSession(applicationId, Tags.empty(), timeoutBudget, app, new BaseDeployLogger());
}
private long createSessionFromExisting(ApplicationId applicationId, TimeoutBudget timeoutBudget) {
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClientTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClientTest.java
index fd6440a9632..653753c97e7 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClientTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/ZooKeeperClientTest.java
@@ -13,6 +13,7 @@ import com.yahoo.config.model.application.provider.MockFileRegistry;
import com.yahoo.config.provision.AllocatedHosts;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.HostSpec;
+import com.yahoo.config.provision.Tags;
import com.yahoo.path.Path;
import com.yahoo.text.Utf8;
import com.yahoo.vespa.config.server.zookeeper.ZKApplicationPackage;
@@ -59,6 +60,7 @@ public class ZooKeeperClientTest {
ApplicationPackage app = FilesApplicationPackage.fromFileWithDeployData(new File("src/test/apps/zkfeed"),
new DeployData("/bar/baz",
ApplicationId.from("default", "appName", "default"),
+ Tags.fromString("tag1 tag2"),
1345L,
true,
3L,
@@ -121,6 +123,7 @@ public class ZooKeeperClientTest {
assertTrue(metaData.getChecksum().length() > 0);
assertTrue(metaData.isInternalRedeploy());
assertEquals("/bar/baz", metaData.getDeployPath());
+ assertEquals(Tags.fromString("tag1 tag2"), metaData.getTags());
assertEquals(1345, metaData.getDeployTimestamp().longValue());
assertEquals(3, metaData.getGeneration().longValue());
assertEquals(2, metaData.getPreviousActiveGeneration());
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java
index 1c71ef0b7fb..816f7e3dcec 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java
@@ -6,6 +6,7 @@ import com.yahoo.component.Version;
import com.yahoo.config.application.api.ApplicationMetaData;
import com.yahoo.config.model.application.provider.BaseDeployLogger;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.Zone;
import com.yahoo.container.jdisc.HttpResponse;
@@ -131,6 +132,7 @@ public class SessionActiveHandlerTest {
void invoke() {
long sessionId = applicationRepository.createSession(applicationId(),
+ Tags.empty(),
new TimeoutBudget(clock, Duration.ofSeconds(10)),
testApp,
new BaseDeployLogger());
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java
index 2b07cffffce..74c6febb7e6 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java
@@ -10,6 +10,7 @@ import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.DockerImage;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.NodeAllocationException;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.TenantName;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.jdisc.http.HttpRequest;
@@ -187,7 +188,7 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest {
}
@Test
- public void require_that_preparing_with_multiple_tenants_work() throws Exception {
+ public void prepare_with_multiple_tenants() throws Exception {
SessionHandler handler = createHandler();
TenantName defaultTenant = TenantName.from("test2");
@@ -206,7 +207,7 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest {
assertEquals(sessionId, sessionId2); // Want to test when they are equal (but for different tenants)
pathPrefix = "/application/v2/tenant/" + tenant + "/session/" + sessionId2 +
- "/prepared?applicationName=" + applicationName;
+ "/prepared?applicationName=" + applicationName;
response = handler.handle(SessionHandlerTest.createTestRequest(pathPrefix));
assertNotNull(response);
assertEquals(SessionHandlerTest.getRenderedString(response), OK, response.getStatus());
@@ -214,7 +215,7 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest {
ApplicationId applicationId3 = ApplicationId.from(tenant.value(), applicationName, "quux");
long sessionId3 = createSession(applicationId3);
pathPrefix = "/application/v2/tenant/" + tenant + "/session/" + sessionId3 +
- "/prepared?applicationName=" + applicationName + "&instance=quux";
+ "/prepared?applicationName=" + applicationName + "&instance=quux";
response = handler.handle(SessionHandlerTest.createTestRequest(pathPrefix));
assertNotNull(response);
assertEquals(SessionHandlerTest.getRenderedString(response), OK, response.getStatus());
@@ -324,7 +325,7 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest {
}
private long createSession(ApplicationId applicationId) {
- return applicationRepository.createSession(applicationId, timeoutBudget, app, new BaseDeployLogger());
+ return applicationRepository.createSession(applicationId, Tags.empty(), timeoutBudget, app, new BaseDeployLogger());
}
private static class FailingSessionPrepareHandler extends SessionPrepareHandler {
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/PrepareParamsTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/PrepareParamsTest.java
index e5b550fdc1a..34921db1bb7 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/PrepareParamsTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/PrepareParamsTest.java
@@ -7,6 +7,7 @@ import com.yahoo.config.model.api.EndpointCertificateMetadata;
import com.yahoo.config.model.api.TenantSecretStore;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.CloudAccount;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.TenantName;
import com.yahoo.container.jdisc.HttpRequest;
@@ -62,6 +63,7 @@ public class PrepareParamsTest {
PrepareParams prepareParams = createParams("http://foo:19071/application/v2/", TenantName.defaultName());
assertEquals(ApplicationId.defaultId(), prepareParams.getApplicationId());
+ assertTrue(prepareParams.tags().isEmpty());
assertFalse(prepareParams.isDryRun());
assertFalse(prepareParams.isVerbose());
assertFalse(prepareParams.ignoreValidationErrors());
@@ -72,6 +74,18 @@ public class PrepareParamsTest {
}
@Test
+ public void testTagsParsing() throws IOException {
+ var prepareParams = createParams(request + "&" + PrepareParams.TAGS_PARAM_NAME + "=tag1%20tag2", TenantName.from("foo"));
+ assertEquals(Tags.fromString("tag1 tag2"), prepareParams.tags());
+
+ // Verify using json object
+ var slime = SlimeUtils.jsonToSlime(json);
+ slime.get().setString(PrepareParams.TAGS_PARAM_NAME, "tag1 tag2");
+ PrepareParams prepareParamsJson = PrepareParams.fromJson(SlimeUtils.toJsonBytes(slime), TenantName.from("foo"), Duration.ofSeconds(60));
+ assertPrepareParamsEqual(prepareParams, prepareParamsJson);
+ }
+
+ @Test
public void testCorrectParsingWithContainerEndpoints() throws IOException {
var endpoints = List.of(new ContainerEndpoint("qrs1", ApplicationClusterEndpoint.Scope.global,
List.of("c1.example.com",
@@ -207,6 +221,7 @@ public class PrepareParamsTest {
assertEquals(urlParams.force(), jsonParams.force());
assertEquals(urlParams.waitForResourcesInPrepare(), jsonParams.waitForResourcesInPrepare());
assertEquals(urlParams.getApplicationId(), jsonParams.getApplicationId());
+ assertEquals(urlParams.tags(), jsonParams.tags());
assertEquals(urlParams.getTimeoutBudget().timeout(), jsonParams.getTimeoutBudget().timeout());
assertEquals(urlParams.vespaVersion(), jsonParams.vespaVersion());
assertEquals(urlParams.containerEndpoints(), jsonParams.containerEndpoints());
diff --git a/container-search/src/main/java/com/yahoo/search/handler/Json2SingleLevelMap.java b/container-search/src/main/java/com/yahoo/search/handler/Json2SingleLevelMap.java
index bf0272f4f66..7dd1772a53e 100644
--- a/container-search/src/main/java/com/yahoo/search/handler/Json2SingleLevelMap.java
+++ b/container-search/src/main/java/com/yahoo/search/handler/Json2SingleLevelMap.java
@@ -22,9 +22,11 @@ import java.util.Map;
* @author baldersheim
*/
class Json2SingleLevelMap {
+
private static final ObjectMapper jsonMapper = new ObjectMapper().configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
private final byte [] buf;
private final JsonParser parser;
+
Json2SingleLevelMap(InputStream data) {
try {
buf = data.readAllBytes();
@@ -33,6 +35,7 @@ class Json2SingleLevelMap {
throw new RuntimeException("Problem reading POSTed data", e);
}
}
+
Map<String, String> parse() {
try {
Map<String, String> map = new HashMap<>();
@@ -47,16 +50,17 @@ class Json2SingleLevelMap {
throw new RuntimeException("Problem reading POSTed data", e);
}
}
+
void parse(Map<String, String> map, String parent) throws IOException {
for (parser.nextToken(); parser.getCurrentToken() != JsonToken.END_OBJECT; parser.nextToken()) {
String fieldName = parent + parser.getCurrentName();
JsonToken token = parser.nextToken();
if ((token == JsonToken.VALUE_STRING) ||
- (token == JsonToken.VALUE_NUMBER_FLOAT) ||
- (token == JsonToken.VALUE_NUMBER_INT) ||
- (token == JsonToken.VALUE_TRUE) ||
- (token == JsonToken.VALUE_FALSE) ||
- (token == JsonToken.VALUE_NULL)) {
+ (token == JsonToken.VALUE_NUMBER_FLOAT) ||
+ (token == JsonToken.VALUE_NUMBER_INT) ||
+ (token == JsonToken.VALUE_TRUE) ||
+ (token == JsonToken.VALUE_FALSE) ||
+ (token == JsonToken.VALUE_NULL)) {
map.put(fieldName, parser.getText());
} else if (token == JsonToken.START_ARRAY) {
map.put(fieldName, skipChildren(parser, buf));
@@ -71,6 +75,7 @@ class Json2SingleLevelMap {
}
}
}
+
private String skipChildren(JsonParser parser, byte [] input) throws IOException {
JsonLocation start = parser.getCurrentLocation();
parser.skipChildren();
@@ -78,4 +83,5 @@ class Json2SingleLevelMap {
int offset = (int)start.getByteOffset() - 1;
return new String(input, offset, (int)(end.getByteOffset() - offset), StandardCharsets.UTF_8);
}
+
}
diff --git a/container-search/src/main/java/com/yahoo/search/querytransform/NGramSearcher.java b/container-search/src/main/java/com/yahoo/search/querytransform/NGramSearcher.java
index afd25132510..bcdc84c1808 100644
--- a/container-search/src/main/java/com/yahoo/search/querytransform/NGramSearcher.java
+++ b/container-search/src/main/java/com/yahoo/search/querytransform/NGramSearcher.java
@@ -69,8 +69,7 @@ public class NGramSearcher extends Searcher {
private boolean rewriteToNGramMatching(Item item, int indexInParent, IndexFacts.Session indexFacts, Query query) {
boolean rewritten = false;
- if (item instanceof SegmentItem) { // handle CJK segmented terms which should be grams instead
- SegmentItem segments = (SegmentItem)item;
+ if (item instanceof SegmentItem segments) { // handle CJK segmented terms which should be grams instead
Index index = indexFacts.getIndex(segments.getIndexName());
if (index.isNGram()) {
Item grams = splitToGrams(segments, toLowerCase(segments.getRawWord()), index.getGramSize(), query);
@@ -78,13 +77,11 @@ public class NGramSearcher extends Searcher {
rewritten = true;
}
}
- else if (item instanceof CompositeItem) {
- CompositeItem composite = (CompositeItem)item;
+ else if (item instanceof CompositeItem composite) {
for (int i=0; i<composite.getItemCount(); i++)
rewritten = rewriteToNGramMatching(composite.getItem(i), i, indexFacts, query) || rewritten;
}
- else if (item instanceof TermItem) {
- TermItem term = (TermItem)item;
+ else if (item instanceof TermItem term) {
Index index = indexFacts.getIndex(term.getIndexName());
if (index.isNGram()) {
Item grams = splitToGrams(term,term.stringValue(), index.getGramSize(), query);
@@ -149,11 +146,10 @@ public class NGramSearcher extends Searcher {
}
private void replaceItemByGrams(Item item, Item grams, int indexInParent) {
- if (!(grams instanceof CompositeItem) || !(item.getParent() instanceof PhraseItem)) { // usually, simply replace
+ if (!(grams instanceof CompositeItem) || !(item.getParent() instanceof PhraseItem phraseParent)) { // usually, simply replace
item.getParent().setItem(indexInParent, grams);
}
else { // but if the parent is a phrase, we cannot add the AND to it, so add each gram to the phrase
- PhraseItem phraseParent = (PhraseItem)item.getParent();
phraseParent.removeItem(indexInParent);
int addedTerms = 0;
for (Iterator<Item> i = ((CompositeItem)grams).getItemIterator(); i.hasNext(); ) {
diff --git a/container-search/src/main/java/com/yahoo/search/searchchain/AsyncExecution.java b/container-search/src/main/java/com/yahoo/search/searchchain/AsyncExecution.java
index adab4d59ec0..205c65a6256 100644
--- a/container-search/src/main/java/com/yahoo/search/searchchain/AsyncExecution.java
+++ b/container-search/src/main/java/com/yahoo/search/searchchain/AsyncExecution.java
@@ -67,7 +67,7 @@ public class AsyncExecution {
* Creates an async execution.
*
* @param chain the chain to execute
- * @param context the the context of this
+ * @param context the context of this
*/
public AsyncExecution(Chain<? extends Searcher> chain, Execution.Context context) {
this(context, chain);
diff --git a/container-search/src/test/java/com/yahoo/search/test/QueryTestCase.java b/container-search/src/test/java/com/yahoo/search/test/QueryTestCase.java
index ae2a8800bbc..5b972e40774 100644
--- a/container-search/src/test/java/com/yahoo/search/test/QueryTestCase.java
+++ b/container-search/src/test/java/com/yahoo/search/test/QueryTestCase.java
@@ -65,7 +65,7 @@ import static org.junit.jupiter.api.Assertions.*;
public class QueryTestCase {
@Test
- void testIt() throws Exception {
+ void testIt() {
JSONObject newroot = new JSONObject("{\"key\": 3}");
var hit = new FastHit();
hit.setField("data", (JsonProducer) s -> s.append(newroot));
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/DeploymentData.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/DeploymentData.java
index a35d01f6891..d3331c3cfd4 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/DeploymentData.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/DeploymentData.java
@@ -5,6 +5,7 @@ import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.CloudAccount;
import com.yahoo.config.provision.DockerImage;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.athenz.api.AthenzDomain;
import com.yahoo.vespa.hosted.controller.api.integration.billing.Quota;
@@ -22,12 +23,14 @@ import static java.util.Objects.requireNonNull;
/**
* Data pertaining to a deployment to be done on a config server.
+ * Accessor names must match the names in com.yahoo.vespa.config.server.session.PrepareParams.
*
* @author jonmv
*/
public class DeploymentData {
private final ApplicationId instance;
+ private final Tags tags;
private final ZoneId zone;
private final byte[] applicationPackage;
private final Version platform;
@@ -41,7 +44,7 @@ public class DeploymentData {
private final Optional<CloudAccount> cloudAccount;
private final boolean dryRun;
- public DeploymentData(ApplicationId instance, ZoneId zone, byte[] applicationPackage, Version platform,
+ public DeploymentData(ApplicationId instance, Tags tags, ZoneId zone, byte[] applicationPackage, Version platform,
Set<ContainerEndpoint> containerEndpoints,
Optional<EndpointCertificateMetadata> endpointCertificateMetadata,
Optional<DockerImage> dockerImageRepo,
@@ -51,6 +54,7 @@ public class DeploymentData {
List<X509Certificate> operatorCertificates,
Optional<CloudAccount> cloudAccount, boolean dryRun) {
this.instance = requireNonNull(instance);
+ this.tags = requireNonNull(tags);
this.zone = requireNonNull(zone);
this.applicationPackage = requireNonNull(applicationPackage);
this.platform = requireNonNull(platform);
@@ -69,6 +73,8 @@ public class DeploymentData {
return instance;
}
+ public Tags tags() { return tags; }
+
public ZoneId zone() {
return zone;
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java
index e3a0ad7cb84..66e62ff7b95 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java
@@ -65,7 +65,7 @@ public class Application {
Set.of(), OptionalLong.empty(), RevisionHistory.empty(), List.of());
}
- // DO NOT USE! For serialization purposes, only.
+ // Do not use directly - edit through LockedApplication.
public Application(TenantAndApplicationId id, Instant createdAt, DeploymentSpec deploymentSpec, ValidationOverrides validationOverrides,
Optional<IssueId> deploymentIssueId, Optional<IssueId> ownershipIssueId, Optional<User> owner,
OptionalInt majorVersion, ApplicationMetrics metrics, Set<PublicKey> deployKeys, OptionalLong projectId,
@@ -230,11 +230,8 @@ public class Application {
@Override
public boolean equals(Object o) {
if (this == o) return true;
- if (! (o instanceof Application)) return false;
-
- Application that = (Application) o;
-
- return id.equals(that.id);
+ if (! (o instanceof Application other)) return false;
+ return id.equals(other.id);
}
@Override
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
index 19775ef420d..d8234e4f269 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
@@ -12,6 +12,7 @@ import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.CloudAccount;
import com.yahoo.config.provision.DockerImage;
import com.yahoo.config.provision.InstanceName;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.log.LogLevel;
@@ -166,10 +167,11 @@ public class ApplicationController {
int count = 0;
for (TenantAndApplicationId id : curator.readApplicationIds()) {
lockApplicationIfPresent(id, application -> {
- for (InstanceName instance : application.get().deploymentSpec().instanceNames())
- if ( ! application.get().instances().containsKey(instance))
- application = withNewInstance(application, id.instance(instance));
-
+ for (var declaredInstance : application.get().deploymentSpec().instances())
+ if ( ! application.get().instances().containsKey(declaredInstance.name()))
+ application = withNewInstance(application,
+ id.instance(declaredInstance.name()),
+ declaredInstance.tags());
store(application);
});
count++;
@@ -451,14 +453,14 @@ public class ApplicationController {
*
* @throws IllegalArgumentException if the instance already exists, or has an invalid instance name.
*/
- public void createInstance(ApplicationId id) {
+ public void createInstance(ApplicationId id, Tags tags) {
lockApplicationOrThrow(TenantAndApplicationId.from(id), application -> {
- store(withNewInstance(application, id));
+ store(withNewInstance(application, id, tags));
});
}
/** Returns given application with a new instance */
- public LockedApplication withNewInstance(LockedApplication application, ApplicationId instance) {
+ public LockedApplication withNewInstance(LockedApplication application, ApplicationId instance, Tags tags) {
if (instance.instance().isTester())
throw new IllegalArgumentException("'" + instance + "' is a tester application!");
InstanceId.validate(instance.instance().value());
@@ -469,7 +471,7 @@ public class ApplicationController {
throw new IllegalArgumentException("Could not create '" + instance + "': Instance " + dashToUnderscore(instance) + " already exists");
log.info("Created " + instance);
- return application.withNewInstance(instance.instance());
+ return application.withNewInstance(instance.instance(), tags);
}
/** Deploys an application package for an existing application instance. */
@@ -496,10 +498,11 @@ public class ApplicationController {
ApplicationPackage applicationPackage = new ApplicationPackage(applicationStore.get(deployment, revision));
AtomicReference<RevisionId> lastRevision = new AtomicReference<>();
+ Instance instance;
try (Mutex lock = lock(applicationId)) {
LockedApplication application = new LockedApplication(requireApplication(applicationId), lock);
application.get().revisions().last().map(ApplicationVersion::id).ifPresent(lastRevision::set);
- Instance instance = application.get().require(job.application().instance());
+ instance = application.get().require(job.application().instance());
if ( ! applicationPackage.trustedCertificates().isEmpty()
&& run.testerCertificate().isPresent())
@@ -512,7 +515,7 @@ public class ApplicationController {
} // Release application lock while doing the deployment, which is a lengthy task.
// Carry out deployment without holding the application lock.
- ActivateResult result = deploy(job.application(), applicationPackage, zone, platform, containerEndpoints,
+ ActivateResult result = deploy(job.application(), instance.tags(), applicationPackage, zone, platform, containerEndpoints,
endpointCertificateMetadata, run.isDryRun());
endpointCertificateMetadata.ifPresent(e -> deployLogger.accept("Using CA signed certificate version %s".formatted(e.version())));
@@ -543,9 +546,9 @@ public class ApplicationController {
lockApplicationOrThrow(applicationId, application ->
store(application.with(job.application().instance(),
- instance -> instance.withNewDeployment(zone, revision, platform,
- clock.instant(), warningsFrom(result),
- quotaUsage))));
+ i -> i.withNewDeployment(zone, revision, platform,
+ clock.instant(), warningsFrom(result),
+ quotaUsage))));
return result;
}
}
@@ -557,16 +560,19 @@ public class ApplicationController {
application = application.with(applicationPackage.deploymentSpec());
application = application.with(applicationPackage.validationOverrides());
- var existingInstances = application.get().instances().keySet();
- var declaredInstances = applicationPackage.deploymentSpec().instanceNames();
- for (var name : declaredInstances)
- if ( ! existingInstances.contains(name))
- application = withNewInstance(application, application.get().id().instance(name));
+ var existingInstances = application.get().instances();
+ var declaredInstances = applicationPackage.deploymentSpec().instances();
+ for (var declaredInstance : declaredInstances) {
+ if ( ! existingInstances.containsKey(declaredInstance.name()))
+ application = withNewInstance(application, application.get().id().instance(declaredInstance.name()), declaredInstance.tags());
+ else if ( ! existingInstances.get(declaredInstance.name()).tags().equals(declaredInstance.tags()))
+ application = application.with(declaredInstance.name(), instance -> instance.with(declaredInstance.tags()));
+ }
// Delete zones not listed in DeploymentSpec, if allowed
// We do this at deployment time for externally built applications, and at submission time
// for internally built ones, to be able to return a validation failure message when necessary
- for (InstanceName name : existingInstances) {
+ for (InstanceName name : existingInstances.keySet()) {
application = withoutDeletedDeployments(application, name);
}
@@ -599,7 +605,7 @@ public class ApplicationController {
ApplicationPackage applicationPackage = new ApplicationPackage(
artifactRepository.getSystemApplicationPackage(application.id(), zone, version)
);
- return deploy(application.id(), applicationPackage, zone, version, Set.of(), /* No application cert */ Optional.empty(), false);
+ return deploy(application.id(), Tags.empty(), applicationPackage, zone, version, Set.of(), /* No application cert */ Optional.empty(), false);
} else {
throw new RuntimeException("This system application does not have an application package: " + application.id().toShortString());
}
@@ -607,10 +613,10 @@ public class ApplicationController {
/** Deploys the given tester application to the given zone. */
public ActivateResult deployTester(TesterId tester, ApplicationPackage applicationPackage, ZoneId zone, Version platform) {
- return deploy(tester.id(), applicationPackage, zone, platform, Set.of(), /* No application cert for tester*/ Optional.empty(), false);
+ return deploy(tester.id(), Tags.empty(), applicationPackage, zone, platform, Set.of(), /* No application cert for tester*/ Optional.empty(), false);
}
- private ActivateResult deploy(ApplicationId application, ApplicationPackage applicationPackage,
+ private ActivateResult deploy(ApplicationId application, Tags tags, ApplicationPackage applicationPackage,
ZoneId zone, Version platform, Set<ContainerEndpoint> endpoints,
Optional<EndpointCertificateMetadata> endpointCertificateMetadata,
boolean dryRun) {
@@ -646,7 +652,7 @@ public class ApplicationController {
.collect(toList());
Optional<CloudAccount> cloudAccount = decideCloudAccountOf(deployment, applicationPackage.deploymentSpec());
ConfigServer.PreparedApplication preparedApplication =
- configServer.deploy(new DeploymentData(application, zone, applicationPackage.zippedContent(), platform,
+ configServer.deploy(new DeploymentData(application, tags, zone, applicationPackage.zippedContent(), platform,
endpoints, endpointCertificateMetadata, dockerImageRepo, domain,
deploymentQuota, tenantSecretStores, operatorCertificates,
cloudAccount, dryRun));
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Instance.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Instance.java
index d66d1491f73..430bafe5c44 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Instance.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Instance.java
@@ -5,6 +5,7 @@ import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.InstanceName;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.RevisionId;
@@ -40,6 +41,7 @@ import java.util.stream.Collectors;
public class Instance {
private final ApplicationId id;
+ private final Tags tags;
private final Map<ZoneId, Deployment> deployments;
private final List<AssignedRotation> rotations;
private final RotationStatus rotationStatus;
@@ -47,14 +49,15 @@ public class Instance {
private final Change change;
/** Creates an empty instance */
- public Instance(ApplicationId id) {
- this(id, Set.of(), Map.of(), List.of(), RotationStatus.EMPTY, Change.empty());
+ public Instance(ApplicationId id, Tags tags) {
+ this(id, tags, Set.of(), Map.of(), List.of(), RotationStatus.EMPTY, Change.empty());
}
/** Creates an empty instance*/
- public Instance(ApplicationId id, Collection<Deployment> deployments, Map<JobType, Instant> jobPauses,
+ public Instance(ApplicationId id, Tags tags, Collection<Deployment> deployments, Map<JobType, Instant> jobPauses,
List<AssignedRotation> rotations, RotationStatus rotationStatus, Change change) {
this.id = Objects.requireNonNull(id, "id cannot be null");
+ this.tags = Objects.requireNonNull(tags, "tags cannot be null");
this.deployments = Objects.requireNonNull(deployments, "deployments cannot be null").stream()
.collect(Collectors.toUnmodifiableMap(Deployment::zone, Function.identity()));
this.jobPauses = Map.copyOf(Objects.requireNonNull(jobPauses, "deploymentJobs cannot be null"));
@@ -63,6 +66,10 @@ public class Instance {
this.change = Objects.requireNonNull(change, "change cannot be null");
}
+ public Instance with(Tags tags) {
+ return new Instance(id, tags, deployments.values(), jobPauses, rotations, rotationStatus, change);
+ }
+
public Instance withNewDeployment(ZoneId zone, RevisionId revision, Version version,
Instant instant, Map<DeploymentMetrics.Warning, Integer> warnings, QuotaUsage quotaUsage) {
// Use info from previous deployment if available, otherwise create a new one.
@@ -87,7 +94,7 @@ public class Instance {
else
jobPauses.remove(jobType);
- return new Instance(id, deployments.values(), jobPauses, rotations, rotationStatus, change);
+ return new Instance(id, tags, deployments.values(), jobPauses, rotations, rotationStatus, change);
}
public Instance recordActivityAt(Instant instant, ZoneId zone) {
@@ -118,15 +125,15 @@ public class Instance {
}
public Instance with(List<AssignedRotation> assignedRotations) {
- return new Instance(id, deployments.values(), jobPauses, assignedRotations, rotationStatus, change);
+ return new Instance(id, tags, deployments.values(), jobPauses, assignedRotations, rotationStatus, change);
}
public Instance with(RotationStatus rotationStatus) {
- return new Instance(id, deployments.values(), jobPauses, rotations, rotationStatus, change);
+ return new Instance(id, tags, deployments.values(), jobPauses, rotations, rotationStatus, change);
}
public Instance withChange(Change change) {
- return new Instance(id, deployments.values(), jobPauses, rotations, rotationStatus, change);
+ return new Instance(id, tags, deployments.values(), jobPauses, rotations, rotationStatus, change);
}
private Instance with(Deployment deployment) {
@@ -136,13 +143,15 @@ public class Instance {
}
private Instance with(Map<ZoneId, Deployment> deployments) {
- return new Instance(id, deployments.values(), jobPauses, rotations, rotationStatus, change);
+ return new Instance(id, tags, deployments.values(), jobPauses, rotations, rotationStatus, change);
}
public ApplicationId id() { return id; }
public InstanceName name() { return id.instance(); }
+ public Tags tags() { return tags; }
+
/** Returns an immutable map of the current deployments of this */
public Map<ZoneId, Deployment> deployments() { return deployments; }
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java
index 3e822415e96..fa702a166d2 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java
@@ -4,6 +4,7 @@ package com.yahoo.vespa.hosted.controller;
import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.application.api.ValidationOverrides;
import com.yahoo.config.provision.InstanceName;
+import com.yahoo.config.provision.Tags;
import com.yahoo.transaction.Mutex;
import com.yahoo.vespa.curator.Lock;
import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId;
@@ -88,9 +89,9 @@ public class LockedApplication {
projectId, revisions, instances.values());
}
- LockedApplication withNewInstance(InstanceName instance) {
+ LockedApplication withNewInstance(InstanceName instance, Tags tags) {
var instances = new HashMap<>(this.instances);
- instances.put(instance, new Instance(id.instance(instance)));
+ instances.put(instance, new Instance(id.instance(instance), tags));
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides,
deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, deployKeys,
projectId, instances, revisions);
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackage.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackage.java
index 4bc9aeb00e4..c2c95a0c4bf 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackage.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackage.java
@@ -16,6 +16,7 @@ import com.yahoo.config.application.api.ValidationOverrides;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.Tags;
import com.yahoo.security.X509CertificateUtils;
import com.yahoo.slime.Inspector;
import com.yahoo.slime.Slime;
@@ -202,7 +203,8 @@ public class ApplicationPackage {
new InputStreamReader(new ByteArrayInputStream(servicesXml.content()), UTF_8),
InstanceName.defaultName(),
Environment.prod,
- RegionName.defaultName())
+ RegionName.defaultName(),
+ Tags.empty())
.run(); // Populates the zip archive cache with files that would be included.
}
catch (IllegalArgumentException e) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java
index 36036d6d36d..cca5310ccb9 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java
@@ -8,6 +8,7 @@ import com.yahoo.concurrent.UncheckedTimeoutException;
import com.yahoo.config.application.api.DeploymentSpec.UpgradePolicy;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.transaction.Mutex;
import com.yahoo.vespa.hosted.controller.Application;
@@ -321,15 +322,13 @@ public class JobController {
public List<ApplicationId> instances() {
return controller.applications().readable().stream()
.flatMap(application -> application.instances().values().stream())
- .map(Instance::id)
- .collect(toUnmodifiableList());
+ .map(Instance::id).toList();
}
/** Returns all job types which have been run for the given application. */
private List<JobType> jobs(ApplicationId id) {
return JobType.allIn(controller.zoneRegistry()).stream()
- .filter(type -> last(id, type).isPresent())
- .collect(toUnmodifiableList());
+ .filter(type -> last(id, type).isPresent()).toList();
}
/** Returns an immutable map of all known runs for the given application and job type. */
@@ -340,9 +339,8 @@ public class JobController {
/** Lists the start time of non-redeployment runs of the given job, in order of increasing age. */
public List<Instant> jobStarts(JobId id) {
return runs(id).descendingMap().values().stream()
- .filter(run -> ! run.isRedeployment())
- .map(Run::start)
- .collect(toUnmodifiableList());
+ .filter(run -> !run.isRedeployment())
+ .map(Run::start).toList();
}
/** Returns when given deployment last started deploying, falling back to time of deployment if it cannot be determined from job runs */
@@ -698,7 +696,7 @@ public class JobController {
controller.applications().lockApplicationOrThrow(TenantAndApplicationId.from(id), application -> {
if ( ! application.get().instances().containsKey(id.instance()))
- application = controller.applications().withNewInstance(application, id);
+ application = controller.applications().withNewInstance(application, id, Tags.empty());
// TODO(mpolden): Enable for public CD once all tests have been updated
if (controller.system() != SystemName.PublicCd) {
controller.applications().validatePackage(applicationPackage, application.get());
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java
index 5aa847f648a..37c45f38e36 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java
@@ -9,6 +9,7 @@ import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.security.KeyUtils;
import com.yahoo.slime.ArrayTraverser;
@@ -97,6 +98,7 @@ public class ApplicationSerializer {
// Instance fields
private static final String instanceNameField = "instanceName";
+ private static final String tagsField = "tags";
private static final String deploymentsField = "deployments";
private static final String deploymentJobsField = "deploymentJobs"; // TODO jonmv: clean up serialisation format
private static final String assignedRotationsField = "assignedRotations";
@@ -184,6 +186,7 @@ public class ApplicationSerializer {
for (Instance instance : application.instances().values()) {
Cursor instanceObject = array.addObject();
instanceObject.setString(instanceNameField, instance.name().value());
+ instanceObject.setString(tagsField, instance.tags().asString());
deploymentsToSlime(instance.deployments().values(), instanceObject.setArray(deploymentsField));
toSlime(instance.jobPauses(), instanceObject.setObject(deploymentJobsField));
assignedRotationsToSlime(instance.rotations(), instanceObject);
@@ -380,12 +383,14 @@ public class ApplicationSerializer {
List<Instance> instances = new ArrayList<>();
field.traverse((ArrayTraverser) (name, object) -> {
InstanceName instanceName = InstanceName.from(object.field(instanceNameField).asString());
- List<Deployment> deployments = deploymentsFromSlime(object.field(deploymentsField), id.instance(instanceName));
+ Tags tags = Tags.fromString(object.field(tagsField).asString());
+ List < Deployment > deployments = deploymentsFromSlime(object.field(deploymentsField), id.instance(instanceName));
Map<JobType, Instant> jobPauses = jobPausesFromSlime(object.field(deploymentJobsField));
List<AssignedRotation> assignedRotations = assignedRotationsFromSlime(object);
RotationStatus rotationStatus = rotationStatusFromSlime(object);
Change change = changeFromSlime(object.field(deployingField));
instances.add(new Instance(id.instance(instanceName),
+ tags,
deployments,
jobPauses,
assignedRotations,
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
index e8fe7b7d5f0..81fb72e19fd 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
@@ -22,6 +22,7 @@ import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.NodeResources;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.zone.RoutingMethod;
import com.yahoo.config.provision.zone.ZoneId;
@@ -2035,7 +2036,7 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
if (controller.applications().getApplication(applicationId).isEmpty())
createApplication(tenantName, applicationName, request);
- controller.applications().createInstance(applicationId.instance(instanceName));
+ controller.applications().createInstance(applicationId.instance(instanceName), Tags.empty());
Slime slime = new Slime();
toSlime(applicationId.instance(instanceName), slime.setObject(), request);
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java
index 5380cf4ee27..0c908b1f035 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java
@@ -7,6 +7,7 @@ import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.zone.RoutingMethod;
import com.yahoo.config.provision.zone.ZoneApi;
@@ -370,7 +371,7 @@ public final class ControllerTester {
public Application createApplication(String tenant, String applicationName, String instanceName) {
Application application = createApplication(tenant, applicationName);
- controller().applications().createInstance(application.id().instance(instanceName));
+ controller().applications().createInstance(application.id().instance(instanceName), Tags.empty());
return application;
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/DeploymentQuotaCalculatorTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/DeploymentQuotaCalculatorTest.java
index 3163c8b6439..db45933f498 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/DeploymentQuotaCalculatorTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/DeploymentQuotaCalculatorTest.java
@@ -8,6 +8,7 @@ import com.yahoo.config.application.api.ValidationOverrides;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Instance;
@@ -63,8 +64,8 @@ public class DeploymentQuotaCalculatorTest {
var existing_dev_deployment = new Application(TenantAndApplicationId.from(ApplicationId.defaultId()), Instant.EPOCH, DeploymentSpec.empty, ValidationOverrides.empty, Optional.empty(),
Optional.empty(), Optional.empty(), OptionalInt.empty(), new ApplicationMetrics(1, 1), Set.of(), OptionalLong.empty(), RevisionHistory.empty(),
- List.of(new Instance(ApplicationId.defaultId()).withNewDeployment(ZoneId.from(Environment.dev, RegionName.defaultName()),
- RevisionId.forProduction(1), Version.emptyVersion, Instant.EPOCH, Map.of(), QuotaUsage.create(0.53d))));
+ List.of(new Instance(ApplicationId.defaultId(), Tags.empty()).withNewDeployment(ZoneId.from(Environment.dev, RegionName.defaultName()),
+ RevisionId.forProduction(1), Version.emptyVersion, Instant.EPOCH, Map.of(), QuotaUsage.create(0.53d))));
Quota calculated = DeploymentQuotaCalculator.calculate(Quota.unlimited().withBudget(2), List.of(existing_dev_deployment), ApplicationId.defaultId(), ZoneId.defaultId(),
DeploymentSpec.fromXml(
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificatesTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificatesTest.java
index 9a2d9eddba7..274cf3c5867 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificatesTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificatesTest.java
@@ -8,6 +8,7 @@ import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.zone.RoutingMethod;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.security.KeyAlgorithm;
@@ -100,7 +101,7 @@ public class EndpointCertificatesTest {
return x509CertificateBuilder.build();
}
- private final Instance testInstance = new Instance(ApplicationId.defaultId());
+ private final Instance testInstance = new Instance(ApplicationId.defaultId(), Tags.empty());
private final String testKeyName = "testKeyName";
private final String testCertName = "testCertName";
private ZoneId testZone;
@@ -234,7 +235,7 @@ public class EndpointCertificatesTest {
@Test
void includes_application_endpoint_when_declared() {
- Instance instance = new Instance(ApplicationId.from("t1", "a1", "default"));
+ Instance instance = new Instance(ApplicationId.from("t1", "a1", "default"), Tags.empty());
ZoneId zone1 = ZoneId.from(Environment.prod, RegionName.from("aws-us-east-1c"));
ZoneId zone2 = ZoneId.from(Environment.prod, RegionName.from("aws-us-west-2a"));
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java
index d8cef45f124..5fba33c9094 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java
@@ -8,6 +8,7 @@ import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.zone.RoutingMethod;
import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.config.provision.zone.ZoneId;
@@ -986,7 +987,7 @@ public class DeploymentTriggerTest {
@Test
void testUserInstancesNotInDeploymentSpec() {
var app = tester.newDeploymentContext();
- tester.controller().applications().createInstance(app.application().id().instance("user"));
+ tester.controller().applications().createInstance(app.application().id().instance("user"), Tags.empty());
app.submit().deploy();
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java
index 849261d5ae4..40217890351 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java
@@ -6,6 +6,7 @@ import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.application.api.ValidationOverrides;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.security.KeyUtils;
import com.yahoo.slime.SlimeUtils;
@@ -127,18 +128,21 @@ public class ApplicationSerializerTest {
RevisionHistory revisions = RevisionHistory.ofRevisions(List.of(applicationVersion1),
Map.of(new JobId(id1, DeploymentContext.productionUsEast3), List.of(applicationVersion2)));
- List<Instance> instances = List.of(new Instance(id1,
- deployments,
- Map.of(DeploymentContext.systemTest, Instant.ofEpochMilli(333)),
- List.of(AssignedRotation.fromStrings("foo", "default", "my-rotation", Set.of("us-west-1"))),
- rotationStatus,
- Change.of(new Version("6.1"))),
- new Instance(id3,
- List.of(),
- Map.of(),
- List.of(),
- RotationStatus.EMPTY,
- Change.of(Version.fromString("6.7")).withPin()));
+ List<Instance> instances =
+ List.of(new Instance(id1,
+ Tags.fromString("tag1 tag2"),
+ deployments,
+ Map.of(DeploymentContext.systemTest, Instant.ofEpochMilli(333)),
+ List.of(AssignedRotation.fromStrings("foo", "default", "my-rotation", Set.of("us-west-1"))),
+ rotationStatus,
+ Change.of(new Version("6.1"))),
+ new Instance(id3,
+ Tags.empty(),
+ List.of(),
+ Map.of(),
+ List.of(),
+ RotationStatus.EMPTY,
+ Change.of(Version.fromString("6.7")).withPin()));
Application original = new Application(TenantAndApplicationId.from(id1),
Instant.now().truncatedTo(ChronoUnit.MILLIS),
@@ -177,6 +181,9 @@ public class ApplicationSerializerTest {
assertEquals(original.revisions().production(), serialized.revisions().production());
assertEquals(original.revisions().development(), serialized.revisions().development());
+ assertEquals(original.require(id1.instance()).tags(), serialized.require(id1.instance()).tags());
+ assertEquals(original.require(id3.instance()).tags(), serialized.require(id3.instance()).tags());
+
assertEquals(original.deploymentSpec().xmlForm(), serialized.deploymentSpec().xmlForm());
assertEquals(original.validationOverrides().xmlForm(), serialized.validationOverrides().xmlForm());
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/athenz/AthenzApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/athenz/AthenzApiTest.java
index 3a539987443..2f4b4154c08 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/athenz/AthenzApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/athenz/AthenzApiTest.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.controller.restapi.athenz;
import com.yahoo.application.container.handler.Request;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.Tags;
import com.yahoo.vespa.athenz.api.AthenzDomain;
import com.yahoo.vespa.hosted.controller.ControllerTester;
import com.yahoo.vespa.hosted.controller.restapi.ContainerTester;
@@ -25,8 +26,8 @@ public class AthenzApiTest extends ControllerContainerTest {
controllerTester.createTenant("sandbox", AthenzApiHandler.sandboxDomainIn(tester.controller().system()), 123L);
controllerTester.createApplication("sandbox", "app", "default");
- tester.controller().applications().createInstance(ApplicationId.from("sandbox", "app", hostedOperator.getName()));
- tester.controller().applications().createInstance(ApplicationId.from("sandbox", "app", defaultUser.getName()));
+ tester.controller().applications().createInstance(ApplicationId.from("sandbox", "app", hostedOperator.getName()), Tags.empty());
+ tester.controller().applications().createInstance(ApplicationId.from("sandbox", "app", defaultUser.getName()), Tags.empty());
controllerTester.createApplication("sandbox", "opp", "default");
controllerTester.createTenant("tenant1", "domain1", 123L);
diff --git a/vespa-maven-plugin/src/main/java/ai/vespa/hosted/plugin/AbstractVespaMojo.java b/vespa-maven-plugin/src/main/java/ai/vespa/hosted/plugin/AbstractVespaMojo.java
index aaf67f6b8ea..ce85b7d6f32 100644
--- a/vespa-maven-plugin/src/main/java/ai/vespa/hosted/plugin/AbstractVespaMojo.java
+++ b/vespa-maven-plugin/src/main/java/ai/vespa/hosted/plugin/AbstractVespaMojo.java
@@ -43,6 +43,9 @@ public abstract class AbstractVespaMojo extends AbstractMojo {
@Parameter(property = "instance")
protected String instance;
+ @Parameter(property = "tags")
+ protected String tags;
+
@Parameter(property = "apiKey")
protected String apiKey;
diff --git a/vespa-maven-plugin/src/main/java/ai/vespa/hosted/plugin/EffectiveServicesMojo.java b/vespa-maven-plugin/src/main/java/ai/vespa/hosted/plugin/EffectiveServicesMojo.java
index 556af8b6f85..377975b3d01 100644
--- a/vespa-maven-plugin/src/main/java/ai/vespa/hosted/plugin/EffectiveServicesMojo.java
+++ b/vespa-maven-plugin/src/main/java/ai/vespa/hosted/plugin/EffectiveServicesMojo.java
@@ -3,6 +3,7 @@ package ai.vespa.hosted.plugin;
import com.yahoo.config.application.XmlPreProcessor;
import com.yahoo.config.provision.InstanceName;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.zone.ZoneId;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
@@ -44,16 +45,17 @@ public class EffectiveServicesMojo extends AbstractVespaDeploymentMojo {
ZoneId zone = zoneOf(environment, region);
Path output = Paths.get(outputDirectory).resolve("services-" + zone.environment().value() + "-" + zone.region().value() + ".xml");
- Files.write(output, effectiveServices(services, zone, InstanceName.from(instance)).getBytes(StandardCharsets.UTF_8));
+ Files.write(output, effectiveServices(services, zone, InstanceName.from(instance), Tags.fromString(tags)).getBytes(StandardCharsets.UTF_8));
getLog().info("Effective services for " + zone + " written to " + output);
}
- static String effectiveServices(File servicesFile, ZoneId zone, InstanceName instance) throws Exception {
+ static String effectiveServices(File servicesFile, ZoneId zone, InstanceName instance, Tags tags) throws Exception {
Document processedServicesXml = new XmlPreProcessor(servicesFile.getParentFile(),
servicesFile,
instance,
zone.environment(),
- zone.region())
+ zone.region(),
+ tags)
.run();
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
diff --git a/vespa-maven-plugin/src/test/java/ai/vespa/hosted/plugin/EffectiveServicesMojoTest.java b/vespa-maven-plugin/src/test/java/ai/vespa/hosted/plugin/EffectiveServicesMojoTest.java
index 3cb08f5f2b6..fb7376b13cb 100644
--- a/vespa-maven-plugin/src/test/java/ai/vespa/hosted/plugin/EffectiveServicesMojoTest.java
+++ b/vespa-maven-plugin/src/test/java/ai/vespa/hosted/plugin/EffectiveServicesMojoTest.java
@@ -2,6 +2,7 @@
package ai.vespa.hosted.plugin;
import com.yahoo.config.provision.InstanceName;
+import com.yahoo.config.provision.Tags;
import com.yahoo.config.provision.zone.ZoneId;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@@ -26,21 +27,21 @@ class EffectiveServicesMojoTest {
@DisplayName("when zone matches environment-only directive")
void devServices() throws Exception {
assertEquals(Files.readString(Paths.get("src/test/resources/effective-services/dev.xml")),
- effectiveServices(servicesFile, ZoneId.from("dev", "us-east-3"), InstanceName.defaultName()));
+ effectiveServices(servicesFile, ZoneId.from("dev", "us-east-3"), InstanceName.defaultName(), Tags.empty()));
}
@Test
@DisplayName("when zone matches region-and-environment directive")
void prodUsEast3() throws Exception {
assertEquals(Files.readString(Paths.get("src/test/resources/effective-services/prod_us-east-3.xml")),
- effectiveServices(servicesFile, ZoneId.from("prod", "us-east-3"), InstanceName.defaultName()));
+ effectiveServices(servicesFile, ZoneId.from("prod", "us-east-3"), InstanceName.defaultName(), Tags.empty()));
}
@Test
@DisplayName("when zone doesn't match any directives")
void prodUsWest1Services() throws Exception {
assertEquals(Files.readString(Paths.get("src/test/resources/effective-services/prod_us-west-1.xml")),
- effectiveServices(servicesFile, ZoneId.from("prod", "us-west-1"), InstanceName.defaultName()));
+ effectiveServices(servicesFile, ZoneId.from("prod", "us-west-1"), InstanceName.defaultName(), Tags.empty()));
}
}