aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--config-model/src/main/java/com/yahoo/config/model/deploy/DeployState.java4
-rw-r--r--config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java9
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/Index.java12
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/derived/IndexSchema.java10
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/IndexOperation.java11
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainer.java24
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java11
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/ConfiguredDirectSslProvider.java66
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/ConfiguredFilebasedSslProvider.java (renamed from config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/ConfiguredSslProvider.java)6
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/JettyConnectorBuilder.java4
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java6
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerServiceBuilder.java2
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/content/Content.java2
-rw-r--r--config-model/src/main/javacc/SDParser.jj4
-rw-r--r--config-model/src/test/derived/indexschema/index-info.cfg10
-rw-r--r--config-model/src/test/derived/indexschema/indexschema.cfg2
-rw-r--r--config-model/src/test/derived/indexschema/indexschema.sd4
-rw-r--r--config-model/src/test/derived/indexschema/vsmfields.cfg6
-rwxr-xr-xconfig-model/src/test/java/com/yahoo/vespa/model/container/ContainerClusterTest.java42
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/container/xml/JettyContainerModelBuilderTest.java41
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/zone/ZoneList.java9
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/GlobalComponentRegistry.java2
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/InjectedGlobalComponentRegistry.java11
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java8
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ActivatedModelsBuilder.java7
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/PrepareParams.java20
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java22
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TlsSecretsKeys.java86
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/InjectedGlobalComponentRegistryTest.java2
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/MockSecretStore.java35
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/ModelContextImplTest.java3
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/TestComponentRegistry.java19
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionPreparerTest.java51
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionTest.java2
-rw-r--r--container-disc/src/main/java/com/yahoo/container/jdisc/DeprecatedSecretStoreProvider.java34
-rw-r--r--container-disc/src/main/java/com/yahoo/container/jdisc/SecretStoreProvider.java23
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java26
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java51
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/AssignedRotation.java61
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointId.java53
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java5
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java62
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImpl.java5
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/cost/CostCalculator.java5
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiHandler.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiHandler.java13
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java12
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java10
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTester.java7
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneFilterMock.java5
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java28
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java7
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepositoryTest.java1
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/VersionStatusTest.java17
-rw-r--r--hosted-api/pom.xml10
-rw-r--r--hosted-api/src/test/java/ai/vespa/hosted/api/MultiPartStreamerTest.java16
-rw-r--r--hosted-api/src/test/java/ai/vespa/hosted/api/SignaturesTest.java14
-rw-r--r--jdisc_http_service/abi-spec.json4
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/impl/ConfiguredSslContextFactoryProvider.java34
-rw-r--r--jdisc_http_service/src/main/resources/configdefinitions/jdisc.http.connector.def10
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerService.java13
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerServiceMock.java4
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/SharedLoadBalancerService.java2
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java6
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java2
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java14
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/lb/SharedLoadBalancerServiceTest.java4
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisionerTest.java86
-rw-r--r--parent/pom.xml13
-rw-r--r--tenant-base/pom.xml10
-rw-r--r--tenant-cd/src/main/java/ai/vespa/hosted/cd/ProductionTest.java3
-rw-r--r--tenant-cd/src/main/java/ai/vespa/hosted/cd/StagingTest.java4
-rw-r--r--tenant-cd/src/main/java/ai/vespa/hosted/cd/SystemTest.java4
-rw-r--r--vespa-testrunner-components/src/main/java/com/yahoo/vespa/hosted/testrunner/PomXmlGenerator.java14
-rw-r--r--vespa-testrunner-components/src/test/resources/pom.xml_system_tests14
76 files changed, 1029 insertions, 238 deletions
diff --git a/config-model/src/main/java/com/yahoo/config/model/deploy/DeployState.java b/config-model/src/main/java/com/yahoo/config/model/deploy/DeployState.java
index 21a8297910f..1892c8920a7 100644
--- a/config-model/src/main/java/com/yahoo/config/model/deploy/DeployState.java
+++ b/config-model/src/main/java/com/yahoo/config/model/deploy/DeployState.java
@@ -15,6 +15,7 @@ import com.yahoo.config.model.api.ContainerEndpoint;
import com.yahoo.config.model.api.HostProvisioner;
import com.yahoo.config.model.api.Model;
import com.yahoo.config.model.api.ModelContext;
+import com.yahoo.config.model.api.TlsSecrets;
import com.yahoo.config.model.api.ValidationParameters;
import com.yahoo.config.model.application.provider.BaseDeployLogger;
import com.yahoo.config.model.application.provider.MockFileRegistry;
@@ -256,6 +257,8 @@ public class DeployState implements ConfigDefinitionStore {
public Instant now() { return now; }
+ public Optional<TlsSecrets> tlsSecrets() { return properties.tlsSecrets(); }
+
public static class Builder {
private ApplicationPackage applicationPackage = MockApplicationPackage.createEmpty();
@@ -273,6 +276,7 @@ public class DeployState implements ConfigDefinitionStore {
private Zone zone = Zone.defaultZone();
private Instant now = Instant.now();
private Version wantedNodeVespaVersion = Vtag.currentVersion;
+ private Optional<TlsSecrets> tlsSecrets = Optional.empty();
public Builder applicationPackage(ApplicationPackage applicationPackage) {
this.applicationPackage = applicationPackage;
diff --git a/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java b/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java
index 87ff9d1bb2a..d974db73547 100644
--- a/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java
+++ b/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java
@@ -5,6 +5,7 @@ import com.google.common.collect.ImmutableList;
import com.yahoo.config.model.api.ConfigServerSpec;
import com.yahoo.config.model.api.ContainerEndpoint;
import com.yahoo.config.model.api.ModelContext;
+import com.yahoo.config.model.api.TlsSecrets;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.Rotation;
@@ -13,6 +14,7 @@ import com.yahoo.config.provision.Zone;
import java.net.URI;
import java.util.Collections;
import java.util.List;
+import java.util.Optional;
import java.util.Set;
/**
@@ -39,6 +41,7 @@ public class TestProperties implements ModelContext.Properties {
private boolean useFdispatchByDefault = true;
private boolean dispatchWithProtobuf = true;
private boolean useAdaptiveDispatch = false;
+ private Optional<TlsSecrets> tlsSecrets = Optional.empty();
@Override public boolean multitenant() { return multitenant; }
@@ -58,6 +61,7 @@ public class TestProperties implements ModelContext.Properties {
@Override public boolean useDedicatedNodeForLogserver() { return useDedicatedNodeForLogserver; }
@Override public boolean useFdispatchByDefault() { return useFdispatchByDefault; }
@Override public boolean dispatchWithProtobuf() { return dispatchWithProtobuf; }
+ @Override public Optional<TlsSecrets> tlsSecrets() { return tlsSecrets; }
public TestProperties setApplicationId(ApplicationId applicationId) {
this.applicationId = applicationId;
@@ -90,6 +94,11 @@ public class TestProperties implements ModelContext.Properties {
}
+ public TestProperties setTlsSecrets(Optional<TlsSecrets> tlsSecrets) {
+ this.tlsSecrets = tlsSecrets;
+ return this;
+ }
+
public static class Spec implements ConfigServerSpec {
private final String hostName;
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/Index.java b/config-model/src/main/java/com/yahoo/searchdefinition/Index.java
index d7e9e0da081..0ea3f5c24a3 100644
--- a/config-model/src/main/java/com/yahoo/searchdefinition/Index.java
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/Index.java
@@ -56,8 +56,8 @@ public class Index implements Cloneable, Serializable {
/** The boolean index definition, if set */
private BooleanIndexDefinition boolIndex;
- // TODO: Remove when experimental posting list format is made default
- private boolean experimentalPostingListFormat = false;
+ /** Whether the posting lists of this index field should have interleaved features (num occs, field length) in document id stream. */
+ private boolean interleavedFeatures = false;
public Index(String name) {
this(name, false);
@@ -184,12 +184,12 @@ public class Index implements Cloneable, Serializable {
boolIndex = def;
}
- public void setExperimentalPostingListFormat(boolean value) {
- experimentalPostingListFormat = value;
+ public void setInterleavedFeatures(boolean value) {
+ interleavedFeatures = value;
}
- public boolean useExperimentalPostingListFormat() {
- return experimentalPostingListFormat;
+ public boolean useInterleavedFeatures() {
+ return interleavedFeatures;
}
}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/IndexSchema.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/IndexSchema.java
index 3b62807ce73..60b8ee78c7b 100644
--- a/config-model/src/main/java/com/yahoo/searchdefinition/derived/IndexSchema.java
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/IndexSchema.java
@@ -114,7 +114,7 @@ public class IndexSchema extends Derived implements IndexschemaConfig.Producer {
.prefix(f.hasPrefix())
.phrases(f.hasPhrases())
.positions(f.hasPositions())
- .interleavedfeatures(f.useExperimentalPostingListFormat());
+ .interleavedfeatures(f.useInterleavedFeatures());
if (!f.getCollectionType().equals("SINGLE")) {
ifB.collectiontype(IndexschemaConfig.Indexfield.Collectiontype.Enum.valueOf(f.getCollectionType()));
}
@@ -175,8 +175,8 @@ public class IndexSchema extends Derived implements IndexschemaConfig.Producer {
private boolean phrases = false; // TODO dead, but keep a while to ensure config compatibility?
private boolean positions = true;// TODO dead, but keep a while to ensure config compatibility?
private BooleanIndexDefinition boolIndex = null;
- // TODO: Remove when experimental posting list format is made default
- private boolean experimentalPostingListFormat = false;
+ // Whether the posting lists of this index field should have interleaved features (num occs, field length) in document id stream.
+ private boolean interleavedFeatures = false;
public IndexField(String name, Index.Type type, DataType sdFieldType) {
this.name = name;
@@ -186,7 +186,7 @@ public class IndexSchema extends Derived implements IndexschemaConfig.Producer {
public void setIndexSettings(com.yahoo.searchdefinition.Index index) {
if (type.equals(Index.Type.TEXT)) {
prefix = index.isPrefix();
- experimentalPostingListFormat = index.useExperimentalPostingListFormat();
+ interleavedFeatures = index.useInterleavedFeatures();
}
sdType = index.getType();
boolIndex = index.getBooleanIndexDefiniton();
@@ -209,7 +209,7 @@ public class IndexSchema extends Derived implements IndexschemaConfig.Producer {
public boolean hasPrefix() { return prefix; }
public boolean hasPhrases() { return phrases; }
public boolean hasPositions() { return positions; }
- public boolean useExperimentalPostingListFormat() { return experimentalPostingListFormat; }
+ public boolean useInterleavedFeatures() { return interleavedFeatures; }
public BooleanIndexDefinition getBooleanIndexDefinition() {
return boolIndex;
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/IndexOperation.java b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/IndexOperation.java
index 459bb247e5f..39f543c7db3 100644
--- a/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/IndexOperation.java
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/IndexOperation.java
@@ -29,8 +29,7 @@ public class IndexOperation implements FieldOperation {
private OptionalLong lowerBound = OptionalLong.empty();
private OptionalLong upperBound = OptionalLong.empty();
private OptionalDouble densePostingListThreshold = OptionalDouble.empty();
- // TODO: Remove when experimental posting list format is made default
- private Optional<Boolean> experimentalPostingListFormat = Optional.empty();
+ private Optional<Boolean> enableBm25 = Optional.empty();
public String getIndexName() {
return indexName;
@@ -89,8 +88,8 @@ public class IndexOperation implements FieldOperation {
index.setBooleanIndexDefiniton(
new BooleanIndexDefinition(arity, lowerBound, upperBound, densePostingListThreshold));
}
- if (experimentalPostingListFormat.isPresent()) {
- index.setExperimentalPostingListFormat(experimentalPostingListFormat.get());
+ if (enableBm25.isPresent()) {
+ index.setInterleavedFeatures(enableBm25.get());
}
}
@@ -117,8 +116,8 @@ public class IndexOperation implements FieldOperation {
public void setDensePostingListThreshold(double densePostingListThreshold) {
this.densePostingListThreshold = OptionalDouble.of(densePostingListThreshold);
}
- public void setExperimentalPostingListFormat(boolean value) {
- experimentalPostingListFormat = Optional.of(value);
+ public void setEnableBm25(boolean value) {
+ enableBm25 = Optional.of(value);
}
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainer.java b/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainer.java
index b381168838f..48f7fa3c1a2 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainer.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainer.java
@@ -1,8 +1,15 @@
// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.model.container;
+import com.yahoo.config.model.api.TlsSecrets;
import com.yahoo.config.model.api.container.ContainerServiceType;
import com.yahoo.config.model.producer.AbstractConfigProducer;
+import com.yahoo.vespa.model.container.http.ConnectorFactory;
+import com.yahoo.vespa.model.container.http.Http;
+import com.yahoo.vespa.model.container.http.JettyHttpServer;
+import com.yahoo.vespa.model.container.http.ssl.ConfiguredDirectSslProvider;
+
+import java.util.Optional;
/**
* A container that is typically used by container clusters set up from the user application.
@@ -15,14 +22,23 @@ public final class ApplicationContainer extends Container {
private final boolean isHostedVespa;
-
- public ApplicationContainer(AbstractConfigProducer parent, String name, int index, boolean isHostedVespa) {
- this(parent, name, false, index, isHostedVespa);
+ public ApplicationContainer(AbstractConfigProducer parent, String name, int index, boolean isHostedVespa, Optional<TlsSecrets> tlsSecrets) {
+ this(parent, name, false, index, isHostedVespa, tlsSecrets);
}
- public ApplicationContainer(AbstractConfigProducer parent, String name, boolean retired, int index, boolean isHostedVespa) {
+ public ApplicationContainer(AbstractConfigProducer parent, String name, boolean retired, int index, boolean isHostedVespa, Optional<TlsSecrets> tlsSecrets) {
super(parent, name, retired, index);
this.isHostedVespa = isHostedVespa;
+
+ if (isHostedVespa && tlsSecrets.isPresent()) {
+ String connectorName = "tls4443";
+
+ JettyHttpServer server = Optional.ofNullable(getHttp())
+ .map(Http::getHttpServer)
+ .orElse(getDefaultHttpServer());
+ server.addConnector(new ConnectorFactory(connectorName, 4443,
+ new ConfiguredDirectSslProvider(server.getComponentId().getName(), tlsSecrets.get().key(), tlsSecrets.get().certificate(), null, null)));
+ }
}
@Override
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java
index 9cbaa5f91af..e9db64f8e4b 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java
@@ -4,6 +4,7 @@ package com.yahoo.vespa.model.container;
import com.yahoo.component.ComponentId;
import com.yahoo.config.FileReference;
import com.yahoo.config.application.api.ComponentInfo;
+import com.yahoo.config.model.api.TlsSecrets;
import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.config.model.producer.AbstractConfigProducer;
import com.yahoo.container.BundlesConfig;
@@ -22,6 +23,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -45,13 +47,18 @@ public final class ApplicationContainerCluster extends ContainerCluster<Applicat
private ContainerModelEvaluation modelEvaluation;
+ private Optional<TlsSecrets> tlsSecrets;
+
public ApplicationContainerCluster(AbstractConfigProducer<?> parent, String subId, String name, DeployState deployState) {
super(parent, subId, name, deployState);
+
+ this.tlsSecrets = deployState.tlsSecrets();
restApiGroup = new ConfigProducerGroup<>(this, "rest-api");
servletGroup = new ConfigProducerGroup<>(this, "servlet");
addSimpleComponent(DEFAULT_LINGUISTICS_PROVIDER);
addSimpleComponent("com.yahoo.container.jdisc.SecretStoreProvider");
+ addSimpleComponent("com.yahoo.container.jdisc.DeprecatedSecretStoreProvider");
addSimpleComponent("com.yahoo.container.jdisc.CertificateStoreProvider");
}
@@ -139,4 +146,8 @@ public final class ApplicationContainerCluster extends ContainerCluster<Applicat
if (modelEvaluation != null) modelEvaluation.getConfig(builder);
}
+ public Optional<TlsSecrets> getTlsSecrets() {
+ return tlsSecrets;
+ }
+
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/ConfiguredDirectSslProvider.java b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/ConfiguredDirectSslProvider.java
new file mode 100644
index 00000000000..28dba3331d3
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/ConfiguredDirectSslProvider.java
@@ -0,0 +1,66 @@
+// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.model.container.http.ssl;
+
+import com.yahoo.component.ComponentId;
+import com.yahoo.container.bundle.BundleInstantiationSpecification;
+import com.yahoo.jdisc.http.ConnectorConfig;
+import com.yahoo.jdisc.http.ssl.impl.ConfiguredSslContextFactoryProvider;
+import com.yahoo.osgi.provider.model.ComponentModel;
+import com.yahoo.vespa.model.container.component.SimpleComponent;
+
+import java.util.Optional;
+
+import static com.yahoo.component.ComponentSpecification.fromString;
+
+/**
+ * Configure SSL with PEM encoded certificate/key strings
+ *
+ * @author mortent
+ * @author andreer
+ */
+public class ConfiguredDirectSslProvider extends SimpleComponent implements ConnectorConfig.Producer {
+ public static final String COMPONENT_ID_PREFIX = "configured-ssl-provider@";
+ public static final String COMPONENT_CLASS = ConfiguredSslContextFactoryProvider.class.getName();
+ public static final String COMPONENT_BUNDLE = "jdisc_http_service";
+
+ private final String privateKey;
+ private final String certificate;
+ private final String caCertificatePath;
+ private final ConnectorConfig.Ssl.ClientAuth.Enum clientAuthentication;
+
+ public ConfiguredDirectSslProvider(String servername, String privateKey, String certificate, String caCertificatePath, String clientAuthentication) {
+ super(new ComponentModel(
+ new BundleInstantiationSpecification(new ComponentId(COMPONENT_ID_PREFIX+servername),
+ fromString(COMPONENT_CLASS),
+ fromString(COMPONENT_BUNDLE))));
+ this.privateKey = privateKey;
+ this.certificate = certificate;
+ this.caCertificatePath = caCertificatePath;
+ this.clientAuthentication = mapToConfigEnum(clientAuthentication);
+ }
+
+ @Override
+ public void getConfig(ConnectorConfig.Builder builder) {
+ builder.ssl.enabled(true);
+ builder.ssl.privateKey(privateKey);
+ builder.ssl.certificate(certificate);
+ builder.ssl.caCertificateFile(Optional.ofNullable(caCertificatePath).orElse(""));
+ builder.ssl.clientAuth(clientAuthentication);
+ }
+
+ public SimpleComponent getComponent() {
+ return new SimpleComponent(new ComponentModel(getComponentId().stringValue(), COMPONENT_CLASS, COMPONENT_BUNDLE));
+ }
+
+ private static ConnectorConfig.Ssl.ClientAuth.Enum mapToConfigEnum(String clientAuthValue) {
+ if ("disabled".equals(clientAuthValue)) {
+ return ConnectorConfig.Ssl.ClientAuth.Enum.DISABLED;
+ } else if ("want".equals(clientAuthValue)) {
+ return ConnectorConfig.Ssl.ClientAuth.Enum.WANT_AUTH;
+ } else if ("need".equals(clientAuthValue)) {
+ return ConnectorConfig.Ssl.ClientAuth.Enum.NEED_AUTH;
+ } else {
+ return ConnectorConfig.Ssl.ClientAuth.Enum.DISABLED;
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/ConfiguredSslProvider.java b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/ConfiguredFilebasedSslProvider.java
index 3c36933c030..4f84a01ff94 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/ConfiguredSslProvider.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/ConfiguredFilebasedSslProvider.java
@@ -13,9 +13,11 @@ import java.util.Optional;
import static com.yahoo.component.ComponentSpecification.fromString;
/**
+ * Configure SSL using file references
+ *
* @author mortent
*/
-public class ConfiguredSslProvider extends SimpleComponent implements ConnectorConfig.Producer {
+public class ConfiguredFilebasedSslProvider extends SimpleComponent implements ConnectorConfig.Producer {
public static final String COMPONENT_ID_PREFIX = "configured-ssl-provider@";
public static final String COMPONENT_CLASS = ConfiguredSslContextFactoryProvider.class.getName();
public static final String COMPONENT_BUNDLE = "jdisc_http_service";
@@ -25,7 +27,7 @@ public class ConfiguredSslProvider extends SimpleComponent implements ConnectorC
private final String caCertificatePath;
private final ConnectorConfig.Ssl.ClientAuth.Enum clientAuthentication;
- public ConfiguredSslProvider(String servername, String privateKeyPath, String certificatePath, String caCertificatePath, String clientAuthentication) {
+ public ConfiguredFilebasedSslProvider(String servername, String privateKeyPath, String certificatePath, String caCertificatePath, String clientAuthentication) {
super(new ComponentModel(
new BundleInstantiationSpecification(new ComponentId(COMPONENT_ID_PREFIX+servername),
fromString(COMPONENT_CLASS),
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/JettyConnectorBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/JettyConnectorBuilder.java
index 23865eb9bdd..1b457b1250a 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/JettyConnectorBuilder.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/JettyConnectorBuilder.java
@@ -9,7 +9,7 @@ import com.yahoo.vespa.model.builder.xml.dom.VespaDomBuilder;
import com.yahoo.vespa.model.container.component.SimpleComponent;
import com.yahoo.vespa.model.container.http.ConnectorFactory;
import com.yahoo.vespa.model.container.http.ssl.CustomSslProvider;
-import com.yahoo.vespa.model.container.http.ssl.ConfiguredSslProvider;
+import com.yahoo.vespa.model.container.http.ssl.ConfiguredFilebasedSslProvider;
import com.yahoo.vespa.model.container.http.ssl.DefaultSslProvider;
import org.w3c.dom.Element;
@@ -39,7 +39,7 @@ public class JettyConnectorBuilder extends VespaDomBuilder.DomConfigProducerBuil
String certificateFile = XML.getValue(XML.getChild(sslConfigurator, "certificate-file"));
Optional<String> caCertificateFile = XmlHelper.getOptionalChildValue(sslConfigurator, "ca-certificates-file");
Optional<String> clientAuthentication = XmlHelper.getOptionalChildValue(sslConfigurator, "client-authentication");
- return new ConfiguredSslProvider(
+ return new ConfiguredFilebasedSslProvider(
serverName,
privateKeyFile,
certificateFile,
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java
index f68ddecad9d..57e0b969929 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java
@@ -431,7 +431,7 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
}
private void addStandaloneNode(ApplicationContainerCluster cluster) {
- ApplicationContainer container = new ApplicationContainer(cluster, "standalone", cluster.getContainers().size(), cluster.isHostedVespa());
+ ApplicationContainer container = new ApplicationContainer(cluster, "standalone", cluster.getContainers().size(), cluster.isHostedVespa(), cluster.getTlsSecrets());
cluster.addContainers(Collections.singleton(container));
}
@@ -497,7 +497,7 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
Element nodesElement = XML.getChild(containerElement, "nodes");
Element rotationsElement = XML.getChild(containerElement, "rotations");
if (nodesElement == null) { // default single node on localhost
- ApplicationContainer node = new ApplicationContainer(cluster, "container.0", 0, cluster.isHostedVespa());
+ ApplicationContainer node = new ApplicationContainer(cluster, "container.0", 0, cluster.isHostedVespa(), cluster.getTlsSecrets());
HostResource host = allocateSingleNodeHost(cluster, log, containerElement, context);
node.setHostResource(host);
node.initService(context.getDeployLogger());
@@ -686,7 +686,7 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
List<ApplicationContainer> nodes = new ArrayList<>();
for (Map.Entry<HostResource, ClusterMembership> entry : hosts.entrySet()) {
String id = "container." + entry.getValue().index();
- ApplicationContainer container = new ApplicationContainer(cluster, id, entry.getValue().retired(), entry.getValue().index(), cluster.isHostedVespa());
+ ApplicationContainer container = new ApplicationContainer(cluster, id, entry.getValue().retired(), entry.getValue().index(), cluster.isHostedVespa(), cluster.getTlsSecrets());
container.setHostResource(entry.getKey());
container.initService(deployLogger);
nodes.add(container);
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerServiceBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerServiceBuilder.java
index fd0797d6098..46271d3c0a2 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerServiceBuilder.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerServiceBuilder.java
@@ -22,7 +22,7 @@ public class ContainerServiceBuilder extends VespaDomBuilder.DomConfigProducerBu
@Override
protected ApplicationContainer doBuild(DeployState deployState, AbstractConfigProducer parent, Element nodeElem) {
- return new ApplicationContainer(parent, id, index, deployState.isHosted());
+ return new ApplicationContainer(parent, id, index, deployState.isHosted(), deployState.tlsSecrets());
}
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/Content.java b/config-model/src/main/java/com/yahoo/vespa/model/content/Content.java
index 74caf2d8026..8eda707be99 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/content/Content.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/content/Content.java
@@ -324,7 +324,7 @@ public class Content extends ConfigModel {
if (!processedHosts.contains(host)) {
String containerName = String.valueOf(searchNode.getDistributionKey());
ApplicationContainer docprocService = new ApplicationContainer(indexingCluster, containerName, index,
- modelContext.getDeployState().isHosted());
+ modelContext.getDeployState().isHosted(), modelContext.getDeployState().tlsSecrets());
index++;
docprocService.useDynamicPorts();
docprocService.setHostResource(host);
diff --git a/config-model/src/main/javacc/SDParser.jj b/config-model/src/main/javacc/SDParser.jj
index 571ad452b01..6dde12f0fac 100644
--- a/config-model/src/main/javacc/SDParser.jj
+++ b/config-model/src/main/javacc/SDParser.jj
@@ -331,7 +331,7 @@ TOKEN :
| < LOWERBOUND: "lower-bound" >
| < UPPERBOUND: "upper-bound" >
| < DENSEPOSTINGLISTTHRESHOLD: "dense-posting-list-threshold" >
-| < EXPERIMENTALPOSTINGLISTFORMAT: "experimental-posting-list-format" >
+| < ENABLE_BM25: "enable-bm25" >
| < SUMMARYFEATURES_SL: "summary-features" (" ")* ":" (~["}","\n"])* ("\n")? >
| < SUMMARYFEATURES_ML: "summary-features" (<SEARCHLIB_SKIP>)? "{" (~["}"])* "}" >
| < RANKFEATURES_SL: "rank-features" (" ")* ":" (~["}","\n"])* ("\n")? >
@@ -1782,7 +1782,7 @@ Object indexBody(IndexOperation index) :
| <LOWERBOUND> <COLON> num = consumeLong() { index.setLowerBound(num); }
| <UPPERBOUND> <COLON> num = consumeLong() { index.setUpperBound(num); }
| <DENSEPOSTINGLISTTHRESHOLD> <COLON> threshold = consumeFloat() { index.setDensePostingListThreshold(threshold); }
- | <EXPERIMENTALPOSTINGLISTFORMAT> { index.setExperimentalPostingListFormat(true); }
+ | <ENABLE_BM25> { index.setEnableBm25(true); }
)
{ return null; }
}
diff --git a/config-model/src/test/derived/indexschema/index-info.cfg b/config-model/src/test/derived/indexschema/index-info.cfg
index 46c2c3fc307..a83ec45c5e9 100644
--- a/config-model/src/test/derived/indexschema/index-info.cfg
+++ b/config-model/src/test/derived/indexschema/index-info.cfg
@@ -133,15 +133,15 @@ indexinfo[].command[].indexname "exact2"
indexinfo[].command[].command "lowercase"
indexinfo[].command[].indexname "exact2"
indexinfo[].command[].command "exact @@"
-indexinfo[].command[].indexname "experimental"
+indexinfo[].command[].indexname "bm25_field"
indexinfo[].command[].command "index"
-indexinfo[].command[].indexname "experimental"
+indexinfo[].command[].indexname "bm25_field"
indexinfo[].command[].command "lowercase"
-indexinfo[].command[].indexname "experimental"
+indexinfo[].command[].indexname "bm25_field"
indexinfo[].command[].command "stem:BEST"
-indexinfo[].command[].indexname "experimental"
+indexinfo[].command[].indexname "bm25_field"
indexinfo[].command[].command "normalize"
-indexinfo[].command[].indexname "experimental"
+indexinfo[].command[].indexname "bm25_field"
indexinfo[].command[].command "plain-tokens"
indexinfo[].command[].indexname "ia"
indexinfo[].command[].command "index"
diff --git a/config-model/src/test/derived/indexschema/indexschema.cfg b/config-model/src/test/derived/indexschema/indexschema.cfg
index 998e53136a4..e8d064723da 100644
--- a/config-model/src/test/derived/indexschema/indexschema.cfg
+++ b/config-model/src/test/derived/indexschema/indexschema.cfg
@@ -78,7 +78,7 @@ indexfield[].phrases false
indexfield[].positions true
indexfield[].averageelementlen 512
indexfield[].interleavedfeatures false
-indexfield[].name "experimental"
+indexfield[].name "bm25_field"
indexfield[].datatype STRING
indexfield[].collectiontype SINGLE
indexfield[].prefix false
diff --git a/config-model/src/test/derived/indexschema/indexschema.sd b/config-model/src/test/derived/indexschema/indexschema.sd
index 44956f30e9e..49f0f7dfca6 100644
--- a/config-model/src/test/derived/indexschema/indexschema.sd
+++ b/config-model/src/test/derived/indexschema/indexschema.sd
@@ -56,9 +56,9 @@ search indexschema {
exact
}
}
- field experimental type string {
+ field bm25_field type string {
indexing: index
- index: experimental-posting-list-format
+ index: enable-bm25
}
# integer fields
diff --git a/config-model/src/test/derived/indexschema/vsmfields.cfg b/config-model/src/test/derived/indexschema/vsmfields.cfg
index 30ed67f61b7..9dcffd30313 100644
--- a/config-model/src/test/derived/indexschema/vsmfields.cfg
+++ b/config-model/src/test/derived/indexschema/vsmfields.cfg
@@ -55,7 +55,7 @@ fieldspec[].searchmethod AUTOUTF8
fieldspec[].arg1 "exact"
fieldspec[].maxlength 1048576
fieldspec[].fieldtype INDEX
-fieldspec[].name "experimental"
+fieldspec[].name "bm25_field"
fieldspec[].searchmethod AUTOUTF8
fieldspec[].arg1 ""
fieldspec[].maxlength 1048576
@@ -138,8 +138,8 @@ documenttype[].index[].name "exact1"
documenttype[].index[].field[].name "exact1"
documenttype[].index[].name "exact2"
documenttype[].index[].field[].name "exact2"
-documenttype[].index[].name "experimental"
-documenttype[].index[].field[].name "experimental"
+documenttype[].index[].name "bm25_field"
+documenttype[].index[].field[].name "bm25_field"
documenttype[].index[].name "ia"
documenttype[].index[].field[].name "ia"
documenttype[].index[].name "ib"
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/ContainerClusterTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/ContainerClusterTest.java
index ba7fbef439c..ac85a958ed5 100755
--- a/config-model/src/test/java/com/yahoo/vespa/model/container/ContainerClusterTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/container/ContainerClusterTest.java
@@ -5,6 +5,7 @@ import com.yahoo.cloud.config.ClusterInfoConfig;
import com.yahoo.cloud.config.ConfigserverConfig;
import com.yahoo.cloud.config.RoutingProviderConfig;
import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.config.model.api.TlsSecrets;
import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.config.model.deploy.TestProperties;
import com.yahoo.config.model.test.MockRoot;
@@ -13,6 +14,7 @@ import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.Zone;
import com.yahoo.container.handler.ThreadpoolConfig;
+import com.yahoo.jdisc.http.ConnectorConfig;
import com.yahoo.search.config.QrStartConfig;
import com.yahoo.vespa.model.Host;
import com.yahoo.vespa.model.HostResource;
@@ -20,15 +22,22 @@ import com.yahoo.vespa.model.admin.clustercontroller.ClusterControllerContainer;
import com.yahoo.vespa.model.admin.clustercontroller.ClusterControllerContainerCluster;
import com.yahoo.vespa.model.container.component.Component;
import com.yahoo.vespa.model.container.docproc.ContainerDocproc;
+import com.yahoo.vespa.model.container.http.ConnectorFactory;
import com.yahoo.vespa.model.container.search.ContainerSearch;
import com.yahoo.vespa.model.container.search.searchchain.SearchChains;
+import org.hamcrest.Matchers;
import org.junit.Test;
import java.util.Collection;
import java.util.Iterator;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
/**
* @author Simon Thoresen Hult
@@ -210,9 +219,40 @@ public class ContainerClusterTest {
assertEquals(0, cluster.getAllComponents().stream().map(c -> c.getClassId().getName()).filter(c -> c.equals("com.yahoo.jdisc.http.filter.security.RoutingConfigProvider")).count());
}
+ @Test
+ public void requireThatProvidingTlsSecretOpensPort4443() {
+ DeployState state = new DeployState.Builder().properties(new TestProperties().setHostedVespa(true).setTlsSecrets(Optional.of(new TlsSecrets("CERT", "KEY")))).build();
+ MockRoot root = new MockRoot("foo", state);
+ ApplicationContainerCluster cluster = new ApplicationContainerCluster(root, "container0", "container1", state);
+
+ addContainer(state.getDeployLogger(), cluster, "c1", "host-c1");
+ Optional<ApplicationContainer> container = cluster.getContainers().stream().findFirst();
+ assertTrue(container.isPresent());
+
+ var httpServer = (container.get().getHttp() == null) ? container.get().getDefaultHttpServer() : container.get().getHttp().getHttpServer();
+
+ // Verify that there are two connectors
+ List<ConnectorFactory> connectorFactories = httpServer.getConnectorFactories();
+ assertEquals(2, connectorFactories.size());
+ List<Integer> ports = connectorFactories.stream()
+ .map(ConnectorFactory::getListenPort)
+ .collect(Collectors.toList());
+ assertThat(ports, Matchers.containsInAnyOrder(8080, 4443));
+
+ ConnectorFactory tlsPort = connectorFactories.stream().filter(connectorFactory -> connectorFactory.getListenPort() == 4443).findFirst().orElseThrow();
+
+ ConnectorConfig.Builder builder = new ConnectorConfig.Builder();
+ tlsPort.getConfig(builder);
+
+ ConnectorConfig connectorConfig = new ConnectorConfig(builder);
+ assertTrue(connectorConfig.ssl().enabled());
+ assertEquals("CERT", connectorConfig.ssl().certificate());
+ assertEquals("KEY", connectorConfig.ssl().privateKey());
+ assertEquals(4443, connectorConfig.listenPort());
+ }
private static void addContainer(DeployLogger deployLogger, ApplicationContainerCluster cluster, String name, String hostName) {
- ApplicationContainer container = new ApplicationContainer(cluster, name, 0, cluster.isHostedVespa());
+ ApplicationContainer container = new ApplicationContainer(cluster, name, 0, cluster.isHostedVespa(), cluster.getTlsSecrets());
container.setHostResource(new HostResource(new Host(null, hostName)));
container.initService(deployLogger);
cluster.addContainer(container);
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/JettyContainerModelBuilderTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/JettyContainerModelBuilderTest.java
index 03e115f0608..880cccf02e4 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/JettyContainerModelBuilderTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/JettyContainerModelBuilderTest.java
@@ -1,16 +1,19 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.model.container.xml;
+import com.yahoo.config.model.api.TlsSecrets;
import com.yahoo.config.model.builder.xml.test.DomBuilderTest;
+import com.yahoo.config.model.deploy.DeployState;
+import com.yahoo.config.model.deploy.TestProperties;
import com.yahoo.container.ComponentsConfig;
import com.yahoo.container.jdisc.FilterBindingsProvider;
import com.yahoo.jdisc.http.ConnectorConfig;
-import com.yahoo.vespa.model.container.ContainerCluster;
import com.yahoo.vespa.model.container.ApplicationContainerCluster;
+import com.yahoo.vespa.model.container.ContainerCluster;
import com.yahoo.vespa.model.container.component.SimpleComponent;
import com.yahoo.vespa.model.container.http.ConnectorFactory;
import com.yahoo.vespa.model.container.http.JettyHttpServer;
-import com.yahoo.vespa.model.container.http.ssl.ConfiguredSslProvider;
+import com.yahoo.vespa.model.container.http.ssl.ConfiguredFilebasedSslProvider;
import org.junit.Test;
import org.w3c.dom.Element;
@@ -21,6 +24,7 @@ import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.nullValue;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
@@ -174,7 +178,7 @@ public class JettyContainerModelBuilderTest extends ContainerModelBuilderTestBas
ContainerCluster cluster = (ContainerCluster) root.getChildren().get("default");
List<ConnectorFactory> connectorFactories = cluster.getChildrenByTypeRecursive(ConnectorFactory.class);
- connectorFactories.forEach(connectorFactory -> assertChildComponentExists(connectorFactory, ConfiguredSslProvider.COMPONENT_CLASS));
+ connectorFactories.forEach(connectorFactory -> assertChildComponentExists(connectorFactory, ConfiguredFilebasedSslProvider.COMPONENT_CLASS));
}
@Test
@@ -222,6 +226,37 @@ public class JettyContainerModelBuilderTest extends ContainerModelBuilderTestBas
assertTrue(sslProvider.ssl().enabled());
}
+ @Test
+ public void verify_that_container_setup_additional_tls4443(){
+ Element clusterElem = DomBuilderTest.parse(
+ "<jdisc id='default' version='1.0' jetty='true'>",
+ " <http>",
+ " <server port='9000' id='ssl'>",
+ " <ssl>",
+ " <private-key-file>/foo/key</private-key-file>",
+ " <certificate-file>/foo/cert</certificate-file>",
+ " </ssl>",
+ " </server>",
+ " </http>",
+ nodesXml,
+ "",
+ "</jdisc>");
+
+ DeployState deployState = new DeployState.Builder().properties(new TestProperties().setHostedVespa(true).setTlsSecrets(Optional.of(new TlsSecrets("CERT", "KEY")))).build();
+ createModel(root, deployState, null, clusterElem);
+ ConnectorConfig sslProvider = root.getConfig(ConnectorConfig.class, "default/http/jdisc-jetty/ssl");
+ assertTrue(sslProvider.ssl().enabled());
+ assertEquals("", sslProvider.ssl().certificate());
+ assertEquals("", sslProvider.ssl().privateKey());
+
+ ConnectorConfig providedTls = root.getConfig(ConnectorConfig.class, "default/http/jdisc-jetty/tls4443");
+ assertTrue(providedTls.ssl().enabled());
+ assertEquals("CERT", providedTls.ssl().certificate());
+ assertEquals("KEY", providedTls.ssl().privateKey());
+ assertEquals(4443, providedTls.listenPort());
+
+ }
+
private static void assertChildComponentExists(ConnectorFactory connectorFactory, String className) {
Optional<SimpleComponent> simpleComponent = connectorFactory.getChildren().values().stream()
.map(z -> (SimpleComponent) z)
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/zone/ZoneList.java b/config-provisioning/src/main/java/com/yahoo/config/provision/zone/ZoneList.java
index 5f3f2e10898..776f925c424 100644
--- a/config-provisioning/src/main/java/com/yahoo/config/provision/zone/ZoneList.java
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/zone/ZoneList.java
@@ -1,10 +1,13 @@
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.config.provision.zone;
+import com.google.common.collect.ImmutableList;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.Zone;
import java.util.List;
+import java.util.stream.Collectors;
/**
* Provides filters for and access to a list of ZoneIds.
@@ -32,7 +35,9 @@ public interface ZoneList extends ZoneFilter {
/** Returns the ZoneApi of all zones in this list. */
List<? extends ZoneApi> zones();
- /** Returns the id of all zones in this list as — you guessed it — a list. */
- List<ZoneId> ids();
+ /** Returns the ZoneIds of all zones in this list. */
+ default List<ZoneId> ids() {
+ return zones().stream().map(ZoneApi::getId).collect(Collectors.toList());
+ }
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/GlobalComponentRegistry.java b/configserver/src/main/java/com/yahoo/vespa/config/server/GlobalComponentRegistry.java
index d420c3f21fe..1eb18773898 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/GlobalComponentRegistry.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/GlobalComponentRegistry.java
@@ -7,6 +7,7 @@ import com.yahoo.config.model.api.ConfigDefinitionRepo;
import com.yahoo.config.provision.Provisioner;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.Zone;
+import com.yahoo.container.jdisc.secretstore.SecretStore;
import com.yahoo.vespa.config.server.application.PermanentApplicationPackage;
import com.yahoo.vespa.config.server.host.HostRegistries;
import com.yahoo.vespa.config.server.modelfactory.ModelFactoryRegistry;
@@ -46,4 +47,5 @@ public interface GlobalComponentRegistry {
StripedExecutor<TenantName> getZkWatcherExecutor();
FlagSource getFlagSource();
ExecutorService getZkCacheExecutor();
+ SecretStore getSecretStore();
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/InjectedGlobalComponentRegistry.java b/configserver/src/main/java/com/yahoo/vespa/config/server/InjectedGlobalComponentRegistry.java
index ff76afd1c98..9badd19009f 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/InjectedGlobalComponentRegistry.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/InjectedGlobalComponentRegistry.java
@@ -9,6 +9,7 @@ import com.yahoo.config.model.api.ConfigDefinitionRepo;
import com.yahoo.config.provision.Provisioner;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.Zone;
+import com.yahoo.container.jdisc.secretstore.SecretStore;
import com.yahoo.vespa.config.server.application.PermanentApplicationPackage;
import com.yahoo.vespa.config.server.host.HostRegistries;
import com.yahoo.vespa.config.server.modelfactory.ModelFactoryRegistry;
@@ -48,6 +49,7 @@ public class InjectedGlobalComponentRegistry implements GlobalComponentRegistry
private final Zone zone;
private final ConfigServerDB configServerDB;
private final FlagSource flagSource;
+ private final SecretStore secretStore;
private final StripedExecutor<TenantName> zkWatcherExecutor;
private final ExecutorService zkCacheExecutor;
@@ -67,7 +69,8 @@ public class InjectedGlobalComponentRegistry implements GlobalComponentRegistry
HostProvisionerProvider hostProvisionerProvider,
Zone zone,
ConfigServerDB configServerDB,
- FlagSource flagSource) {
+ FlagSource flagSource,
+ SecretStore secretStore) {
this.curator = curator;
this.configCurator = configCurator;
this.metrics = metrics;
@@ -82,6 +85,7 @@ public class InjectedGlobalComponentRegistry implements GlobalComponentRegistry
this.zone = zone;
this.configServerDB = configServerDB;
this.flagSource = flagSource;
+ this.secretStore = secretStore;
this.zkWatcherExecutor = new StripedExecutor<>();
this.zkCacheExecutor = Executors.newFixedThreadPool(1, ThreadFactoryFactory.getThreadFactory(TenantRepository.class.getName()));
}
@@ -137,4 +141,9 @@ public class InjectedGlobalComponentRegistry implements GlobalComponentRegistry
public ExecutorService getZkCacheExecutor() {
return zkCacheExecutor;
}
+
+ @Override
+ public SecretStore getSecretStore() {
+ return secretStore;
+ }
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java
index 4627d350eb2..d875385d14d 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java
@@ -11,6 +11,7 @@ import com.yahoo.config.model.api.ContainerEndpoint;
import com.yahoo.config.model.api.HostProvisioner;
import com.yahoo.config.model.api.Model;
import com.yahoo.config.model.api.ModelContext;
+import com.yahoo.config.model.api.TlsSecrets;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.Rotation;
@@ -134,6 +135,7 @@ public class ModelContextImpl implements ModelContext {
private final boolean useFdispatchByDefault;
private final boolean useAdaptiveDispatch;
private final boolean dispatchWithProtobuf;
+ private final Optional<TlsSecrets> tlsSecrets;
public Properties(ApplicationId applicationId,
boolean multitenantFromConfig,
@@ -147,7 +149,8 @@ public class ModelContextImpl implements ModelContext {
Set<ContainerEndpoint> endpoints,
boolean isBootstrap,
boolean isFirstTimeDeployment,
- FlagSource flagSource) {
+ FlagSource flagSource,
+ Optional<TlsSecrets> tlsSecrets) {
this.applicationId = applicationId;
this.multitenant = multitenantFromConfig || hostedVespa || Boolean.getBoolean("multitenant");
this.configServerSpecs = configServerSpecs;
@@ -168,6 +171,7 @@ public class ModelContextImpl implements ModelContext {
.with(FetchVector.Dimension.APPLICATION_ID, applicationId.serializedForm()).value();
this.useAdaptiveDispatch = Flags.USE_ADAPTIVE_DISPATCH.bindTo(flagSource)
.with(FetchVector.Dimension.APPLICATION_ID, applicationId.serializedForm()).value();
+ this.tlsSecrets = tlsSecrets;
}
@Override
@@ -222,6 +226,8 @@ public class ModelContextImpl implements ModelContext {
@Override
public boolean useAdaptiveDispatch() { return useAdaptiveDispatch; }
+ @Override
+ public Optional<TlsSecrets> tlsSecrets() { return tlsSecrets; }
}
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ActivatedModelsBuilder.java b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ActivatedModelsBuilder.java
index 117a9e0cac5..94cd30de28b 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ActivatedModelsBuilder.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ActivatedModelsBuilder.java
@@ -13,6 +13,7 @@ import com.yahoo.config.provision.AllocatedHosts;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.TenantName;
+import com.yahoo.container.jdisc.secretstore.SecretStore;
import com.yahoo.log.LogLevel;
import com.yahoo.vespa.config.server.ConfigServerSpec;
import com.yahoo.vespa.config.server.GlobalComponentRegistry;
@@ -28,6 +29,7 @@ import com.yahoo.vespa.config.server.session.SilentDeployLogger;
import com.yahoo.vespa.config.server.tenant.ContainerEndpointsCache;
import com.yahoo.vespa.config.server.tenant.Rotations;
import com.yahoo.vespa.config.server.tenant.TenantRepository;
+import com.yahoo.vespa.config.server.tenant.TlsSecretsKeys;
import com.yahoo.vespa.curator.Curator;
import com.yahoo.vespa.flags.FlagSource;
@@ -55,6 +57,7 @@ public class ActivatedModelsBuilder extends ModelsBuilder<Application> {
private final Curator curator;
private final DeployLogger logger;
private final FlagSource flagSource;
+ private final SecretStore secretStore;
public ActivatedModelsBuilder(TenantName tenant,
long appGeneration,
@@ -73,6 +76,7 @@ public class ActivatedModelsBuilder extends ModelsBuilder<Application> {
this.curator = globalComponentRegistry.getCurator();
this.logger = new SilentDeployLogger();
this.flagSource = globalComponentRegistry.getFlagSource();
+ this.secretStore = globalComponentRegistry.getSecretStore();
}
@Override
@@ -132,7 +136,8 @@ public class ActivatedModelsBuilder extends ModelsBuilder<Application> {
ImmutableSet.copyOf(new ContainerEndpointsCache(TenantRepository.getTenantPath(tenant), curator).read(applicationId)),
false, // We may be bootstrapping, but we only know and care during prepare
false, // Always false, assume no one uses it when activating
- flagSource);
+ flagSource,
+ new TlsSecretsKeys(curator, TenantRepository.getTenantPath(tenant), secretStore).readTlsSecretsKeyFromZookeeper(applicationId));
}
}
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 00a7625ee87..5bf70c55f9e 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
@@ -35,6 +35,7 @@ public final class PrepareParams {
static final String VESPA_VERSION_PARAM_NAME = "vespaVersion";
static final String ROTATIONS_PARAM_NAME = "rotations";
static final String CONTAINER_ENDPOINTS_PARAM_NAME = "containerEndpoints";
+ static final String TLS_SECRETS_KEY_NAME_PARAM_NAME = "tlsSecretsKeyName";
private final ApplicationId applicationId;
private final TimeoutBudget timeoutBudget;
@@ -45,10 +46,11 @@ public final class PrepareParams {
private final Optional<Version> vespaVersion;
private final Set<Rotation> rotations;
private final List<ContainerEndpoint> containerEndpoints;
+ private final Optional<String> tlsSecretsKeyName;
private PrepareParams(ApplicationId applicationId, TimeoutBudget timeoutBudget, boolean ignoreValidationErrors,
- boolean dryRun, boolean verbose, boolean isBootstrap, Optional<Version> vespaVersion,
- Set<Rotation> rotations, List<ContainerEndpoint> containerEndpoints) {
+ boolean dryRun, boolean verbose, boolean isBootstrap, Optional<Version> vespaVersion, Set<Rotation> rotations,
+ List<ContainerEndpoint> containerEndpoints, Optional<String> tlsSecretsKeyName) {
this.timeoutBudget = timeoutBudget;
this.applicationId = applicationId;
this.ignoreValidationErrors = ignoreValidationErrors;
@@ -61,6 +63,7 @@ public final class PrepareParams {
if ((rotations != null && !rotations.isEmpty()) && !containerEndpoints.isEmpty()) {
throw new IllegalArgumentException("Cannot set both rotations and containerEndpoints");
}
+ this.tlsSecretsKeyName = tlsSecretsKeyName;
}
public static class Builder {
@@ -74,6 +77,7 @@ public final class PrepareParams {
private Optional<Version> vespaVersion = Optional.empty();
private Set<Rotation> rotations;
private List<ContainerEndpoint> containerEndpoints = List.of();
+ private Optional<String> tlsSecretsKeyName = Optional.empty();
public Builder() { }
@@ -136,12 +140,18 @@ public final class PrepareParams {
if (serialized == null) return this;
Slime slime = SlimeUtils.jsonToSlime(serialized);
containerEndpoints = ContainerEndpointSerializer.endpointListFromSlime(slime);
+ return this;
+ }
+
+ public Builder tlsSecretsKeyName(String tlsSecretsKeyName) {
+ this.tlsSecretsKeyName = Optional.ofNullable(tlsSecretsKeyName)
+ .filter(s -> ! s.isEmpty());
return this;
}
public PrepareParams build() {
return new PrepareParams(applicationId, timeoutBudget, ignoreValidationErrors, dryRun,
- verbose, isBootstrap, vespaVersion, rotations, containerEndpoints);
+ verbose, isBootstrap, vespaVersion, rotations, containerEndpoints, tlsSecretsKeyName);
}
}
@@ -155,6 +165,7 @@ public final class PrepareParams {
.vespaVersion(request.getProperty(VESPA_VERSION_PARAM_NAME))
.rotations(request.getProperty(ROTATIONS_PARAM_NAME))
.containerEndpoints(request.getProperty(CONTAINER_ENDPOINTS_PARAM_NAME))
+ .tlsSecretsKeyName(request.getProperty(TLS_SECRETS_KEY_NAME_PARAM_NAME))
.build();
}
@@ -212,4 +223,7 @@ public final class PrepareParams {
return timeoutBudget;
}
+ public Optional<String> tlsSecretsKeyName() {
+ return tlsSecretsKeyName;
+ }
}
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 30ba9989343..54c96c0461d 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
@@ -13,11 +13,13 @@ import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.application.api.FileRegistry;
import com.yahoo.config.model.api.ConfigDefinitionRepo;
import com.yahoo.config.model.api.ModelContext;
+import com.yahoo.config.model.api.TlsSecrets;
import com.yahoo.config.provision.AllocatedHosts;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.Rotation;
import com.yahoo.config.provision.Zone;
+import com.yahoo.container.jdisc.secretstore.SecretStore;
import com.yahoo.lang.SettableOptional;
import com.yahoo.log.LogLevel;
import com.yahoo.path.Path;
@@ -34,6 +36,7 @@ import com.yahoo.vespa.config.server.provision.HostProvisionerProvider;
import com.yahoo.config.model.api.ContainerEndpoint;
import com.yahoo.vespa.config.server.tenant.ContainerEndpointsCache;
import com.yahoo.vespa.config.server.tenant.Rotations;
+import com.yahoo.vespa.config.server.tenant.TlsSecretsKeys;
import com.yahoo.vespa.curator.Curator;
import com.yahoo.vespa.flags.FlagSource;
import org.xml.sax.SAXException;
@@ -69,6 +72,7 @@ public class SessionPreparer {
private final Curator curator;
private final Zone zone;
private final FlagSource flagSource;
+ private final SecretStore secretStore;
@Inject
public SessionPreparer(ModelFactoryRegistry modelFactoryRegistry,
@@ -79,7 +83,8 @@ public class SessionPreparer {
ConfigDefinitionRepo configDefinitionRepo,
Curator curator,
Zone zone,
- FlagSource flagSource) {
+ FlagSource flagSource,
+ SecretStore secretStore) {
this.modelFactoryRegistry = modelFactoryRegistry;
this.fileDistributionFactory = fileDistributionFactory;
this.hostProvisionerProvider = hostProvisionerProvider;
@@ -89,6 +94,7 @@ public class SessionPreparer {
this.curator = curator;
this.zone = zone;
this.flagSource = flagSource;
+ this.secretStore = secretStore;
}
/**
@@ -112,6 +118,7 @@ public class SessionPreparer {
if ( ! params.isDryRun()) {
preparation.writeStateZK();
preparation.writeRotZK();
+ preparation.writeTlsZK();
var globalServiceId = context.getApplicationPackage().getDeployment()
.map(DeploymentSpec::fromXml)
.flatMap(DeploymentSpec::globalServiceId);
@@ -145,6 +152,8 @@ public class SessionPreparer {
final Set<Rotation> rotationsSet;
final Set<ContainerEndpoint> endpointsSet;
final ModelContext.Properties properties;
+ private final TlsSecretsKeys tlsSecretsKeys;
+ private final Optional<TlsSecrets> tlsSecrets;
private ApplicationPackage applicationPackage;
private List<PreparedModelsBuilder.PreparedModelResult> modelResultList;
@@ -165,7 +174,10 @@ public class SessionPreparer {
this.rotations = new Rotations(curator, tenantPath);
this.containerEndpoints = new ContainerEndpointsCache(tenantPath, curator);
this.rotationsSet = getRotations(params.rotations());
+ this.tlsSecretsKeys = new TlsSecretsKeys(curator, tenantPath, secretStore);
+ this.tlsSecrets = tlsSecretsKeys.getTlsSecrets(params.tlsSecretsKeyName(), applicationId);
this.endpointsSet = getEndpoints(params.containerEndpoints());
+
this.properties = new ModelContextImpl.Properties(params.getApplicationId(),
configserverConfig.multitenant(),
ConfigServerSpec.fromConfig(configserverConfig),
@@ -178,7 +190,8 @@ public class SessionPreparer {
endpointsSet,
params.isBootstrap(),
! currentActiveApplicationSet.isPresent(),
- context.getFlagSource());
+ context.getFlagSource(),
+ tlsSecrets);
this.preparedModelsBuilder = new PreparedModelsBuilder(modelFactoryRegistry,
permanentApplicationPackage,
configDefinitionRepo,
@@ -238,6 +251,11 @@ public class SessionPreparer {
checkTimeout("write rotations to zookeeper");
}
+ void writeTlsZK() {
+ tlsSecretsKeys.writeTlsSecretsKeyToZooKeeper(applicationId, params.tlsSecretsKeyName().orElse(null));
+ checkTimeout("write tlsSecretsKey to zookeeper");
+ }
+
void writeContainerEndpointsZK(Optional<String> globalServiceId) {
if (!params.containerEndpoints().isEmpty()) { // Use endpoints from parameter when explicitly given
containerEndpoints.write(applicationId, params.containerEndpoints());
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TlsSecretsKeys.java b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TlsSecretsKeys.java
new file mode 100644
index 00000000000..eaa4916d8fc
--- /dev/null
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TlsSecretsKeys.java
@@ -0,0 +1,86 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.tenant;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.yahoo.config.model.api.TlsSecrets;
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.container.jdisc.secretstore.SecretStore;
+import com.yahoo.path.Path;
+import com.yahoo.vespa.curator.Curator;
+import com.yahoo.vespa.curator.transaction.CuratorOperations;
+import com.yahoo.vespa.curator.transaction.CuratorTransaction;
+
+import java.util.Optional;
+
+/**
+ * TLS Secret keys for applications (used to retrieve actual certificate/key from secret store). Persisted in ZooKeeper.
+ *
+ * @author andreer
+ */
+public class TlsSecretsKeys {
+
+ private final Path path;
+ private final SecretStore secretStore;
+ private final Curator curator;
+
+ public TlsSecretsKeys(Curator curator, Path tenantPath, SecretStore secretStore) {
+ this.curator = curator;
+ this.path = tenantPath.append("tlsSecretsKeys/");
+ this.secretStore = secretStore;
+ }
+
+ public Optional<TlsSecrets> readTlsSecretsKeyFromZookeeper(ApplicationId application) {
+ try {
+ Optional<byte[]> data = curator.getData(tlsSecretsKeyOf(application));
+ if (data.isEmpty() || data.get().length == 0) return Optional.empty();
+ String tlsSecretsKey = new ObjectMapper().readValue(data.get(), new TypeReference<String>() {});
+ return readFromSecretStore(Optional.ofNullable(tlsSecretsKey));
+ } catch (Exception e) {
+ throw new RuntimeException("Error reading TLS secret key of " + application, e);
+ }
+ }
+
+ public void writeTlsSecretsKeyToZooKeeper(ApplicationId application, String tlsSecretsKey) {
+ if (tlsSecretsKey == null) return;
+ try {
+ byte[] data = new ObjectMapper().writeValueAsBytes(tlsSecretsKey);
+ curator.set(tlsSecretsKeyOf(application), data);
+ } catch (Exception e) {
+ throw new RuntimeException("Could not write TLS secret key of " + application, e);
+ }
+ }
+
+ public Optional<TlsSecrets> getTlsSecrets(Optional<String> secretKeyname, ApplicationId applicationId) {
+ if (secretKeyname == null || secretKeyname.isEmpty()) {
+ return readTlsSecretsKeyFromZookeeper(applicationId);
+ }
+ return readFromSecretStore(secretKeyname);
+ }
+
+ private Optional<TlsSecrets> readFromSecretStore(Optional<String> secretKeyname) {
+ if(secretKeyname.isEmpty()) return Optional.empty();
+ TlsSecrets tlsSecretParameters = TlsSecrets.MISSING;
+ try {
+ String cert = secretStore.getSecret(secretKeyname.get() + "-cert");
+ String key = secretStore.getSecret(secretKeyname.get() + "-key");
+ tlsSecretParameters = new TlsSecrets(cert, key);
+ } catch (RuntimeException e) {
+ // Assume not ready yet
+// log.log(LogLevel.DEBUG, "Could not fetch certificate/key with prefix: " + secretKeyname.get(), e);
+ }
+ return Optional.of(tlsSecretParameters);
+ }
+
+ /** Returns a transaction which deletes these tls secrets key if they exist */
+ public CuratorTransaction delete(ApplicationId application) {
+ if (!curator.exists(tlsSecretsKeyOf(application))) return CuratorTransaction.empty(curator);
+ return CuratorTransaction.from(CuratorOperations.delete(tlsSecretsKeyOf(application).getAbsolute()), curator);
+ }
+
+ /** Returns the path storing the tls secrets key for an application */
+ private Path tlsSecretsKeyOf(ApplicationId application) {
+ return path.append(application.serializedForm());
+ }
+
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/InjectedGlobalComponentRegistryTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/InjectedGlobalComponentRegistryTest.java
index 9b113cae715..e4ff8702ff1 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/InjectedGlobalComponentRegistryTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/InjectedGlobalComponentRegistryTest.java
@@ -78,7 +78,7 @@ public class InjectedGlobalComponentRegistryTest {
globalComponentRegistry =
new InjectedGlobalComponentRegistry(curator, configCurator, metrics, modelFactoryRegistry, sessionPreparer, rpcServer, configserverConfig,
generationCounter, defRepo, permanentApplicationPackage, hostRegistries, hostProvisionerProvider, zone,
- new ConfigServerDB(configserverConfig), new InMemoryFlagSource());
+ new ConfigServerDB(configserverConfig), new InMemoryFlagSource(), new MockSecretStore());
}
@Test
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/MockSecretStore.java b/configserver/src/test/java/com/yahoo/vespa/config/server/MockSecretStore.java
new file mode 100644
index 00000000000..8a77b53875e
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/MockSecretStore.java
@@ -0,0 +1,35 @@
+// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server;
+
+import com.yahoo.container.jdisc.secretstore.SecretStore;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class MockSecretStore implements SecretStore {
+ Map<String, String> secrets = new HashMap<>();
+
+ @Override
+ public String getSecret(String key) {
+ if(secrets.containsKey(key))
+ return secrets.get(key);
+ throw new RuntimeException("Key not found: " + key);
+ }
+
+ @Override
+ public String getSecret(String key, int version) {
+ return getSecret(key);
+ }
+
+ public void put(String key, String value) {
+ secrets.put(key, value);
+ }
+
+ public void remove(String key) {
+ secrets.remove(key);
+ }
+
+ public void clear() {
+ secrets.clear();
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/ModelContextImplTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/ModelContextImplTest.java
index b483705e3f5..860bbdc134c 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/ModelContextImplTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/ModelContextImplTest.java
@@ -62,7 +62,8 @@ public class ModelContextImplTest {
endpoints,
false,
false,
- flagSource),
+ flagSource,
+ null),
Optional.empty(),
new Version(6),
new Version(6));
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/TestComponentRegistry.java b/configserver/src/test/java/com/yahoo/vespa/config/server/TestComponentRegistry.java
index 62685734a47..a304f74858b 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/TestComponentRegistry.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/TestComponentRegistry.java
@@ -5,12 +5,12 @@ import com.google.common.io.Files;
import com.yahoo.cloud.config.ConfigserverConfig;
import com.yahoo.concurrent.InThreadExecutorService;
import com.yahoo.concurrent.StripedExecutor;
-import com.yahoo.concurrent.ThreadFactoryFactory;
import com.yahoo.config.model.NullConfigModelRegistry;
import com.yahoo.config.model.api.ConfigDefinitionRepo;
import com.yahoo.config.provision.Provisioner;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.Zone;
+import com.yahoo.container.jdisc.secretstore.SecretStore;
import com.yahoo.vespa.config.server.application.PermanentApplicationPackage;
import com.yahoo.vespa.config.server.host.HostRegistries;
import com.yahoo.vespa.config.server.modelfactory.ModelFactoryRegistry;
@@ -21,7 +21,6 @@ import com.yahoo.vespa.config.server.session.MockFileDistributionFactory;
import com.yahoo.vespa.config.server.session.SessionPreparer;
import com.yahoo.vespa.config.server.tenant.MockTenantListener;
import com.yahoo.vespa.config.server.tenant.TenantListener;
-import com.yahoo.vespa.config.server.tenant.TenantRepository;
import com.yahoo.vespa.config.server.tenant.TenantRequestHandlerTest;
import com.yahoo.vespa.config.server.zookeeper.ConfigCurator;
import com.yahoo.vespa.curator.Curator;
@@ -34,7 +33,6 @@ import java.time.Clock;
import java.util.Collections;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
/**
@@ -60,6 +58,7 @@ public class TestComponentRegistry implements GlobalComponentRegistry {
private final ConfigServerDB configServerDB;
private final StripedExecutor<TenantName> zkWatcherExecutor;
private final ExecutorService zkCacheExecutor;
+ private final SecretStore secretStore;
private TestComponentRegistry(Curator curator, ConfigCurator configCurator, Metrics metrics,
ModelFactoryRegistry modelFactoryRegistry,
@@ -73,7 +72,8 @@ public class TestComponentRegistry implements GlobalComponentRegistry {
ReloadListener reloadListener,
TenantListener tenantListener,
Zone zone,
- Clock clock) {
+ Clock clock,
+ SecretStore secretStore) {
this.curator = curator;
this.configCurator = configCurator;
this.metrics = metrics;
@@ -92,6 +92,7 @@ public class TestComponentRegistry implements GlobalComponentRegistry {
this.configServerDB = new ConfigServerDB(configserverConfig);
this.zkWatcherExecutor = new StripedExecutor<>(new InThreadExecutorService());
this.zkCacheExecutor = new InThreadExecutorService();
+ this.secretStore = secretStore;
}
public static class Builder {
@@ -161,14 +162,15 @@ public class TestComponentRegistry implements GlobalComponentRegistry {
.orElse(new MockFileDistributionFactory(configserverConfig));
HostProvisionerProvider hostProvisionerProvider = hostProvisioner.
map(HostProvisionerProvider::withProvisioner).orElseGet(HostProvisionerProvider::empty);
+ SecretStore secretStore = new MockSecretStore();
SessionPreparer sessionPreparer = new SessionPreparer(modelFactoryRegistry, fileDistributionFactory,
hostProvisionerProvider, permApp,
configserverConfig, defRepo, curator,
- zone, new InMemoryFlagSource());
+ zone, new InMemoryFlagSource(), secretStore);
return new TestComponentRegistry(curator, ConfigCurator.create(curator), metrics, modelFactoryRegistry,
permApp, fileDistributionFactory, hostRegistries, configserverConfig,
sessionPreparer, hostProvisioner, defRepo, reloadListener, tenantListener,
- zone, clock);
+ zone, clock, secretStore);
}
}
@@ -220,6 +222,11 @@ public class TestComponentRegistry implements GlobalComponentRegistry {
return zkCacheExecutor;
}
+ @Override
+ public SecretStore getSecretStore() {
+ return secretStore;
+ }
+
public FileDistributionFactory getFileDistributionFactory() { return fileDistributionFactory; }
}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionPreparerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionPreparerTest.java
index 74415993c52..88baf1b8d74 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionPreparerTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionPreparerTest.java
@@ -4,6 +4,7 @@ package com.yahoo.vespa.config.server.session;
import com.yahoo.component.Version;
import com.yahoo.config.application.api.DeployLogger;
import com.yahoo.config.model.api.ModelContext;
+import com.yahoo.config.model.api.TlsSecrets;
import com.yahoo.config.model.application.provider.BaseDeployLogger;
import com.yahoo.config.model.application.provider.FilesApplicationPackage;
import com.yahoo.config.provision.ApplicationId;
@@ -16,6 +17,7 @@ import com.yahoo.log.LogLevel;
import com.yahoo.path.Path;
import com.yahoo.slime.Slime;
import com.yahoo.vespa.config.server.MockReloadHandler;
+import com.yahoo.vespa.config.server.MockSecretStore;
import com.yahoo.vespa.config.server.TestComponentRegistry;
import com.yahoo.vespa.config.server.TimeoutBudgetTest;
import com.yahoo.vespa.config.server.application.PermanentApplicationPackage;
@@ -29,6 +31,7 @@ import com.yahoo.vespa.config.server.provision.HostProvisionerProvider;
import com.yahoo.config.model.api.ContainerEndpoint;
import com.yahoo.vespa.config.server.tenant.ContainerEndpointsCache;
import com.yahoo.vespa.config.server.tenant.Rotations;
+import com.yahoo.vespa.config.server.tenant.TlsSecretsKeys;
import com.yahoo.vespa.config.server.zookeeper.ConfigCurator;
import com.yahoo.vespa.curator.mock.MockCurator;
import com.yahoo.vespa.flags.InMemoryFlagSource;
@@ -71,7 +74,7 @@ public class SessionPreparerTest {
private SessionPreparer preparer;
private TestComponentRegistry componentRegistry;
private MockFileDistributionFactory fileDistributionFactory;
-
+ private MockSecretStore secretStore = new MockSecretStore();
@Rule
public TemporaryFolder folder = new TemporaryFolder();
@@ -106,7 +109,8 @@ public class SessionPreparerTest {
componentRegistry.getStaticConfigDefinitionRepo(),
curator,
componentRegistry.getZone(),
- flagSource);
+ flagSource,
+ secretStore);
}
@Test(expected = InvalidApplicationException.class)
@@ -256,6 +260,49 @@ public class SessionPreparerTest {
assertEquals(expected, readContainerEndpoints(applicationId));
}
+ @Test
+ public void require_that_tlssecretkey_is_written() throws IOException {
+ var tlskey = "vespa.tlskeys.tenant1--app1";
+ var applicationId = applicationId("test");
+ var params = new PrepareParams.Builder().applicationId(applicationId).tlsSecretsKeyName(tlskey).build();
+ secretStore.put(tlskey+"-cert", "CERT");
+ secretStore.put(tlskey+"-key", "KEY");
+ prepare(new File("src/test/resources/deploy/hosted-app"), params);
+
+ // Read from zk and verify cert and key are available
+ Optional<TlsSecrets> tlsSecrets = new TlsSecretsKeys(curator, tenantPath, secretStore).readTlsSecretsKeyFromZookeeper(applicationId);
+ assertTrue(tlsSecrets.isPresent());
+ assertEquals("KEY", tlsSecrets.get().key());
+ assertEquals("CERT", tlsSecrets.get().certificate());
+ }
+
+ @Test
+ public void require_that_tlssecretkey_is_missing_when_not_in_secretstore() throws IOException {
+ var tlskey = "vespa.tlskeys.tenant1--app1";
+ var applicationId = applicationId("test");
+ var params = new PrepareParams.Builder().applicationId(applicationId).tlsSecretsKeyName(tlskey).build();
+ prepare(new File("src/test/resources/deploy/hosted-app"), params);
+
+ // Read from zk and verify key/cert is missing
+ Optional<TlsSecrets> tlsSecrets = new TlsSecretsKeys(curator, tenantPath, secretStore).readTlsSecretsKeyFromZookeeper(applicationId);
+ assertTrue(tlsSecrets.isPresent());
+ assertTrue(tlsSecrets.get().isMissing());
+ }
+
+ @Test
+ public void require_that_tlssecretkey_is_missing_when_certificate_not_in_secretstore() throws IOException {
+ var tlskey = "vespa.tlskeys.tenant1--app1";
+ var applicationId = applicationId("test");
+ var params = new PrepareParams.Builder().applicationId(applicationId).tlsSecretsKeyName(tlskey).build();
+ secretStore.put(tlskey+"-key", "KEY");
+ prepare(new File("src/test/resources/deploy/hosted-app"), params);
+
+ // Read from zk and verify key/cert is missing
+ Optional<TlsSecrets> tlsSecrets = new TlsSecretsKeys(curator, tenantPath, secretStore).readTlsSecretsKeyFromZookeeper(applicationId);
+ assertTrue(tlsSecrets.isPresent());
+ assertTrue(tlsSecrets.get().isMissing());
+ }
+
private void prepare(File app) throws IOException {
prepare(app, new PrepareParams.Builder().build());
}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionTest.java
index 95f6c7718e2..b2ad0af8f9a 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionTest.java
@@ -21,7 +21,7 @@ public class SessionTest {
public boolean isPrepared = false;
public MockSessionPreparer() {
- super(null, null, null, null, null, null, new MockCurator(), null, null);
+ super(null, null, null, null, null, null, new MockCurator(), null, null, null);
}
@Override
diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/DeprecatedSecretStoreProvider.java b/container-disc/src/main/java/com/yahoo/container/jdisc/DeprecatedSecretStoreProvider.java
new file mode 100644
index 00000000000..0f47bfe2eb1
--- /dev/null
+++ b/container-disc/src/main/java/com/yahoo/container/jdisc/DeprecatedSecretStoreProvider.java
@@ -0,0 +1,34 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.jdisc;
+
+import com.yahoo.container.di.componentgraph.Provider;
+
+/**
+ * An secret store provider which provides a factory which throws exception on
+ * invocation - as no secret store is currently provided by default.
+ * The purpose of this is to provide a secret store for injection in the case where
+ * no secret store component is provided.
+ *
+ * @author bratseth
+ */
+@SuppressWarnings({"deprecation", "unused"})
+public class DeprecatedSecretStoreProvider implements Provider<com.yahoo.jdisc.http.SecretStore> {
+
+ private static final ThrowingSecretStore instance = new ThrowingSecretStore();
+
+ @Override
+ public com.yahoo.jdisc.http.SecretStore get() { return instance; }
+
+ @Override
+ public void deconstruct() { }
+
+ private static final class ThrowingSecretStore implements com.yahoo.jdisc.http.SecretStore {
+
+ @Override
+ public String getSecret(String key) {
+ throw new UnsupportedOperationException("A secret store is not available");
+ }
+
+ }
+
+}
diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/SecretStoreProvider.java b/container-disc/src/main/java/com/yahoo/container/jdisc/SecretStoreProvider.java
index d966e66f502..6012fbe394c 100644
--- a/container-disc/src/main/java/com/yahoo/container/jdisc/SecretStoreProvider.java
+++ b/container-disc/src/main/java/com/yahoo/container/jdisc/SecretStoreProvider.java
@@ -1,34 +1,29 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.container.jdisc;
+import com.yahoo.container.jdisc.secretstore.SecretStore;
import com.yahoo.container.di.componentgraph.Provider;
-/**
- * An secret store provider which provides a factory which throws exception on
- * invocation - as no secret store is currently provided by default.
- * The purpose of this is to provide a secret store for injection in the case where
- * no secret store component is provided.
- *
- * @author bratseth
- */
-@SuppressWarnings({"deprecation", "unused"})
-public class SecretStoreProvider implements Provider<com.yahoo.jdisc.http.SecretStore> {
+public class SecretStoreProvider implements Provider<SecretStore> {
private static final ThrowingSecretStore instance = new ThrowingSecretStore();
@Override
- public com.yahoo.jdisc.http.SecretStore get() { return instance; }
+ public SecretStore get() { return instance; }
@Override
public void deconstruct() { }
- private static final class ThrowingSecretStore implements com.yahoo.jdisc.http.SecretStore {
+ private static final class ThrowingSecretStore implements SecretStore {
@Override
public String getSecret(String key) {
throw new UnsupportedOperationException("A secret store is not available");
}
+ @Override
+ public String getSecret(String key, int version) {
+ throw new UnsupportedOperationException("A secret store is not available");
+ }
}
-
}
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 b4e3b8b1a9a..c168d09a15a 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
@@ -15,13 +15,12 @@ import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationV
import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId;
import com.yahoo.vespa.hosted.controller.api.integration.organization.User;
import com.yahoo.vespa.hosted.controller.application.ApplicationActivity;
+import com.yahoo.vespa.hosted.controller.application.AssignedRotation;
import com.yahoo.vespa.hosted.controller.application.Change;
import com.yahoo.vespa.hosted.controller.application.Deployment;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs;
-import com.yahoo.vespa.hosted.controller.application.Endpoint;
import com.yahoo.vespa.hosted.controller.application.EndpointList;
import com.yahoo.vespa.hosted.controller.application.RotationStatus;
-import com.yahoo.vespa.hosted.controller.rotation.Rotation;
import com.yahoo.vespa.hosted.controller.rotation.RotationId;
import java.time.Instant;
@@ -58,8 +57,7 @@ public class Application {
private final OptionalInt majorVersion;
private final ApplicationMetrics metrics;
private final Optional<String> pemDeployKey;
- private final Optional<RotationId> legacyRotation;
- private final List<RotationId> rotations;
+ private final List<AssignedRotation> rotations;
private final Map<HostName, RotationStatus> rotationStatus;
/** Creates an empty application */
@@ -68,7 +66,7 @@ public class Application {
new DeploymentJobs(OptionalLong.empty(), Collections.emptyList(), Optional.empty(), false),
Change.empty(), Change.empty(), Optional.empty(), Optional.empty(), OptionalInt.empty(),
new ApplicationMetrics(0, 0),
- Optional.empty(), Optional.empty(), Collections.emptyList(), Collections.emptyMap());
+ Optional.empty(), Collections.emptyList(), Collections.emptyMap());
}
/** Used from persistence layer: Do not use */
@@ -76,18 +74,18 @@ public class Application {
List<Deployment> deployments, DeploymentJobs deploymentJobs, Change change,
Change outstandingChange, Optional<IssueId> ownershipIssueId, Optional<User> owner,
OptionalInt majorVersion, ApplicationMetrics metrics, Optional<String> pemDeployKey,
- Optional<RotationId> legacyRotation, List<RotationId> rotations, Map<HostName, RotationStatus> rotationStatus) {
+ List<AssignedRotation> rotations, Map<HostName, RotationStatus> rotationStatus) {
this(id, createdAt, deploymentSpec, validationOverrides,
deployments.stream().collect(Collectors.toMap(Deployment::zone, Function.identity())),
deploymentJobs, change, outstandingChange, ownershipIssueId, owner, majorVersion,
- metrics, pemDeployKey, legacyRotation, rotations, rotationStatus);
+ metrics, pemDeployKey, rotations, rotationStatus);
}
Application(ApplicationId id, Instant createdAt, DeploymentSpec deploymentSpec, ValidationOverrides validationOverrides,
Map<ZoneId, Deployment> deployments, DeploymentJobs deploymentJobs, Change change,
Change outstandingChange, Optional<IssueId> ownershipIssueId, Optional<User> owner,
OptionalInt majorVersion, ApplicationMetrics metrics, Optional<String> pemDeployKey,
- Optional<RotationId> legacyRotation, List<RotationId> rotations, Map<HostName, RotationStatus> rotationStatus) {
+ List<AssignedRotation> rotations, Map<HostName, RotationStatus> rotationStatus) {
this.id = Objects.requireNonNull(id, "id cannot be null");
this.createdAt = Objects.requireNonNull(createdAt, "instant of creation cannot be null");
this.deploymentSpec = Objects.requireNonNull(deploymentSpec, "deploymentSpec cannot be null");
@@ -101,7 +99,6 @@ public class Application {
this.majorVersion = Objects.requireNonNull(majorVersion, "majorVersion cannot be null");
this.metrics = Objects.requireNonNull(metrics, "metrics cannot be null");
this.pemDeployKey = pemDeployKey;
- this.legacyRotation = Objects.requireNonNull(legacyRotation, "legacyRotation cannot be null");
this.rotations = List.copyOf(Objects.requireNonNull(rotations, "rotations cannot be null"));
this.rotationStatus = ImmutableMap.copyOf(Objects.requireNonNull(rotationStatus, "rotationStatus cannot be null"));
}
@@ -200,11 +197,20 @@ public class Application {
/** Returns the global rotation id of this, if present */
public Optional<RotationId> legacyRotation() {
- return legacyRotation;
+ return rotations.stream()
+ .map(AssignedRotation::rotationId)
+ .findFirst();
}
/** Returns all rotations for this application */
public List<RotationId> rotations() {
+ return rotations.stream()
+ .map(AssignedRotation::rotationId)
+ .collect(Collectors.toList());
+ }
+
+ /** Returns all assigned rotations for this application */
+ public List<AssignedRotation> assignedRotations() {
return rotations;
}
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 782a76b684e..8345aa91685 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
@@ -43,9 +43,11 @@ import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordName;
import com.yahoo.vespa.hosted.controller.api.integration.routing.RoutingEndpoint;
import com.yahoo.vespa.hosted.controller.api.integration.routing.RoutingGenerator;
import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
+import com.yahoo.vespa.hosted.controller.application.AssignedRotation;
import com.yahoo.vespa.hosted.controller.application.Deployment;
import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics;
import com.yahoo.vespa.hosted.controller.application.Endpoint;
+import com.yahoo.vespa.hosted.controller.application.EndpointId;
import com.yahoo.vespa.hosted.controller.application.EndpointList;
import com.yahoo.vespa.hosted.controller.application.JobList;
import com.yahoo.vespa.hosted.controller.application.JobStatus;
@@ -439,7 +441,7 @@ public class ApplicationController {
if (zone.environment() == Environment.prod && application.get().deploymentSpec().globalServiceId().isPresent()) {
try (RotationLock rotationLock = rotationRepository.lock()) {
Rotation rotation = rotationRepository.getOrAssignRotation(application.get(), rotationLock);
- application = application.with(rotation.id());
+ application = application.with(List.of(new AssignedRotation(new ClusterSpec.Id(application.get().deploymentSpec().globalServiceId().get()), EndpointId.default_(), rotation.id())));
store(application); // store assigned rotation even if deployment fails
boolean redirectLegacyDns = redirectLegacyDnsFlag.with(FetchVector.Dimension.APPLICATION_ID, application.get().id().serializedForm())
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 5f958b74c39..02b0afdd48f 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
@@ -15,6 +15,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId;
import com.yahoo.vespa.hosted.controller.api.integration.organization.User;
import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.vespa.hosted.controller.application.AssignedRotation;
import com.yahoo.vespa.hosted.controller.application.Change;
import com.yahoo.vespa.hosted.controller.application.ClusterInfo;
import com.yahoo.vespa.hosted.controller.application.ClusterUtilization;
@@ -56,8 +57,7 @@ public class LockedApplication {
private final OptionalInt majorVersion;
private final ApplicationMetrics metrics;
private final Optional<String> pemDeployKey;
- private final Optional<RotationId> legacyRotation;
- private final List<RotationId> rotations;
+ private final List<AssignedRotation> rotations;
private final Map<HostName, RotationStatus> rotationStatus;
/**
@@ -72,7 +72,7 @@ public class LockedApplication {
application.deployments(),
application.deploymentJobs(), application.change(), application.outstandingChange(),
application.ownershipIssueId(), application.owner(), application.majorVersion(), application.metrics(),
- application.pemDeployKey(), application.legacyRotation(), application.rotations(), application.rotationStatus());
+ application.pemDeployKey(), application.assignedRotations(), application.rotationStatus());
}
private LockedApplication(Lock lock, ApplicationId id, Instant createdAt,
@@ -80,7 +80,7 @@ public class LockedApplication {
Map<ZoneId, Deployment> deployments, DeploymentJobs deploymentJobs, Change change,
Change outstandingChange, Optional<IssueId> ownershipIssueId, Optional<User> owner,
OptionalInt majorVersion, ApplicationMetrics metrics, Optional<String> pemDeployKey,
- Optional<RotationId> legacyRotation, List<RotationId> rotations, Map<HostName, RotationStatus> rotationStatus) {
+ List<AssignedRotation> rotations, Map<HostName, RotationStatus> rotationStatus) {
this.lock = lock;
this.id = id;
this.createdAt = createdAt;
@@ -95,7 +95,6 @@ public class LockedApplication {
this.majorVersion = majorVersion;
this.metrics = metrics;
this.pemDeployKey = pemDeployKey;
- this.legacyRotation = legacyRotation;
this.rotations = rotations;
this.rotationStatus = rotationStatus;
}
@@ -104,35 +103,35 @@ public class LockedApplication {
public Application get() {
return new Application(id, createdAt, deploymentSpec, validationOverrides, deployments, deploymentJobs, change,
outstandingChange, ownershipIssueId, owner, majorVersion, metrics, pemDeployKey,
- legacyRotation, rotations, rotationStatus);
+ rotations, rotationStatus);
}
public LockedApplication withBuiltInternally(boolean builtInternally) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
deploymentJobs.withBuiltInternally(builtInternally), change, outstandingChange,
ownershipIssueId, owner, majorVersion, metrics, pemDeployKey,
- legacyRotation, rotations, rotationStatus);
+ rotations, rotationStatus);
}
public LockedApplication withProjectId(OptionalLong projectId) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
deploymentJobs.withProjectId(projectId), change, outstandingChange,
ownershipIssueId, owner, majorVersion, metrics, pemDeployKey,
- legacyRotation, rotations, rotationStatus);
+ rotations, rotationStatus);
}
public LockedApplication withDeploymentIssueId(IssueId issueId) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
deploymentJobs.with(issueId), change, outstandingChange,
ownershipIssueId, owner, majorVersion, metrics, pemDeployKey,
- legacyRotation, rotations, rotationStatus);
+ rotations, rotationStatus);
}
public LockedApplication withJobPause(JobType jobType, OptionalLong pausedUntil) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
deploymentJobs.withPause(jobType, pausedUntil), change, outstandingChange,
ownershipIssueId, owner, majorVersion, metrics, pemDeployKey,
- legacyRotation, rotations, rotationStatus);
+ rotations, rotationStatus);
}
public LockedApplication withJobCompletion(long projectId, JobType jobType, JobStatus.JobRun completion,
@@ -140,14 +139,14 @@ public class LockedApplication {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
deploymentJobs.withCompletion(projectId, jobType, completion, jobError),
change, outstandingChange, ownershipIssueId, owner, majorVersion, metrics,
- pemDeployKey, legacyRotation, rotations, rotationStatus);
+ pemDeployKey, rotations, rotationStatus);
}
public LockedApplication withJobTriggering(JobType jobType, JobStatus.JobRun job) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
deploymentJobs.withTriggering(jobType, job), change, outstandingChange,
ownershipIssueId, owner, majorVersion, metrics, pemDeployKey,
- legacyRotation, rotations, rotationStatus);
+ rotations, rotationStatus);
}
public LockedApplication withNewDeployment(ZoneId zone, ApplicationVersion applicationVersion, Version version,
@@ -198,45 +197,45 @@ public class LockedApplication {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
deploymentJobs.without(jobType), change, outstandingChange,
ownershipIssueId, owner, majorVersion, metrics, pemDeployKey,
- legacyRotation, rotations, rotationStatus);
+ rotations, rotationStatus);
}
public LockedApplication with(DeploymentSpec deploymentSpec) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
deploymentJobs, change, outstandingChange,
ownershipIssueId, owner, majorVersion, metrics, pemDeployKey,
- legacyRotation, rotations, rotationStatus);
+ rotations, rotationStatus);
}
public LockedApplication with(ValidationOverrides validationOverrides) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
deploymentJobs, change, outstandingChange, ownershipIssueId, owner, majorVersion,
- metrics, pemDeployKey, legacyRotation, rotations, rotationStatus);
+ metrics, pemDeployKey, rotations, rotationStatus);
}
public LockedApplication withChange(Change change) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
deploymentJobs, change, outstandingChange, ownershipIssueId, owner, majorVersion,
- metrics, pemDeployKey, legacyRotation, rotations, rotationStatus);
+ metrics, pemDeployKey, rotations, rotationStatus);
}
public LockedApplication withOutstandingChange(Change outstandingChange) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
deploymentJobs, change, outstandingChange, ownershipIssueId, owner, majorVersion,
- metrics, pemDeployKey, legacyRotation, rotations, rotationStatus);
+ metrics, pemDeployKey, rotations, rotationStatus);
}
public LockedApplication withOwnershipIssueId(IssueId issueId) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
deploymentJobs, change, outstandingChange, Optional.ofNullable(issueId), owner,
- majorVersion, metrics, pemDeployKey, legacyRotation, rotations, rotationStatus);
+ majorVersion, metrics, pemDeployKey, rotations, rotationStatus);
}
public LockedApplication withOwner(User owner) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
deploymentJobs, change, outstandingChange, ownershipIssueId,
Optional.ofNullable(owner), majorVersion, metrics, pemDeployKey,
- legacyRotation, rotations, rotationStatus);
+ rotations, rotationStatus);
}
/** Set a major version for this, or set to null to remove any major version override */
@@ -244,31 +243,31 @@ public class LockedApplication {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
deploymentJobs, change, outstandingChange, ownershipIssueId, owner,
majorVersion == null ? OptionalInt.empty() : OptionalInt.of(majorVersion),
- metrics, pemDeployKey, legacyRotation, rotations, rotationStatus);
+ metrics, pemDeployKey, rotations, rotationStatus);
}
public LockedApplication with(MetricsService.ApplicationMetrics metrics) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
deploymentJobs, change, outstandingChange, ownershipIssueId, owner, majorVersion,
- metrics, pemDeployKey, legacyRotation, rotations, rotationStatus);
+ metrics, pemDeployKey, rotations, rotationStatus);
}
public LockedApplication withPemDeployKey(String pemDeployKey) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
deploymentJobs, change, outstandingChange, ownershipIssueId, owner, majorVersion,
- metrics, Optional.ofNullable(pemDeployKey), legacyRotation, rotations, rotationStatus);
+ metrics, Optional.ofNullable(pemDeployKey), rotations, rotationStatus);
}
- public LockedApplication with(RotationId rotation) {
+ public LockedApplication with(List<AssignedRotation> assignedRotations) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
deploymentJobs, change, outstandingChange, ownershipIssueId, owner, majorVersion,
- metrics, pemDeployKey, Optional.of(rotation), List.of(rotation), rotationStatus);
+ metrics, pemDeployKey, assignedRotations, rotationStatus);
}
public LockedApplication withRotationStatus(Map<HostName, RotationStatus> rotationStatus) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
deploymentJobs, change, outstandingChange, ownershipIssueId, owner, majorVersion,
- metrics, pemDeployKey, legacyRotation, rotations, rotationStatus);
+ metrics, pemDeployKey, rotations, rotationStatus);
}
/** Don't expose non-leaf sub-objects. */
@@ -281,7 +280,7 @@ public class LockedApplication {
private LockedApplication with(Map<ZoneId, Deployment> deployments) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
deploymentJobs, change, outstandingChange, ownershipIssueId, owner, majorVersion,
- metrics, pemDeployKey, legacyRotation, rotations, rotationStatus);
+ metrics, pemDeployKey, rotations, rotationStatus);
}
@Override
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/AssignedRotation.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/AssignedRotation.java
new file mode 100644
index 00000000000..e1ed278a79e
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/AssignedRotation.java
@@ -0,0 +1,61 @@
+package com.yahoo.vespa.hosted.controller.application;
+
+import com.yahoo.config.provision.ClusterSpec;
+import com.yahoo.vespa.hosted.controller.rotation.RotationId;
+
+import java.util.Objects;
+
+/**
+ * Contains the tuple of [clusterId, endpointId, rotationId], to keep track
+ * of which services have assigned which rotations under which name.
+ *
+ * @author ogronnesby
+ */
+public class AssignedRotation {
+ private final ClusterSpec.Id clusterId;
+ private final EndpointId endpointId;
+ private final RotationId rotationId;
+
+ public AssignedRotation(ClusterSpec.Id clusterId, EndpointId endpointId, RotationId rotationId) {
+ this.clusterId = requireNonEmpty(clusterId, clusterId.value(), "clusterId");
+ this.endpointId = Objects.requireNonNull(endpointId);
+ this.rotationId = Objects.requireNonNull(rotationId);
+ }
+
+ public ClusterSpec.Id clusterId() { return clusterId; }
+ public EndpointId endpointId() { return endpointId; }
+ public RotationId rotationId() { return rotationId; }
+
+ @Override
+ public String toString() {
+ return "AssignedRotation{" +
+ "clusterId=" + clusterId +
+ ", endpointId='" + endpointId + '\'' +
+ ", rotationId=" + rotationId +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ AssignedRotation that = (AssignedRotation) o;
+ return clusterId.equals(that.clusterId) &&
+ endpointId.equals(that.endpointId) &&
+ rotationId.equals(that.rotationId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(clusterId, endpointId, rotationId);
+ }
+
+ private static <T> T requireNonEmpty(T object, String value, String field) {
+ Objects.requireNonNull(object);
+ Objects.requireNonNull(value);
+ if (value.isEmpty()) {
+ throw new IllegalArgumentException("Field '" + field + "' was empty");
+ }
+ return object;
+ }
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointId.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointId.java
new file mode 100644
index 00000000000..13c242c7b5f
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointId.java
@@ -0,0 +1,53 @@
+package com.yahoo.vespa.hosted.controller.application;
+
+import java.util.Objects;
+
+/**
+ * A type to represent the ID of an endpoint. This is typically the first part of
+ * an endpoint name.
+ *
+ * @author ogronnesby
+ */
+public class EndpointId {
+ private static final EndpointId DEFAULT = new EndpointId("default");
+
+ private final String id;
+
+ public EndpointId(String id) {
+ this.id = requireNotEmpty(id);
+ }
+
+ public String id() { return id; }
+
+ @Override
+ public String toString() {
+ return "EndpointId{" +
+ "id='" + id + '\'' +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ EndpointId that = (EndpointId) o;
+ return Objects.equals(id, that.id);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id);
+ }
+
+ private static String requireNotEmpty(String input) {
+ Objects.requireNonNull(input);
+ if (input.isEmpty()) {
+ throw new IllegalArgumentException("The value EndpointId was empty");
+ }
+ return input;
+ }
+
+ public static EndpointId default_() { return DEFAULT; }
+
+ public static EndpointId of(String id) { return new EndpointId(id); }
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java
index 9302ecbe738..c4f0597572b 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.controller.maintenance;
import com.yahoo.config.provision.CloudName;
import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.jdisc.Metric;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.config.provision.ApplicationId;
@@ -81,8 +82,8 @@ public class ResourceMeterMaintainer extends Maintainer {
private List<NodeRepositoryNode> getNodes() {
return controller().zoneRegistry().zones()
.ofCloud(CloudName.from("aws"))
- .reachable().ids().stream()
- .flatMap(zoneId -> uncheck(() -> nodeRepository.listNodes(zoneId, true).nodes().stream()))
+ .reachable().zones().stream()
+ .flatMap(zone -> uncheck(() -> nodeRepository.listNodes(zone.getId(), true).nodes().stream()))
.filter(node -> node.getOwner() != null && !node.getOwner().getTenant().equals("hosted-vespa"))
.filter(node -> node.getState() == NodeState.active)
.collect(Collectors.toList());
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 1f20bdf5533..271221e15b2 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
@@ -21,6 +21,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.deployment.SourceRevisi
import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId;
import com.yahoo.vespa.hosted.controller.api.integration.organization.User;
import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.vespa.hosted.controller.application.AssignedRotation;
import com.yahoo.vespa.hosted.controller.application.Change;
import com.yahoo.vespa.hosted.controller.application.ClusterInfo;
import com.yahoo.vespa.hosted.controller.application.ClusterUtilization;
@@ -29,6 +30,7 @@ import com.yahoo.vespa.hosted.controller.application.DeploymentActivity;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobError;
import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics;
+import com.yahoo.vespa.hosted.controller.application.EndpointId;
import com.yahoo.vespa.hosted.controller.application.JobStatus;
import com.yahoo.vespa.hosted.controller.application.RotationStatus;
import com.yahoo.vespa.hosted.controller.rotation.RotationId;
@@ -38,6 +40,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -77,6 +80,10 @@ public class ApplicationSerializer {
private final String writeQualityField = "writeQuality";
private final String queryQualityField = "queryQuality";
private final String pemDeployKeyField = "pemDeployKey";
+ private final String assignedRotationsField = "assignedRotations";
+ private final String assignedRotationEndpointField = "endpointId";
+ private final String assignedRotationClusterField = "clusterId";
+ private final String assignedRotationRotationField = "rotationId";
private final String rotationsField = "endpoints";
private final String deprecatedRotationField = "rotation";
private final String rotationStatusField = "rotationStatus";
@@ -171,8 +178,8 @@ public class ApplicationSerializer {
root.setDouble(writeQualityField, application.metrics().writeServiceQuality());
application.pemDeployKey().ifPresent(pemDeployKey -> root.setString(pemDeployKeyField, pemDeployKey));
application.legacyRotation().ifPresent(rotation -> root.setString(deprecatedRotationField, rotation.asString()));
- Cursor rotations = root.setArray(rotationsField);
- application.rotations().forEach(rotation -> rotations.addString(rotation.asString()));
+ rotationsToSlime(application.assignedRotations(), root, rotationsField);
+ assignedRotationsToSlime(application.assignedRotations(), root, assignedRotationsField);
toSlime(application.rotationStatus(), root.setArray(rotationStatusField));
return slime;
}
@@ -320,6 +327,21 @@ public class ApplicationSerializer {
});
}
+ private void rotationsToSlime(List<AssignedRotation> rotations, Cursor parent, String fieldName) {
+ final var rotationsArray = parent.setArray(fieldName);
+ rotations.forEach(rot -> rotationsArray.addString(rot.rotationId().asString()));
+ }
+
+ private void assignedRotationsToSlime(List<AssignedRotation> rotations, Cursor parent, String fieldName) {
+ final var rotationsArray = parent.setArray(fieldName);
+ for (var rotation : rotations) {
+ final var object = rotationsArray.addObject();
+ object.setString(assignedRotationEndpointField, rotation.endpointId().id());
+ object.setString(assignedRotationRotationField, rotation.rotationId().asString());
+ object.setString(assignedRotationClusterField, rotation.clusterId().value());
+ }
+ }
+
// ------------------ Deserialization
public Application fromSlime(Slime slime) {
@@ -339,13 +361,12 @@ public class ApplicationSerializer {
ApplicationMetrics metrics = new ApplicationMetrics(root.field(queryQualityField).asDouble(),
root.field(writeQualityField).asDouble());
Optional<String> pemDeployKey = optionalString(root.field(pemDeployKeyField));
- Optional<RotationId> legacyRotation = optionalString(root.field(deprecatedRotationField)).map(RotationId::new);
- List<RotationId> rotations = rotationsFromSlime(root);
+ List<AssignedRotation> assignedRotations = assignedRotationsFromSlime(deploymentSpec, root);
Map<HostName, RotationStatus> rotationStatus = rotationStatusFromSlime(root.field(rotationStatusField));
return new Application(id, createdAt, deploymentSpec, validationOverrides, deployments, deploymentJobs,
deploying, outstandingChange, ownershipIssueId, owner, majorVersion, metrics,
- pemDeployKey, legacyRotation, rotations, rotationStatus);
+ pemDeployKey, assignedRotations, rotationStatus);
}
private List<Deployment> deploymentsFromSlime(Inspector array) {
@@ -525,15 +546,36 @@ public class ApplicationSerializer {
Instant.ofEpochMilli(object.field(atField).asLong())));
}
- private List<RotationId> rotationsFromSlime(Inspector root) {
- final var rotations = rotationListFromSlime(root.field(rotationsField));
+ private List<AssignedRotation> assignedRotationsFromSlime(DeploymentSpec deploymentSpec, Inspector root) {
+ final var assignedRotations = new LinkedHashSet<AssignedRotation>();
+
+ // Add the legacy rotation field to the set - this needs to be first
+ // TODO: Remove when we retire the rotations field
final var legacyRotation = legacyRotationFromSlime(root.field(deprecatedRotationField));
+ if (legacyRotation.isPresent() && deploymentSpec.globalServiceId().isPresent()) {
+ final var clusterId = new ClusterSpec.Id(deploymentSpec.globalServiceId().get());
+ assignedRotations.add(new AssignedRotation(clusterId, EndpointId.default_(), legacyRotation.get()));
+ }
- if (legacyRotation.isPresent() && ! rotations.contains(legacyRotation.get())) {
- rotations.add(legacyRotation.get());
+ // Now add the same entries from "stupid" list of rotations
+ // TODO: Remove when we retire the rotations field
+ final var rotations = rotationListFromSlime(root.field(rotationsField));
+ for (var rotation : rotations) {
+ if (deploymentSpec.globalServiceId().isPresent()) {
+ final var clusterId = new ClusterSpec.Id(deploymentSpec.globalServiceId().get());
+ assignedRotations.add(new AssignedRotation(clusterId, EndpointId.default_(), rotation));
+ }
}
- return rotations;
+ // Last - add the actual entries we want. Do _not_ remove this during clean-up
+ root.field(assignedRotationsField).traverse((ArrayTraverser) (idx, inspector) -> {
+ final var clusterId = new ClusterSpec.Id(inspector.field(assignedRotationClusterField).asString());
+ final var endpointId = EndpointId.of(inspector.field(assignedRotationEndpointField).asString());
+ final var rotationId = new RotationId(inspector.field(assignedRotationRotationField).asString());
+ assignedRotations.add(new AssignedRotation(clusterId, endpointId, rotationId));
+ });
+
+ return List.copyOf(assignedRotations);
}
private List<RotationId> rotationListFromSlime(Inspector field) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImpl.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImpl.java
index a208249b410..73a029ad3b3 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImpl.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImpl.java
@@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.inject.Inject;
import com.yahoo.config.provision.Environment;
+import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.config.provision.zone.ZoneList;
import com.yahoo.jdisc.http.HttpRequest.Method;
@@ -114,9 +115,9 @@ public class ConfigServerRestExecutorImpl implements ConfigServerRestExecutor {
if ( ! environmentName.isEmpty())
zones = zones.in(Environment.from(environmentName));
- for (ZoneId zoneId : zones.ids()) {
+ for (ZoneApi zone : zones.zones()) {
responseStructure.uris.add(proxyRequest.getScheme() + "://" + proxyRequest.getControllerPrefix() +
- zoneId.environment().value() + "/" + zoneId.region().value());
+ zone.getEnvironment().value() + "/" + zone.getRegionName().value());
}
JsonNode node = mapper.valueToTree(responseStructure);
return new ProxyResponse(proxyRequest, node.toString(), 200, Optional.empty(), "application/json");
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/cost/CostCalculator.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/cost/CostCalculator.java
index 18c00d69b62..c44a80f7a20 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/cost/CostCalculator.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/cost/CostCalculator.java
@@ -2,6 +2,7 @@ package com.yahoo.vespa.hosted.controller.restapi.cost;
import com.yahoo.config.provision.CloudName;
import com.yahoo.config.provision.Environment;
+import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.api.identifiers.Property;
import com.yahoo.vespa.hosted.controller.api.integration.noderepository.NodeOwner;
@@ -34,8 +35,8 @@ public class CostCalculator {
String date = LocalDate.now(clock).toString();
List<NodeRepositoryNode> nodes = controller.zoneRegistry().zones()
- .reachable().in(Environment.prod).ofCloud(cloudName).ids().stream()
- .flatMap(zoneId -> uncheck(() -> nodeRepository.listNodes(zoneId, true).nodes().stream()))
+ .reachable().in(Environment.prod).ofCloud(cloudName).zones().stream()
+ .flatMap(zone -> uncheck(() -> nodeRepository.listNodes(zone.getId(), true).nodes().stream()))
.filter(node -> node.getOwner() != null && !node.getOwner().getTenant().equals("hosted-vespa"))
.collect(Collectors.toList());
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiHandler.java
index 5454d71185a..bc360fe3c6f 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiHandler.java
@@ -5,6 +5,7 @@ import com.yahoo.component.Version;
import com.yahoo.config.provision.CloudName;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.io.IOUtils;
@@ -30,6 +31,7 @@ import java.util.List;
import java.util.Set;
import java.util.StringJoiner;
import java.util.logging.Level;
+import java.util.stream.Collectors;
/**
* This implements the /os/v1 API which provides operators with information about, and scheduling of OS upgrades for
@@ -123,7 +125,7 @@ public class OsApiHandler extends AuditLoggingRequestHandler {
ZoneList zones = controller.zoneRegistry().zones().controllerUpgraded();
if (path.get("region") != null) zones = zones.in(RegionName.from(path.get("region")));
if (path.get("environment") != null) zones = zones.in(Environment.from(path.get("environment")));
- return zones.ids();
+ return zones.zones().stream().map(ZoneApi::getId).collect(Collectors.toList());
}
private Slime setOsVersion(HttpRequest request) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiHandler.java
index b115e659c28..6cfaed93fa9 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiHandler.java
@@ -3,6 +3,8 @@ package com.yahoo.vespa.hosted.controller.restapi.zone.v1;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.Zone;
+import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
@@ -70,8 +72,8 @@ public class ZoneApiHandler extends LoggingRequestHandler {
}
private HttpResponse root(HttpRequest request) {
- List<Environment> environments = zoneRegistry.zones().all().ids().stream()
- .map(ZoneId::environment)
+ List<Environment> environments = zoneRegistry.zones().all().zones().stream()
+ .map(ZoneApi::getEnvironment)
.distinct()
.sorted(Comparator.comparing(Environment::value))
.collect(Collectors.toList());
@@ -90,17 +92,16 @@ public class ZoneApiHandler extends LoggingRequestHandler {
}
private HttpResponse environment(HttpRequest request, Environment environment) {
- List<ZoneId> zones = zoneRegistry.zones().all().in(environment).ids();
Slime slime = new Slime();
Cursor root = slime.setArray();
- zones.forEach(zone -> {
+ zoneRegistry.zones().all().in(environment).zones().forEach(zone -> {
Cursor object = root.addObject();
- object.setString("name", zone.region().value());
+ object.setString("name", zone.getRegionName().value());
object.setString("url", request.getUri()
.resolve("/zone/v2/environment/")
.resolve(environment.value() + "/")
.resolve("region/")
- .resolve(zone.region().value())
+ .resolve(zone.getRegionName().value())
.toString());
});
return new SlimeJsonResponse(slime);
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java
index 9d95383fbfb..f0259fc4d51 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java
@@ -94,16 +94,16 @@ public class ZoneApiHandler extends AuditLoggingRequestHandler {
Cursor root = slime.setObject();
Cursor uris = root.setArray("uris");
ZoneList zoneList = zoneRegistry.zones().reachable();
- zoneList.ids().forEach(zoneId -> uris.addString(request.getUri()
+ zoneList.zones().forEach(zone -> uris.addString(request.getUri()
.resolve("/zone/v2/")
- .resolve(zoneId.environment().value() + "/")
- .resolve(zoneId.region().value())
+ .resolve(zone.getEnvironment().value() + "/")
+ .resolve(zone.getRegionName().value())
.toString()));
Cursor zones = root.setArray("zones");
- zoneList.ids().forEach(zoneId -> {
+ zoneList.zones().forEach(zone -> {
Cursor object = zones.addObject();
- object.setString("environment", zoneId.environment().value());
- object.setString("region", zoneId.region().value());
+ object.setString("environment", zone.getEnvironment().value());
+ object.setString("region", zone.getRegionName().value());
});
return new SlimeJsonResponse(slime);
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java
index 87f35d3b2c1..ab5fd2714e5 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java
@@ -6,6 +6,7 @@ import com.yahoo.collections.ListMap;
import com.yahoo.component.Version;
import com.yahoo.component.Vtag;
import com.yahoo.config.provision.HostName;
+import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.log.LogLevel;
import com.yahoo.vespa.hosted.controller.Application;
@@ -155,20 +156,17 @@ public class VersionStatus {
}
private static ListMap<Version, HostName> findSystemApplicationVersions(Controller controller) {
- List<ZoneId> zones = controller.zoneRegistry().zones()
- .controllerUpgraded()
- .ids();
ListMap<Version, HostName> versions = new ListMap<>();
- for (ZoneId zone : zones) {
+ for (ZoneApi zone : controller.zoneRegistry().zones().controllerUpgraded().zones()) {
for (SystemApplication application : SystemApplication.all()) {
List<Node> eligibleForUpgradeApplicationNodes = controller.configServer().nodeRepository()
- .list(zone, application.id()).stream()
+ .list(zone.getId(), application.id()).stream()
.filter(SystemUpgrader::eligibleForUpgrade)
.collect(Collectors.toList());
if (eligibleForUpgradeApplicationNodes.isEmpty())
continue;
- boolean configConverged = application.configConvergedIn(zone, controller, Optional.empty());
+ boolean configConverged = application.configConvergedIn(zone.getId(), controller, Optional.empty());
if (!configConverged) {
log.log(LogLevel.WARNING, "Config for " + application.id() + " in " + zone + " has not converged");
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTester.java
index 3ce32347e35..887406ecba8 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTester.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTester.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.TenantName;
+import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.test.ManualClock;
import com.yahoo.vespa.hosted.controller.Application;
@@ -123,10 +124,10 @@ public class DeploymentTester {
/** Upgrade system applications in all zones to given version */
public void upgradeSystemApplications(Version version) {
- for (ZoneId zone : tester.zoneRegistry().zones().all().ids()) {
+ for (ZoneApi zone : tester.zoneRegistry().zones().all().zones()) {
for (SystemApplication application : SystemApplication.all()) {
- tester.configServer().setVersion(application.id(), zone, version);
- tester.configServer().convergeServices(application.id(), zone);
+ tester.configServer().setVersion(application.id(), zone.getId(), version);
+ tester.configServer().convergeServices(application.id(), zone.getId());
}
}
computeVersionStatus();
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneFilterMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneFilterMock.java
index 57f29fb72af..00e6162d5e5 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneFilterMock.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneFilterMock.java
@@ -82,11 +82,6 @@ public class ZoneFilterMock implements ZoneList {
}
@Override
- public List<ZoneId> ids() {
- return List.copyOf(zones.stream().map(ZoneApi::getId).collect(Collectors.toList()));
- }
-
- @Override
public ZoneList ofCloud(CloudName cloud) {
return filter(zone -> zone.getCloudName().equals(cloud));
}
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 be9624fc693..67b6b1ac61c 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
@@ -16,6 +16,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.deployment.SourceRevisi
import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId;
import com.yahoo.vespa.hosted.controller.api.integration.organization.User;
import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.vespa.hosted.controller.application.AssignedRotation;
import com.yahoo.vespa.hosted.controller.application.Change;
import com.yahoo.vespa.hosted.controller.application.ClusterInfo;
import com.yahoo.vespa.hosted.controller.application.ClusterUtilization;
@@ -24,6 +25,7 @@ import com.yahoo.vespa.hosted.controller.application.DeploymentActivity;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobError;
import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics;
+import com.yahoo.vespa.hosted.controller.application.EndpointId;
import com.yahoo.vespa.hosted.controller.application.JobStatus;
import com.yahoo.vespa.hosted.controller.application.RotationStatus;
import com.yahoo.vespa.hosted.controller.rotation.RotationId;
@@ -116,8 +118,7 @@ public class ApplicationSerializerTest {
OptionalInt.of(7),
new MetricsService.ApplicationMetrics(0.5, 0.9),
Optional.of("-----BEGIN PUBLIC KEY-----\n∠( ᐛ 」∠)_\n-----END PUBLIC KEY-----"),
- Optional.of(new RotationId("my-rotation")),
- List.of(new RotationId("my-rotation")),
+ List.of(new AssignedRotation(new ClusterSpec.Id("foo"), EndpointId.default_(), new RotationId("my-rotation"))),
rotationStatus);
Application serialized = applicationSerializer.fromSlime(applicationSerializer.toSlime(original));
@@ -261,14 +262,21 @@ public class ApplicationSerializerTest {
rotations.addString("multiple-rotation-1");
rotations.addString("multiple-rotation-2");
+ final var assignedRotations = cursor.setArray("assignedRotations");
+ final var assignedRotation = assignedRotations.addObject();
+ assignedRotation.setString("clusterId", "foobar");
+ assignedRotation.setString("endpointId", "nice-endpoint");
+ assignedRotation.setString("rotationId", "assigned-rotation");
+
// Parse and test the output from parsing contains both legacy rotation and multiple rotations
final var application = applicationSerializer.fromSlime(slime);
assertEquals(
List.of(
- new RotationId("multiple-rotation-1"),
- new RotationId("multiple-rotation-2"),
- new RotationId("single-rotation")
+ new RotationId("single-rotation"),
+ new RotationId("multiple-rotation-1"),
+ new RotationId("multiple-rotation-2"),
+ new RotationId("assigned-rotation")
),
application.rotations()
);
@@ -276,6 +284,16 @@ public class ApplicationSerializerTest {
assertEquals(
Optional.of(new RotationId("single-rotation")), application.legacyRotation()
);
+
+ assertEquals(
+ List.of(
+ new AssignedRotation(new ClusterSpec.Id("foo"), EndpointId.of("default"), new RotationId("single-rotation")),
+ new AssignedRotation(new ClusterSpec.Id("foo"), EndpointId.of("default"), new RotationId("multiple-rotation-1")),
+ new AssignedRotation(new ClusterSpec.Id("foo"), EndpointId.of("default"), new RotationId("multiple-rotation-2")),
+ new AssignedRotation(new ClusterSpec.Id("foobar"), EndpointId.of("nice-endpoint"), new RotationId("assigned-rotation"))
+ ),
+ application.assignedRotations()
+ );
}
@Test
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java
index ef86ffa125f..c7be543dd00 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java
@@ -6,6 +6,7 @@ import com.yahoo.application.container.handler.Request;
import com.yahoo.application.container.handler.Response;
import com.yahoo.component.ComponentSpecification;
import com.yahoo.component.Version;
+import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.container.http.filter.FilterChainRepository;
import com.yahoo.jdisc.http.filter.SecurityRequestFilter;
@@ -59,10 +60,10 @@ public class ContainerTester {
public void upgradeSystem(Version version) {
controller().curator().writeControllerVersion(controller().hostname(), version);
- for (ZoneId zone : controller().zoneRegistry().zones().all().ids()) {
+ for (ZoneApi zone : controller().zoneRegistry().zones().all().zones()) {
for (SystemApplication application : SystemApplication.all()) {
- configServer().setVersion(application.id(), zone, version);
- configServer().convergeServices(application.id(), zone);
+ configServer().setVersion(application.id(), zone.getId(), version);
+ configServer().convergeServices(application.id(), zone.getId());
}
}
computeVersionStatus();
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepositoryTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepositoryTest.java
index 02a82e35f10..8f02fa74c6e 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepositoryTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepositoryTest.java
@@ -15,7 +15,6 @@ import org.junit.rules.ExpectedException;
import java.net.URI;
import java.util.List;
-import java.util.Optional;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/VersionStatusTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/VersionStatusTest.java
index 8e3dc24193f..655c16ccceb 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/VersionStatusTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/VersionStatusTest.java
@@ -6,6 +6,7 @@ import com.yahoo.component.Version;
import com.yahoo.component.Vtag;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.HostName;
+import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.ControllerTester;
@@ -60,10 +61,10 @@ public class VersionStatusTest {
Version version0 = Version.fromString("6.1");
Version version1 = Version.fromString("6.5");
// Upgrade some config servers
- for (ZoneId zone : tester.zoneRegistry().zones().all().ids()) {
- for (Node node : tester.configServer().nodeRepository().list(zone, SystemApplication.configServer.id())) {
- tester.configServer().nodeRepository().putByHostname(zone, new Node(node.hostname(), node.state(), node.type(),
- node.owner(), version1, node.wantedVersion()));
+ for (ZoneApi zone : tester.zoneRegistry().zones().all().zones()) {
+ for (Node node : tester.configServer().nodeRepository().list(zone.getId(), SystemApplication.configServer.id())) {
+ Node upgradedNode = new Node(node.hostname(), node.state(), node.type(), node.owner(), version1, node.wantedVersion());
+ tester.configServer().nodeRepository().putByHostname(zone.getId(), upgradedNode);
break;
}
}
@@ -105,10 +106,10 @@ public class VersionStatusTest {
// Downgrade one config server in each zone
Version ancientVersion = Version.fromString("5.1");
- for (ZoneId zone : tester.controller().zoneRegistry().zones().all().ids()) {
- for (Node node : tester.configServer().nodeRepository().list(zone, SystemApplication.configServer.id())) {
- tester.configServer().nodeRepository().putByHostname(zone, new Node(node.hostname(), node.state(), node.type(),
- node.owner(), ancientVersion, node.wantedVersion()));
+ for (ZoneApi zone : tester.controller().zoneRegistry().zones().all().zones()) {
+ for (Node node : tester.configServer().nodeRepository().list(zone.getId(), SystemApplication.configServer.id())) {
+ Node downgradedNode = new Node(node.hostname(), node.state(), node.type(), node.owner(), ancientVersion, node.wantedVersion());
+ tester.configServer().nodeRepository().putByHostname(zone.getId(), downgradedNode);
break;
}
}
diff --git a/hosted-api/pom.xml b/hosted-api/pom.xml
index f20244a8816..928a173f9d8 100644
--- a/hosted-api/pom.xml
+++ b/hosted-api/pom.xml
@@ -34,9 +34,13 @@
</dependency>
<dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>4.12</version>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-engine</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.junit.vintage</groupId>
+ <artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
diff --git a/hosted-api/src/test/java/ai/vespa/hosted/api/MultiPartStreamerTest.java b/hosted-api/src/test/java/ai/vespa/hosted/api/MultiPartStreamerTest.java
index a55c0d91cd3..bfc544e82f8 100644
--- a/hosted-api/src/test/java/ai/vespa/hosted/api/MultiPartStreamerTest.java
+++ b/hosted-api/src/test/java/ai/vespa/hosted/api/MultiPartStreamerTest.java
@@ -1,9 +1,8 @@
// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package ai.vespa.hosted.api;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
import java.io.IOException;
import java.net.URI;
@@ -12,16 +11,13 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
-import static org.junit.Assert.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
-public class MultiPartStreamerTest {
-
- @Rule
- public TemporaryFolder tmp = new TemporaryFolder();
+class MultiPartStreamerTest {
@Test
- public void test() throws IOException {
- Path file = tmp.newFile().toPath();
+ void test(@TempDir Path tmp) throws IOException {
+ Path file = tmp.resolve("file");
Files.write(file, new byte[]{0x48, 0x69});
MultiPartStreamer streamer = new MultiPartStreamer("My boundary");
diff --git a/hosted-api/src/test/java/ai/vespa/hosted/api/SignaturesTest.java b/hosted-api/src/test/java/ai/vespa/hosted/api/SignaturesTest.java
index 0a0d4a48edf..6749fb902f9 100644
--- a/hosted-api/src/test/java/ai/vespa/hosted/api/SignaturesTest.java
+++ b/hosted-api/src/test/java/ai/vespa/hosted/api/SignaturesTest.java
@@ -1,7 +1,7 @@
// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package ai.vespa.hosted.api;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
@@ -16,9 +16,9 @@ import java.time.ZoneOffset;
import static ai.vespa.hosted.api.Signatures.sha256Digest;
import static ai.vespa.hosted.api.Signatures.sha256Digester;
import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* Tests that messages can be signed and verified, and that the keys used for this can be parsed.
@@ -32,7 +32,7 @@ import static org.junit.Assert.assertTrue;
*
* @author jonmv
*/
-public class SignaturesTest {
+class SignaturesTest {
private static final String ecPemPublicKey = "-----BEGIN PUBLIC KEY-----\n" +
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuKVFA8dXk43kVfYKzkUqhEY2rDT9\n" +
@@ -58,7 +58,7 @@ public class SignaturesTest {
"∠( ᐛ 」∠)_").getBytes(UTF_8);
@Test
- public void testHashing() throws Exception {
+ void testHashing() throws Exception {
byte[] hash1 = MessageDigest.getInstance("SHA-256").digest(message);
byte[] hash2 = sha256Digest(() -> new ByteArrayInputStream(message));
DigestInputStream digester = sha256Digester(new ByteArrayInputStream(message));
@@ -70,7 +70,7 @@ public class SignaturesTest {
}
@Test
- public void testSigning() {
+ void testSigning() {
Clock clock = Clock.fixed(Instant.EPOCH, ZoneOffset.UTC);
RequestSigner signer = new RequestSigner(ecPemPrivateKey, "myKey", clock);
diff --git a/jdisc_http_service/abi-spec.json b/jdisc_http_service/abi-spec.json
index 04e6d22a445..a326b5792be 100644
--- a/jdisc_http_service/abi-spec.json
+++ b/jdisc_http_service/abi-spec.json
@@ -78,7 +78,9 @@
"public void <init>(com.yahoo.jdisc.http.ConnectorConfig$Ssl)",
"public com.yahoo.jdisc.http.ConnectorConfig$Ssl$Builder enabled(boolean)",
"public com.yahoo.jdisc.http.ConnectorConfig$Ssl$Builder privateKeyFile(java.lang.String)",
+ "public com.yahoo.jdisc.http.ConnectorConfig$Ssl$Builder privateKey(java.lang.String)",
"public com.yahoo.jdisc.http.ConnectorConfig$Ssl$Builder certificateFile(java.lang.String)",
+ "public com.yahoo.jdisc.http.ConnectorConfig$Ssl$Builder certificate(java.lang.String)",
"public com.yahoo.jdisc.http.ConnectorConfig$Ssl$Builder caCertificateFile(java.lang.String)",
"public com.yahoo.jdisc.http.ConnectorConfig$Ssl$Builder clientAuth(com.yahoo.jdisc.http.ConnectorConfig$Ssl$ClientAuth$Enum)",
"public com.yahoo.jdisc.http.ConnectorConfig$Ssl build()"
@@ -131,7 +133,9 @@
"public void <init>(com.yahoo.jdisc.http.ConnectorConfig$Ssl$Builder)",
"public boolean enabled()",
"public java.lang.String privateKeyFile()",
+ "public java.lang.String privateKey()",
"public java.lang.String certificateFile()",
+ "public java.lang.String certificate()",
"public java.lang.String caCertificateFile()",
"public com.yahoo.jdisc.http.ConnectorConfig$Ssl$ClientAuth$Enum clientAuth()"
],
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/impl/ConfiguredSslContextFactoryProvider.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/impl/ConfiguredSslContextFactoryProvider.java
index facb54bc37a..2021105fc52 100644
--- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/impl/ConfiguredSslContextFactoryProvider.java
+++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/impl/ConfiguredSslContextFactoryProvider.java
@@ -60,15 +60,23 @@ public class ConfiguredSslContextFactoryProvider implements SslContextFactoryPro
private static void validateConfig(ConnectorConfig.Ssl config) {
if (!config.enabled()) return;
- if (config.certificateFile().isEmpty()) {
- throw new IllegalArgumentException("Missing certificate file.");
- }
- if (config.privateKeyFile().isEmpty()) {
- throw new IllegalArgumentException("Missing private key file.");
- }
+ if(hasBoth(config.certificate(), config.certificateFile()))
+ throw new IllegalArgumentException("Specified both certificate and certificate file.");
+
+ if(hasBoth(config.privateKey(), config.privateKeyFile()))
+ throw new IllegalArgumentException("Specified both private key and private key file.");
+
+ if(hasNeither(config.certificate(), config.certificateFile()))
+ throw new IllegalArgumentException("Specified neither certificate or certificate file.");
+
+ if(hasNeither(config.privateKey(), config.privateKeyFile()))
+ throw new IllegalArgumentException("Specified neither private key or private key file.");
}
+ private static boolean hasBoth(String a, String b) { return !a.isBlank() && !b.isBlank(); }
+ private static boolean hasNeither(String a, String b) { return a.isBlank() && b.isBlank(); }
+
private static KeyStore createTruststore(ConnectorConfig.Ssl sslConfig) {
List<X509Certificate> caCertificates = X509CertificateUtils.certificateListFromPem(readToString(sslConfig.caCertificateFile()));
return KeyStoreBuilder.withType(KeyStoreType.JKS)
@@ -77,11 +85,21 @@ public class ConfiguredSslContextFactoryProvider implements SslContextFactoryPro
}
private static KeyStore createKeystore(ConnectorConfig.Ssl sslConfig) {
- PrivateKey privateKey = KeyUtils.fromPemEncodedPrivateKey(readToString(sslConfig.privateKeyFile()));
- List<X509Certificate> certificates = X509CertificateUtils.certificateListFromPem(readToString(sslConfig.certificateFile()));
+ PrivateKey privateKey = KeyUtils.fromPemEncodedPrivateKey(getPrivateKey(sslConfig));
+ List<X509Certificate> certificates = X509CertificateUtils.certificateListFromPem(getCertificate(sslConfig));
return KeyStoreBuilder.withType(KeyStoreType.JKS).withKeyEntry("default", privateKey, certificates).build();
}
+ private static String getPrivateKey(ConnectorConfig.Ssl config) {
+ if(!config.privateKey().isBlank()) return config.privateKey();
+ return readToString(config.privateKeyFile());
+ }
+
+ private static String getCertificate(ConnectorConfig.Ssl config) {
+ if(!config.certificate().isBlank()) return config.certificate();
+ return readToString(config.certificateFile());
+ }
+
private static String readToString(String filename) {
try {
return Files.readString(Paths.get(filename), StandardCharsets.UTF_8);
diff --git a/jdisc_http_service/src/main/resources/configdefinitions/jdisc.http.connector.def b/jdisc_http_service/src/main/resources/configdefinitions/jdisc.http.connector.def
index 7735420d803..c6c6fad345b 100644
--- a/jdisc_http_service/src/main/resources/configdefinitions/jdisc.http.connector.def
+++ b/jdisc_http_service/src/main/resources/configdefinitions/jdisc.http.connector.def
@@ -56,12 +56,18 @@ throttling.idleTimeout double default=-1.0
# Whether to enable SSL for this connector.
ssl.enabled bool default=false
-# File with private key in PEM format
+# File with private key in PEM format. Specify either this or privateKey, but not both
ssl.privateKeyFile string default=""
-# File with certificate in PEM format
+# Private key in PEM format. Specify either this or privateKeyFile, but not both
+ssl.privateKey string default=""
+
+# File with certificate in PEM format. Specify either this or certificate, but not both
ssl.certificateFile string default=""
+# Certificate in PEM format. Specify either this or certificateFile, but not both
+ssl.certificate string default=""
+
# with trusted CA certificates in PEM format. Used to verify clients
ssl.caCertificateFile string default=""
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerService.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerService.java
index 6f45403f0e6..f6398c04e61 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerService.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerService.java
@@ -13,8 +13,17 @@ import java.util.Set;
*/
public interface LoadBalancerService {
- /** Create a load balancer for given application cluster. Implementations are expected to be idempotent */
- LoadBalancerInstance create(ApplicationId application, ClusterSpec.Id cluster, Set<Real> reals);
+ /**
+ * Create a load balancer for given application cluster. Implementations are expected to be idempotent
+ *
+ * @param application Application owning the LB
+ * @param cluster Target cluster of the LB
+ * @param reals Reals that should be configured on the LB
+ * @param force Whether reconfiguration should be forced (e.g. allow configuring an empty set of reals on a
+ * pre-existing load balancer).
+ * @return The provisioned load balancer instance
+ */
+ LoadBalancerInstance create(ApplicationId application, ClusterSpec.Id cluster, Set<Real> reals, boolean force);
/** Permanently remove load balancer for given application cluster */
void remove(ApplicationId application, ClusterSpec.Id cluster);
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerServiceMock.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerServiceMock.java
index e89a4dc8bf8..91f02a31f6b 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerServiceMock.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerServiceMock.java
@@ -29,10 +29,10 @@ public class LoadBalancerServiceMock implements LoadBalancerService {
}
@Override
- public LoadBalancerInstance create(ApplicationId application, ClusterSpec.Id cluster, Set<Real> reals) {
+ public LoadBalancerInstance create(ApplicationId application, ClusterSpec.Id cluster, Set<Real> reals, boolean force) {
var id = new LoadBalancerId(application, cluster);
var oldInstance = instances.get(id);
- if (oldInstance != null && !oldInstance.reals().isEmpty() && reals.isEmpty()) {
+ if (!force && oldInstance != null && !oldInstance.reals().isEmpty() && reals.isEmpty()) {
throw new IllegalArgumentException("Refusing to remove all reals from load balancer " + id);
}
var instance = new LoadBalancerInstance(
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/SharedLoadBalancerService.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/SharedLoadBalancerService.java
index 87b7c73386e..331ffe7e202 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/SharedLoadBalancerService.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/SharedLoadBalancerService.java
@@ -32,7 +32,7 @@ public class SharedLoadBalancerService implements LoadBalancerService {
}
@Override
- public LoadBalancerInstance create(ApplicationId application, ClusterSpec.Id cluster, Set<Real> reals) {
+ public LoadBalancerInstance create(ApplicationId application, ClusterSpec.Id cluster, Set<Real> reals, boolean force) {
final var proxyNodes = nodeRepository.getNodes(NodeType.proxy);
proxyNodes.sort(hostnameComparator);
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java
index d0dc090bc74..52e7a28acc8 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java
@@ -76,7 +76,7 @@ public class NodeRepositoryMaintenance extends AbstractComponent {
metricsReporter = new MetricsReporter(nodeRepository, metric, orchestrator, serviceMonitor, periodicApplicationMaintainer::pendingDeployments, durationFromEnv("metrics_interval").orElse(defaults.metricsInterval));
infrastructureProvisioner = new InfrastructureProvisioner(nodeRepository, infraDeployer, durationFromEnv("infrastructure_provision_interval").orElse(defaults.infrastructureProvisionInterval));
loadBalancerExpirer = provisionServiceProvider.getLoadBalancerService().map(lbService ->
- new LoadBalancerExpirer(nodeRepository, durationFromEnv("load_balancer_expiry").orElse(defaults.loadBalancerExpiry), lbService));
+ new LoadBalancerExpirer(nodeRepository, durationFromEnv("load_balancer_expirer_interval").orElse(defaults.loadBalancerExpirerInterval), lbService));
hostProvisionMaintainer = provisionServiceProvider.getHostProvisioner().map(hostProvisioner ->
new HostProvisionMaintainer(nodeRepository, durationFromEnv("host_provisioner_interval").orElse(defaults.hostProvisionerInterval), hostProvisioner, flagSource));
hostDeprovisionMaintainer = provisionServiceProvider.getHostProvisioner().map(hostProvisioner ->
@@ -143,7 +143,7 @@ public class NodeRepositoryMaintenance extends AbstractComponent {
private final Duration metricsInterval;
private final Duration retiredInterval;
private final Duration infrastructureProvisionInterval;
- private final Duration loadBalancerExpiry;
+ private final Duration loadBalancerExpirerInterval;
private final Duration hostProvisionerInterval;
private final Duration hostDeprovisionerInterval;
@@ -161,7 +161,7 @@ public class NodeRepositoryMaintenance extends AbstractComponent {
metricsInterval = Duration.ofMinutes(1);
infrastructureProvisionInterval = Duration.ofMinutes(1);
throttlePolicy = NodeFailer.ThrottlePolicy.hosted;
- loadBalancerExpiry = Duration.ofMinutes(10);
+ loadBalancerExpirerInterval = Duration.ofMinutes(10);
reservationExpiry = Duration.ofMinutes(20); // Need to be long enough for deployment to be finished for all config model versions
hostProvisionerInterval = Duration.ofMinutes(5);
hostDeprovisionerInterval = Duration.ofMinutes(5);
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java
index 61ca19a4cb9..c68e086dfb9 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java
@@ -107,7 +107,7 @@ public class CuratorDatabaseClient {
CuratorTransaction curatorTransaction = curatorDatabase.newCuratorTransactionIn(transaction);
for (Node node : nodes) {
if (node.state() != expectedState)
- throw new IllegalArgumentException(node + " is not in the " + node.state() + " state");
+ throw new IllegalArgumentException(node + " is not in the " + expectedState + " state");
node = node.with(node.history().recordStateTransition(null, expectedState, Agent.system, clock.instant()));
curatorTransaction.add(CuratorOperations.create(toPath(node).getAbsolute(), nodeSerializer.toJson(node)));
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java
index ca7ee1b13a1..0828f3369a2 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java
@@ -103,10 +103,12 @@ public class LoadBalancerProvisioner {
try (var loadBalancersLock = db.lockLoadBalancers()) {
var id = new LoadBalancerId(application, clusterId);
var now = nodeRepository.clock().instant();
- var instance = create(application, clusterId, allocatedContainers(application, clusterId));
var loadBalancer = db.readLoadBalancers().get(id);
+ if (loadBalancer == null && activate) return; // Nothing to activate as this load balancer was never prepared
+
+ var force = loadBalancer != null && loadBalancer.state() != LoadBalancer.State.active;
+ var instance = create(application, clusterId, allocatedContainers(application, clusterId), force);
if (loadBalancer == null) {
- if (activate) return; // Nothing to activate as this load balancer was never prepared
loadBalancer = new LoadBalancer(id, instance, LoadBalancer.State.reserved, now);
} else {
var newState = activate ? LoadBalancer.State.active : loadBalancer.state();
@@ -117,7 +119,7 @@ public class LoadBalancerProvisioner {
}
}
- private LoadBalancerInstance create(ApplicationId application, ClusterSpec.Id cluster, List<Node> nodes) {
+ private LoadBalancerInstance create(ApplicationId application, ClusterSpec.Id cluster, List<Node> nodes, boolean force) {
Map<HostName, Set<String>> hostnameToIpAdresses = nodes.stream()
.collect(Collectors.toMap(node -> HostName.from(node.hostname()),
this::reachableIpAddresses));
@@ -126,12 +128,12 @@ public class LoadBalancerProvisioner {
ipAddresses.forEach(ipAddress -> reals.add(new Real(hostname, ipAddress)));
});
log.log(LogLevel.INFO, "Creating load balancer for " + cluster + " in " + application.toShortString() +
- ", targeting: " + nodes);
+ ", targeting: " + reals);
try {
- return service.create(application, cluster, reals);
+ return service.create(application, cluster, reals, force);
} catch (Exception e) {
throw new LoadBalancerServiceException("Failed to (re)configure load balancer for " + cluster + " in " +
- application + ", targeting: " + nodes + ". The operation will be " +
+ application + ", targeting: " + reals + ". The operation will be " +
"retried on next deployment", e);
}
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/lb/SharedLoadBalancerServiceTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/lb/SharedLoadBalancerServiceTest.java
index 40c307c6bef..5344fbc3c5f 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/lb/SharedLoadBalancerServiceTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/lb/SharedLoadBalancerServiceTest.java
@@ -29,7 +29,7 @@ public class SharedLoadBalancerServiceTest {
@Test
public void test_create_lb() {
tester.makeReadyNodes(2, "default", NodeType.proxy);
- final var lb = loadBalancerService.create(applicationId, clusterId, reals);
+ final var lb = loadBalancerService.create(applicationId, clusterId, reals, false);
assertEquals(HostName.from("host-1.yahoo.com"), lb.hostname());
assertEquals(Optional.empty(), lb.dnsZone());
@@ -39,7 +39,7 @@ public class SharedLoadBalancerServiceTest {
@Test(expected = IllegalStateException.class)
public void test_exception_on_missing_proxies() {
- loadBalancerService.create(applicationId, clusterId, reals);
+ loadBalancerService.create(applicationId, clusterId, reals, false);
}
@Test
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisionerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisionerTest.java
index 0b3c3d209be..77273f98f76 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisionerTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisionerTest.java
@@ -9,23 +9,28 @@ import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.HostSpec;
import com.yahoo.config.provision.NodeResources;
+import com.yahoo.config.provision.NodeType;
import com.yahoo.transaction.NestedTransaction;
import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.lb.LoadBalancer;
import com.yahoo.vespa.hosted.provision.lb.LoadBalancerInstance;
import com.yahoo.vespa.hosted.provision.lb.Real;
import com.yahoo.vespa.hosted.provision.node.Agent;
+import com.yahoo.vespa.hosted.provision.node.IP;
import org.junit.Test;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
@@ -37,6 +42,8 @@ public class LoadBalancerProvisionerTest {
private final ApplicationId app1 = ApplicationId.from("tenant1", "application1", "default");
private final ApplicationId app2 = ApplicationId.from("tenant2", "application2", "default");
+ private final ApplicationId infraApp1 = ApplicationId.from("vespa", "tenant-host", "default");
+
private ProvisioningTester tester = new ProvisioningTester.Builder().build();
@Test
@@ -131,19 +138,94 @@ public class LoadBalancerProvisionerTest {
.orElseThrow());
}
+ @Test
+ public void provision_load_balancers_with_dynamic_node_provisioning() {
+ var nodes = prepare(app1, Capacity.fromCount(2, new NodeResources(1, 1, 1), false, true),
+ true,
+ clusterRequest(ClusterSpec.Type.container, ClusterSpec.Id.from("qrs")));
+ Supplier<LoadBalancer> lb = () -> tester.nodeRepository().loadBalancers().owner(app1).asList().get(0);
+ assertTrue("Load balancer provisioned with empty reals", tester.loadBalancerService().instances().get(lb.get().id()).reals().isEmpty());
+ assignIps(tester.nodeRepository().getNodes(app1));
+ tester.activate(app1, nodes);
+ assertFalse("Load balancer is reconfigured with reals", tester.loadBalancerService().instances().get(lb.get().id()).reals().isEmpty());
+
+ // Application is removed, nodes are deleted and load balancer is deactivated
+ NestedTransaction removeTransaction = new NestedTransaction();
+ tester.provisioner().remove(removeTransaction, app1);
+ removeTransaction.commit();
+ tester.nodeRepository().database().removeNodes(tester.nodeRepository().getNodes());
+ assertTrue("Nodes are deleted", tester.nodeRepository().getNodes().isEmpty());
+ assertSame("Load balancer is deactivated", LoadBalancer.State.inactive, lb.get().state());
+
+ // Application is redeployed
+ nodes = prepare(app1, Capacity.fromCount(2, new NodeResources(1, 1, 1), false, true),
+ true,
+ clusterRequest(ClusterSpec.Type.container, ClusterSpec.Id.from("qrs")));
+ assertTrue("Load balancer is reconfigured with empty reals", tester.loadBalancerService().instances().get(lb.get().id()).reals().isEmpty());
+ assignIps(tester.nodeRepository().getNodes(app1));
+ tester.activate(app1, nodes);
+ assertFalse("Load balancer is reconfigured with reals", tester.loadBalancerService().instances().get(lb.get().id()).reals().isEmpty());
+ }
+
+ @Test
+ public void does_not_provision_load_balancers_for_non_tenant_node_type() {
+ tester.activate(infraApp1, prepare(infraApp1, Capacity.fromRequiredNodeType(NodeType.host),
+ false,
+ clusterRequest(ClusterSpec.Type.container,
+ ClusterSpec.Id.from("tenant-host"))));
+ assertTrue("No load balancer provisioned", tester.loadBalancerService().instances().isEmpty());
+ assertEquals(List.of(), tester.nodeRepository().loadBalancers().owner(infraApp1).asList());
+ }
+
+ @Test
+ public void does_not_provision_load_balancers_for_non_container_cluster() {
+ tester.activate(app1, prepare(app1, clusterRequest(ClusterSpec.Type.content,
+ ClusterSpec.Id.from("tenant-host"))));
+ assertTrue("No load balancer provisioned", tester.loadBalancerService().instances().isEmpty());
+ assertEquals(List.of(), tester.nodeRepository().loadBalancers().owner(app1).asList());
+ }
+
private void dirtyNodesOf(ApplicationId application) {
tester.nodeRepository().setDirty(tester.nodeRepository().getNodes(application), Agent.system, this.getClass().getSimpleName());
}
private Set<HostSpec> prepare(ApplicationId application, ClusterSpec... specs) {
- tester.makeReadyNodes(specs.length * 2, "d-1-1-1");
+ return prepare(application, Capacity.fromCount(2, new NodeResources(1, 1, 1), false, true), false, specs);
+ }
+
+ private Set<HostSpec> prepare(ApplicationId application, Capacity capacity, boolean dynamicDockerNodes, ClusterSpec... specs) {
+ if (dynamicDockerNodes) {
+ makeDynamicDockerNodes(specs.length * 2, capacity.type());
+ } else {
+ tester.makeReadyNodes(specs.length * 2, "d-1-1-1", capacity.type());
+ }
Set<HostSpec> allNodes = new LinkedHashSet<>();
for (ClusterSpec spec : specs) {
- allNodes.addAll(tester.prepare(application, spec, Capacity.fromCount(2, new NodeResources(1, 1, 1), false, true), 1, false));
+ allNodes.addAll(tester.prepare(application, spec, capacity, 1, false));
}
return allNodes;
}
+ private void makeDynamicDockerNodes(int n, NodeType nodeType) {
+ List<Node> nodes = new ArrayList<>(n);
+ for (int i = 1; i <= n; i++) {
+ var node = Node.createDockerNode(Set.of(), Set.of(), "node" + i, Optional.empty(),
+ NodeResources.fromLegacyName("d-1-1-1"), nodeType);
+ nodes.add(node);
+ }
+ nodes = tester.nodeRepository().database().addNodesInState(nodes, Node.State.reserved);
+ nodes = tester.nodeRepository().setDirty(nodes, Agent.system, getClass().getSimpleName());
+ tester.nodeRepository().setReady(nodes, Agent.system, getClass().getSimpleName());
+ }
+
+ private void assignIps(List<Node> nodes) {
+ try (var lock = tester.nodeRepository().lockAllocation()) {
+ for (int i = 0; i < nodes.size(); i++) {
+ tester.nodeRepository().write(nodes.get(i).with(IP.Config.EMPTY.with(Set.of("127.0.0." + i))), lock);
+ }
+ }
+ }
+
private static ClusterSpec clusterRequest(ClusterSpec.Type type, ClusterSpec.Id id) {
return ClusterSpec.request(type, id, Version.fromString("6.42"), false);
}
diff --git a/parent/pom.xml b/parent/pom.xml
index 1855553bc20..e2012214d89 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -515,6 +515,16 @@
<version>${curator.version}</version>
</dependency>
<dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-engine</artifactId>
+ <version>${junit.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.junit.vintage</groupId>
+ <artifactId>junit-vintage-engine</artifactId>
+ <version>${junit.version}</version>
+ </dependency>
+ <dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
@@ -765,7 +775,8 @@
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<test.hide>true</test.hide>
<doclint>all</doclint>
- <surefire.version>2.21.0</surefire.version>
+ <surefire.version>2.22.0</surefire.version>
+ <junit.version>5.4.2</junit.version>
<protobuf.version>3.7.0</protobuf.version>
</properties>
diff --git a/tenant-base/pom.xml b/tenant-base/pom.xml
index b829089465f..10c42ba8acd 100644
--- a/tenant-base/pom.xml
+++ b/tenant-base/pom.xml
@@ -35,7 +35,8 @@
<vespaversion>${project.version}</vespaversion>
<target_jdk_version>11</target_jdk_version>
<compiler_plugin_version>3.8.0</compiler_plugin_version>
- <surefire_version>2.22.0</surefire_version> <!-- NOTE bjorncs 15.06.2017: Version 2.20 has OoM issues -->
+ <surefire_version>2.22.0</surefire_version>
+ <junit.jupiter.version>5.4.2</junit.jupiter.version>
<endpoint>https://api.vespa-external.aws.oath.cloud:4443</endpoint>
</properties>
@@ -72,6 +73,13 @@
<version>${vespaversion}</version>
<scope>test</scope>
</dependency>
+
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-engine</artifactId>
+ <version>${junit.jupiter.version}</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<profiles>
diff --git a/tenant-cd/src/main/java/ai/vespa/hosted/cd/ProductionTest.java b/tenant-cd/src/main/java/ai/vespa/hosted/cd/ProductionTest.java
index 6cf5fb07f58..0dfeab5d327 100644
--- a/tenant-cd/src/main/java/ai/vespa/hosted/cd/ProductionTest.java
+++ b/tenant-cd/src/main/java/ai/vespa/hosted/cd/ProductionTest.java
@@ -16,6 +16,9 @@ package ai.vespa.hosted.cd;
*/
public interface ProductionTest {
+ /** Use with JUnit 5 @Tag to have this run in the production jobs in the pipeline. */
+ String name = "ai.vespa.hosted.cd.ProductionTest";
+
// Want to verify metrics (Vespa).
// Want to verify external metrics (YAMAS, other).
// May want to verify search gives expected results.
diff --git a/tenant-cd/src/main/java/ai/vespa/hosted/cd/StagingTest.java b/tenant-cd/src/main/java/ai/vespa/hosted/cd/StagingTest.java
index 40377da30ef..6e1487ced0f 100644
--- a/tenant-cd/src/main/java/ai/vespa/hosted/cd/StagingTest.java
+++ b/tenant-cd/src/main/java/ai/vespa/hosted/cd/StagingTest.java
@@ -17,6 +17,10 @@ package ai.vespa.hosted.cd;
* @author jonmv
*/
public interface StagingTest {
+
+ /** Use with JUnit 5 @Tag to have this run in the staging test job in the pipeline. */
+ String name = "ai.vespa.hosted.cd.StagingTest";
+
// Want to verify documents are not damaged by upgrade.
// May want to verify metrics during upgrade.
}
diff --git a/tenant-cd/src/main/java/ai/vespa/hosted/cd/SystemTest.java b/tenant-cd/src/main/java/ai/vespa/hosted/cd/SystemTest.java
index c67d86fc8de..f2f06d53515 100644
--- a/tenant-cd/src/main/java/ai/vespa/hosted/cd/SystemTest.java
+++ b/tenant-cd/src/main/java/ai/vespa/hosted/cd/SystemTest.java
@@ -15,6 +15,10 @@ package ai.vespa.hosted.cd;
* @author jonmv
*/
public interface SystemTest {
+
+ /** Use with JUnit 5 @Tag to have this run in the system test job in the pipeline. */
+ String name = "ai.vespa.hosted.cd.SystemTest";
+
// Want to feed some documents.
// Want to verify document processing and routing is as expected.
// Want to check recall on those documents.
diff --git a/vespa-testrunner-components/src/main/java/com/yahoo/vespa/hosted/testrunner/PomXmlGenerator.java b/vespa-testrunner-components/src/main/java/com/yahoo/vespa/hosted/testrunner/PomXmlGenerator.java
index 221ff2bc9a4..4ebabe63c1d 100644
--- a/vespa-testrunner-components/src/main/java/com/yahoo/vespa/hosted/testrunner/PomXmlGenerator.java
+++ b/vespa-testrunner-components/src/main/java/com/yahoo/vespa/hosted/testrunner/PomXmlGenerator.java
@@ -36,16 +36,22 @@ public class PomXmlGenerator {
" <version>1.0.0</version>\n" +
"\n" +
" <properties>\n" +
- " <maven_version>4.12</maven_version>\n" +
+ " <junit_version>5.4.2</junit_version>\n" +
" <surefire_version>2.22.0</surefire_version>\n" +
"%PROPERTIES%" +
" </properties>\n" +
"\n" +
" <dependencies>\n" +
" <dependency>\n" +
- " <groupId>junit</groupId>\n" +
- " <artifactId>junit</artifactId>\n" +
- " <version>${maven_version}</version>\n" +
+ " <groupId>org.junit.vintage</groupId>\n" +
+ " <artifactId>junit-vintage-engine</artifactId>\n" +
+ " <version>${junit_version}</version>\n" +
+ " <scope>test</scope>\n" +
+ " </dependency>\n" +
+ " <dependency>\n" +
+ " <groupId>org.junit.jupiter</groupId>\n" +
+ " <artifactId>junit-jupiter-engine</artifactId>\n" +
+ " <version>${junit_version}</version>\n" +
" <scope>test</scope>\n" +
" </dependency>\n" +
"%DEPENDENCIES%" +
diff --git a/vespa-testrunner-components/src/test/resources/pom.xml_system_tests b/vespa-testrunner-components/src/test/resources/pom.xml_system_tests
index 86c36afd636..263bd27a4f3 100644
--- a/vespa-testrunner-components/src/test/resources/pom.xml_system_tests
+++ b/vespa-testrunner-components/src/test/resources/pom.xml_system_tests
@@ -6,7 +6,7 @@
<version>1.0.0</version>
<properties>
- <maven_version>4.12</maven_version>
+ <junit_version>5.4.2</junit_version>
<surefire_version>2.22.0</surefire_version>
<my-comp.jar.path>components/my-comp.jar</my-comp.jar.path>
<main.jar.path>main.jar</main.jar.path>
@@ -14,9 +14,15 @@
<dependencies>
<dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>${maven_version}</version>
+ <groupId>org.junit.vintage</groupId>
+ <artifactId>junit-vintage-engine</artifactId>
+ <version>${junit_version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-engine</artifactId>
+ <version>${junit_version}</version>
<scope>test</scope>
</dependency>
<dependency>