summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--client/README.md6
-rw-r--r--config-model-api/abi-spec.json506
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/model/api/EndpointCertificateMetadata.java35
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/model/api/EndpointCertificateSecrets.java30
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java3
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/model/api/TlsSecrets.java5
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/model/api/package-info.java2
-rw-r--r--config-model/src/main/java/com/yahoo/config/model/deploy/DeployState.java5
-rw-r--r--config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java11
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/multifieldresolver/RankProfileTypeSettingsProcessor.java2
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/application/validation/EndpointCertificateSecretsValidator.java (renamed from config-model/src/main/java/com/yahoo/vespa/model/application/validation/TlsSecretsValidator.java)6
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/application/validation/Validation.java2
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java8
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/HostedSslConnectorFactory.java16
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java10
-rw-r--r--config-model/src/test/derived/tensor2/first.sd8
-rw-r--r--config-model/src/test/derived/tensor2/rank-profiles.cfg14
-rw-r--r--config-model/src/test/derived/tensor2/second.sd7
-rw-r--r--config-model/src/test/java/com/yahoo/searchdefinition/derived/ExportingTestCase.java12
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/application/validation/EndpointCertificateSecretsValidatorTest.java (renamed from config-model/src/test/java/com/yahoo/vespa/model/application/validation/TlsSecretsValidatorTest.java)18
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java8
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/container/xml/JettyContainerModelBuilderTest.java4
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java26
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java12
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ActivatedModelsBuilder.java8
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/PrepareParams.java23
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java36
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/tenant/EndpointCertificateMetadataSerializer.java55
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/tenant/EndpointCertificateMetadataStore.java65
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/tenant/EndpointCertificateRetriever.java56
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TlsSecretsKeys.java136
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java13
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/MockSecretStore.java12
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionPreparerTest.java52
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/tenant/EndpointCertificateMetadataStoreTest.java90
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TlsSecretsKeysTest.java73
-rw-r--r--container-core/abi-spec.json2
-rw-r--r--container-dependency-versions/pom.xml2
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServer.java5
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java8
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java29
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java26
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java9
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobMetrics.java64
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPolicies.java233
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java29
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializer.java68
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ZoneRoutingPolicySerializer.java44
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java21
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/GlobalRouting.java85
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingId.java (renamed from controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/RoutingId.java)10
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicies.java288
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicy.java (renamed from controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/RoutingPolicy.java)77
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicyId.java57
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/Status.java53
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/ZoneRoutingPolicy.java49
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VespaVersion.java6
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java7
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/ApplicationPackageBuilder.java14
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java36
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunnerTest.java23
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java6
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunnerTest.java43
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializerTest.java55
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ZoneRoutingPolicySerializerTest.java29
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java19
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/RoutingPoliciesTest.java (renamed from controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPoliciesTest.java)218
-rw-r--r--eval/src/tests/ann/sift_benchmark.cpp9
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/Flags.java6
-rw-r--r--security-utils/src/main/java/com/yahoo/security/X509CertificateUtils.java21
-rw-r--r--security-utils/src/test/java/com/yahoo/security/X509CertificateUtilsTest.java16
-rw-r--r--zkfacade/abi-spec.json2
72 files changed, 2281 insertions, 763 deletions
diff --git a/client/README.md b/client/README.md
index ddea3591e38..d08b19a1a12 100644
--- a/client/README.md
+++ b/client/README.md
@@ -1,12 +1,12 @@
# vespa_query_dsl
This lib is used for composing vespa YQL queries
-referece: https://docs.vespa.ai/documentation/reference/query-language-reference.html
+Reference: https://docs.vespa.ai/documentation/reference/query-language-reference.html
# usage
-please refer the unit test:
+Please refer to the unit test:
-https://github.com/vespa-engine/vespa/blob/master/client/src/test/groovy/com/yahoo/vespa/client/dsl/QTest.groovy
+https://github.com/vespa-engine/vespa/tree/master/client/src/test/groovy/ai/vespa/client/dsl/QTest.groovy
# todos
- [ ] support `predicate` (https://docs.vespa.ai/documentation/predicate-fields.html)
diff --git a/config-model-api/abi-spec.json b/config-model-api/abi-spec.json
index 6b466c65cdb..0f5a5e6271d 100644
--- a/config-model-api/abi-spec.json
+++ b/config-model-api/abi-spec.json
@@ -605,5 +605,511 @@
"public static final com.yahoo.config.application.api.ValidationOverrides empty",
"public static final com.yahoo.config.application.api.ValidationOverrides all"
]
+ },
+ "com.yahoo.config.model.api.ApplicationInfo": {
+ "superClass": "java.lang.Object",
+ "interfaces": [],
+ "attributes": [
+ "public"
+ ],
+ "methods": [
+ "public void <init>(com.yahoo.config.provision.ApplicationId, long, com.yahoo.config.model.api.Model)",
+ "public com.yahoo.config.provision.ApplicationId getApplicationId()",
+ "public long getGeneration()",
+ "public com.yahoo.config.model.api.Model getModel()",
+ "public java.lang.String toString()"
+ ],
+ "fields": []
+ },
+ "com.yahoo.config.model.api.ConfigChangeAction$Type": {
+ "superClass": "java.lang.Enum",
+ "interfaces": [],
+ "attributes": [
+ "public",
+ "final",
+ "enum"
+ ],
+ "methods": [
+ "public static com.yahoo.config.model.api.ConfigChangeAction$Type[] values()",
+ "public static com.yahoo.config.model.api.ConfigChangeAction$Type valueOf(java.lang.String)",
+ "public java.lang.String toString()"
+ ],
+ "fields": [
+ "public static final enum com.yahoo.config.model.api.ConfigChangeAction$Type RESTART",
+ "public static final enum com.yahoo.config.model.api.ConfigChangeAction$Type REFEED"
+ ]
+ },
+ "com.yahoo.config.model.api.ConfigChangeAction": {
+ "superClass": "java.lang.Object",
+ "interfaces": [],
+ "attributes": [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods": [
+ "public abstract com.yahoo.config.model.api.ConfigChangeAction$Type getType()",
+ "public abstract java.lang.String getMessage()",
+ "public abstract java.util.List getServices()",
+ "public abstract boolean allowed()"
+ ],
+ "fields": []
+ },
+ "com.yahoo.config.model.api.ConfigChangeRefeedAction": {
+ "superClass": "java.lang.Object",
+ "interfaces": [
+ "com.yahoo.config.model.api.ConfigChangeAction"
+ ],
+ "attributes": [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods": [
+ "public com.yahoo.config.model.api.ConfigChangeAction$Type getType()",
+ "public java.lang.String name()",
+ "public abstract java.lang.String getDocumentType()"
+ ],
+ "fields": []
+ },
+ "com.yahoo.config.model.api.ConfigChangeRestartAction": {
+ "superClass": "java.lang.Object",
+ "interfaces": [
+ "com.yahoo.config.model.api.ConfigChangeAction"
+ ],
+ "attributes": [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods": [
+ "public com.yahoo.config.model.api.ConfigChangeAction$Type getType()",
+ "public boolean allowed()"
+ ],
+ "fields": []
+ },
+ "com.yahoo.config.model.api.ConfigDefinitionRepo": {
+ "superClass": "java.lang.Object",
+ "interfaces": [],
+ "attributes": [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods": [
+ "public abstract java.util.Map getConfigDefinitions()",
+ "public abstract com.yahoo.vespa.config.buildergen.ConfigDefinition get(com.yahoo.vespa.config.ConfigDefinitionKey)"
+ ],
+ "fields": []
+ },
+ "com.yahoo.config.model.api.ConfigDefinitionStore": {
+ "superClass": "java.lang.Object",
+ "interfaces": [],
+ "attributes": [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods": [
+ "public abstract com.yahoo.vespa.config.ConfigDefinition getConfigDefinition(com.yahoo.vespa.config.ConfigDefinitionKey)"
+ ],
+ "fields": []
+ },
+ "com.yahoo.config.model.api.ConfigModelPlugin": {
+ "superClass": "java.lang.Object",
+ "interfaces": [],
+ "attributes": [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods": [],
+ "fields": []
+ },
+ "com.yahoo.config.model.api.ConfigServerSpec": {
+ "superClass": "java.lang.Object",
+ "interfaces": [],
+ "attributes": [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods": [
+ "public abstract java.lang.String getHostName()",
+ "public abstract int getConfigServerPort()",
+ "public int getHttpPort()",
+ "public abstract int getZooKeeperPort()"
+ ],
+ "fields": []
+ },
+ "com.yahoo.config.model.api.ContainerEndpoint": {
+ "superClass": "java.lang.Object",
+ "interfaces": [],
+ "attributes": [
+ "public"
+ ],
+ "methods": [
+ "public void <init>(java.lang.String, java.util.List)",
+ "public java.lang.String clusterId()",
+ "public java.util.List names()",
+ "public boolean equals(java.lang.Object)",
+ "public int hashCode()",
+ "public java.lang.String toString()"
+ ],
+ "fields": []
+ },
+ "com.yahoo.config.model.api.EndpointCertificateMetadata": {
+ "superClass": "java.lang.Object",
+ "interfaces": [],
+ "attributes": [
+ "public"
+ ],
+ "methods": [
+ "public void <init>(java.lang.String, java.lang.String, int)",
+ "public java.lang.String keyName()",
+ "public java.lang.String certName()",
+ "public int version()",
+ "public java.lang.String toString()"
+ ],
+ "fields": []
+ },
+ "com.yahoo.config.model.api.EndpointCertificateSecrets": {
+ "superClass": "java.lang.Object",
+ "interfaces": [],
+ "attributes": [
+ "public"
+ ],
+ "methods": [
+ "public void <init>(java.lang.String, java.lang.String)",
+ "public java.lang.String certificate()",
+ "public java.lang.String key()",
+ "public boolean isMissing()"
+ ],
+ "fields": [
+ "public static final com.yahoo.config.model.api.EndpointCertificateSecrets MISSING"
+ ]
+ },
+ "com.yahoo.config.model.api.FileDistribution": {
+ "superClass": "java.lang.Object",
+ "interfaces": [],
+ "attributes": [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods": [
+ "public abstract void startDownload(java.lang.String, int, java.util.Set)",
+ "public abstract java.io.File getFileReferencesDir()"
+ ],
+ "fields": []
+ },
+ "com.yahoo.config.model.api.HostInfo": {
+ "superClass": "java.lang.Object",
+ "interfaces": [],
+ "attributes": [
+ "public"
+ ],
+ "methods": [
+ "public void <init>(java.lang.String, java.util.Collection)",
+ "public java.lang.String getHostname()",
+ "public java.util.Collection getServices()",
+ "public boolean equals(java.lang.Object)",
+ "public int hashCode()"
+ ],
+ "fields": []
+ },
+ "com.yahoo.config.model.api.HostProvisioner": {
+ "superClass": "java.lang.Object",
+ "interfaces": [],
+ "attributes": [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods": [
+ "public abstract com.yahoo.config.provision.HostSpec allocateHost(java.lang.String)",
+ "public abstract java.util.List prepare(com.yahoo.config.provision.ClusterSpec, com.yahoo.config.provision.Capacity, int, com.yahoo.config.provision.ProvisionLogger)"
+ ],
+ "fields": []
+ },
+ "com.yahoo.config.model.api.Model": {
+ "superClass": "java.lang.Object",
+ "interfaces": [],
+ "attributes": [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods": [
+ "public abstract com.yahoo.vespa.config.ConfigPayload getConfig(com.yahoo.vespa.config.ConfigKey, com.yahoo.vespa.config.buildergen.ConfigDefinition)",
+ "public abstract java.util.Set allConfigsProduced()",
+ "public abstract java.util.Collection getHosts()",
+ "public abstract java.util.Set allConfigIds()",
+ "public abstract void distributeFiles(com.yahoo.config.model.api.FileDistribution)",
+ "public abstract java.util.Set fileReferences()",
+ "public abstract com.yahoo.config.provision.AllocatedHosts allocatedHosts()",
+ "public boolean allowModelVersionMismatch(java.time.Instant)",
+ "public boolean skipOldConfigModels(java.time.Instant)",
+ "public com.yahoo.component.Version version()"
+ ],
+ "fields": []
+ },
+ "com.yahoo.config.model.api.ModelContext$Properties": {
+ "superClass": "java.lang.Object",
+ "interfaces": [],
+ "attributes": [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods": [
+ "public abstract boolean multitenant()",
+ "public abstract com.yahoo.config.provision.ApplicationId applicationId()",
+ "public abstract java.util.List configServerSpecs()",
+ "public abstract com.yahoo.config.provision.HostName loadBalancerName()",
+ "public abstract java.net.URI ztsUrl()",
+ "public abstract java.lang.String athenzDnsSuffix()",
+ "public abstract boolean hostedVespa()",
+ "public abstract com.yahoo.config.provision.Zone zone()",
+ "public abstract java.util.Set endpoints()",
+ "public abstract boolean isBootstrap()",
+ "public abstract boolean isFirstTimeDeployment()",
+ "public boolean useDedicatedNodeForLogserver()",
+ "public abstract boolean useAdaptiveDispatch()",
+ "public java.util.Optional tlsSecrets()",
+ "public java.util.Optional endpointCertificateSecrets()",
+ "public abstract double defaultTermwiseLimit()",
+ "public abstract boolean useBucketSpaceMetric()"
+ ],
+ "fields": []
+ },
+ "com.yahoo.config.model.api.ModelContext": {
+ "superClass": "java.lang.Object",
+ "interfaces": [],
+ "attributes": [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods": [
+ "public abstract com.yahoo.config.application.api.ApplicationPackage applicationPackage()",
+ "public abstract java.util.Optional previousModel()",
+ "public abstract java.util.Optional permanentApplicationPackage()",
+ "public abstract java.util.Optional hostProvisioner()",
+ "public abstract com.yahoo.config.application.api.DeployLogger deployLogger()",
+ "public abstract com.yahoo.config.model.api.ConfigDefinitionRepo configDefinitionRepo()",
+ "public abstract com.yahoo.config.application.api.FileRegistry getFileRegistry()",
+ "public abstract com.yahoo.config.model.api.ModelContext$Properties properties()",
+ "public java.util.Optional appDir()",
+ "public abstract com.yahoo.component.Version modelVespaVersion()",
+ "public abstract com.yahoo.component.Version wantedNodeVespaVersion()"
+ ],
+ "fields": []
+ },
+ "com.yahoo.config.model.api.ModelCreateResult": {
+ "superClass": "java.lang.Object",
+ "interfaces": [],
+ "attributes": [
+ "public"
+ ],
+ "methods": [
+ "public void <init>(com.yahoo.config.model.api.Model, java.util.List)",
+ "public com.yahoo.config.model.api.Model getModel()",
+ "public java.util.List getConfigChangeActions()"
+ ],
+ "fields": []
+ },
+ "com.yahoo.config.model.api.ModelFactory": {
+ "superClass": "java.lang.Object",
+ "interfaces": [],
+ "attributes": [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods": [
+ "public abstract com.yahoo.component.Version version()",
+ "public abstract com.yahoo.config.model.api.Model createModel(com.yahoo.config.model.api.ModelContext)",
+ "public abstract com.yahoo.config.model.api.ModelCreateResult createAndValidateModel(com.yahoo.config.model.api.ModelContext, com.yahoo.config.model.api.ValidationParameters)"
+ ],
+ "fields": []
+ },
+ "com.yahoo.config.model.api.ModelState": {
+ "superClass": "java.lang.Object",
+ "interfaces": [],
+ "attributes": [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods": [
+ "public abstract com.yahoo.config.model.api.Model getModel()"
+ ],
+ "fields": []
+ },
+ "com.yahoo.config.model.api.PortInfo": {
+ "superClass": "java.lang.Object",
+ "interfaces": [],
+ "attributes": [
+ "public"
+ ],
+ "methods": [
+ "public void <init>(int, java.util.Collection)",
+ "public int getPort()",
+ "public java.util.Collection getTags()",
+ "public boolean equals(java.lang.Object)",
+ "public int hashCode()"
+ ],
+ "fields": []
+ },
+ "com.yahoo.config.model.api.ServiceInfo": {
+ "superClass": "java.lang.Object",
+ "interfaces": [],
+ "attributes": [
+ "public"
+ ],
+ "methods": [
+ "public void <init>(java.lang.String, java.lang.String, java.util.Collection, java.util.Map, java.lang.String, java.lang.String)",
+ "public java.lang.String getServiceName()",
+ "public java.lang.String getConfigId()",
+ "public java.lang.String getServiceType()",
+ "public java.util.Optional getProperty(java.lang.String)",
+ "public java.util.Collection getPorts()",
+ "public java.lang.String getHostName()",
+ "public boolean equals(java.lang.Object)",
+ "public int hashCode()"
+ ],
+ "fields": []
+ },
+ "com.yahoo.config.model.api.SuperModel": {
+ "superClass": "java.lang.Object",
+ "interfaces": [],
+ "attributes": [
+ "public"
+ ],
+ "methods": [
+ "public void <init>()",
+ "public void <init>(java.util.Map)",
+ "public java.util.Map getModelsPerTenant()",
+ "public java.util.Map getModels()",
+ "public java.util.List getAllApplicationInfos()",
+ "public java.util.Optional getApplicationInfo(com.yahoo.config.provision.ApplicationId)",
+ "public com.yahoo.config.model.api.SuperModel cloneAndSetApplication(com.yahoo.config.model.api.ApplicationInfo)",
+ "public com.yahoo.config.model.api.SuperModel cloneAndRemoveApplication(com.yahoo.config.provision.ApplicationId)"
+ ],
+ "fields": []
+ },
+ "com.yahoo.config.model.api.SuperModelListener": {
+ "superClass": "java.lang.Object",
+ "interfaces": [],
+ "attributes": [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods": [
+ "public abstract void applicationActivated(com.yahoo.config.model.api.SuperModel, com.yahoo.config.model.api.ApplicationInfo)",
+ "public abstract void applicationRemoved(com.yahoo.config.model.api.SuperModel, com.yahoo.config.provision.ApplicationId)"
+ ],
+ "fields": []
+ },
+ "com.yahoo.config.model.api.SuperModelProvider": {
+ "superClass": "java.lang.Object",
+ "interfaces": [],
+ "attributes": [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods": [
+ "public abstract void registerListener(com.yahoo.config.model.api.SuperModelListener)",
+ "public abstract com.yahoo.config.model.api.SuperModel getSuperModel()"
+ ],
+ "fields": []
+ },
+ "com.yahoo.config.model.api.TlsSecrets": {
+ "superClass": "java.lang.Object",
+ "interfaces": [],
+ "attributes": [
+ "public"
+ ],
+ "methods": [
+ "public void <init>(java.lang.String, java.lang.String)",
+ "public void <init>(com.yahoo.config.model.api.EndpointCertificateSecrets)",
+ "public java.lang.String certificate()",
+ "public java.lang.String key()",
+ "public boolean isMissing()"
+ ],
+ "fields": [
+ "public static final com.yahoo.config.model.api.TlsSecrets MISSING"
+ ]
+ },
+ "com.yahoo.config.model.api.ValidationParameters$CheckRouting": {
+ "superClass": "java.lang.Enum",
+ "interfaces": [],
+ "attributes": [
+ "public",
+ "final",
+ "enum"
+ ],
+ "methods": [
+ "public static com.yahoo.config.model.api.ValidationParameters$CheckRouting[] values()",
+ "public static com.yahoo.config.model.api.ValidationParameters$CheckRouting valueOf(java.lang.String)"
+ ],
+ "fields": [
+ "public static final enum com.yahoo.config.model.api.ValidationParameters$CheckRouting TRUE",
+ "public static final enum com.yahoo.config.model.api.ValidationParameters$CheckRouting FALSE"
+ ]
+ },
+ "com.yahoo.config.model.api.ValidationParameters$FailOnIncompatibleChange": {
+ "superClass": "java.lang.Enum",
+ "interfaces": [],
+ "attributes": [
+ "public",
+ "final",
+ "enum"
+ ],
+ "methods": [
+ "public static com.yahoo.config.model.api.ValidationParameters$FailOnIncompatibleChange[] values()",
+ "public static com.yahoo.config.model.api.ValidationParameters$FailOnIncompatibleChange valueOf(java.lang.String)"
+ ],
+ "fields": [
+ "public static final enum com.yahoo.config.model.api.ValidationParameters$FailOnIncompatibleChange TRUE",
+ "public static final enum com.yahoo.config.model.api.ValidationParameters$FailOnIncompatibleChange FALSE"
+ ]
+ },
+ "com.yahoo.config.model.api.ValidationParameters$IgnoreValidationErrors": {
+ "superClass": "java.lang.Enum",
+ "interfaces": [],
+ "attributes": [
+ "public",
+ "final",
+ "enum"
+ ],
+ "methods": [
+ "public static com.yahoo.config.model.api.ValidationParameters$IgnoreValidationErrors[] values()",
+ "public static com.yahoo.config.model.api.ValidationParameters$IgnoreValidationErrors valueOf(java.lang.String)"
+ ],
+ "fields": [
+ "public static final enum com.yahoo.config.model.api.ValidationParameters$IgnoreValidationErrors TRUE",
+ "public static final enum com.yahoo.config.model.api.ValidationParameters$IgnoreValidationErrors FALSE"
+ ]
+ },
+ "com.yahoo.config.model.api.ValidationParameters": {
+ "superClass": "java.lang.Object",
+ "interfaces": [],
+ "attributes": [
+ "public"
+ ],
+ "methods": [
+ "public void <init>()",
+ "public void <init>(com.yahoo.config.model.api.ValidationParameters$IgnoreValidationErrors)",
+ "public void <init>(com.yahoo.config.model.api.ValidationParameters$CheckRouting)",
+ "public void <init>(com.yahoo.config.model.api.ValidationParameters$IgnoreValidationErrors, com.yahoo.config.model.api.ValidationParameters$FailOnIncompatibleChange, com.yahoo.config.model.api.ValidationParameters$CheckRouting)",
+ "public boolean ignoreValidationErrors()",
+ "public boolean failOnIncompatibleChanges()",
+ "public boolean checkRouting()"
+ ],
+ "fields": []
}
} \ No newline at end of file
diff --git a/config-model-api/src/main/java/com/yahoo/config/model/api/EndpointCertificateMetadata.java b/config-model-api/src/main/java/com/yahoo/config/model/api/EndpointCertificateMetadata.java
new file mode 100644
index 00000000000..a1fae9bb148
--- /dev/null
+++ b/config-model-api/src/main/java/com/yahoo/config/model/api/EndpointCertificateMetadata.java
@@ -0,0 +1,35 @@
+package com.yahoo.config.model.api;
+
+public class EndpointCertificateMetadata {
+
+ private final String keyName;
+ private final String certName;
+ private final int version;
+
+ public EndpointCertificateMetadata(String keyName, String certName, int version) {
+ this.keyName = keyName;
+ this.certName = certName;
+ this.version = version;
+ }
+
+ public String keyName() {
+ return keyName;
+ }
+
+ public String certName() {
+ return certName;
+ }
+
+ public int version() {
+ return version;
+ }
+
+ @Override
+ public String toString() {
+ return "EndpointCertificateMetadata{" +
+ "keyName='" + keyName + '\'' +
+ ", certName='" + certName + '\'' +
+ ", version=" + version +
+ '}';
+ }
+}
diff --git a/config-model-api/src/main/java/com/yahoo/config/model/api/EndpointCertificateSecrets.java b/config-model-api/src/main/java/com/yahoo/config/model/api/EndpointCertificateSecrets.java
new file mode 100644
index 00000000000..6fcbac4f422
--- /dev/null
+++ b/config-model-api/src/main/java/com/yahoo/config/model/api/EndpointCertificateSecrets.java
@@ -0,0 +1,30 @@
+// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.model.api;
+
+public class EndpointCertificateSecrets {
+ public static final EndpointCertificateSecrets MISSING = new EndpointCertificateSecrets();
+
+ private final String certificate;
+ private final String key;
+
+ private EndpointCertificateSecrets() {
+ this(null, null);
+ }
+
+ public EndpointCertificateSecrets(String certificate, String key) {
+ this.certificate = certificate;
+ this.key = key;
+ }
+
+ public String certificate() {
+ return certificate;
+ }
+
+ public String key() {
+ return key;
+ }
+
+ public boolean isMissing() {
+ return this == MISSING;
+ }
+}
diff --git a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java
index 323aa473580..81ac02a5400 100644
--- a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java
+++ b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java
@@ -54,8 +54,9 @@ public interface ModelContext {
// TODO: Remove when Vespa 7.112 is the oldest config model in use
default boolean useDedicatedNodeForLogserver() { return true; }
boolean useAdaptiveDispatch();
- // TODO: Remove temporary default implementation
+ // TODO: Remove temporary default implementations
default Optional<TlsSecrets> tlsSecrets() { return Optional.empty(); }
+ default Optional<EndpointCertificateSecrets> endpointCertificateSecrets() { return Optional.empty(); }
double defaultTermwiseLimit();
boolean useBucketSpaceMetric();
}
diff --git a/config-model-api/src/main/java/com/yahoo/config/model/api/TlsSecrets.java b/config-model-api/src/main/java/com/yahoo/config/model/api/TlsSecrets.java
index 6a8b5a237ab..0937b8b77ec 100644
--- a/config-model-api/src/main/java/com/yahoo/config/model/api/TlsSecrets.java
+++ b/config-model-api/src/main/java/com/yahoo/config/model/api/TlsSecrets.java
@@ -16,6 +16,11 @@ public class TlsSecrets {
this.key = key;
}
+ public TlsSecrets(EndpointCertificateSecrets endpointCertificateSecrets) {
+ this.certificate = endpointCertificateSecrets.certificate();
+ this.key = endpointCertificateSecrets.key();
+ }
+
public String certificate() {
return certificate;
}
diff --git a/config-model-api/src/main/java/com/yahoo/config/model/api/package-info.java b/config-model-api/src/main/java/com/yahoo/config/model/api/package-info.java
index 52ce35a19fb..a3478026520 100644
--- a/config-model-api/src/main/java/com/yahoo/config/model/api/package-info.java
+++ b/config-model-api/src/main/java/com/yahoo/config/model/api/package-info.java
@@ -1,5 +1,7 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
@ExportPackage
+@PublicApi // Not really "public", only annotated as such to enable the ABI checker plugin
package com.yahoo.config.model.api;
import com.yahoo.osgi.annotation.ExportPackage;
+import com.yahoo.api.annotations.PublicApi;
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 b286b94c699..7c9e930bb4f 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,7 +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.EndpointCertificateSecrets;
import com.yahoo.config.model.api.ValidationParameters;
import com.yahoo.config.model.application.provider.BaseDeployLogger;
import com.yahoo.config.model.application.provider.MockFileRegistry;
@@ -255,7 +255,7 @@ public class DeployState implements ConfigDefinitionStore {
public Instant now() { return now; }
- public Optional<TlsSecrets> tlsSecrets() { return properties.tlsSecrets(); }
+ public Optional<EndpointCertificateSecrets> endpointCertificateSecrets() { return properties.endpointCertificateSecrets(); }
public Optional<String> tlsClientAuthority() {
var caFile = applicationPackage.getClientSecurityFile();
@@ -289,7 +289,6 @@ 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 9d561a79c75..9f4d1b09f91 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.EndpointCertificateSecrets;
import com.yahoo.config.model.api.TlsSecrets;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.HostName;
@@ -39,7 +40,7 @@ public class TestProperties implements ModelContext.Properties {
private boolean useDedicatedNodeForLogserver = false;
private boolean useAdaptiveDispatch = false;
private double defaultTermwiseLimit = 1.0;
- private Optional<TlsSecrets> tlsSecrets = Optional.empty();
+ private Optional<EndpointCertificateSecrets> endpointCertificateSecrets = Optional.empty();
@Override public boolean multitenant() { return multitenant; }
@@ -56,7 +57,8 @@ public class TestProperties implements ModelContext.Properties {
@Override public boolean isFirstTimeDeployment() { return isFirstTimeDeployment; }
@Override public boolean useAdaptiveDispatch() { return useAdaptiveDispatch; }
@Override public boolean useDedicatedNodeForLogserver() { return useDedicatedNodeForLogserver; }
- @Override public Optional<TlsSecrets> tlsSecrets() { return tlsSecrets; }
+ @Override public Optional<EndpointCertificateSecrets> endpointCertificateSecrets() { return endpointCertificateSecrets; }
+ @Override public Optional<TlsSecrets> tlsSecrets() { return endpointCertificateSecrets.map(TlsSecrets::new); }
@Override public double defaultTermwiseLimit() { return defaultTermwiseLimit; }
@Override public boolean useBucketSpaceMetric() { return true; }
@@ -95,9 +97,8 @@ public class TestProperties implements ModelContext.Properties {
return this;
}
-
- public TestProperties setTlsSecrets(Optional<TlsSecrets> tlsSecrets) {
- this.tlsSecrets = tlsSecrets;
+ public TestProperties setEndpointCertificateSecrets(Optional<EndpointCertificateSecrets> endpointCertificateSecrets) {
+ this.endpointCertificateSecrets = endpointCertificateSecrets;
return this;
}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/multifieldresolver/RankProfileTypeSettingsProcessor.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/multifieldresolver/RankProfileTypeSettingsProcessor.java
index 58ef47b7ba9..441d1b5b8df 100644
--- a/config-model/src/main/java/com/yahoo/searchdefinition/processing/multifieldresolver/RankProfileTypeSettingsProcessor.java
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/multifieldresolver/RankProfileTypeSettingsProcessor.java
@@ -69,7 +69,7 @@ public class RankProfileTypeSettingsProcessor extends Processor {
}
private void addAttributeTypeToRankProfiles(String attributeName, String attributeType) {
- for (RankProfile profile : rankProfileRegistry.all()) {
+ for (RankProfile profile : rankProfileRegistry.rankProfilesOf(search)) {
profile.addAttributeType(attributeName, attributeType);
}
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/TlsSecretsValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/EndpointCertificateSecretsValidator.java
index 2f972b8ecb3..f00ad0f0dbb 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/TlsSecretsValidator.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/EndpointCertificateSecretsValidator.java
@@ -1,17 +1,17 @@
// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.model.application.validation;
-import com.yahoo.config.model.api.TlsSecrets;
+import com.yahoo.config.model.api.EndpointCertificateSecrets;
import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.config.provision.CertificateNotReadyException;
import com.yahoo.vespa.model.VespaModel;
-public class TlsSecretsValidator extends Validator {
+public class EndpointCertificateSecretsValidator extends Validator {
/** This check is delayed until validation to allow node provisioning to complete while we are waiting for cert */
@Override
public void validate(VespaModel model, DeployState deployState) {
- if (deployState.tlsSecrets().isPresent() && deployState.tlsSecrets().get() == TlsSecrets.MISSING) {
+ if (deployState.endpointCertificateSecrets().isPresent() && deployState.endpointCertificateSecrets().get() == EndpointCertificateSecrets.MISSING) {
throw new CertificateNotReadyException("TLS enabled, but could not retrieve certificate yet");
}
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/Validation.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/Validation.java
index 8eabc61f71f..1e4a45428b8 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/Validation.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/Validation.java
@@ -57,7 +57,7 @@ public class Validation {
new DeploymentSpecValidator().validate(model, deployState);
new RankingConstantsValidator().validate(model, deployState);
new SecretStoreValidator().validate(model, deployState);
- new TlsSecretsValidator().validate(model, deployState);
+ new EndpointCertificateSecretsValidator().validate(model, deployState);
new AccessControlFilterValidator().validate(model, deployState);
List<ConfigChangeAction> result = Collections.emptyList();
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 5e0dde6161d..efd00528d54 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
@@ -5,7 +5,6 @@ import com.yahoo.component.ComponentId;
import com.yahoo.component.ComponentSpecification;
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;
@@ -55,7 +54,6 @@ public final class ApplicationContainerCluster extends ContainerCluster<Applicat
private ContainerModelEvaluation modelEvaluation;
- private Optional<TlsSecrets> tlsSecrets;
private Optional<String> tlsClientAuthority;
private MbusParams mbusParams;
@@ -65,8 +63,6 @@ public final class ApplicationContainerCluster extends ContainerCluster<Applicat
public ApplicationContainerCluster(AbstractConfigProducer<?> parent, String subId, String name, DeployState deployState) {
super(parent, subId, name, deployState);
-
- this.tlsSecrets = deployState.tlsSecrets();
this.tlsClientAuthority = deployState.tlsClientAuthority();
restApiGroup = new ConfigProducerGroup<>(this, "rest-api");
servletGroup = new ConfigProducerGroup<>(this, "servlet");
@@ -205,10 +201,6 @@ public final class ApplicationContainerCluster extends ContainerCluster<Applicat
}
}
- public Optional<TlsSecrets> getTlsSecrets() {
- return tlsSecrets;
- }
-
public Optional<String> getTlsClientAuthority() {
return tlsClientAuthority;
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/HostedSslConnectorFactory.java b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/HostedSslConnectorFactory.java
index 7a08a3c1a7b..12db3b87243 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/HostedSslConnectorFactory.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/HostedSslConnectorFactory.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 com.yahoo.vespa.model.container.http.ssl;
-import com.yahoo.config.model.api.TlsSecrets;
+import com.yahoo.config.model.api.EndpointCertificateSecrets;
import com.yahoo.jdisc.http.ConnectorConfig;
import com.yahoo.jdisc.http.ConnectorConfig.Ssl.ClientAuth;
import com.yahoo.vespa.model.container.component.SimpleComponent;
@@ -23,15 +23,15 @@ public class HostedSslConnectorFactory extends ConnectorFactory {
/**
* Create connector factory that uses a certificate provided by the config-model / configserver.
*/
- public static HostedSslConnectorFactory withProvidedCertificate(String serverName, TlsSecrets tlsSecrets) {
- return new HostedSslConnectorFactory(createConfiguredDirectSslProvider(serverName, tlsSecrets, /*tlsCaCertificates*/null), false);
+ public static HostedSslConnectorFactory withProvidedCertificate(String serverName, EndpointCertificateSecrets endpointCertificateSecrets) {
+ return new HostedSslConnectorFactory(createConfiguredDirectSslProvider(serverName, endpointCertificateSecrets, /*tlsCaCertificates*/null), false);
}
/**
* Create connector factory that uses a certificate provided by the config-model / configserver and a truststore configured by the application.
*/
- public static HostedSslConnectorFactory withProvidedCertificateAndTruststore(String serverName, TlsSecrets tlsSecrets, String tlsCaCertificates) {
- return new HostedSslConnectorFactory(createConfiguredDirectSslProvider(serverName, tlsSecrets, tlsCaCertificates), true);
+ public static HostedSslConnectorFactory withProvidedCertificateAndTruststore(String serverName, EndpointCertificateSecrets endpointCertificateSecrets, String tlsCaCertificates) {
+ return new HostedSslConnectorFactory(createConfiguredDirectSslProvider(serverName, endpointCertificateSecrets, tlsCaCertificates), true);
}
/**
@@ -47,11 +47,11 @@ public class HostedSslConnectorFactory extends ConnectorFactory {
}
private static ConfiguredDirectSslProvider createConfiguredDirectSslProvider(
- String serverName, TlsSecrets tlsSecrets, String tlsCaCertificates) {
+ String serverName, EndpointCertificateSecrets endpointCertificateSecrets, String tlsCaCertificates) {
return new ConfiguredDirectSslProvider(
serverName,
- tlsSecrets.key(),
- tlsSecrets.certificate(),
+ endpointCertificateSecrets.key(),
+ endpointCertificateSecrets.certificate(),
/*caCertificatePath*/null,
tlsCaCertificates,
ClientAuth.Enum.WANT_AUTH);
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 3da0b01f614..aef2697a5dd 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
@@ -13,7 +13,7 @@ import com.yahoo.config.model.ConfigModelContext;
import com.yahoo.config.model.ConfigModelContext.ApplicationType;
import com.yahoo.config.model.api.ConfigServerSpec;
import com.yahoo.config.model.api.ContainerEndpoint;
-import com.yahoo.config.model.api.TlsSecrets;
+import com.yahoo.config.model.api.EndpointCertificateSecrets;
import com.yahoo.config.model.application.provider.IncludeDirs;
import com.yahoo.config.model.builder.xml.ConfigModelBuilder;
import com.yahoo.config.model.builder.xml.ConfigModelId;
@@ -327,15 +327,15 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
String serverName = server.getComponentId().getName();
// If the deployment contains certificate/private key reference, setup TLS port
- if (deployState.tlsSecrets().isPresent()) {
+ if (deployState.endpointCertificateSecrets().isPresent()) {
boolean authorizeClient = deployState.zone().system().isPublic();
if (authorizeClient && deployState.tlsClientAuthority().isEmpty()) {
throw new RuntimeException("Client certificate authority security/clients.pem is missing - see: https://cloud.vespa.ai/security-model#data-plane");
}
- TlsSecrets tlsSecrets = deployState.tlsSecrets().get();
+ EndpointCertificateSecrets endpointCertificateSecrets = deployState.endpointCertificateSecrets().get();
HostedSslConnectorFactory connectorFactory = authorizeClient
- ? HostedSslConnectorFactory.withProvidedCertificateAndTruststore(serverName, tlsSecrets, deployState.tlsClientAuthority().get())
- : HostedSslConnectorFactory.withProvidedCertificate(serverName, tlsSecrets);
+ ? HostedSslConnectorFactory.withProvidedCertificateAndTruststore(serverName, endpointCertificateSecrets, deployState.tlsClientAuthority().get())
+ : HostedSslConnectorFactory.withProvidedCertificate(serverName, endpointCertificateSecrets);
server.addConnector(connectorFactory);
} else {
server.addConnector(HostedSslConnectorFactory.withDefaultCertificateAndTruststore(serverName));
diff --git a/config-model/src/test/derived/tensor2/first.sd b/config-model/src/test/derived/tensor2/first.sd
new file mode 100644
index 00000000000..80554572503
--- /dev/null
+++ b/config-model/src/test/derived/tensor2/first.sd
@@ -0,0 +1,8 @@
+search first {
+ document first {
+ field first_field type tensor(first[10]) {
+ indexing: summary | attribute
+ }
+ }
+}
+
diff --git a/config-model/src/test/derived/tensor2/rank-profiles.cfg b/config-model/src/test/derived/tensor2/rank-profiles.cfg
new file mode 100644
index 00000000000..7e087832042
--- /dev/null
+++ b/config-model/src/test/derived/tensor2/rank-profiles.cfg
@@ -0,0 +1,14 @@
+rankprofile[].name "default"
+rankprofile[].fef.property[].name "vespa.type.attribute.second_field"
+rankprofile[].fef.property[].value "tensor(second[10])"
+rankprofile[].name "unranked"
+rankprofile[].fef.property[].name "vespa.rank.firstphase"
+rankprofile[].fef.property[].value "value(0)"
+rankprofile[].fef.property[].name "vespa.hitcollector.heapsize"
+rankprofile[].fef.property[].value "0"
+rankprofile[].fef.property[].name "vespa.hitcollector.arraysize"
+rankprofile[].fef.property[].value "0"
+rankprofile[].fef.property[].name "vespa.dump.ignoredefaultfeatures"
+rankprofile[].fef.property[].value "true"
+rankprofile[].fef.property[].name "vespa.type.attribute.second_field"
+rankprofile[].fef.property[].value "tensor(second[10])"
diff --git a/config-model/src/test/derived/tensor2/second.sd b/config-model/src/test/derived/tensor2/second.sd
new file mode 100644
index 00000000000..ace0540c8bd
--- /dev/null
+++ b/config-model/src/test/derived/tensor2/second.sd
@@ -0,0 +1,7 @@
+search second {
+ document second {
+ field second_field type tensor(second[10]) {
+ indexing: summary | attribute
+ }
+ }
+}
diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/derived/ExportingTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/derived/ExportingTestCase.java
index ebd2c752d5e..61065cd4bcc 100644
--- a/config-model/src/test/java/com/yahoo/searchdefinition/derived/ExportingTestCase.java
+++ b/config-model/src/test/java/com/yahoo/searchdefinition/derived/ExportingTestCase.java
@@ -1,6 +1,7 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.searchdefinition.derived;
+import com.yahoo.searchdefinition.SearchBuilder;
import com.yahoo.searchdefinition.parser.ParseException;
import org.junit.Test;
@@ -138,4 +139,15 @@ public class ExportingTestCase extends AbstractExportingTestCase {
assertCorrectDeriving("tensor");
}
+ @Test
+ public void testTensor2() throws IOException, ParseException {
+ String dir = "src/test/derived/tensor2/";
+ SearchBuilder builder = new SearchBuilder();
+ builder.importFile(dir + "first.sd");
+ builder.importFile(dir + "second.sd");
+ builder.build();
+ derive("tensor2", builder, builder.getSearch("second"));
+ assertCorrectConfigFiles("tensor2");
+ }
+
}
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/TlsSecretsValidatorTest.java b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/EndpointCertificateSecretsValidatorTest.java
index cdb4ce955e2..21df39ebde8 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/TlsSecretsValidatorTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/EndpointCertificateSecretsValidatorTest.java
@@ -3,7 +3,7 @@ package com.yahoo.vespa.model.application.validation;
import com.yahoo.config.application.api.ApplicationPackage;
import com.yahoo.config.model.NullConfigModelRegistry;
-import com.yahoo.config.model.api.TlsSecrets;
+import com.yahoo.config.model.api.EndpointCertificateSecrets;
import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.config.model.deploy.TestProperties;
import com.yahoo.config.model.test.MockApplicationPackage;
@@ -24,7 +24,7 @@ import static org.junit.Assert.assertTrue;
/**
* @author andreer
*/
-public class TlsSecretsValidatorTest {
+public class EndpointCertificateSecretsValidatorTest {
@Rule
public final ExpectedException exceptionRule = ExpectedException.none();
@@ -43,21 +43,21 @@ public class TlsSecretsValidatorTest {
@Test
public void missing_certificate_fails_validation() throws Exception {
- DeployState deployState = deployState(servicesXml(), deploymentXml(), Optional.of(TlsSecrets.MISSING));
+ DeployState deployState = deployState(servicesXml(), deploymentXml(), Optional.of(EndpointCertificateSecrets.MISSING));
VespaModel model = new VespaModel(new NullConfigModelRegistry(), deployState);
exceptionRule.expect(CertificateNotReadyException.class);
exceptionRule.expectMessage("TLS enabled, but could not retrieve certificate yet");
- new TlsSecretsValidator().validate(model, deployState);
+ new EndpointCertificateSecretsValidator().validate(model, deployState);
}
@Test
public void validation_succeeds_with_certificate() throws Exception {
- DeployState deployState = deployState(servicesXml(), deploymentXml(), Optional.of(new TlsSecrets("cert", "key")));
+ DeployState deployState = deployState(servicesXml(), deploymentXml(), Optional.of(new EndpointCertificateSecrets("cert", "key")));
VespaModel model = new VespaModel(new NullConfigModelRegistry(), deployState);
- new TlsSecretsValidator().validate(model, deployState);
+ new EndpointCertificateSecretsValidator().validate(model, deployState);
}
@Test
@@ -65,10 +65,10 @@ public class TlsSecretsValidatorTest {
DeployState deployState = deployState(servicesXml(), deploymentXml(), Optional.empty());
VespaModel model = new VespaModel(new NullConfigModelRegistry(), deployState);
- new TlsSecretsValidator().validate(model, deployState);
+ new EndpointCertificateSecretsValidator().validate(model, deployState);
}
- private static DeployState deployState(String servicesXml, String deploymentXml, Optional<TlsSecrets> tlsSecrets) {
+ private static DeployState deployState(String servicesXml, String deploymentXml, Optional<EndpointCertificateSecrets> endpointCertificateSecretsSecrets) {
ApplicationPackage app = new MockApplicationPackage.Builder()
.withServices(servicesXml)
.withDeploymentSpec(deploymentXml)
@@ -79,7 +79,7 @@ public class TlsSecretsValidatorTest {
.properties(
new TestProperties()
.setHostedVespa(true)
- .setTlsSecrets(tlsSecrets));
+ .setEndpointCertificateSecrets(endpointCertificateSecretsSecrets));
final DeployState deployState = builder.build();
assertTrue("Test must emulate a hosted deployment.", deployState.isHosted());
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java
index 54d1c1c9793..1bbc4ea2684 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java
@@ -5,7 +5,7 @@ import com.yahoo.component.ComponentId;
import com.yahoo.config.application.api.ApplicationPackage;
import com.yahoo.config.model.NullConfigModelRegistry;
import com.yahoo.config.model.api.ContainerEndpoint;
-import com.yahoo.config.model.api.TlsSecrets;
+import com.yahoo.config.model.api.EndpointCertificateSecrets;
import com.yahoo.config.model.builder.xml.test.DomBuilderTest;
import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.config.model.deploy.TestProperties;
@@ -693,7 +693,7 @@ public class ContainerModelBuilderTest extends ContainerModelBuilderTestBase {
.properties(
new TestProperties()
.setHostedVespa(true)
- .setTlsSecrets(Optional.of(new TlsSecrets("CERT", "KEY"))))
+ .setEndpointCertificateSecrets(Optional.of(new EndpointCertificateSecrets("CERT", "KEY"))))
.zone(new Zone(SystemName.Public, Environment.prod, RegionName.defaultName()))
.build();
createModel(root, state, null, clusterElem);
@@ -772,13 +772,13 @@ public class ContainerModelBuilderTest extends ContainerModelBuilderTestBase {
}
@Test
- public void requireThatProvidingTlsSecretOpensPort4443() {
+ public void requireThatProvidingEndpointCertificateSecretsOpensPort4443() {
Element clusterElem = DomBuilderTest.parse(
"<container version='1.0'>",
nodesXml,
"</container>" );
- DeployState state = new DeployState.Builder().properties(new TestProperties().setHostedVespa(true).setTlsSecrets(Optional.of(new TlsSecrets("CERT", "KEY")))).build();
+ DeployState state = new DeployState.Builder().properties(new TestProperties().setHostedVespa(true).setEndpointCertificateSecrets(Optional.of(new EndpointCertificateSecrets("CERT", "KEY")))).build();
createModel(root, state, null, clusterElem);
ApplicationContainer container = (ApplicationContainer)root.getProducer("container/container.0");
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 863781073f8..68f507c810d 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,7 +1,7 @@
// 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.api.EndpointCertificateSecrets;
import com.yahoo.config.model.builder.xml.test.DomBuilderTest;
import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.config.model.deploy.TestProperties;
@@ -258,7 +258,7 @@ public class JettyContainerModelBuilderTest extends ContainerModelBuilderTestBas
.properties(
new TestProperties()
.setHostedVespa(true)
- .setTlsSecrets(Optional.of(new TlsSecrets("CERT", "KEY"))))
+ .setEndpointCertificateSecrets(Optional.of(new EndpointCertificateSecrets("CERT", "KEY"))))
.modelHostProvisioner(new HostsXmlProvisioner(new StringReader(hostsxml)))
.build();
MockRoot root = new MockRoot("root", deployState);
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java b/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java
index d2f26738301..e9032555b09 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java
@@ -26,6 +26,7 @@ import com.yahoo.log.LogLevel;
import com.yahoo.path.Path;
import com.yahoo.slime.Slime;
import com.yahoo.transaction.NestedTransaction;
+import com.yahoo.transaction.Transaction;
import com.yahoo.vespa.config.server.application.Application;
import com.yahoo.vespa.config.server.application.ApplicationSet;
import com.yahoo.vespa.config.server.application.CompressedApplicationInputStream;
@@ -361,15 +362,23 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
// until the config server where the deployment happened picks it up and deletes
// the local session
long sessionId = activeSession.get();
- RemoteSession remoteSession = getRemoteSession(tenant, sessionId);
- remoteSession.createDeleteTransaction().commit();
- log.log(LogLevel.INFO, TenantRepository.logPre(applicationId) + "Waiting for session " + sessionId + " to be deleted");
-
- if ( ! waitTime.isZero() && localSessionHasBeenDeleted(applicationId, sessionId, waitTime)) {
- log.log(LogLevel.INFO, TenantRepository.logPre(applicationId) + "Session " + sessionId + " deleted");
- } else {
- throw new InternalServerException("Session " + sessionId + " was not deleted (waited " + waitTime + ")");
+ RemoteSession remoteSession;
+ try {
+ remoteSession = getRemoteSession(tenant, sessionId);
+ Transaction deleteTransaction = remoteSession.createDeleteTransaction();
+ deleteTransaction.commit();
+ log.log(LogLevel.INFO, TenantRepository.logPre(applicationId) + "Waiting for session " + sessionId + " to be deleted");
+
+ if ( ! waitTime.isZero() && localSessionHasBeenDeleted(applicationId, sessionId, waitTime)) {
+ log.log(LogLevel.INFO, TenantRepository.logPre(applicationId) + "Session " + sessionId + " deleted");
+ } else {
+ deleteTransaction.rollbackOrLog();
+ throw new InternalServerException(applicationId + " was not deleted (waited " + waitTime + "), session " + sessionId);
+ }
+ } catch (NotFoundException e) {
+ // For the case where waiting timed out in a previous attempt at deleting the application, continue and do the steps below
+ log.log(LogLevel.INFO, TenantRepository.logPre(applicationId) + "Active session exists, but has not been deleted properly. Trying to cleanup");
}
NestedTransaction transaction = new NestedTransaction();
@@ -468,6 +477,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
if (tenant == null) throw new NotFoundException("Tenant '" + applicationId.tenant() + "' not found");
long sessionId = getSessionIdForApplication(tenant, applicationId);
RemoteSession session = tenant.getRemoteSessionRepo().getSession(sessionId);
+ if (session == null) throw new NotFoundException("Remote session " + sessionId + " not found");
return session.ensureApplicationLoaded().getForVersionOrLatest(version, clock.instant());
} catch (NotFoundException e) {
log.log(LogLevel.WARNING, "Failed getting application for '" + applicationId + "': " + e.getMessage());
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 52d47a9398b..8b2c3e2cb0a 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.EndpointCertificateSecrets;
import com.yahoo.config.model.api.TlsSecrets;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.HostName;
@@ -130,7 +131,7 @@ public class ModelContextImpl implements ModelContext {
private final boolean isBootstrap;
private final boolean isFirstTimeDeployment;
private final boolean useAdaptiveDispatch;
- private final Optional<TlsSecrets> tlsSecrets;
+ private final Optional<EndpointCertificateSecrets> endpointCertificateSecrets;
private final double defaultTermwiseLimit;
private final boolean useBucketSpaceMetric;
@@ -146,7 +147,7 @@ public class ModelContextImpl implements ModelContext {
boolean isBootstrap,
boolean isFirstTimeDeployment,
FlagSource flagSource,
- Optional<TlsSecrets> tlsSecrets) {
+ Optional<EndpointCertificateSecrets> endpointCertificateSecrets) {
this.applicationId = applicationId;
this.multitenant = multitenantFromConfig || hostedVespa || Boolean.getBoolean("multitenant");
this.configServerSpecs = configServerSpecs;
@@ -160,7 +161,7 @@ public class ModelContextImpl implements ModelContext {
this.isFirstTimeDeployment = isFirstTimeDeployment;
this.useAdaptiveDispatch = Flags.USE_ADAPTIVE_DISPATCH.bindTo(flagSource)
.with(FetchVector.Dimension.APPLICATION_ID, applicationId.serializedForm()).value();
- this.tlsSecrets = tlsSecrets;
+ this.endpointCertificateSecrets = endpointCertificateSecrets;
defaultTermwiseLimit = Flags.DEFAULT_TERM_WISE_LIMIT.bindTo(flagSource)
.with(FetchVector.Dimension.APPLICATION_ID, applicationId.serializedForm()).value();
this.useBucketSpaceMetric = Flags.USE_BUCKET_SPACE_METRIC.bindTo(flagSource)
@@ -208,7 +209,10 @@ public class ModelContextImpl implements ModelContext {
public boolean useAdaptiveDispatch() { return useAdaptiveDispatch; }
@Override
- public Optional<TlsSecrets> tlsSecrets() { return tlsSecrets; }
+ public Optional<TlsSecrets> tlsSecrets() { return endpointCertificateSecrets.map(TlsSecrets::new); }
+
+ @Override
+ public Optional<EndpointCertificateSecrets> endpointCertificateSecrets() { return endpointCertificateSecrets; }
@Override
public double defaultTermwiseLimit() { return defaultTermwiseLimit; }
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 bc6419f230f..a2fc2bfd6a0 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
@@ -27,8 +27,9 @@ import com.yahoo.vespa.config.server.provision.HostProvisionerProvider;
import com.yahoo.vespa.config.server.session.SessionZooKeeperClient;
import com.yahoo.vespa.config.server.session.SilentDeployLogger;
import com.yahoo.vespa.config.server.tenant.ContainerEndpointsCache;
+import com.yahoo.vespa.config.server.tenant.EndpointCertificateRetriever;
import com.yahoo.vespa.config.server.tenant.TenantRepository;
-import com.yahoo.vespa.config.server.tenant.TlsSecretsKeys;
+import com.yahoo.vespa.config.server.tenant.EndpointCertificateMetadataStore;
import com.yahoo.vespa.curator.Curator;
import com.yahoo.vespa.flags.FlagSource;
@@ -135,7 +136,10 @@ public class ActivatedModelsBuilder extends ModelsBuilder<Application> {
false, // We may be bootstrapping, but we only know and care during prepare
false, // Always false, assume no one uses it when activating
flagSource,
- new TlsSecretsKeys(curator, TenantRepository.getTenantPath(tenant), secretStore).readTlsSecretsKeyFromZookeeper(applicationId));
+ new EndpointCertificateMetadataStore(curator, TenantRepository.getTenantPath(tenant))
+ .readEndpointCertificateMetadata(applicationId)
+ .flatMap(new EndpointCertificateRetriever(secretStore)::readEndpointCertificateSecrets));
+
}
}
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 ab3e0e863ce..1a41c1efd7a 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
@@ -3,6 +3,7 @@ package com.yahoo.vespa.config.server.session;
import com.yahoo.component.Version;
import com.yahoo.config.model.api.ContainerEndpoint;
+import com.yahoo.config.model.api.EndpointCertificateMetadata;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.TenantName;
import com.yahoo.container.jdisc.HttpRequest;
@@ -11,6 +12,7 @@ import com.yahoo.vespa.config.SlimeUtils;
import com.yahoo.vespa.config.server.TimeoutBudget;
import com.yahoo.vespa.config.server.http.SessionHandler;
import com.yahoo.vespa.config.server.tenant.ContainerEndpointSerializer;
+import com.yahoo.vespa.config.server.tenant.EndpointCertificateMetadataSerializer;
import java.time.Clock;
import java.time.Duration;
@@ -32,6 +34,7 @@ public final class PrepareParams {
static final String VESPA_VERSION_PARAM_NAME = "vespaVersion";
static final String CONTAINER_ENDPOINTS_PARAM_NAME = "containerEndpoints";
static final String TLS_SECRETS_KEY_NAME_PARAM_NAME = "tlsSecretsKeyName";
+ static final String ENDPOINT_CERTIFICATE_METADATA_PARAM_NAME = "endpointCertificateMetadata";
private final ApplicationId applicationId;
private final TimeoutBudget timeoutBudget;
@@ -42,10 +45,12 @@ public final class PrepareParams {
private final Optional<Version> vespaVersion;
private final List<ContainerEndpoint> containerEndpoints;
private final Optional<String> tlsSecretsKeyName;
+ private final Optional<EndpointCertificateMetadata> endpointCertificateMetadata;
private PrepareParams(ApplicationId applicationId, TimeoutBudget timeoutBudget, boolean ignoreValidationErrors,
boolean dryRun, boolean verbose, boolean isBootstrap, Optional<Version> vespaVersion,
- List<ContainerEndpoint> containerEndpoints, Optional<String> tlsSecretsKeyName) {
+ List<ContainerEndpoint> containerEndpoints, Optional<String> tlsSecretsKeyName,
+ Optional<EndpointCertificateMetadata> endpointCertificateMetadata) {
this.timeoutBudget = timeoutBudget;
this.applicationId = applicationId;
this.ignoreValidationErrors = ignoreValidationErrors;
@@ -55,6 +60,7 @@ public final class PrepareParams {
this.vespaVersion = vespaVersion;
this.containerEndpoints = containerEndpoints;
this.tlsSecretsKeyName = tlsSecretsKeyName;
+ this.endpointCertificateMetadata = endpointCertificateMetadata;
}
public static class Builder {
@@ -68,6 +74,7 @@ public final class PrepareParams {
private Optional<Version> vespaVersion = Optional.empty();
private List<ContainerEndpoint> containerEndpoints = List.of();
private Optional<String> tlsSecretsKeyName = Optional.empty();
+ private Optional<EndpointCertificateMetadata> endpointCertificateMetadata = Optional.empty();
public Builder() { }
@@ -128,9 +135,16 @@ public final class PrepareParams {
return this;
}
+ public Builder endpointCertificateMetadata(String serialized) {
+ if(serialized == null) return this;
+ Slime slime = SlimeUtils.jsonToSlime(serialized);
+ endpointCertificateMetadata = Optional.of(EndpointCertificateMetadataSerializer.fromSlime(slime.get()));
+ return this;
+ }
+
public PrepareParams build() {
return new PrepareParams(applicationId, timeoutBudget, ignoreValidationErrors, dryRun,
- verbose, isBootstrap, vespaVersion, containerEndpoints, tlsSecretsKeyName);
+ verbose, isBootstrap, vespaVersion, containerEndpoints, tlsSecretsKeyName, endpointCertificateMetadata);
}
}
@@ -144,6 +158,7 @@ public final class PrepareParams {
.vespaVersion(request.getProperty(VESPA_VERSION_PARAM_NAME))
.containerEndpoints(request.getProperty(CONTAINER_ENDPOINTS_PARAM_NAME))
.tlsSecretsKeyName(request.getProperty(TLS_SECRETS_KEY_NAME_PARAM_NAME))
+ .endpointCertificateMetadata(request.getProperty(ENDPOINT_CERTIFICATE_METADATA_PARAM_NAME))
.build();
}
@@ -200,4 +215,8 @@ public final class PrepareParams {
public Optional<String> tlsSecretsKeyName() {
return tlsSecretsKeyName;
}
+
+ public Optional<EndpointCertificateMetadata> endpointCertificateMetadata() {
+ return endpointCertificateMetadata;
+ }
}
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 171eab35507..0115876ded9 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
@@ -12,8 +12,9 @@ import com.yahoo.config.application.api.DeployLogger;
import com.yahoo.config.application.api.FileRegistry;
import com.yahoo.config.model.api.ConfigDefinitionRepo;
import com.yahoo.config.model.api.ContainerEndpoint;
+import com.yahoo.config.model.api.EndpointCertificateMetadata;
import com.yahoo.config.model.api.ModelContext;
-import com.yahoo.config.model.api.TlsSecrets;
+import com.yahoo.config.model.api.EndpointCertificateSecrets;
import com.yahoo.config.provision.AllocatedHosts;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.HostName;
@@ -33,7 +34,9 @@ import com.yahoo.vespa.config.server.modelfactory.ModelFactoryRegistry;
import com.yahoo.vespa.config.server.modelfactory.PreparedModelsBuilder;
import com.yahoo.vespa.config.server.provision.HostProvisionerProvider;
import com.yahoo.vespa.config.server.tenant.ContainerEndpointsCache;
-import com.yahoo.vespa.config.server.tenant.TlsSecretsKeys;
+import com.yahoo.vespa.config.server.tenant.EndpointCertificateMetadataSerializer;
+import com.yahoo.vespa.config.server.tenant.EndpointCertificateMetadataStore;
+import com.yahoo.vespa.config.server.tenant.EndpointCertificateRetriever;
import com.yahoo.vespa.curator.Curator;
import com.yahoo.vespa.flags.FlagSource;
import org.xml.sax.SAXException;
@@ -113,7 +116,7 @@ public class SessionPreparer {
preparation.makeResult(allocatedHosts);
if ( ! params.isDryRun()) {
preparation.writeStateZK();
- preparation.writeTlsZK();
+ preparation.writeEndpointCertificateMetadataZK();
preparation.writeContainerEndpointsZK();
preparation.distribute();
}
@@ -142,8 +145,10 @@ public class SessionPreparer {
final ContainerEndpointsCache containerEndpoints;
final Set<ContainerEndpoint> endpointsSet;
final ModelContext.Properties properties;
- private final TlsSecretsKeys tlsSecretsKeys;
- private final Optional<TlsSecrets> tlsSecrets;
+ private final EndpointCertificateMetadataStore endpointCertificateMetadataStore;
+ private final EndpointCertificateRetriever endpointCertificateRetriever;
+ private final Optional<EndpointCertificateMetadata> endpointCertificateMetadata;
+ private final Optional<EndpointCertificateSecrets> endpointCertificateSecrets;
private ApplicationPackage applicationPackage;
private List<PreparedModelsBuilder.PreparedModelResult> modelResultList;
@@ -162,8 +167,16 @@ public class SessionPreparer {
this.applicationId = params.getApplicationId();
this.vespaVersion = params.vespaVersion().orElse(Vtag.currentVersion);
this.containerEndpoints = new ContainerEndpointsCache(tenantPath, curator);
- this.tlsSecretsKeys = new TlsSecretsKeys(curator, tenantPath, secretStore);
- this.tlsSecrets = tlsSecretsKeys.getTlsSecrets(params.tlsSecretsKeyName(), applicationId);
+ this.endpointCertificateMetadataStore = new EndpointCertificateMetadataStore(curator, tenantPath);
+ this.endpointCertificateRetriever = new EndpointCertificateRetriever(secretStore);
+
+ this.endpointCertificateMetadata = params.endpointCertificateMetadata()
+ .or(() -> params.tlsSecretsKeyName().map(EndpointCertificateMetadataSerializer::fromString));
+
+ endpointCertificateSecrets = endpointCertificateMetadata
+ .or(() -> endpointCertificateMetadataStore.readEndpointCertificateMetadata(applicationId))
+ .flatMap(endpointCertificateRetriever::readEndpointCertificateSecrets);
+
this.endpointsSet = getEndpoints(params.containerEndpoints());
this.properties = new ModelContextImpl.Properties(params.getApplicationId(),
@@ -178,7 +191,7 @@ public class SessionPreparer {
params.isBootstrap(),
! currentActiveApplicationSet.isPresent(),
context.getFlagSource(),
- tlsSecrets);
+ endpointCertificateSecrets);
this.preparedModelsBuilder = new PreparedModelsBuilder(modelFactoryRegistry,
permanentApplicationPackage,
configDefinitionRepo,
@@ -233,9 +246,10 @@ public class SessionPreparer {
checkTimeout("write state to zookeeper");
}
- void writeTlsZK() {
- tlsSecretsKeys.writeTlsSecretsKeyToZooKeeper(applicationId, params.tlsSecretsKeyName().orElse(null));
- checkTimeout("write tlsSecretsKey to zookeeper");
+ void writeEndpointCertificateMetadataZK() {
+ endpointCertificateMetadata.ifPresent(metadata ->
+ endpointCertificateMetadataStore.writeEndpointCertificateMetadata(applicationId, metadata));
+ checkTimeout("write endpoint certificate metadata to zookeeper");
}
void writeContainerEndpointsZK() {
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/EndpointCertificateMetadataSerializer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/EndpointCertificateMetadataSerializer.java
new file mode 100644
index 00000000000..6d092aaa18b
--- /dev/null
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/EndpointCertificateMetadataSerializer.java
@@ -0,0 +1,55 @@
+package com.yahoo.vespa.config.server.tenant;
+
+import com.yahoo.config.model.api.EndpointCertificateMetadata;
+import com.yahoo.slime.Cursor;
+import com.yahoo.slime.Inspector;
+import com.yahoo.slime.Slime;
+
+/**
+ * (de)serializes endpoint certificate metadata
+ *
+ * @author andreer
+ */
+public class EndpointCertificateMetadataSerializer {
+
+ // WARNING: Since there are multiple servers in a ZooKeeper cluster and they upgrade one by one
+ // (and rewrite all nodes on startup), changes to the serialized format must be made
+ // such that what is serialized on version N+1 can be read by version N:
+ // - ADDING FIELDS: Always ok
+ // - REMOVING FIELDS: Stop reading the field first. Stop writing it on a later version.
+ // - CHANGING THE FORMAT OF A FIELD: Don't do it bro.
+
+ private final static String keyNameField = "keyName";
+ private final static String certNameField = "certName";
+ private final static String versionField = "version";
+
+ public static void toSlime(EndpointCertificateMetadata metadata, Cursor object) {
+ object.setString(keyNameField, metadata.keyName());
+ object.setString(certNameField, metadata.certName());
+ object.setLong(versionField, metadata.version());
+ }
+
+ public static EndpointCertificateMetadata fromSlime(Inspector inspector) {
+ switch (inspector.type()) {
+ case STRING: // TODO: Remove once all are transmitted and stored as JSON
+ return new EndpointCertificateMetadata(
+ inspector.asString() + "-key",
+ inspector.asString() + "-cert",
+ 0
+ );
+ case OBJECT:
+ return new EndpointCertificateMetadata(
+ inspector.field(keyNameField).asString(),
+ inspector.field(certNameField).asString(),
+ Math.toIntExact(inspector.field(versionField).asLong())
+ );
+
+ default:
+ throw new IllegalArgumentException("Unknown format encountered for TLS secrets metadata!");
+ }
+ }
+
+ public static EndpointCertificateMetadata fromString(String tlsSecretsKeys) {
+ return fromSlime(new Slime().setString(tlsSecretsKeys));
+ }
+}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/EndpointCertificateMetadataStore.java b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/EndpointCertificateMetadataStore.java
new file mode 100644
index 00000000000..6500449e557
--- /dev/null
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/EndpointCertificateMetadataStore.java
@@ -0,0 +1,65 @@
+// 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.yahoo.config.model.api.EndpointCertificateMetadata;
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.path.Path;
+import com.yahoo.slime.Slime;
+import com.yahoo.vespa.config.SlimeUtils;
+import com.yahoo.vespa.curator.Curator;
+import com.yahoo.vespa.curator.transaction.CuratorOperations;
+import com.yahoo.vespa.curator.transaction.CuratorTransaction;
+
+import java.util.Optional;
+
+/**
+ * Stores the endpoint certificate metadata for an application.
+ * This metadata is then used to retrieve the actual secrets from {@link EndpointCertificateRetriever}.
+ *
+ * @author andreer
+ */
+public class EndpointCertificateMetadataStore {
+
+ private final Path path;
+ private final Curator curator;
+
+ public EndpointCertificateMetadataStore(Curator curator, Path tenantPath) {
+ this.curator = curator;
+ this.path = tenantPath.append("tlsSecretsKeys/");
+ }
+
+ /** Reads the endpoint certificate metadata from ZooKeeper, if it exists */
+ public Optional<EndpointCertificateMetadata> readEndpointCertificateMetadata(ApplicationId application) {
+ try {
+ Optional<byte[]> data = curator.getData(endpointCertificateMetadataPathOf(application));
+ if (data.isEmpty() || data.get().length == 0) return Optional.empty();
+ Slime slime = SlimeUtils.jsonToSlime(data.get());
+ EndpointCertificateMetadata endpointCertificateMetadata = EndpointCertificateMetadataSerializer.fromSlime(slime.get());
+ return Optional.of(endpointCertificateMetadata);
+ } catch (Exception e) {
+ throw new RuntimeException("Error reading TLS secret key of " + application, e);
+ }
+ }
+
+ /** Writes the endpoint certificate metadata to ZooKeeper */
+ public void writeEndpointCertificateMetadata(ApplicationId application, EndpointCertificateMetadata endpointCertificateMetadata) {
+ try {
+ Slime slime = new Slime();
+ EndpointCertificateMetadataSerializer.toSlime(endpointCertificateMetadata, slime.setObject());
+ curator.set(endpointCertificateMetadataPathOf(application), SlimeUtils.toJsonBytes(slime));
+ } catch (Exception e) {
+ throw new RuntimeException("Could not write TLS secret key of " + application, e);
+ }
+ }
+
+ /** Returns a transaction which deletes these tls secrets key if they exist */
+ public CuratorTransaction delete(ApplicationId application) {
+ if (!curator.exists(endpointCertificateMetadataPathOf(application))) return CuratorTransaction.empty(curator);
+ return CuratorTransaction.from(CuratorOperations.delete(endpointCertificateMetadataPathOf(application).getAbsolute()), curator);
+ }
+
+ /** Returns the path storing the tls secrets key for an application */
+ private Path endpointCertificateMetadataPathOf(ApplicationId application) {
+ return path.append(application.serializedForm());
+ }
+}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/EndpointCertificateRetriever.java b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/EndpointCertificateRetriever.java
new file mode 100644
index 00000000000..5f40e5e1411
--- /dev/null
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/EndpointCertificateRetriever.java
@@ -0,0 +1,56 @@
+// 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.yahoo.config.model.api.EndpointCertificateMetadata;
+import com.yahoo.config.model.api.EndpointCertificateSecrets;
+import com.yahoo.container.jdisc.secretstore.SecretStore;
+import com.yahoo.security.KeyUtils;
+import com.yahoo.security.X509CertificateUtils;
+
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.X509Certificate;
+import java.util.Optional;
+
+/**
+ * Used to retrieve actual endpoint certificate/key from secret store.
+ *
+ * @author andreer
+ */
+public class EndpointCertificateRetriever {
+
+ private final SecretStore secretStore;
+
+ public EndpointCertificateRetriever(SecretStore secretStore) {
+ this.secretStore = secretStore;
+ }
+
+ public Optional<EndpointCertificateSecrets> readEndpointCertificateSecrets(EndpointCertificateMetadata metadata) {
+ return Optional.of(readFromSecretStore(metadata));
+ }
+
+ private EndpointCertificateSecrets readFromSecretStore(EndpointCertificateMetadata endpointCertificateMetadata) {
+ try {
+ String cert = secretStore.getSecret(endpointCertificateMetadata.certName(), endpointCertificateMetadata.version());
+ String key = secretStore.getSecret(endpointCertificateMetadata.keyName(), endpointCertificateMetadata.version());
+
+ verifyKeyMatchesCertificate(endpointCertificateMetadata, cert, key);
+
+ return new EndpointCertificateSecrets(cert, key);
+ } catch (RuntimeException e) {
+ // Assume not ready yet
+ return EndpointCertificateSecrets.MISSING;
+ }
+ }
+
+ private void verifyKeyMatchesCertificate(EndpointCertificateMetadata endpointCertificateMetadata, String cert, String key) {
+ X509Certificate x509Certificate = X509CertificateUtils.fromPem(cert);
+
+ PrivateKey privateKey = KeyUtils.fromPemEncodedPrivateKey(key);
+ PublicKey publicKey = x509Certificate.getPublicKey();
+
+ if(!X509CertificateUtils.privateKeyMatchesPublicKey(privateKey, publicKey)) {
+ throw new IllegalArgumentException("Failed to retrieve endpoint secrets: Certificate and key data do not match for " + endpointCertificateMetadata);
+ }
+ }
+}
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
deleted file mode 100644
index da6fc490da9..00000000000
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TlsSecretsKeys.java
+++ /dev/null
@@ -1,136 +0,0 @@
-// 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.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.slime.Cursor;
-import com.yahoo.slime.Slime;
-import com.yahoo.vespa.config.SlimeUtils;
-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();
-
- Slime slime = SlimeUtils.jsonToSlime(data.get());
- final var inspector = slime.get();
-
- switch (inspector.type()) {
- case STRING: // TODO: Remove once all are stored as JSON
- return readFromSecretStore(Optional.ofNullable(inspector.asString()));
- case OBJECT:
- var tlsSecretsInfo = new TlsSecretsMetadata();
- tlsSecretsInfo.certName = inspector.field("certName").asString();
- tlsSecretsInfo.keyName = inspector.field("keyName").asString();
- tlsSecretsInfo.version = Math.toIntExact(inspector.field("version").asLong());
- return Optional.of(readFromSecretStore(tlsSecretsInfo));
- default:
- throw new IllegalArgumentException("Unknown format encountered for TLS secrets metadata!");
- }
- } 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;
- writeTlsSecretsAsString(application, tlsSecretsKey);
- }
-
- private void writeTlsSecretsAsString(ApplicationId application, String tlsSecretsKey) {
- try {
- Slime slime = new Slime();
- slime.setString(tlsSecretsKey);
- curator.set(tlsSecretsKeyOf(application), SlimeUtils.toJsonBytes(slime));
- } catch (Exception e) {
- throw new RuntimeException("Could not write TLS secret key of " + application, e);
- }
- }
-
- void writeTlsSecretsMetadata(ApplicationId application, TlsSecretsMetadata tlsSecretsMetadata) {
- try {
- Slime slime = new Slime();
- Cursor cursor = slime.setObject();
- cursor.setString(TlsSecretsMetadata.certNameField, tlsSecretsMetadata.certName);
- cursor.setString(TlsSecretsMetadata.keyNameField, tlsSecretsMetadata.keyName);
- cursor.setLong(TlsSecretsMetadata.versionField, tlsSecretsMetadata.version);
- curator.set(tlsSecretsKeyOf(application), SlimeUtils.toJsonBytes(slime));
- } 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();
- try {
- String cert = secretStore.getSecret(secretKeyname.get() + "-cert");
- String key = secretStore.getSecret(secretKeyname.get() + "-key");
- return Optional.of(new TlsSecrets(cert, key));
- } catch (RuntimeException e) {
- // Assume not ready yet
- return Optional.of(TlsSecrets.MISSING);
- }
- }
-
- private TlsSecrets readFromSecretStore(TlsSecretsMetadata tlsSecretsMetadata) {
- try {
- String cert = secretStore.getSecret(tlsSecretsMetadata.certName, tlsSecretsMetadata.version);
- String key = secretStore.getSecret(tlsSecretsMetadata.keyName, tlsSecretsMetadata.version);
- return new TlsSecrets(cert, key);
- } catch (RuntimeException e) {
- // Assume not ready yet
- return TlsSecrets.MISSING;
- }
- }
-
- /** 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());
- }
-
- static class TlsSecretsMetadata {
- final static String keyNameField = "keyName";
- final static String certNameField = "certName";
- final static String versionField = "version";
- String keyName;
- String certName;
- int version;
- }
-}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java
index fb745bbb76b..d924d22cb39 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java
@@ -266,13 +266,22 @@ public class ApplicationRepositoryTest {
}
{
+ PrepareResult prepareResult = deployApp(testApp);
try {
- deployApp(testApp);
applicationRepository.delete(applicationId(), Duration.ZERO);
fail("Should have gotten an exception");
} catch (InternalServerException e) {
- assertEquals("Session 5 was not deleted (waited PT0S)", e.getMessage());
+ assertEquals("test1.testapp was not deleted (waited PT0S), session " + prepareResult.sessionId(), e.getMessage());
}
+
+ // No active session or remote session (deleted in step above), but an exception was thrown above
+ // A new delete should cleanup and be successful
+ LocalSession activeSession = applicationRepository.getActiveSession(applicationId());
+ assertNull(activeSession);
+ Tenant tenant = tenantRepository.getTenant(applicationId().tenant());
+ assertNull(tenant.getRemoteSessionRepo().getSession(prepareResult.sessionId()));
+
+ assertTrue(applicationRepository.delete(applicationId()));
}
}
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
index 8a77b53875e..12f48778144 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/MockSecretStore.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/MockSecretStore.java
@@ -7,22 +7,26 @@ import java.util.HashMap;
import java.util.Map;
public class MockSecretStore implements SecretStore {
- Map<String, String> secrets = new HashMap<>();
+ Map<String, Map<Integer, String>> secrets = new HashMap<>();
@Override
public String getSecret(String key) {
if(secrets.containsKey(key))
- return secrets.get(key);
+ return secrets.get(key).get(0);
throw new RuntimeException("Key not found: " + key);
}
@Override
public String getSecret(String key, int version) {
- return getSecret(key);
+ return secrets.get(key).get(version);
+ }
+
+ public void put(String key, int version, String value) {
+ secrets.computeIfAbsent(key, k -> new HashMap<>()).put(version, value);
}
public void put(String key, String value) {
- secrets.put(key, value);
+ put(key, 0, value);
}
public void remove(String key) {
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 a099db5ebe8..40115170b69 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,7 +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.ContainerEndpoint;
-import com.yahoo.config.model.api.TlsSecrets;
+import com.yahoo.config.model.api.EndpointCertificateSecrets;
import com.yahoo.config.model.application.provider.BaseDeployLogger;
import com.yahoo.config.model.application.provider.FilesApplicationPackage;
import com.yahoo.config.provision.ApplicationId;
@@ -22,6 +22,11 @@ import com.yahoo.config.provision.exception.LoadBalancerServiceException;
import com.yahoo.io.IOUtils;
import com.yahoo.log.LogLevel;
import com.yahoo.path.Path;
+import com.yahoo.security.KeyAlgorithm;
+import com.yahoo.security.KeyUtils;
+import com.yahoo.security.SignatureAlgorithm;
+import com.yahoo.security.X509CertificateBuilder;
+import com.yahoo.security.X509CertificateUtils;
import com.yahoo.slime.Slime;
import com.yahoo.transaction.NestedTransaction;
import com.yahoo.vespa.config.server.MockReloadHandler;
@@ -37,7 +42,8 @@ import com.yahoo.vespa.config.server.model.TestModelFactory;
import com.yahoo.vespa.config.server.modelfactory.ModelFactoryRegistry;
import com.yahoo.vespa.config.server.provision.HostProvisionerProvider;
import com.yahoo.vespa.config.server.tenant.ContainerEndpointsCache;
-import com.yahoo.vespa.config.server.tenant.TlsSecretsKeys;
+import com.yahoo.vespa.config.server.tenant.EndpointCertificateMetadataStore;
+import com.yahoo.vespa.config.server.tenant.EndpointCertificateRetriever;
import com.yahoo.vespa.config.server.zookeeper.ConfigCurator;
import com.yahoo.vespa.curator.mock.MockCurator;
import com.yahoo.vespa.flags.InMemoryFlagSource;
@@ -46,9 +52,14 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
+import javax.security.auth.x500.X500Principal;
import java.io.File;
import java.io.IOException;
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.cert.X509Certificate;
import java.time.Instant;
+import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@@ -73,6 +84,9 @@ public class SessionPreparerTest {
private static final File invalidTestApp = new File("src/test/apps/illegalApp");
private static final Version version123 = new Version(1, 2, 3);
private static final Version version321 = new Version(3, 2, 1);
+ private KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256);
+ private X509Certificate certificate = X509CertificateBuilder.fromKeypair(keyPair, new X500Principal("CN=subject"),
+ Instant.now(), Instant.now().plus(1, ChronoUnit.DAYS), SignatureAlgorithm.SHA512_WITH_ECDSA, BigInteger.valueOf(12345)).build();
private final InMemoryFlagSource flagSource = new InMemoryFlagSource();
private MockCurator curator;
@@ -231,15 +245,37 @@ public class SessionPreparerTest {
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");
+
+ secretStore.put("vespa.tlskeys.tenant1--app1-cert", X509CertificateUtils.toPem(certificate));
+ secretStore.put("vespa.tlskeys.tenant1--app1-key", KeyUtils.toPem(keyPair.getPrivate()));
+
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());
+ Optional<EndpointCertificateSecrets> endpointCertificateSecrets = new EndpointCertificateMetadataStore(curator, tenantPath)
+ .readEndpointCertificateMetadata(applicationId)
+ .flatMap(p -> new EndpointCertificateRetriever(secretStore).readEndpointCertificateSecrets(p));
+ assertTrue(endpointCertificateSecrets.isPresent());
+ assertTrue(endpointCertificateSecrets.get().key().startsWith("-----BEGIN EC PRIVATE KEY"));
+ assertTrue(endpointCertificateSecrets.get().certificate().startsWith("-----BEGIN CERTIFICATE"));
+ }
+
+ @Test
+ public void require_that_endpoint_certificate_metadata_is_written() throws IOException {
+ var applicationId = applicationId("test");
+ var params = new PrepareParams.Builder().applicationId(applicationId).endpointCertificateMetadata("{\"keyName\": \"vespa.tlskeys.tenant1--app1-key\", \"certName\":\"vespa.tlskeys.tenant1--app1-cert\", \"version\": 7}").build();
+ secretStore.put("vespa.tlskeys.tenant1--app1-cert", 7, X509CertificateUtils.toPem(certificate));
+ secretStore.put("vespa.tlskeys.tenant1--app1-key", 7, KeyUtils.toPem(keyPair.getPrivate()));
+ prepare(new File("src/test/resources/deploy/hosted-app"), params);
+
+ // Read from zk and verify cert and key are available
+ Optional<EndpointCertificateSecrets> endpointCertificateSecrets = new EndpointCertificateMetadataStore(curator, tenantPath)
+ .readEndpointCertificateMetadata(applicationId)
+ .flatMap(p -> new EndpointCertificateRetriever(secretStore).readEndpointCertificateSecrets(p));
+
+ assertTrue(endpointCertificateSecrets.isPresent());
+ assertTrue(endpointCertificateSecrets.get().key().startsWith("-----BEGIN EC PRIVATE KEY"));
+ assertTrue(endpointCertificateSecrets.get().certificate().startsWith("-----BEGIN CERTIFICATE"));
}
@Test(expected = CertificateNotReadyException.class)
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/EndpointCertificateMetadataStoreTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/EndpointCertificateMetadataStoreTest.java
new file mode 100644
index 00000000000..d71eab25ce3
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/EndpointCertificateMetadataStoreTest.java
@@ -0,0 +1,90 @@
+// Copyright 2020 Oath Inc. 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.yahoo.config.model.api.EndpointCertificateMetadata;
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.ApplicationName;
+import com.yahoo.config.provision.InstanceName;
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.path.Path;
+import com.yahoo.security.KeyAlgorithm;
+import com.yahoo.security.KeyUtils;
+import com.yahoo.security.SignatureAlgorithm;
+import com.yahoo.security.X509CertificateBuilder;
+import com.yahoo.security.X509CertificateUtils;
+import com.yahoo.vespa.config.server.MockSecretStore;
+import com.yahoo.vespa.curator.mock.MockCurator;
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.security.auth.x500.X500Principal;
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.cert.X509Certificate;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class EndpointCertificateMetadataStoreTest {
+
+ private static final Path tenantPath = Path.createRoot();
+ private static final Path endpointCertificateMetadataPath = Path.createRoot().append("tlsSecretsKeys").append("default:test:default");
+ private static final ApplicationId applicationId = ApplicationId.from(TenantName.defaultName(),
+ ApplicationName.from("test"), InstanceName.defaultName());
+
+ private MockCurator curator;
+ private MockSecretStore secretStore = new MockSecretStore();
+ private EndpointCertificateMetadataStore endpointCertificateMetadataStore;
+ private EndpointCertificateRetriever endpointCertificateRetriever;
+ private KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256);
+ private X509Certificate certificate = X509CertificateBuilder.fromKeypair(keyPair, new X500Principal("CN=subject"),
+ Instant.now(), Instant.now().plus(1, ChronoUnit.DAYS), SignatureAlgorithm.SHA512_WITH_ECDSA, BigInteger.valueOf(12345)).build();
+
+ @Before
+ public void setUp() {
+ curator = new MockCurator();
+ endpointCertificateMetadataStore = new EndpointCertificateMetadataStore(curator, tenantPath);
+ endpointCertificateRetriever = new EndpointCertificateRetriever(secretStore);
+
+ secretStore.put("vespa.tlskeys.tenant1--app1-cert", X509CertificateUtils.toPem(certificate));
+ secretStore.put("vespa.tlskeys.tenant1--app1-key", KeyUtils.toPem(keyPair.getPrivate()));
+ }
+
+ @Test
+ public void reads_string_format() {
+ curator.set(endpointCertificateMetadataPath, ("\"vespa.tlskeys.tenant1--app1\"").getBytes());
+
+ // Read from zk and verify cert and key are available
+ var endpointCertificateSecrets = endpointCertificateMetadataStore.readEndpointCertificateMetadata(applicationId)
+ .flatMap(endpointCertificateRetriever::readEndpointCertificateSecrets);
+ assertTrue(endpointCertificateSecrets.isPresent());
+ assertTrue(endpointCertificateSecrets.get().key().startsWith("-----BEGIN EC PRIVATE KEY"));
+ assertTrue(endpointCertificateSecrets.get().certificate().startsWith("-----BEGIN CERTIFICATE"));
+ }
+
+ @Test
+ public void reads_object_format() {
+ curator.set(endpointCertificateMetadataPath,
+ "{\"keyName\": \"vespa.tlskeys.tenant1--app1-key\", \"certName\":\"vespa.tlskeys.tenant1--app1-cert\", \"version\": 0}"
+ .getBytes());
+
+ // Read from zk and verify cert and key are available
+ var secrets = endpointCertificateMetadataStore.readEndpointCertificateMetadata(applicationId)
+ .flatMap(endpointCertificateRetriever::readEndpointCertificateSecrets);
+ assertTrue(secrets.isPresent());
+ assertTrue(secrets.get().key().startsWith("-----BEGIN EC PRIVATE KEY"));
+ assertTrue(secrets.get().certificate().startsWith("-----BEGIN CERTIFICATE"));
+ }
+
+ @Test
+ public void can_write_object_format() {
+ var endpointCertificateMetadata = new EndpointCertificateMetadata("key-name", "cert-name", 1);
+
+ endpointCertificateMetadataStore.writeEndpointCertificateMetadata(applicationId, endpointCertificateMetadata);
+
+ assertEquals("{\"keyName\":\"key-name\",\"certName\":\"cert-name\",\"version\":1}",
+ new String(curator.getData(endpointCertificateMetadataPath).orElseThrow()));
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TlsSecretsKeysTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TlsSecretsKeysTest.java
deleted file mode 100644
index c71c7b8e040..00000000000
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TlsSecretsKeysTest.java
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright 2020 Oath Inc. 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.yahoo.config.provision.ApplicationId;
-import com.yahoo.config.provision.ApplicationName;
-import com.yahoo.config.provision.InstanceName;
-import com.yahoo.config.provision.TenantName;
-import com.yahoo.path.Path;
-import com.yahoo.vespa.config.server.MockSecretStore;
-import com.yahoo.vespa.curator.mock.MockCurator;
-import org.junit.Before;
-import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-public class TlsSecretsKeysTest {
-
- private static final Path tenantPath = Path.createRoot();
- private static final Path tlsSecretsKeysPath = Path.createRoot().append("tlsSecretsKeys").append("default:test:default");
- private static final String tlskey = "vespa.tlskeys.tenant1--app1";
- private static final ApplicationId applicationId = ApplicationId.from(TenantName.defaultName(),
- ApplicationName.from("test"), InstanceName.defaultName());
-
- private MockCurator curator;
- private MockSecretStore secretStore = new MockSecretStore();
- private TlsSecretsKeys tlsSecretsKeys;
-
- @Before
- public void setUp() {
- curator = new MockCurator();
- tlsSecretsKeys = new TlsSecretsKeys(curator, tenantPath, secretStore);
- secretStore.put(tlskey + "-cert", "CERT");
- secretStore.put(tlskey + "-key", "KEY");
- }
-
- @Test
- public void reads_string_format() {
- curator.set(tlsSecretsKeysPath, ('"' + tlskey + '"').getBytes());
-
- // Read from zk and verify cert and key are available
- var tlsSecrets = tlsSecretsKeys.readTlsSecretsKeyFromZookeeper(applicationId);
- assertTrue(tlsSecrets.isPresent());
- assertEquals("KEY", tlsSecrets.get().key());
- assertEquals("CERT", tlsSecrets.get().certificate());
- }
-
- @Test
- public void reads_object_format() {
- curator.set(tlsSecretsKeysPath,
- "{\"keyName\": \"vespa.tlskeys.tenant1--app1-key\", \"certName\":\"vespa.tlskeys.tenant1--app1-cert\", \"version\": 0}"
- .getBytes());
-
- // Read from zk and verify cert and key are available
- var tlsSecrets = tlsSecretsKeys.readTlsSecretsKeyFromZookeeper(applicationId);
- assertTrue(tlsSecrets.isPresent());
- assertEquals("KEY", tlsSecrets.get().key());
- assertEquals("CERT", tlsSecrets.get().certificate());
- }
-
- @Test
- public void can_write_object_format() {
- var tlsSecretsMetadata = new TlsSecretsKeys.TlsSecretsMetadata();
- tlsSecretsMetadata.certName = "cert-name";
- tlsSecretsMetadata.keyName = "key-name";
- tlsSecretsMetadata.version = 1;
-
- tlsSecretsKeys.writeTlsSecretsMetadata(applicationId, tlsSecretsMetadata);
-
- assertEquals("{\"certName\":\"cert-name\",\"keyName\":\"key-name\",\"version\":1}",
- new String(curator.getData(tlsSecretsKeysPath).get()));
- }
-}
diff --git a/container-core/abi-spec.json b/container-core/abi-spec.json
index 5dc9a863970..91c0e4cc6bf 100644
--- a/container-core/abi-spec.json
+++ b/container-core/abi-spec.json
@@ -854,4 +854,4 @@
],
"fields": []
}
-}
+} \ No newline at end of file
diff --git a/container-dependency-versions/pom.xml b/container-dependency-versions/pom.xml
index 30cdbbfe531..a621545446f 100644
--- a/container-dependency-versions/pom.xml
+++ b/container-dependency-versions/pom.xml
@@ -446,7 +446,7 @@
<javax.inject.version>1</javax.inject.version>
<javax.servlet-api.version>3.1.0</javax.servlet-api.version>
<jaxb.version>2.3.0</jaxb.version>
- <jetty.version>9.4.25.v20191220</jetty.version>
+ <jetty.version>9.4.26.v20200117</jetty.version>
<lz4.version>1.3.0</lz4.version>
<org.json.version>20090211</org.json.version>
<slf4j.version>1.7.5</slf4j.version>
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServer.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServer.java
index 74e4ac9d404..a009f002954 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServer.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServer.java
@@ -11,6 +11,7 @@ import com.yahoo.vespa.hosted.controller.api.application.v4.model.EndpointStatus
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
import com.yahoo.vespa.hosted.controller.api.identifiers.Hostname;
import com.yahoo.vespa.hosted.controller.api.integration.certificates.ApplicationCertificate;
+import com.yahoo.vespa.hosted.controller.api.integration.deployment.TesterCloud;
import com.yahoo.vespa.serviceview.bindings.ApplicationView;
import java.io.InputStream;
@@ -87,4 +88,8 @@ public interface ConfigServer {
/** List all flag data for the given zone */
List<FlagData> listFlagData(ZoneId zone);
+ /** Gets status for tester application */
+ // TODO: Remove default implementation when implemented in internal repo
+ default TesterCloud.Status getTesterStatus(DeploymentId deployment) { return TesterCloud.Status.SUCCESS; }
+
}
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 dfc9574fcd7..82120f13b75 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
@@ -1,4 +1,4 @@
-// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller;
import com.google.common.collect.ImmutableList;
@@ -59,7 +59,7 @@ import com.yahoo.vespa.hosted.controller.deployment.DeploymentTrigger;
import com.yahoo.vespa.hosted.controller.deployment.Run;
import com.yahoo.vespa.hosted.controller.deployment.Versions;
import com.yahoo.vespa.hosted.controller.dns.NameServiceQueue.Priority;
-import com.yahoo.vespa.hosted.controller.maintenance.RoutingPolicies;
+import com.yahoo.vespa.hosted.controller.routing.RoutingPolicies;
import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
import com.yahoo.vespa.hosted.controller.rotation.RotationLock;
import com.yahoo.vespa.hosted.controller.rotation.RotationRepository;
@@ -686,9 +686,9 @@ public class ApplicationController {
catch (RuntimeException e) {
log.log(Level.WARNING, "Failed to get endpoint information for " + id, e);
}
- return routingPolicies.get(id).stream()
+ return routingPolicies.get(id).values().stream()
.filter(policy -> policy.endpointIn(controller.system()).scope() == Endpoint.Scope.zone)
- .collect(Collectors.toUnmodifiableMap(policy -> policy.cluster(),
+ .collect(Collectors.toUnmodifiableMap(policy -> policy.id().cluster(),
policy -> policy.endpointIn(controller.system()).url()));
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java
index 4f6fe2ac2db..d3e21f0d399 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java
@@ -9,6 +9,7 @@ import com.yahoo.config.provision.CloudName;
import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.zone.ZoneApi;
+import com.yahoo.jdisc.Metric;
import com.yahoo.vespa.curator.Lock;
import com.yahoo.vespa.flags.FlagSource;
import com.yahoo.vespa.hosted.controller.api.integration.ApplicationIdSnapshot;
@@ -70,6 +71,7 @@ public class Controller extends AbstractComponent implements ApplicationIdSource
private final FlagSource flagSource;
private final NameServiceForwarder nameServiceForwarder;
private final MavenRepository mavenRepository;
+ private final Metric metric;
/**
* Creates a controller
@@ -77,22 +79,15 @@ public class Controller extends AbstractComponent implements ApplicationIdSource
* @param curator the curator instance storing the persistent state of the controller.
*/
@Inject
- public Controller(CuratorDb curator, RotationsConfig rotationsConfig,
- AccessControl accessControl,
- FlagSource flagSource,
- MavenRepository mavenRepository,
- ServiceRegistry serviceRegistry) {
- this(curator, rotationsConfig,
- accessControl,
- com.yahoo.net.HostName::getLocalhost, flagSource,
- mavenRepository, serviceRegistry);
+ public Controller(CuratorDb curator, RotationsConfig rotationsConfig, AccessControl accessControl, FlagSource flagSource,
+ MavenRepository mavenRepository, ServiceRegistry serviceRegistry, Metric metric) {
+ this(curator, rotationsConfig, accessControl, com.yahoo.net.HostName::getLocalhost, flagSource,
+ mavenRepository, serviceRegistry, metric);
}
- public Controller(CuratorDb curator, RotationsConfig rotationsConfig,
- AccessControl accessControl,
- Supplier<String> hostnameSupplier,
- FlagSource flagSource, MavenRepository mavenRepository,
- ServiceRegistry serviceRegistry) {
+ public Controller(CuratorDb curator, RotationsConfig rotationsConfig, AccessControl accessControl,
+ Supplier<String> hostnameSupplier, FlagSource flagSource, MavenRepository mavenRepository,
+ ServiceRegistry serviceRegistry, Metric metric) {
this.hostnameSupplier = Objects.requireNonNull(hostnameSupplier, "HostnameSupplier cannot be null");
this.curator = Objects.requireNonNull(curator, "Curator cannot be null");
@@ -101,7 +96,7 @@ public class Controller extends AbstractComponent implements ApplicationIdSource
this.clock = Objects.requireNonNull(serviceRegistry.clock(), "Clock cannot be null");
this.flagSource = Objects.requireNonNull(flagSource, "FlagSource cannot be null");
this.mavenRepository = Objects.requireNonNull(mavenRepository, "MavenRepository cannot be null");
-
+ this.metric = Objects.requireNonNull(metric, "Metric cannot be null");
metrics = new ConfigServerMetrics(serviceRegistry.configServer());
nameServiceForwarder = new NameServiceForwarder(curator);
@@ -265,6 +260,10 @@ public class Controller extends AbstractComponent implements ApplicationIdSource
return auditLogger;
}
+ public Metric metric() {
+ return metric;
+ }
+
private Set<CloudName> clouds() {
return zoneRegistry.zones().all().zones().stream()
.map(ZoneApi::getCloudName)
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java
index bd61d85fbc0..9d09394a571 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java
@@ -17,6 +17,9 @@ import com.yahoo.security.KeyUtils;
import com.yahoo.security.SignatureAlgorithm;
import com.yahoo.security.X509CertificateBuilder;
import com.yahoo.security.X509CertificateUtils;
+import com.yahoo.vespa.flags.BooleanFlag;
+import com.yahoo.vespa.flags.FetchVector;
+import com.yahoo.vespa.flags.Flags;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.Instance;
@@ -469,7 +472,7 @@ public class InternalStepRunner implements StepRunner {
}
private Optional<RunStatus> endTests(RunId id, DualLogger logger) {
- if ( ! deployment(id.application(), id.type()).isPresent()) {
+ if (deployment(id.application(), id.type()).isEmpty()) {
logger.log(INFO, "Deployment expired before tests could complete.");
return Optional.of(aborted);
}
@@ -485,15 +488,22 @@ public class InternalStepRunner implements StepRunner {
}
}
- Optional<URI> testerEndpoint = controller.jobController().testerEndpoint(id);
- if ( ! testerEndpoint.isPresent()) {
- logger.log("Endpoints for tester not found -- trying again later.");
- return Optional.empty();
- }
-
controller.jobController().updateTestLog(id);
- TesterCloud.Status testStatus = controller.jobController().cloud().getStatus(testerEndpoint.get());
+ BooleanFlag useConfigServerForTesterAPI = Flags.USE_CONFIG_SERVER_FOR_TESTER_API_CALLS.bindTo(controller.flagSource());
+ ZoneId zoneId = id.type().zone(controller.system());
+ TesterCloud.Status testStatus;
+ if (useConfigServerForTesterAPI.with(FetchVector.Dimension.ZONE_ID, zoneId.value()).value()) {
+ testStatus = controller.serviceRegistry().configServer().getTesterStatus(new DeploymentId(id.application(), zoneId));
+ } else {
+ Optional<URI> testerEndpoint = controller.jobController().testerEndpoint(id);
+ if (testerEndpoint.isEmpty()) {
+ logger.log("Endpoints for tester not found -- trying again later.");
+ return Optional.empty();
+ }
+ testStatus = controller.jobController().cloud().getStatus(testerEndpoint.get());
+ }
+
switch (testStatus) {
case NOT_STARTED:
throw new IllegalStateException("Tester reports tests not started, even though they should have!");
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java
index 811daed256e..c8cfc8ac286 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java
@@ -1,9 +1,10 @@
-// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.deployment;
import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.jdisc.Metric;
import com.yahoo.vespa.curator.Lock;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
@@ -79,6 +80,7 @@ public class JobController {
private final BufferedLogStore logs;
private final TesterCloud cloud;
private final Badges badges;
+ private final JobMetrics metric;
private AtomicReference<Consumer<Run>> runner = new AtomicReference<>(__ -> { });
@@ -88,6 +90,7 @@ public class JobController {
this.logs = new BufferedLogStore(curator, controller.serviceRegistry().runDataStore());
this.cloud = controller.serviceRegistry().testerCloud();
this.badges = new Badges(controller.zoneRegistry().badgeUrl());
+ this.metric = new JobMetrics(controller.metric(), controller.system());
}
public TesterCloud cloud() { return cloud; }
@@ -360,6 +363,7 @@ public class JobController {
}
});
logs.flush(id);
+ metric.jobFinished(run.id().job(), finishedRun.status());
return finishedRun;
});
}
@@ -416,6 +420,7 @@ public class JobController {
RunId newId = new RunId(id, type, last.map(run -> run.id().number()).orElse(0L) + 1);
curator.writeLastRun(Run.initial(newId, versions, controller.clock().instant()));
+ metric.jobStarted(newId.job());
});
});
}
@@ -526,7 +531,7 @@ public class JobController {
DeploymentId testerId = new DeploymentId(id.tester().id(), id.type().zone(controller.system()));
return controller.applications().getDeploymentEndpoints(testerId)
.stream().findAny()
- .or(() -> controller.applications().routingPolicies().get(testerId).stream()
+ .or(() -> controller.applications().routingPolicies().get(testerId).values().stream()
.findAny()
.map(policy -> policy.endpointIn(controller.system()).url()));
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobMetrics.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobMetrics.java
new file mode 100644
index 00000000000..a6ffb56492f
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobMetrics.java
@@ -0,0 +1,64 @@
+// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.deployment;
+
+import com.yahoo.config.provision.SystemName;
+import com.yahoo.jdisc.Metric;
+import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobId;
+
+import java.util.Map;
+
+/**
+ * Records metrics related to deployment jobs.
+ *
+ * @author jonmv
+ */
+public class JobMetrics {
+
+ public static final String start = "deployment.start";
+ public static final String outOfCapacity = "deployment.outOfCapacity";
+ public static final String deploymentFailure = "deployment.deploymentFailure";
+ public static final String convergenceFailure = "deployment.convergenceFailure";
+ public static final String testFailure = "deployment.testFailure";
+ public static final String error = "deployment.error";
+ public static final String abort = "deployment.abort";
+ public static final String success = "deployment.success";
+
+ private final Metric metric;
+ private final SystemName system;
+
+ public JobMetrics(Metric metric, SystemName system) {
+ this.metric = metric;
+ this.system = system;
+ }
+
+ public void jobStarted(JobId id) {
+ metric.add(start, 1, metric.createContext(contextOf(id)));
+ }
+
+ public void jobFinished(JobId id, RunStatus status) {
+ metric.add(valueOf(status), 1, metric.createContext(contextOf(id)));
+ }
+
+ Map<String, String> contextOf(JobId id) {
+ return Map.of("tenant", id.application().tenant().value(),
+ "application", id.application().application().value(),
+ "instance", id.application().instance().value(),
+ "job", id.type().jobName(),
+ "environment", id.type().environment().value(),
+ "region", id.type().zone(system).region().value());
+ }
+
+ static String valueOf(RunStatus status) {
+ switch (status) {
+ case outOfCapacity: return outOfCapacity;
+ case deploymentFailed: return deploymentFailure;
+ case installationFailed: return convergenceFailure;
+ case testFailure: return testFailure;
+ case error: return error;
+ case aborted: return abort;
+ case success: return success;
+ default: throw new IllegalArgumentException("Unexpected run status '" + status + "'");
+ }
+ }
+
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPolicies.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPolicies.java
deleted file mode 100644
index ee38b2c9516..00000000000
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPolicies.java
+++ /dev/null
@@ -1,233 +0,0 @@
-// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.controller.maintenance;
-
-import com.yahoo.config.application.api.DeploymentSpec;
-import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.config.provision.zone.ZoneId;
-import com.yahoo.vespa.curator.Lock;
-import com.yahoo.vespa.hosted.controller.Controller;
-import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
-import com.yahoo.vespa.hosted.controller.api.integration.configserver.LoadBalancer;
-import com.yahoo.vespa.hosted.controller.api.integration.dns.AliasTarget;
-import com.yahoo.vespa.hosted.controller.api.integration.dns.Record;
-import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordData;
-import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordName;
-import com.yahoo.vespa.hosted.controller.application.Endpoint;
-import com.yahoo.vespa.hosted.controller.application.EndpointId;
-import com.yahoo.vespa.hosted.controller.application.RoutingId;
-import com.yahoo.vespa.hosted.controller.application.RoutingPolicy;
-import com.yahoo.vespa.hosted.controller.dns.NameServiceQueue.Priority;
-import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.logging.Logger;
-import java.util.stream.Collectors;
-
-/**
- * Updates routing policies and their associated DNS records based on an deployment's load balancers.
- *
- * @author mortent
- * @author mpolden
- */
-public class RoutingPolicies {
-
- private static final Logger LOGGER = Logger.getLogger(RoutingPolicies.class.getName());
-
- private final Controller controller;
- private final CuratorDb db;
-
- public RoutingPolicies(Controller controller) {
- this.controller = Objects.requireNonNull(controller, "controller must be non-null");
- this.db = controller.curator();
- try (var lock = db.lockRoutingPolicies()) { // Update serialized format
- for (var policy : db.readRoutingPolicies().entrySet()) {
- db.writeRoutingPolicies(policy.getKey(), policy.getValue());
- }
- }
- }
-
- /** Read all known routing policies for given instance */
- public Set<RoutingPolicy> get(ApplicationId application) {
- return db.readRoutingPolicies(application);
- }
-
- /** Read all known routing policies for given deployment */
- public Set<RoutingPolicy> get(DeploymentId deployment) {
- return get(deployment.applicationId(), deployment.zoneId());
- }
-
- /** Read all known routing policies for given deployment */
- public Set<RoutingPolicy> get(ApplicationId application, ZoneId zone) {
- return db.readRoutingPolicies(application).stream()
- .filter(policy -> policy.zone().equals(zone))
- .collect(Collectors.toUnmodifiableSet());
- }
-
- /**
- * Refresh routing policies for application in given zone. This is idempotent and changes will only be performed if
- * load balancers for given application have changed.
- */
- public void refresh(ApplicationId application, DeploymentSpec deploymentSpec, ZoneId zone) {
- if (!controller.zoneRegistry().zones().directlyRouted().ids().contains(zone)) return;
- var lbs = new AllocatedLoadBalancers(application, zone, controller.serviceRegistry().configServer().getLoadBalancers(application, zone),
- deploymentSpec);
- try (var lock = db.lockRoutingPolicies()) {
- removeObsoleteEndpointsFromDns(lbs, lock);
- storePoliciesOf(lbs, lock);
- removeObsoletePolicies(lbs, lock);
- registerEndpointsInDns(lbs, lock);
- }
- }
-
- /** Create global endpoints for given route, if any */
- private void registerEndpointsInDns(AllocatedLoadBalancers loadBalancers, @SuppressWarnings("unused") Lock lock) {
- Map<RoutingId, List<RoutingPolicy>> routingTable = routingTableFrom(get(loadBalancers.application));
-
- // Create DNS record for each routing ID
- for (Map.Entry<RoutingId, List<RoutingPolicy>> routeEntry : routingTable.entrySet()) {
- Endpoint endpoint = RoutingPolicy.endpointOf(routeEntry.getKey().application(), routeEntry.getKey().endpointId(),
- controller.system());
- Set<AliasTarget> targets = routeEntry.getValue()
- .stream()
- .filter(policy -> policy.dnsZone().isPresent())
- .map(policy -> new AliasTarget(policy.canonicalName(),
- policy.dnsZone().get(),
- policy.zone()))
- .collect(Collectors.toSet());
- controller.nameServiceForwarder().createAlias(RecordName.from(endpoint.dnsName()), targets, Priority.normal);
- }
- }
-
- /** Store routing policies for given route. Returns the persisted policies. */
- private void storePoliciesOf(AllocatedLoadBalancers loadBalancers, @SuppressWarnings("unused") Lock lock) {
- var policies = new LinkedHashSet<>(get(loadBalancers.application));
- for (LoadBalancer loadBalancer : loadBalancers.list) {
- var endpointIds = loadBalancers.endpointIdsOf(loadBalancer);
- var policy = createPolicy(loadBalancers.application, loadBalancers.zone, loadBalancer, endpointIds);
- if (!policies.add(policy)) {
- // Update existing policy
- policies.remove(policy);
- policies.add(policy);
- }
- }
- db.writeRoutingPolicies(loadBalancers.application, policies);
- }
-
- /** Create a policy for given load balancer and register a CNAME for it */
- private RoutingPolicy createPolicy(ApplicationId application, ZoneId zone, LoadBalancer loadBalancer,
- Set<EndpointId> endpointIds) {
- var routingPolicy = new RoutingPolicy(application, loadBalancer.cluster(), zone, loadBalancer.hostname(),
- loadBalancer.dnsZone(), endpointIds, isActive(loadBalancer));
- var name = RecordName.from(routingPolicy.endpointIn(controller.system()).dnsName());
- var data = RecordData.fqdn(loadBalancer.hostname().value());
- controller.nameServiceForwarder().createCname(name, data, Priority.normal);
- return routingPolicy;
- }
-
- /** Remove obsolete policies for given route and their CNAME records */
- private void removeObsoletePolicies(AllocatedLoadBalancers loadBalancers, @SuppressWarnings("unused") Lock lock) {
- var allPolicies = new LinkedHashSet<>(get(loadBalancers.application));
- var removalCandidates = new HashSet<>(allPolicies);
- var activeLoadBalancers = loadBalancers.list.stream()
- .map(LoadBalancer::hostname)
- .collect(Collectors.toSet());
- // Remove active load balancers and irrelevant zones from candidates
- removalCandidates.removeIf(policy -> activeLoadBalancers.contains(policy.canonicalName()) ||
- !policy.zone().equals(loadBalancers.zone));
- for (var policy : removalCandidates) {
- var dnsName = policy.endpointIn(controller.system()).dnsName();
- controller.nameServiceForwarder().removeRecords(Record.Type.CNAME, RecordName.from(dnsName), Priority.normal);
- allPolicies.remove(policy);
- }
- db.writeRoutingPolicies(loadBalancers.application, allPolicies);
- }
-
- /** Remove unreferenced global endpoints for given route from DNS */
- private void removeObsoleteEndpointsFromDns(AllocatedLoadBalancers loadBalancers, @SuppressWarnings("unused") Lock lock) {
- var zonePolicies = get(loadBalancers.application, loadBalancers.zone);
- var removalCandidates = routingTableFrom(zonePolicies).keySet();
- var activeRoutingIds = routingIdsFrom(loadBalancers);
- removalCandidates.removeAll(activeRoutingIds);
- for (var id : removalCandidates) {
- var endpoint = RoutingPolicy.endpointOf(id.application(), id.endpointId(), controller.system());
- controller.nameServiceForwarder().removeRecords(Record.Type.ALIAS, RecordName.from(endpoint.dnsName()), Priority.normal);
- }
- }
-
- /** Compute routing IDs from given load balancers */
- private static Set<RoutingId> routingIdsFrom(AllocatedLoadBalancers loadBalancers) {
- Set<RoutingId> routingIds = new LinkedHashSet<>();
- for (var loadBalancer : loadBalancers.list) {
- for (var endpointId : loadBalancers.endpointIdsOf(loadBalancer)) {
- routingIds.add(new RoutingId(loadBalancer.application(), endpointId));
- }
- }
- return Collections.unmodifiableSet(routingIds);
- }
-
- /** Compute a routing table from given policies */
- private static Map<RoutingId, List<RoutingPolicy>> routingTableFrom(Set<RoutingPolicy> routingPolicies) {
- var routingTable = new LinkedHashMap<RoutingId, List<RoutingPolicy>>();
- for (var policy : routingPolicies) {
- for (var rotation : policy.endpoints()) {
- var id = new RoutingId(policy.owner(), rotation);
- routingTable.putIfAbsent(id, new ArrayList<>());
- routingTable.get(id).add(policy);
- }
- }
- return routingTable;
- }
-
- private static boolean isActive(LoadBalancer loadBalancer) {
- switch (loadBalancer.state()) {
- case reserved: // Count reserved as active as we want callers (application API) to see the endpoint as early
- // as possible
- case active: return true;
- }
- return false;
- }
-
- /** Load balancers allocated to a deployment */
- private static class AllocatedLoadBalancers {
-
- private final ApplicationId application;
- private final ZoneId zone;
- private final List<LoadBalancer> list;
- private final DeploymentSpec deploymentSpec;
-
- private AllocatedLoadBalancers(ApplicationId application, ZoneId zone, List<LoadBalancer> loadBalancers,
- DeploymentSpec deploymentSpec) {
- this.application = application;
- this.zone = zone;
- this.list = List.copyOf(loadBalancers);
- this.deploymentSpec = deploymentSpec;
- }
-
- /** Compute all endpoint IDs for given load balancer */
- private Set<EndpointId> endpointIdsOf(LoadBalancer loadBalancer) {
- if (zone.environment().isManuallyDeployed()) { // Manual deployments do not have any configurable endpoints
- return Set.of();
- }
- var instanceSpec = deploymentSpec.instance(loadBalancer.application().instance());
- if (instanceSpec.isEmpty()) {
- return Set.of();
- }
- return instanceSpec.get().endpoints().stream()
- .filter(endpoint -> endpoint.containerId().equals(loadBalancer.cluster().value()))
- .filter(endpoint -> endpoint.regions().contains(zone.region()))
- .map(com.yahoo.config.application.api.Endpoint::endpointId)
- .map(EndpointId::of)
- .collect(Collectors.toSet());
- }
-
- }
-
-}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java
index 22894a084b6..1a2ffc69249 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java
@@ -1,4 +1,4 @@
-// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.google.common.util.concurrent.UncheckedTimeoutException;
@@ -18,19 +18,21 @@ import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.api.integration.certificates.ApplicationCertificate;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.RunId;
-import com.yahoo.vespa.hosted.controller.application.RoutingPolicy;
+import com.yahoo.vespa.hosted.controller.routing.GlobalRouting;
+import com.yahoo.vespa.hosted.controller.routing.RoutingPolicy;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.auditlog.AuditLog;
import com.yahoo.vespa.hosted.controller.deployment.Run;
import com.yahoo.vespa.hosted.controller.deployment.Step;
import com.yahoo.vespa.hosted.controller.dns.NameServiceQueue;
+import com.yahoo.vespa.hosted.controller.routing.RoutingPolicyId;
+import com.yahoo.vespa.hosted.controller.routing.ZoneRoutingPolicy;
import com.yahoo.vespa.hosted.controller.tenant.Tenant;
import com.yahoo.vespa.hosted.controller.versions.ControllerVersion;
import com.yahoo.vespa.hosted.controller.versions.OsVersion;
import com.yahoo.vespa.hosted.controller.versions.OsVersionStatus;
import com.yahoo.vespa.hosted.controller.versions.VersionStatus;
import com.yahoo.vespa.hosted.controller.versions.VespaVersion;
-import org.apache.zookeeper.data.Stat;
import java.io.IOException;
import java.io.UncheckedIOException;
@@ -80,6 +82,7 @@ public class CuratorDb {
private static final Path jobRoot = root.append("jobs");
private static final Path controllerRoot = root.append("controllers");
private static final Path routingPoliciesRoot = root.append("routingPolicies");
+ private static final Path zoneRoutingPoliciesRoot = root.append("zoneRoutingPolicies");
private static final Path applicationCertificateRoot = root.append("applicationCertificates");
private final StringSetSerializer stringSetSerializer = new StringSetSerializer();
@@ -93,6 +96,7 @@ public class CuratorDb {
private final OsVersionSerializer osVersionSerializer = new OsVersionSerializer();
private final OsVersionStatusSerializer osVersionStatusSerializer = new OsVersionStatusSerializer(osVersionSerializer, nodeVersionSerializer);
private final RoutingPolicySerializer routingPolicySerializer = new RoutingPolicySerializer();
+ private final ZoneRoutingPolicySerializer zoneRoutingPolicySerializer = new ZoneRoutingPolicySerializer(routingPolicySerializer);
private final AuditLogSerializer auditLogSerializer = new AuditLogSerializer();
private final NameServiceQueueSerializer nameServiceQueueSerializer = new NameServiceQueueSerializer();
@@ -485,19 +489,28 @@ public class CuratorDb {
// -------------- Routing policies ----------------------------------------
- public void writeRoutingPolicies(ApplicationId application, Set<RoutingPolicy> policies) {
+ public void writeRoutingPolicies(ApplicationId application, Map<RoutingPolicyId, RoutingPolicy> policies) {
curator.set(routingPolicyPath(application), asJson(routingPolicySerializer.toSlime(policies)));
}
- public Map<ApplicationId, Set<RoutingPolicy>> readRoutingPolicies() {
+ public Map<ApplicationId, Map<RoutingPolicyId, RoutingPolicy>> readRoutingPolicies() {
return curator.getChildren(routingPoliciesRoot).stream()
.map(ApplicationId::fromSerializedForm)
.collect(Collectors.toUnmodifiableMap(Function.identity(), this::readRoutingPolicies));
}
- public Set<RoutingPolicy> readRoutingPolicies(ApplicationId application) {
+ public Map<RoutingPolicyId, RoutingPolicy> readRoutingPolicies(ApplicationId application) {
return readSlime(routingPolicyPath(application)).map(slime -> routingPolicySerializer.fromSlime(application, slime))
- .orElseGet(Collections::emptySet);
+ .orElseGet(Map::of);
+ }
+
+ public void writeZoneRoutingPolicy(ZoneRoutingPolicy policy) {
+ curator.set(zoneRoutingPolicyPath(policy.zone()), asJson(zoneRoutingPolicySerializer.toSlime(policy)));
+ }
+
+ public ZoneRoutingPolicy readZoneRoutingPolicy(ZoneId zone) {
+ return readSlime(zoneRoutingPolicyPath(zone)).map(data -> zoneRoutingPolicySerializer.fromSlime(zone, data))
+ .orElse(new ZoneRoutingPolicy(zone, GlobalRouting.DEFAULT_STATUS));
}
// -------------- Application web certificates ----------------------------
@@ -581,6 +594,8 @@ public class CuratorDb {
return routingPoliciesRoot.append(application.serializedForm());
}
+ private static Path zoneRoutingPolicyPath(ZoneId zone) { return zoneRoutingPoliciesRoot.append(zone.value()); }
+
private static Path nameServiceQueuePath() {
return root.append("nameServiceQueue");
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializer.java
index 54a3ef7551a..2429c5ee8c5 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializer.java
@@ -1,4 +1,4 @@
-// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.config.provision.ApplicationId;
@@ -6,13 +6,20 @@ import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.slime.ArrayTraverser;
+import com.yahoo.slime.Cursor;
+import com.yahoo.slime.Inspector;
import com.yahoo.slime.Slime;
import com.yahoo.vespa.hosted.controller.application.EndpointId;
-import com.yahoo.vespa.hosted.controller.application.RoutingPolicy;
+import com.yahoo.vespa.hosted.controller.routing.GlobalRouting;
+import com.yahoo.vespa.hosted.controller.routing.RoutingPolicy;
+import com.yahoo.vespa.hosted.controller.routing.RoutingPolicyId;
+import com.yahoo.vespa.hosted.controller.routing.Status;
+import java.time.Instant;
import java.util.Collections;
+import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
-import java.util.Set;
+import java.util.Map;
/**
* Serializer and deserializer for a {@link RoutingPolicy}.
@@ -35,45 +42,64 @@ public class RoutingPolicySerializer {
private static final String zoneField = "zone";
private static final String dnsZoneField = "dnsZone";
private static final String rotationsField = "rotations";
- private static final String activeField = "active";
+ private static final String loadBalancerActiveField = "active";
+ private static final String globalRoutingField = "globalRouting";
+ private static final String agentField = "agent";
+ private static final String changedAtField = "changedAt";
+ private static final String statusField = "status";
- public Slime toSlime(Set<RoutingPolicy> routingPolicies) {
+ public Slime toSlime(Map<RoutingPolicyId, RoutingPolicy> routingPolicies) {
var slime = new Slime();
var root = slime.setObject();
var policyArray = root.setArray(routingPoliciesField);
- routingPolicies.forEach(policy -> {
+ routingPolicies.values().forEach(policy -> {
var policyObject = policyArray.addObject();
- policyObject.setString(clusterField, policy.cluster().value());
- policyObject.setString(zoneField, policy.zone().value());
+ policyObject.setString(clusterField, policy.id().cluster().value());
+ policyObject.setString(zoneField, policy.id().zone().value());
policyObject.setString(canonicalNameField, policy.canonicalName().value());
policy.dnsZone().ifPresent(dnsZone -> policyObject.setString(dnsZoneField, dnsZone));
var rotationArray = policyObject.setArray(rotationsField);
policy.endpoints().forEach(endpointId -> {
rotationArray.addString(endpointId.id());
});
- policyObject.setBool(activeField, policy.active());
+ policyObject.setBool(loadBalancerActiveField, policy.status().isActive());
+ globalRoutingToSlime(policy.status().globalRouting(), policyObject.setObject(globalRoutingField));
});
return slime;
}
- public Set<RoutingPolicy> fromSlime(ApplicationId owner, Slime slime) {
- var policies = new LinkedHashSet<RoutingPolicy>();
+ public Map<RoutingPolicyId, RoutingPolicy> fromSlime(ApplicationId owner, Slime slime) {
+ var policies = new LinkedHashMap<RoutingPolicyId, RoutingPolicy>();
var root = slime.get();
var field = root.field(routingPoliciesField);
field.traverse((ArrayTraverser) (i, inspect) -> {
var endpointIds = new LinkedHashSet<EndpointId>();
inspect.field(rotationsField).traverse((ArrayTraverser) (j, endpointId) -> endpointIds.add(EndpointId.of(endpointId.asString())));
- var activeFieldInspector = inspect.field(activeField);
- // TODO(mpolden): Remove field presence check after January 2020
- boolean active = !activeFieldInspector.valid() || activeFieldInspector.asBool();
- policies.add(new RoutingPolicy(owner,
- ClusterSpec.Id.from(inspect.field(clusterField).asString()),
- ZoneId.from(inspect.field(zoneField).asString()),
- HostName.from(inspect.field(canonicalNameField).asString()),
- Serializers.optionalString(inspect.field(dnsZoneField)),
- endpointIds, active));
+ var id = new RoutingPolicyId(owner,
+ ClusterSpec.Id.from(inspect.field(clusterField).asString()),
+ ZoneId.from(inspect.field(zoneField).asString()));
+ policies.put(id, new RoutingPolicy(id,
+ HostName.from(inspect.field(canonicalNameField).asString()),
+ Serializers.optionalString(inspect.field(dnsZoneField)),
+ endpointIds,
+ new Status(inspect.field(loadBalancerActiveField).asBool(),
+ globalRoutingFromSlime(inspect.field(globalRoutingField)))));
});
- return Collections.unmodifiableSet(policies);
+ return Collections.unmodifiableMap(policies);
+ }
+
+ public void globalRoutingToSlime(GlobalRouting globalRouting, Cursor object) {
+ object.setString(statusField, globalRouting.status().name());
+ object.setString(agentField, globalRouting.agent().name());
+ object.setLong(changedAtField, globalRouting.changedAt().toEpochMilli());
+ }
+
+ public GlobalRouting globalRoutingFromSlime(Inspector object) {
+ if (!object.valid()) return GlobalRouting.DEFAULT_STATUS;
+ var status = GlobalRouting.Status.valueOf(object.field(statusField).asString());
+ var agent = GlobalRouting.Agent.valueOf(object.field(agentField).asString());
+ var changedAt = Serializers.optionalInstant(object.field(changedAtField)).orElse(Instant.EPOCH);
+ return new GlobalRouting(status, agent, changedAt);
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ZoneRoutingPolicySerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ZoneRoutingPolicySerializer.java
new file mode 100644
index 00000000000..6688d16ad14
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ZoneRoutingPolicySerializer.java
@@ -0,0 +1,44 @@
+// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.persistence;
+
+import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.slime.Slime;
+import com.yahoo.vespa.hosted.controller.routing.ZoneRoutingPolicy;
+
+import java.util.Objects;
+
+/**
+ * Serializer for {@link ZoneRoutingPolicy}.
+ *
+ * @author mpolden
+ */
+public class ZoneRoutingPolicySerializer {
+
+ // WARNING: Since there are multiple servers in a ZooKeeper cluster and they upgrade one by one
+ // (and rewrite all nodes on startup), changes to the serialized format must be made
+ // such that what is serialized on version N+1 can be read by version N:
+ // - ADDING FIELDS: Always ok
+ // - REMOVING FIELDS: Stop reading the field first. Stop writing it on a later version.
+ // - CHANGING THE FORMAT OF A FIELD: Don't do it bro.
+
+ private static final String GLOBAL_ROUTING_FIELD = "globalRouting";
+
+ private final RoutingPolicySerializer routingPolicySerializer;
+
+ public ZoneRoutingPolicySerializer(RoutingPolicySerializer routingPolicySerializer) {
+ this.routingPolicySerializer = Objects.requireNonNull(routingPolicySerializer, "routingPolicySerializer must be non-null");
+ }
+
+ public ZoneRoutingPolicy fromSlime(ZoneId zone, Slime slime) {
+ var root = slime.get();
+ return new ZoneRoutingPolicy(zone, routingPolicySerializer.globalRoutingFromSlime(root.field(GLOBAL_ROUTING_FIELD)));
+ }
+
+ public Slime toSlime(ZoneRoutingPolicy policy) {
+ var slime = new Slime();
+ var root = slime.setObject();
+ routingPolicySerializer.globalRoutingToSlime(policy.globalRouting(), root.setObject(GLOBAL_ROUTING_FIELD));
+ return slime;
+ }
+
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
index 378013b5e6d..f6cf776cbfa 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
@@ -1,4 +1,4 @@
-// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.application;
import ai.vespa.hosted.api.Signatures;
@@ -68,7 +68,6 @@ import com.yahoo.vespa.hosted.controller.application.Deployment;
import com.yahoo.vespa.hosted.controller.application.DeploymentCost;
import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics;
import com.yahoo.vespa.hosted.controller.application.Endpoint;
-import com.yahoo.vespa.hosted.controller.application.RoutingPolicy;
import com.yahoo.vespa.hosted.controller.application.SystemApplication;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentStatus;
@@ -804,9 +803,9 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
.forEach(globalEndpointUrls::add);
// Per-cluster endpoints. These are backed by load balancers.
- Set<RoutingPolicy> routingPolicies = controller.applications().routingPolicies().get(instance.id());
+ var routingPolicies = controller.applications().routingPolicies().get(instance.id()).values();
for (var policy : routingPolicies) {
- policy.rotationEndpointsIn(controller.system()).asList().stream()
+ policy.globalEndpointsIn(controller.system()).asList().stream()
.map(Endpoint::url)
.map(URI::toString)
.forEach(globalEndpointUrls::add);
@@ -929,10 +928,10 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
.ifPresent(rotation -> object.setString("rotationId", rotation.asString()));
// Per-cluster rotations
- Set<RoutingPolicy> routingPolicies = controller.applications().routingPolicies().get(instance.id());
- for (RoutingPolicy policy : routingPolicies) {
- if (!policy.active()) continue;
- policy.rotationEndpointsIn(controller.system()).asList().stream()
+ var routingPolicies = controller.applications().routingPolicies().get(instance.id()).values();
+ for (var policy : routingPolicies) {
+ if (!policy.status().isActive()) continue;
+ policy.globalEndpointsIn(controller.system()).asList().stream()
.map(Endpoint::url)
.map(URI::toString)
.forEach(globalRotationsArray::addString);
@@ -1043,11 +1042,11 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
// Add endpoint(s) defined by routing policies
var endpointArray = response.setArray("endpoints");
- for (var policy : controller.applications().routingPolicies().get(deploymentId)) {
- if (!policy.active()) continue;
+ for (var policy : controller.applications().routingPolicies().get(deploymentId).values()) {
+ if (!policy.status().isActive()) continue;
Cursor endpointObject = endpointArray.addObject();
Endpoint endpoint = policy.endpointIn(controller.system());
- endpointObject.setString("cluster", policy.cluster().value());
+ endpointObject.setString("cluster", policy.id().cluster().value());
endpointObject.setBool("tls", endpoint.tls());
endpointObject.setString("url", endpoint.url().toString());
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/GlobalRouting.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/GlobalRouting.java
new file mode 100644
index 00000000000..1b2cf4a7896
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/GlobalRouting.java
@@ -0,0 +1,85 @@
+// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.routing;
+
+import java.time.Instant;
+import java.util.Objects;
+
+/**
+ * Represents the global routing status of a {@link RoutingPolicy} or {@link ZoneRoutingPolicy}. This contains the
+ * time global routing status was last changed and who changed it.
+ *
+ * This is immutable.
+ *
+ * @author mpolden
+ */
+public class GlobalRouting {
+
+ public static final GlobalRouting DEFAULT_STATUS = new GlobalRouting(Status.in, Agent.system, Instant.EPOCH);
+
+ private final Status status;
+ private final Agent agent;
+ private final Instant changedAt;
+
+ /** DO NOT USE. Public for serialization purposes */
+ public GlobalRouting(Status status, Agent agent, Instant changedAt) {
+ this.status = Objects.requireNonNull(status, "status must be non-null");
+ this.agent = Objects.requireNonNull(agent, "agent must be non-null");
+ this.changedAt = Objects.requireNonNull(changedAt, "changedAt must be non-null");
+ }
+
+ /** The current status of this */
+ public Status status() {
+ return status;
+ }
+
+ /** The agent who last changed this */
+ public Agent agent() {
+ return agent;
+ }
+
+ /** The time this was last changed */
+ public Instant changedAt() {
+ return changedAt;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ GlobalRouting that = (GlobalRouting) o;
+ return status == that.status &&
+ agent == that.agent &&
+ changedAt.equals(that.changedAt);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(status, agent, changedAt);
+ }
+
+ @Override
+ public String toString() {
+ return "status " + status + ", changed by " + agent + " @ " + changedAt;
+ }
+
+ public static GlobalRouting status(Status status, Agent agent, Instant instant) {
+ return new GlobalRouting(status, agent, instant);
+ }
+
+ // Used in serialization. Do not change.
+ public enum Status {
+ /** Status is determined by health checks **/
+ in,
+
+ /** Status is explicitly set to out */
+ out,
+ }
+
+ /** Agents that can change the state of global routing */
+ public enum Agent {
+ operator,
+ tenant,
+ system,
+ }
+
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/RoutingId.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingId.java
index 7b0ec3d27ba..5543d0ea0b7 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/RoutingId.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingId.java
@@ -1,7 +1,8 @@
-// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.controller.application;
+// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.routing;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.vespa.hosted.controller.application.EndpointId;
import java.util.Objects;
@@ -42,4 +43,9 @@ public class RoutingId {
return Objects.hash(application, endpointId);
}
+ @Override
+ public String toString() {
+ return "routing id for " + endpointId + " of " + application;
+ }
+
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicies.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicies.java
new file mode 100644
index 00000000000..c05152e7795
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicies.java
@@ -0,0 +1,288 @@
+// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.routing;
+
+import com.yahoo.config.application.api.DeploymentSpec;
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.vespa.curator.Lock;
+import com.yahoo.vespa.hosted.controller.Controller;
+import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
+import com.yahoo.vespa.hosted.controller.api.integration.configserver.LoadBalancer;
+import com.yahoo.vespa.hosted.controller.api.integration.dns.AliasTarget;
+import com.yahoo.vespa.hosted.controller.api.integration.dns.Record;
+import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordData;
+import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordName;
+import com.yahoo.vespa.hosted.controller.application.EndpointId;
+import com.yahoo.vespa.hosted.controller.dns.NameServiceQueue.Priority;
+import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Updates routing policies and their associated DNS records based on an deployment's load balancers.
+ *
+ * @author mortent
+ * @author mpolden
+ */
+public class RoutingPolicies {
+
+ private final Controller controller;
+ private final CuratorDb db;
+
+ public RoutingPolicies(Controller controller) {
+ this.controller = Objects.requireNonNull(controller, "controller must be non-null");
+ this.db = controller.curator();
+ try (var lock = db.lockRoutingPolicies()) { // Update serialized format
+ for (var policy : db.readRoutingPolicies().entrySet()) {
+ db.writeRoutingPolicies(policy.getKey(), policy.getValue());
+ }
+ }
+ }
+
+ /** Read all known routing policies for given instance */
+ public Map<RoutingPolicyId, RoutingPolicy> get(ApplicationId application) {
+ return db.readRoutingPolicies(application);
+ }
+
+ /** Read all known routing policies for given deployment */
+ public Map<RoutingPolicyId, RoutingPolicy> get(DeploymentId deployment) {
+ return get(deployment.applicationId(), deployment.zoneId());
+ }
+
+ /** Read all known routing policies for given deployment */
+ public Map<RoutingPolicyId, RoutingPolicy> get(ApplicationId application, ZoneId zone) {
+ return db.readRoutingPolicies(application).entrySet()
+ .stream()
+ .filter(kv -> kv.getKey().zone().equals(zone))
+ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+ }
+
+ /**
+ * Refresh routing policies for application in given zone. This is idempotent and changes will only be performed if
+ * load balancers for given application have changed.
+ */
+ public void refresh(ApplicationId application, DeploymentSpec deploymentSpec, ZoneId zone) {
+ if (!controller.zoneRegistry().zones().directlyRouted().ids().contains(zone)) return;
+ var loadBalancers = new AllocatedLoadBalancers(application, zone, controller.serviceRegistry().configServer()
+ .getLoadBalancers(application, zone),
+ deploymentSpec);
+ var inactiveZones = inactiveZones(application, deploymentSpec);
+ try (var lock = db.lockRoutingPolicies()) {
+ removeGlobalDnsUnreferencedBy(loadBalancers, lock);
+ storePoliciesOf(loadBalancers, lock);
+ removePoliciesUnreferencedBy(loadBalancers, lock);
+ updateGlobalDnsOf(get(loadBalancers.application).values(), inactiveZones, lock);
+ }
+ }
+
+ /** Set the status of all global endpoints in given zone */
+ public void setGlobalRoutingStatus(ZoneId zone, GlobalRouting.Status status) {
+ try (var lock = db.lockRoutingPolicies()) {
+ db.writeZoneRoutingPolicy(new ZoneRoutingPolicy(zone, GlobalRouting.status(status, GlobalRouting.Agent.operator,
+ controller.clock().instant())));
+ var allPolicies = db.readRoutingPolicies();
+ for (var applicationPolicies : allPolicies.values()) {
+ updateGlobalDnsOf(applicationPolicies.values(), Set.of(), lock);
+ }
+ }
+ }
+
+ /** Set the status of all global endpoints for given deployment */
+ public void setGlobalRoutingStatus(DeploymentId deployment, GlobalRouting.Status status, GlobalRouting.Agent agent) {
+ try (var lock = db.lockRoutingPolicies()) {
+ var policies = get(deployment.applicationId());
+ var newPolicies = new LinkedHashMap<>(policies);
+ for (var policy : policies.values()) {
+ if (!policy.id().zone().equals(deployment.zoneId())) continue; // Wrong zone
+ var newPolicy = policy.with(policy.status().with(GlobalRouting.status(status, agent,
+ controller.clock().instant())));
+ newPolicies.put(policy.id(), newPolicy);
+ }
+ db.writeRoutingPolicies(deployment.applicationId(), newPolicies);
+ updateGlobalDnsOf(newPolicies.values(), Set.of(), lock);
+ }
+ }
+
+ /** Update global DNS record for given policies */
+ private void updateGlobalDnsOf(Collection<RoutingPolicy> routingPolicies, Set<ZoneId> inactiveZones, @SuppressWarnings("unused") Lock lock) {
+ // Create DNS record for each routing ID
+ var routingTable = routingTableFrom(routingPolicies);
+ for (Map.Entry<RoutingId, List<RoutingPolicy>> routeEntry : routingTable.entrySet()) {
+ var targets = new LinkedHashSet<AliasTarget>();
+ var staleTargets = new LinkedHashSet<AliasTarget>();
+ for (var policy : routeEntry.getValue()) {
+ if (policy.dnsZone().isEmpty()) continue;
+ var target = new AliasTarget(policy.canonicalName(), policy.dnsZone().get(), policy.id().zone());
+ var zonePolicy = db.readZoneRoutingPolicy(policy.id().zone());
+ // Remove target zone if global routing status is set out at:
+ // - zone level (ZoneRoutingPolicy)
+ // - deployment level (RoutingPolicy)
+ // - application package level (deployment.xml)
+ if (anyOut(zonePolicy.globalRouting(), policy.status().globalRouting()) ||
+ inactiveZones.contains(policy.id().zone())) {
+ staleTargets.add(target);
+ } else {
+ targets.add(target);
+ }
+ }
+ if (!targets.isEmpty()) {
+ var endpoint = RoutingPolicy.globalEndpointOf(routeEntry.getKey().application(),
+ routeEntry.getKey().endpointId(), controller.system());
+ controller.nameServiceForwarder().createAlias(RecordName.from(endpoint.dnsName()), targets, Priority.normal);
+ }
+ staleTargets.forEach(t -> controller.nameServiceForwarder().removeRecords(Record.Type.ALIAS, t.asData(), Priority.normal));
+ }
+ }
+
+ /** Store routing policies for given load balancers */
+ private void storePoliciesOf(AllocatedLoadBalancers loadBalancers, @SuppressWarnings("unused") Lock lock) {
+ var policies = new LinkedHashMap<>(get(loadBalancers.application));
+ for (LoadBalancer loadBalancer : loadBalancers.list) {
+ var policyId = new RoutingPolicyId(loadBalancer.application(), loadBalancer.cluster(), loadBalancers.zone);
+ var existingPolicy = policies.get(policyId);
+ var newPolicy = new RoutingPolicy(policyId, loadBalancer.hostname(), loadBalancer.dnsZone(),
+ loadBalancers.endpointIdsOf(loadBalancer),
+ new Status(isActive(loadBalancer), GlobalRouting.DEFAULT_STATUS));
+ // Preserve global routing status for existing policy
+ if (existingPolicy != null) {
+ newPolicy = newPolicy.with(newPolicy.status().with(existingPolicy.status().globalRouting()));
+ }
+ updateZoneDnsOf(newPolicy);
+ policies.put(newPolicy.id(), newPolicy);
+ }
+ db.writeRoutingPolicies(loadBalancers.application, policies);
+ }
+
+ /** Update zone DNS record for given policy */
+ private void updateZoneDnsOf(RoutingPolicy policy) {
+ var name = RecordName.from(policy.endpointIn(controller.system()).dnsName());
+ var data = RecordData.fqdn(policy.canonicalName().value());
+ controller.nameServiceForwarder().createCname(name, data, Priority.normal);
+ }
+
+ /** Remove policies and zone DNS records unreferenced by given load balancers */
+ private void removePoliciesUnreferencedBy(AllocatedLoadBalancers loadBalancers, @SuppressWarnings("unused") Lock lock) {
+ var policies = get(loadBalancers.application);
+ var newPolicies = new LinkedHashMap<>(policies);
+ var activeLoadBalancers = loadBalancers.list.stream().map(LoadBalancer::hostname).collect(Collectors.toSet());
+ for (var policy : policies.values()) {
+ // Leave active load balancers and irrelevant zones alone
+ if (activeLoadBalancers.contains(policy.canonicalName()) ||
+ !policy.id().zone().equals(loadBalancers.zone)) continue;
+
+ var dnsName = policy.endpointIn(controller.system()).dnsName();
+ controller.nameServiceForwarder().removeRecords(Record.Type.CNAME, RecordName.from(dnsName), Priority.normal);
+ newPolicies.remove(policy.id());
+ }
+ db.writeRoutingPolicies(loadBalancers.application, newPolicies);
+ }
+
+ /** Remove unreferenced global endpoints from DNS */
+ private void removeGlobalDnsUnreferencedBy(AllocatedLoadBalancers loadBalancers, @SuppressWarnings("unused") Lock lock) {
+ var zonePolicies = get(loadBalancers.application, loadBalancers.zone).values();
+ var removalCandidates = new HashSet<>(routingTableFrom(zonePolicies).keySet());
+ var activeRoutingIds = routingIdsFrom(loadBalancers);
+ removalCandidates.removeAll(activeRoutingIds);
+ for (var id : removalCandidates) {
+ var endpoint = RoutingPolicy.globalEndpointOf(id.application(), id.endpointId(), controller.system());
+ controller.nameServiceForwarder().removeRecords(Record.Type.ALIAS, RecordName.from(endpoint.dnsName()), Priority.normal);
+ }
+ }
+
+ /** Compute routing IDs from given load balancers */
+ private static Set<RoutingId> routingIdsFrom(AllocatedLoadBalancers loadBalancers) {
+ Set<RoutingId> routingIds = new LinkedHashSet<>();
+ for (var loadBalancer : loadBalancers.list) {
+ for (var endpointId : loadBalancers.endpointIdsOf(loadBalancer)) {
+ routingIds.add(new RoutingId(loadBalancer.application(), endpointId));
+ }
+ }
+ return Collections.unmodifiableSet(routingIds);
+ }
+
+ /** Compute a routing table from given policies */
+ private static Map<RoutingId, List<RoutingPolicy>> routingTableFrom(Collection<RoutingPolicy> routingPolicies) {
+ var routingTable = new LinkedHashMap<RoutingId, List<RoutingPolicy>>();
+ for (var policy : routingPolicies) {
+ for (var endpoint : policy.endpoints()) {
+ var id = new RoutingId(policy.id().owner(), endpoint);
+ routingTable.putIfAbsent(id, new ArrayList<>());
+ routingTable.get(id).add(policy);
+ }
+ }
+ return Collections.unmodifiableMap(routingTable);
+ }
+
+ private static boolean anyOut(GlobalRouting... globalRouting) {
+ return Arrays.stream(globalRouting)
+ .map(GlobalRouting::status)
+ .anyMatch(status -> status == GlobalRouting.Status.out);
+ }
+
+ private static boolean isActive(LoadBalancer loadBalancer) {
+ switch (loadBalancer.state()) {
+ case reserved: // Count reserved as active as we want callers (application API) to see the endpoint as early
+ // as possible
+ case active: return true;
+ }
+ return false;
+ }
+
+ /** Load balancers allocated to a deployment */
+ private static class AllocatedLoadBalancers {
+
+ private final ApplicationId application;
+ private final ZoneId zone;
+ private final List<LoadBalancer> list;
+ private final DeploymentSpec deploymentSpec;
+
+ private AllocatedLoadBalancers(ApplicationId application, ZoneId zone, List<LoadBalancer> loadBalancers,
+ DeploymentSpec deploymentSpec) {
+ this.application = application;
+ this.zone = zone;
+ this.list = List.copyOf(loadBalancers);
+ this.deploymentSpec = deploymentSpec;
+ }
+
+ /** Compute all endpoint IDs for given load balancer */
+ private Set<EndpointId> endpointIdsOf(LoadBalancer loadBalancer) {
+ if (zone.environment().isManuallyDeployed()) { // Manual deployments do not have any configurable endpoints
+ return Set.of();
+ }
+ var instanceSpec = deploymentSpec.instance(loadBalancer.application().instance());
+ if (instanceSpec.isEmpty()) {
+ return Set.of();
+ }
+ return instanceSpec.get().endpoints().stream()
+ .filter(endpoint -> endpoint.containerId().equals(loadBalancer.cluster().value()))
+ .filter(endpoint -> endpoint.regions().contains(zone.region()))
+ .map(com.yahoo.config.application.api.Endpoint::endpointId)
+ .map(EndpointId::of)
+ .collect(Collectors.toSet());
+ }
+
+ }
+
+ /** Returns zones where global routing is declared inactive for instance through deploymentSpec */
+ private static Set<ZoneId> inactiveZones(ApplicationId instance, DeploymentSpec deploymentSpec) {
+ var instanceSpec = deploymentSpec.instance(instance.instance());
+ if (instanceSpec.isEmpty()) return Set.of();
+ return instanceSpec.get().zones().stream()
+ .filter(zone -> zone.environment().isProduction())
+ .filter(zone -> !zone.active())
+ .map(zone -> ZoneId.from(zone.environment(), zone.region().get()))
+ .collect(Collectors.toUnmodifiableSet());
+ }
+
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/RoutingPolicy.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicy.java
index 80a62d94f2e..b1b6d1ae58a 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/RoutingPolicy.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicy.java
@@ -1,60 +1,46 @@
-// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.controller.application;
+// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.routing;
import com.google.common.collect.ImmutableSortedSet;
import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.SystemName;
-import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.vespa.hosted.controller.application.Endpoint;
import com.yahoo.vespa.hosted.controller.application.Endpoint.Port;
+import com.yahoo.vespa.hosted.controller.application.EndpointId;
+import com.yahoo.vespa.hosted.controller.application.EndpointList;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
/**
- * Represents the DNS routing policy for a load balancer. A routing policy is uniquely identified by its owner, cluster
- * and zone.
+ * Represents the DNS routing policy for a {@link com.yahoo.vespa.hosted.controller.application.Deployment}.
*
* @author mortent
* @author mpolden
*/
public class RoutingPolicy {
- private final ApplicationId owner;
- private final ClusterSpec.Id cluster;
- private final ZoneId zone;
+ private final RoutingPolicyId id;
private final HostName canonicalName;
private final Optional<String> dnsZone;
private final Set<EndpointId> endpoints;
- private final boolean active;
+ private final Status status;
/** DO NOT USE. Public for serialization purposes */
- public RoutingPolicy(ApplicationId owner, ClusterSpec.Id cluster, ZoneId zone, HostName canonicalName,
- Optional<String> dnsZone, Set<EndpointId> endpoints, boolean active) {
- this.owner = Objects.requireNonNull(owner, "owner must be non-null");
- this.cluster = Objects.requireNonNull(cluster, "cluster must be non-null");
- this.zone = Objects.requireNonNull(zone, "zone must be non-null");
+ public RoutingPolicy(RoutingPolicyId id, HostName canonicalName, Optional<String> dnsZone, Set<EndpointId> endpoints,
+ Status status) {
+ this.id = Objects.requireNonNull(id, "id must be non-null");
this.canonicalName = Objects.requireNonNull(canonicalName, "canonicalName must be non-null");
this.dnsZone = Objects.requireNonNull(dnsZone, "dnsZone must be non-null");
this.endpoints = ImmutableSortedSet.copyOf(Objects.requireNonNull(endpoints, "endpoints must be non-null"));
- this.active = active;
+ this.status = Objects.requireNonNull(status, "status must be non-null");
}
- /** The application owning this */
- public ApplicationId owner() {
- return owner;
- }
-
- /** The zone this applies to */
- public ZoneId zone() {
- return zone;
- }
-
- /** The cluster this applies to */
- public ClusterSpec.Id cluster() {
- return cluster;
+ /** The ID of this */
+ public RoutingPolicyId id() {
+ return id;
}
/** The canonical name for this (rhs of a CNAME or ALIAS record) */
@@ -72,19 +58,24 @@ public class RoutingPolicy {
return endpoints;
}
- /** Returns whether this is active (the underlying load balancer is in an active state) */
- public boolean active() {
- return active;
+ /** Returns the status of this */
+ public Status status() {
+ return status;
+ }
+
+ /** Returns a copy of this with status set to given status */
+ public RoutingPolicy with(Status status) {
+ return new RoutingPolicy(id, canonicalName, dnsZone, endpoints, status);
}
/** Returns the endpoint of this */
public Endpoint endpointIn(SystemName system) {
- return Endpoint.of(owner).target(cluster, zone).on(Port.tls()).directRouting().in(system);
+ return Endpoint.of(id.owner()).target(id.cluster(), id.zone()).on(Port.tls()).directRouting().in(system);
}
- /** Returns rotation endpoints of this */
- public EndpointList rotationEndpointsIn(SystemName system) {
- return EndpointList.of(endpoints.stream().map(endpointId -> endpointOf(owner, endpointId, system)));
+ /** Returns global endpoints which this is a member of */
+ public EndpointList globalEndpointsIn(SystemName system) {
+ return EndpointList.of(endpoints.stream().map(endpointId -> globalEndpointOf(id.owner(), endpointId, system)));
}
@Override
@@ -92,25 +83,23 @@ public class RoutingPolicy {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RoutingPolicy that = (RoutingPolicy) o;
- return owner.equals(that.owner) &&
- cluster.equals(that.cluster) &&
- zone.equals(that.zone);
+ return id.equals(that.id);
}
@Override
public int hashCode() {
- return Objects.hash(owner, cluster, zone);
+ return Objects.hash(id);
}
@Override
public String toString() {
return String.format("%s [rotations: %s%s], %s owned by %s, in %s", canonicalName, endpoints,
- dnsZone.map(z -> ", DNS zone: " + z).orElse(""), cluster, owner.toShortString(),
- zone.value());
+ dnsZone.map(z -> ", DNS zone: " + z).orElse(""), id.cluster(), id.owner().toShortString(),
+ id.zone().value());
}
- /** Returns the endpoint of given rotation */
- public static Endpoint endpointOf(ApplicationId application, EndpointId endpointId, SystemName system) {
+ /** Creates a global endpoint for given application */
+ public static Endpoint globalEndpointOf(ApplicationId application, EndpointId endpointId, SystemName system) {
return Endpoint.of(application).named(endpointId).on(Port.tls()).directRouting().in(system);
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicyId.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicyId.java
new file mode 100644
index 00000000000..06002e874f1
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicyId.java
@@ -0,0 +1,57 @@
+// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.routing;
+
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.ClusterSpec;
+import com.yahoo.config.provision.zone.ZoneId;
+
+import java.util.Objects;
+
+/**
+ * Unique identifier for a {@link RoutingPolicy}.
+ *
+ * @author mpolden
+ */
+public class RoutingPolicyId {
+
+ private final ApplicationId owner;
+ private final ClusterSpec.Id cluster;
+ private final ZoneId zone;
+
+ public RoutingPolicyId(ApplicationId owner, ClusterSpec.Id cluster, ZoneId zone) {
+ this.owner = Objects.requireNonNull(owner, "owner must be non-null");
+ this.cluster = Objects.requireNonNull(cluster, "cluster must be non-null");
+ this.zone = Objects.requireNonNull(zone, "zone must be non-null");
+ }
+
+ /** The application owning this */
+ public ApplicationId owner() {
+ return owner;
+ }
+
+ /** The zone this applies to */
+ public ZoneId zone() {
+ return zone;
+ }
+
+ /** The cluster this applies to */
+ public ClusterSpec.Id cluster() {
+ return cluster;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ RoutingPolicyId that = (RoutingPolicyId) o;
+ return owner.equals(that.owner) &&
+ cluster.equals(that.cluster) &&
+ zone.equals(that.zone);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(owner, cluster, zone);
+ }
+
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/Status.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/Status.java
new file mode 100644
index 00000000000..51e59c7cf4f
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/Status.java
@@ -0,0 +1,53 @@
+// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.routing;
+
+import java.util.Objects;
+
+/**
+ * Represents the status of a routing policy.
+ *
+ * This is immutable.
+ *
+ * @author mpolden
+ */
+public class Status {
+
+ private final boolean active;
+ private final GlobalRouting globalRouting;
+
+ /** DO NOT USE. Public for serialization purposes */
+ public Status(boolean active, GlobalRouting globalRouting) {
+ this.active = active;
+ this.globalRouting = Objects.requireNonNull(globalRouting, "globalRouting must be non-null");
+ }
+
+ /** Returns whether this is considered active according to the load balancer status */
+ public boolean isActive() {
+ return active;
+ }
+
+ /** Return status of global routing */
+ public GlobalRouting globalRouting() {
+ return globalRouting;
+ }
+
+ /** Returns a copy of this with global routing changed */
+ public Status with(GlobalRouting globalRouting) {
+ return new Status(active, globalRouting);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Status status = (Status) o;
+ return active == status.active &&
+ globalRouting.equals(status.globalRouting);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(active, globalRouting);
+ }
+
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/ZoneRoutingPolicy.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/ZoneRoutingPolicy.java
new file mode 100644
index 00000000000..262cacd325e
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/ZoneRoutingPolicy.java
@@ -0,0 +1,49 @@
+// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.routing;
+
+import com.yahoo.config.provision.zone.ZoneId;
+
+import java.util.Objects;
+
+/**
+ * Represents the DNS routing policy for a zone. This takes precedence over of an individual {@link RoutingPolicy}.
+ *
+ * This is immutable.
+ *
+ * @author mpolden
+ */
+public class ZoneRoutingPolicy {
+
+ private final ZoneId zone;
+ private final GlobalRouting globalRouting;
+
+ public ZoneRoutingPolicy(ZoneId zone, GlobalRouting globalRouting) {
+ this.zone = Objects.requireNonNull(zone, "zone must be non-null");
+ this.globalRouting = Objects.requireNonNull(globalRouting, "globalRouting must be non-null");
+ }
+
+ /** The zone this applies to */
+ public ZoneId zone() {
+ return zone;
+ }
+
+ /** The status of global routing */
+ public GlobalRouting globalRouting() {
+ return globalRouting;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ZoneRoutingPolicy that = (ZoneRoutingPolicy) o;
+ return zone.equals(that.zone) &&
+ globalRouting.equals(that.globalRouting);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(zone, globalRouting);
+ }
+
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VespaVersion.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VespaVersion.java
index f722eb4f6bb..7c3c30738d6 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VespaVersion.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VespaVersion.java
@@ -1,17 +1,13 @@
-// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.versions;
import com.yahoo.component.Version;
-import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.application.ApplicationList;
import com.yahoo.vespa.hosted.controller.application.InstanceList;
-import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
-import com.yahoo.vespa.hosted.controller.deployment.DeploymentStatusList;
import java.time.Instant;
import java.time.ZoneOffset;
-import java.util.stream.Collectors;
import static com.yahoo.config.application.api.DeploymentSpec.UpgradePolicy;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java
index 84bdedba33c..c83463bc1ea 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java
@@ -1,4 +1,4 @@
-// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller;
import com.yahoo.component.Version;
@@ -32,6 +32,7 @@ import com.yahoo.vespa.hosted.controller.application.SystemApplication;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.athenz.impl.AthenzFacade;
import com.yahoo.vespa.hosted.controller.integration.ConfigServerMock;
+import com.yahoo.vespa.hosted.controller.integration.MetricsMock;
import com.yahoo.vespa.hosted.controller.integration.ServiceRegistryMock;
import com.yahoo.vespa.hosted.controller.integration.ZoneRegistryMock;
import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
@@ -355,7 +356,6 @@ public final class ControllerTester {
return application;
}
-
public void deploy(ApplicationId id, ZoneId zone) {
deploy(id, zone, new ApplicationPackage(new byte[0]));
}
@@ -392,7 +392,8 @@ public final class ControllerTester {
() -> "test-controller",
new InMemoryFlagSource(),
new MockMavenRepository(),
- serviceRegistry);
+ serviceRegistry,
+ new MetricsMock());
// Calculate initial versions
controller.updateVersionStatus(VersionStatus.compute(controller));
return controller;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/ApplicationPackageBuilder.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/ApplicationPackageBuilder.java
index e4b5e77b377..9b0706d184f 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/ApplicationPackageBuilder.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/ApplicationPackageBuilder.java
@@ -101,13 +101,19 @@ public class ApplicationPackageBuilder {
}
public ApplicationPackageBuilder region(RegionName regionName) {
- return region(regionName.value());
+ return region(regionName, true);
}
public ApplicationPackageBuilder region(String regionName) {
- environmentBody.append(" <region active='true'>");
- environmentBody.append(regionName);
- environmentBody.append("</region>\n");
+ return region(RegionName.from(regionName), true);
+ }
+
+ public ApplicationPackageBuilder region(RegionName regionName, boolean active) {
+ environmentBody.append(" <region active='")
+ .append(active)
+ .append("'>")
+ .append(regionName.value())
+ .append("</region>\n");
return this;
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java
index 2d0b625dcb3..2792a59b523 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java
@@ -1,10 +1,12 @@
-// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.deployment;
import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.AthenzDomain;
import com.yahoo.config.provision.AthenzService;
+import com.yahoo.config.provision.ClusterSpec;
+import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.security.KeyAlgorithm;
import com.yahoo.security.KeyUtils;
@@ -26,9 +28,14 @@ import com.yahoo.vespa.hosted.controller.api.integration.routing.RoutingEndpoint
import com.yahoo.vespa.hosted.controller.api.integration.routing.RoutingGeneratorMock;
import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
import com.yahoo.vespa.hosted.controller.application.Deployment;
+import com.yahoo.vespa.hosted.controller.application.EndpointId;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.integration.ConfigServerMock;
import com.yahoo.vespa.hosted.controller.maintenance.JobRunner;
+import com.yahoo.vespa.hosted.controller.routing.GlobalRouting;
+import com.yahoo.vespa.hosted.controller.routing.RoutingPolicy;
+import com.yahoo.vespa.hosted.controller.routing.RoutingPolicyId;
+import com.yahoo.vespa.hosted.controller.routing.Status;
import javax.security.auth.x500.X500Principal;
import java.math.BigInteger;
@@ -38,6 +45,7 @@ import java.security.cert.X509Certificate;
import java.time.Instant;
import java.util.Collections;
import java.util.HashSet;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -57,8 +65,8 @@ import static org.junit.Assert.assertTrue;
*
* References to this should be acquired through {@link DeploymentTester#newDeploymentContext}.
*
- * Tester code that is not specific to deployments should be added to either {@link ControllerTester} or
- * {@link DeploymentTester} instead of this class.
+ * Tester code that is not specific to a single application's deployment context should be added to either
+ * {@link ControllerTester} or {@link DeploymentTester} instead of this class.
*
* @author mpolden
* @author jonmv
@@ -197,6 +205,28 @@ public class DeploymentContext {
return this;
}
+ /** Add a routing policy for this in given zone, with status set to active */
+ public DeploymentContext addRoutingPolicy(ZoneId zone, boolean active) {
+ return addRoutingPolicy(instanceId, zone, active);
+ }
+
+ /** Add a routing policy for tester instance of this in given zone, with status set to active */
+ public DeploymentContext addTesterRoutingPolicy(ZoneId zone, boolean active) {
+ return addRoutingPolicy(testerId.id(), zone, active);
+ }
+
+ private DeploymentContext addRoutingPolicy(ApplicationId instance, ZoneId zone, boolean active) {
+ var clusterId = "default" + (!active ? "-inactive" : "");
+ var id = new RoutingPolicyId(instance, ClusterSpec.Id.from(clusterId), zone);
+ var policies = new LinkedHashMap<>(tester.controller().curator().readRoutingPolicies(instance));
+ policies.put(id, new RoutingPolicy(id, HostName.from("lb-host"),
+ Optional.empty(),
+ Set.of(EndpointId.of("c0")),
+ new Status(active, GlobalRouting.DEFAULT_STATUS)));
+ tester.controller().curator().writeRoutingPolicies(instance, policies);
+ return this;
+ }
+
/** Submit given application package for deployment */
public DeploymentContext submit(ApplicationPackage applicationPackage) {
return submit(applicationPackage, defaultSourceRevision);
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunnerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunnerTest.java
index 51726035cb3..e052b967c31 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunnerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunnerTest.java
@@ -1,11 +1,10 @@
-// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.deployment;
import com.google.common.collect.ImmutableList;
import com.yahoo.component.Version;
import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.provision.AthenzDomain;
-import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.NodeResources;
import com.yahoo.config.provision.SystemName;
@@ -24,7 +23,6 @@ import com.yahoo.vespa.hosted.controller.api.integration.deployment.RunId;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.TesterCloud;
import com.yahoo.vespa.hosted.controller.api.integration.stubs.MockMailer;
import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
-import com.yahoo.vespa.hosted.controller.application.RoutingPolicy;
import com.yahoo.vespa.hosted.controller.integration.ZoneApiMock;
import org.junit.Before;
import org.junit.Test;
@@ -43,20 +41,18 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
-import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import static com.yahoo.vespa.hosted.controller.api.integration.LogEntry.Type.error;
import static com.yahoo.vespa.hosted.controller.api.integration.LogEntry.Type.info;
import static com.yahoo.vespa.hosted.controller.api.integration.LogEntry.Type.warning;
-import static com.yahoo.vespa.hosted.controller.deployment.DeploymentTester.instanceId;
import static com.yahoo.vespa.hosted.controller.deployment.DeploymentContext.applicationPackage;
import static com.yahoo.vespa.hosted.controller.deployment.DeploymentContext.publicCdApplicationPackage;
+import static com.yahoo.vespa.hosted.controller.deployment.DeploymentTester.instanceId;
import static com.yahoo.vespa.hosted.controller.deployment.Step.Status.failed;
import static com.yahoo.vespa.hosted.controller.deployment.Step.Status.succeeded;
import static com.yahoo.vespa.hosted.controller.deployment.Step.Status.unfinished;
-import static java.util.Collections.emptySet;
import static java.util.Collections.singletonList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -208,19 +204,8 @@ public class InternalStepRunnerTest {
tester.configServer().convergeServices(app.testerId().id(), JobType.systemTest.zone(system()));
assertEquals(unfinished, tester.jobs().last(app.instanceId(), JobType.systemTest).get().stepStatuses().get(Step.installReal));
assertEquals(unfinished, tester.jobs().last(app.instanceId(), JobType.systemTest).get().stepStatuses().get(Step.installTester));
-
- tester.controller().curator().writeRoutingPolicies(app.instanceId(), Set.of(new RoutingPolicy(app.instanceId(),
- ClusterSpec.Id.from("default"),
- JobType.systemTest.zone(system()),
- HostName.from("host"),
- Optional.empty(),
- emptySet(), true)));
- tester.controller().curator().writeRoutingPolicies(app.testerId().id(), Set.of(new RoutingPolicy(app.testerId().id(),
- ClusterSpec.Id.from("default"),
- JobType.systemTest.zone(system()),
- HostName.from("host"),
- Optional.empty(),
- emptySet(), true)));
+ app.addRoutingPolicy(JobType.systemTest.zone(system()), true);
+ app.addTesterRoutingPolicy(JobType.systemTest.zone(system()), true);
tester.runner().run();;
assertEquals(succeeded, tester.jobs().last(app.instanceId(), JobType.systemTest).get().stepStatuses().get(Step.installReal));
assertEquals(succeeded, tester.jobs().last(app.instanceId(), JobType.systemTest).get().stepStatuses().get(Step.installTester));
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java
index b37d3d340cb..fef8ab32d17 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java
@@ -27,6 +27,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.configserver.Node;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.NotFoundException;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.PrepareResponse;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ServiceConvergence;
+import com.yahoo.vespa.hosted.controller.api.integration.deployment.TesterCloud;
import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
import com.yahoo.vespa.hosted.controller.application.SystemApplication;
import com.yahoo.vespa.serviceview.bindings.ApplicationView;
@@ -269,6 +270,11 @@ public class ConfigServerMock extends AbstractComponent implements ConfigServer
return List.of();
}
+ @Override
+ public TesterCloud.Status getTesterStatus(DeploymentId deployment) {
+ return TesterCloud.Status.SUCCESS;
+ }
+
public void addLoadBalancers(ZoneId zone, List<LoadBalancer> loadBalancers) {
this.loadBalancers.putIfAbsent(zone, new ArrayList<>());
this.loadBalancers.get(zone).addAll(loadBalancers);
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunnerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunnerTest.java
index 21e4735f7bf..9de0020ce4a 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunnerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunnerTest.java
@@ -11,12 +11,14 @@ import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester;
import com.yahoo.vespa.hosted.controller.deployment.JobController;
+import com.yahoo.vespa.hosted.controller.deployment.JobMetrics;
import com.yahoo.vespa.hosted.controller.deployment.Run;
import com.yahoo.vespa.hosted.controller.deployment.RunStatus;
import com.yahoo.vespa.hosted.controller.deployment.Step;
import com.yahoo.vespa.hosted.controller.deployment.Step.Status;
import com.yahoo.vespa.hosted.controller.deployment.StepRunner;
import com.yahoo.vespa.hosted.controller.deployment.Versions;
+import com.yahoo.vespa.hosted.controller.integration.MetricsMock;
import org.junit.Test;
import java.time.Duration;
@@ -43,6 +45,7 @@ import static com.yahoo.vespa.hosted.controller.api.integration.deployment.JobTy
import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.aborted;
import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.error;
import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.running;
+import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.success;
import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.testFailure;
import static com.yahoo.vespa.hosted.controller.deployment.Step.Status.failed;
import static com.yahoo.vespa.hosted.controller.deployment.Step.Status.succeeded;
@@ -59,6 +62,7 @@ import static com.yahoo.vespa.hosted.controller.deployment.Step.report;
import static com.yahoo.vespa.hosted.controller.deployment.Step.startTests;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -348,7 +352,44 @@ public class JobRunnerTest {
jobs.start(id, systemTest, versions);
tester.clock().advance(JobRunner.jobTimeout.plus(Duration.ofSeconds(1)));
runner.run();
- assertTrue(jobs.last(id, systemTest).get().status() == aborted);
+ assertSame(aborted, jobs.last(id, systemTest).get().status());
+ }
+
+ @Test
+ public void jobMetrics() {
+ DeploymentTester tester = new DeploymentTester();
+ JobController jobs = tester.controller().jobController();
+ Map<Step, RunStatus> outcomes = new EnumMap<>(Step.class);
+ JobRunner runner = new JobRunner(tester.controller(), Duration.ofDays(1), new JobControl(tester.controller().curator()),
+ inThreadExecutor(), mappedRunner(outcomes));
+
+ TenantAndApplicationId appId = tester.createApplication("tenant", "real", "default").id();
+ ApplicationId id = appId.defaultInstance();
+ jobs.submit(appId, versions.targetApplication().source(), Optional.empty(), Optional.empty(), Optional.empty(), 2, applicationPackage, new byte[0]);
+
+ for (RunStatus status : RunStatus.values()) {
+ if (status == success) continue; // Status not used for steps.
+ outcomes.put(deployTester, status);
+ jobs.start(id, systemTest, versions);
+ runner.run();
+ jobs.finish(jobs.last(id, systemTest).get().id());
+ }
+
+ Map<String, String> context = Map.of("tenant", "tenant",
+ "application", "real",
+ "instance", "default",
+ "job", "system-test",
+ "environment", "test",
+ "region", "us-east-1");
+ MetricsMock metric = ((MetricsMock) tester.controller().metric());
+ assertEquals(RunStatus.values().length - 1, metric.getMetric(context::equals, JobMetrics.start).get().intValue());
+ assertEquals(1, metric.getMetric(context::equals, JobMetrics.abort).get().intValue());
+ assertEquals(1, metric.getMetric(context::equals, JobMetrics.error).get().intValue());
+ assertEquals(1, metric.getMetric(context::equals, JobMetrics.success).get().intValue());
+ assertEquals(1, metric.getMetric(context::equals, JobMetrics.convergenceFailure).get().intValue());
+ assertEquals(1, metric.getMetric(context::equals, JobMetrics.deploymentFailure).get().intValue());
+ assertEquals(1, metric.getMetric(context::equals, JobMetrics.outOfCapacity).get().intValue());
+ assertEquals(1, metric.getMetric(context::equals, JobMetrics.testFailure).get().intValue());
}
public static ExecutorService inThreadExecutor() {
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializerTest.java
index 23355bd6033..c9ec5adc98c 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializerTest.java
@@ -1,22 +1,26 @@
-// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
-import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableMap;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.config.SlimeUtils;
import com.yahoo.vespa.hosted.controller.application.EndpointId;
-import com.yahoo.vespa.hosted.controller.application.RoutingPolicy;
+import com.yahoo.vespa.hosted.controller.routing.GlobalRouting;
+import com.yahoo.vespa.hosted.controller.routing.RoutingPolicy;
+import com.yahoo.vespa.hosted.controller.routing.RoutingPolicyId;
+import com.yahoo.vespa.hosted.controller.routing.Status;
import org.junit.Test;
+import java.time.Instant;
import java.util.Iterator;
+import java.util.Map;
import java.util.Optional;
import java.util.Set;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
/**
* @author mortent
@@ -29,41 +33,46 @@ public class RoutingPolicySerializerTest {
public void serialization() {
var owner = ApplicationId.defaultId();
var endpoints = Set.of(EndpointId.of("r1"), EndpointId.of("r2"));
- var policies = ImmutableSet.of(new RoutingPolicy(owner,
- ClusterSpec.Id.from("my-cluster1"),
- ZoneId.from("prod", "us-north-1"),
+ var id1 = new RoutingPolicyId(owner,
+ ClusterSpec.Id.from("my-cluster1"),
+ ZoneId.from("prod", "us-north-1"));
+ var id2 = new RoutingPolicyId(owner,
+ ClusterSpec.Id.from("my-cluster2"),
+ ZoneId.from("prod", "us-north-2"));
+ var policies = ImmutableMap.of(id1, new RoutingPolicy(id1,
HostName.from("long-and-ugly-name"),
Optional.of("zone1"),
- endpoints, true),
- new RoutingPolicy(owner,
- ClusterSpec.Id.from("my-cluster2"),
- ZoneId.from("prod", "us-north-2"),
+ endpoints, new Status(true, GlobalRouting.DEFAULT_STATUS)),
+ id2, new RoutingPolicy(id2,
HostName.from("long-and-ugly-name-2"),
Optional.empty(),
- endpoints, false));
+ endpoints, new Status(false,
+ new GlobalRouting(GlobalRouting.Status.out,
+ GlobalRouting.Agent.tenant,
+ Instant.ofEpochSecond(123)))));
var serialized = serializer.fromSlime(owner, serializer.toSlime(policies));
assertEquals(policies.size(), serialized.size());
- for (Iterator<RoutingPolicy> it1 = policies.iterator(), it2 = serialized.iterator(); it1.hasNext();) {
+ for (Iterator<RoutingPolicy> it1 = policies.values().iterator(), it2 = serialized.values().iterator(); it1.hasNext();) {
var expected = it1.next();
var actual = it2.next();
- assertEquals(expected.owner(), actual.owner());
- assertEquals(expected.cluster(), actual.cluster());
- assertEquals(expected.zone(), actual.zone());
+ assertEquals(expected.id(), actual.id());
assertEquals(expected.canonicalName(), actual.canonicalName());
assertEquals(expected.dnsZone(), actual.dnsZone());
assertEquals(expected.endpoints(), actual.endpoints());
- assertEquals(expected.active(), actual.active());
+ assertEquals(expected.status(), actual.status());
}
}
+ // TODO(mpolden): Remove after January 2020
@Test
public void legacy_serialization() {
- var json = "{\"routingPolicies\":[{\"cluster\":\"default\",\"zone\":\"prod.us-north-1\"," +
- "\"canonicalName\":\"lb-0\"," +
- "\"dnsZone\":\"dns-zone-id\",\"rotations\":[]}]}";
- var serialized = serializer.fromSlime(ApplicationId.defaultId(), SlimeUtils.jsonToSlime(json));
- assertTrue(serialized.iterator().next().active());
-
+ var json = "{\"routingPolicies\":[{\"cluster\":\"default\",\"zone\":\"prod.us-north-1\",\"canonicalName\":\"lb-host\",\"dnsZone\":\"dnsZoneId\",\"rotations\":[\"default\"],\"active\":true}]}";
+ var owner = ApplicationId.defaultId();
+ var serialized = serializer.fromSlime(owner, SlimeUtils.jsonToSlime(json));
+ var id = new RoutingPolicyId(owner, ClusterSpec.Id.from("default"), ZoneId.from("prod", "us-north-1"));
+ var expected = Map.of(id, new RoutingPolicy(id, HostName.from("lb-host"), Optional.of("dnsZoneId"),
+ Set.of(EndpointId.defaultId()), new Status(true, GlobalRouting.DEFAULT_STATUS)));
+ assertEquals(expected, serialized);
}
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ZoneRoutingPolicySerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ZoneRoutingPolicySerializerTest.java
new file mode 100644
index 00000000000..6a089c5e1b0
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ZoneRoutingPolicySerializerTest.java
@@ -0,0 +1,29 @@
+// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.persistence;
+
+import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.vespa.hosted.controller.routing.GlobalRouting;
+import com.yahoo.vespa.hosted.controller.routing.ZoneRoutingPolicy;
+import org.junit.Test;
+
+import java.time.Instant;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author mpolden
+ */
+public class ZoneRoutingPolicySerializerTest {
+
+ @Test
+ public void serialization() {
+ var serializer = new ZoneRoutingPolicySerializer(new RoutingPolicySerializer());
+ var zone = ZoneId.from("prod", "us-north-1");
+ var policy = new ZoneRoutingPolicy(zone,
+ GlobalRouting.status(GlobalRouting.Status.out, GlobalRouting.Agent.operator,
+ Instant.ofEpochMilli(123)));
+ var serialized = serializer.fromSlime(zone, serializer.toSlime(policy));
+ assertEquals(policy, serialized);
+ }
+
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java
index 30be5d9b399..96681dc1c8b 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java
@@ -1,4 +1,4 @@
-// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.application;
import ai.vespa.hosted.api.MultiPartStreamer;
@@ -11,7 +11,6 @@ import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.AthenzService;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.Environment;
-import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.zone.ZoneId;
@@ -52,8 +51,6 @@ import com.yahoo.vespa.hosted.controller.application.Change;
import com.yahoo.vespa.hosted.controller.application.ClusterInfo;
import com.yahoo.vespa.hosted.controller.application.Deployment;
import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics;
-import com.yahoo.vespa.hosted.controller.application.EndpointId;
-import com.yahoo.vespa.hosted.controller.application.RoutingPolicy;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.athenz.HostedAthenzIdentities;
import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder;
@@ -1434,18 +1431,8 @@ public class ApplicationApiTest extends ControllerContainerTest {
.region("us-west-1")
.build();
app.submit(applicationPackage).deploy();
- Set<RoutingPolicy> policies = Set.of(new RoutingPolicy(app.instanceId(),
- ClusterSpec.Id.from("default"),
- ZoneId.from(Environment.prod, RegionName.from("us-west-1")),
- HostName.from("lb-0-canonical-name"),
- Optional.of("dns-zone-1"), Set.of(EndpointId.of("c0")), true),
- // Inactive policy is not included
- new RoutingPolicy(app.instanceId(),
- ClusterSpec.Id.from("deleted-cluster"),
- ZoneId.from(Environment.prod, RegionName.from("us-west-1")),
- HostName.from("lb-1-canonical-name"),
- Optional.of("dns-zone-1"), Set.of(), false));
- tester.controller().curator().writeRoutingPolicies(app.instanceId(), policies);
+ app.addRoutingPolicy(ZoneId.from(Environment.prod, RegionName.from("us-west-1")), true);
+ app.addRoutingPolicy(ZoneId.from(Environment.prod, RegionName.from("us-west-1")), false);
// GET application
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1", GET)
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPoliciesTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/RoutingPoliciesTest.java
index 1bb20296bd2..c0420c7b895 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPoliciesTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/RoutingPoliciesTest.java
@@ -1,5 +1,5 @@
-// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.controller.maintenance;
+// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.routing;
import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.application.api.ValidationId;
@@ -15,7 +15,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.dns.Record;
import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordData;
import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordName;
import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
-import com.yahoo.vespa.hosted.controller.application.RoutingPolicy;
+import com.yahoo.vespa.hosted.controller.application.EndpointId;
import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentContext;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester;
@@ -24,7 +24,12 @@ import com.yahoo.vespa.hosted.rotation.config.RotationsConfig;
import org.junit.Test;
import java.net.URI;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -68,21 +73,9 @@ public class RoutingPoliciesTest {
// Creates alias records
context1.submit(applicationPackage).deploy();
- var endpoint1 = "r0.app1.tenant1.global.vespa.oath.cloud";
- var endpoint2 = "r1.app1.tenant1.global.vespa.oath.cloud";
- var endpoint3 = "r2.app1.tenant1.global.vespa.oath.cloud";
-
- assertEquals(endpoint1 + " points to c0 in all regions",
- List.of("lb-0--tenant1:app1:default--prod.us-central-1/dns-zone-1/prod.us-central-1",
- "lb-0--tenant1:app1:default--prod.us-west-1/dns-zone-1/prod.us-west-1"),
- tester.aliasDataOf(endpoint1));
- assertEquals(endpoint2 + " points to c0 us-west-1",
- List.of("lb-0--tenant1:app1:default--prod.us-west-1/dns-zone-1/prod.us-west-1"),
- tester.aliasDataOf(endpoint2));
- assertEquals(endpoint3 + " points to c1 in all regions",
- List.of("lb-1--tenant1:app1:default--prod.us-central-1/dns-zone-1/prod.us-central-1",
- "lb-1--tenant1:app1:default--prod.us-west-1/dns-zone-1/prod.us-west-1"),
- tester.aliasDataOf(endpoint3));
+ tester.assertTargets(context1.instanceId(), EndpointId.of("r0"), 0, zone1, zone2);
+ tester.assertTargets(context1.instanceId(), EndpointId.of("r1"), 0, zone1);
+ tester.assertTargets(context1.instanceId(), EndpointId.of("r2"), 1, zone1, zone2);
assertEquals("Routing policy count is equal to cluster count",
numberOfDeployments * clustersPerZone,
tester.policiesOf(context1.instance().id()).size());
@@ -100,12 +93,10 @@ public class RoutingPoliciesTest {
tester.provisionLoadBalancers(clustersPerZone, context1.instanceId(), zone3);
context1.submit(applicationPackage2).deploy();
- // Endpoint is updated to contain cluster in new deployment
- assertEquals(endpoint1 + " points to c0 in all regions",
- List.of("lb-0--tenant1:app1:default--prod.us-central-1/dns-zone-1/prod.us-central-1",
- "lb-0--tenant1:app1:default--prod.us-east-3/dns-zone-1/prod.us-east-3",
- "lb-0--tenant1:app1:default--prod.us-west-1/dns-zone-1/prod.us-west-1"),
- tester.aliasDataOf(endpoint1));
+ // Endpoints are updated to contain cluster in new deployment
+ tester.assertTargets(context1.instanceId(), EndpointId.of("r0"), 0, zone1, zone2, zone3);
+ tester.assertTargets(context1.instanceId(), EndpointId.of("r1"), 0, zone1);
+ tester.assertTargets(context1.instanceId(), EndpointId.of("r2"), 1, zone1, zone2, zone3);
// Another application is deployed with a single cluster and global endpoint
var endpoint4 = "r0.app2.tenant1.global.vespa.oath.cloud";
@@ -116,10 +107,7 @@ public class RoutingPoliciesTest {
.endpoint("r0", "c0")
.build();
context2.submit(applicationPackage3).deploy();
- assertEquals(endpoint4 + " points to c0 in all regions",
- List.of("lb-0--tenant1:app2:default--prod.us-central-1/dns-zone-1/prod.us-central-1",
- "lb-0--tenant1:app2:default--prod.us-west-1/dns-zone-1/prod.us-west-1"),
- tester.aliasDataOf(endpoint4));
+ tester.assertTargets(context2.instanceId(), EndpointId.of("r0"), 0, zone1, zone2);
// All endpoints for app1 are removed
ApplicationPackage applicationPackage4 = new ApplicationPackageBuilder()
@@ -129,10 +117,10 @@ public class RoutingPoliciesTest {
.allow(ValidationId.globalEndpointChange)
.build();
context1.submit(applicationPackage4).deploy();
- assertEquals("DNS records are removed", List.of(), tester.aliasDataOf(endpoint1));
- assertEquals("DNS records are removed", List.of(), tester.aliasDataOf(endpoint2));
- assertEquals("DNS records are removed", List.of(), tester.aliasDataOf(endpoint3));
- Set<RoutingPolicy> policies = tester.policiesOf(context1.instanceId());
+ tester.assertTargets(context1.instanceId(), EndpointId.of("r0"), 0);
+ tester.assertTargets(context1.instanceId(), EndpointId.of("r1"), 0);
+ tester.assertTargets(context1.instanceId(), EndpointId.of("r2"), 0);
+ var policies = tester.policiesOf(context1.instanceId());
assertEquals(clustersPerZone * numberOfDeployments, policies.size());
assertTrue("Rotation membership is removed from all policies",
policies.stream().allMatch(policy -> policy.endpoints().isEmpty()));
@@ -226,8 +214,8 @@ public class RoutingPoliciesTest {
"c1.app1.tenant1.us-central-1.vespa.oath.cloud"
);
assertEquals(expectedRecords, tester.recordNames());
- assertTrue("Removes stale routing policies " + context2.application(), tester.controllerTester().controller().applications().routingPolicies().get(context2.instanceId()).isEmpty());
- assertEquals("Keeps routing policies for " + context1.application(), 4, tester.controllerTester().controller().applications().routingPolicies().get(context1.instanceId()).size());
+ assertTrue("Removes stale routing policies " + context2.application(), tester.routingPolicies().get(context2.instanceId()).isEmpty());
+ assertEquals("Keeps routing policies for " + context1.application(), 4, tester.routingPolicies().get(context1.instanceId()).size());
}
@Test
@@ -348,6 +336,145 @@ public class RoutingPoliciesTest {
assertEquals("CNAME points to current load blancer", newHostname.value() + ".",
tester.cnameDataOf(expectedRecords.iterator().next()).get(0));
}
+
+ @Test
+ public void set_global_endpoint_status() {
+ var tester = new RoutingPoliciesTester();
+ var context = tester.newDeploymentContext("tenant1", "app1", "default");
+
+ // Provision load balancers and deploy application
+ tester.provisionLoadBalancers(1, context.instanceId(), zone1, zone2);
+ var applicationPackage = new ApplicationPackageBuilder()
+ .region(zone1.region())
+ .region(zone2.region())
+ .endpoint("r0", "c0", zone1.region().value(), zone2.region().value())
+ .endpoint("r1", "c0", zone1.region().value(), zone2.region().value())
+ .build();
+ context.submit(applicationPackage).deploy();
+
+ // Global DNS record is created
+ tester.assertTargets(context.instanceId(), EndpointId.of("r0"), 0, zone1, zone2);
+ tester.assertTargets(context.instanceId(), EndpointId.of("r1"), 0, zone1, zone2);
+
+ // Global routing status is overridden in one zone
+ var changedAt = tester.controllerTester().clock().instant();
+ tester.routingPolicies().setGlobalRoutingStatus(context.deploymentIdIn(zone1), GlobalRouting.Status.out,
+ GlobalRouting.Agent.tenant);
+ context.flushDnsUpdates();
+
+ // Inactive zone is removed from global DNS record
+ tester.assertTargets(context.instanceId(), EndpointId.of("r0"), 0, zone2);
+ tester.assertTargets(context.instanceId(), EndpointId.of("r1"), 0, zone2);
+
+ // Status details is stored in policy
+ var policy1 = tester.routingPolicies().get(context.deploymentIdIn(zone1)).values().iterator().next();
+ assertEquals(GlobalRouting.Status.out, policy1.status().globalRouting().status());
+ assertEquals(GlobalRouting.Agent.tenant, policy1.status().globalRouting().agent());
+ assertEquals(changedAt.truncatedTo(ChronoUnit.MILLIS), policy1.status().globalRouting().changedAt());
+
+ // Other zone remains in
+ var policy2 = tester.routingPolicies().get(context.deploymentIdIn(zone2)).values().iterator().next();
+ assertEquals(GlobalRouting.Status.in, policy2.status().globalRouting().status());
+ assertEquals(GlobalRouting.Agent.system, policy2.status().globalRouting().agent());
+ assertEquals(Instant.EPOCH, policy2.status().globalRouting().changedAt());
+
+ // Next deployment does not affect status
+ context.submit(applicationPackage).deploy();
+ context.flushDnsUpdates();
+ tester.assertTargets(context.instanceId(), EndpointId.of("r0"), 0, zone2);
+ tester.assertTargets(context.instanceId(), EndpointId.of("r1"), 0, zone2);
+
+ // Deployment is set back in
+ tester.controllerTester().clock().advance(Duration.ofHours(1));
+ changedAt = tester.controllerTester().clock().instant();
+ tester.routingPolicies().setGlobalRoutingStatus(context.deploymentIdIn(zone1), GlobalRouting.Status.in, GlobalRouting.Agent.tenant);
+ context.flushDnsUpdates();
+ tester.assertTargets(context.instanceId(), EndpointId.of("r0"), 0, zone1, zone2);
+ tester.assertTargets(context.instanceId(), EndpointId.of("r1"), 0, zone1, zone2);
+
+ policy1 = tester.routingPolicies().get(context.deploymentIdIn(zone1)).values().iterator().next();
+ assertEquals(GlobalRouting.Status.in, policy1.status().globalRouting().status());
+ assertEquals(GlobalRouting.Agent.tenant, policy1.status().globalRouting().agent());
+ assertEquals(changedAt.truncatedTo(ChronoUnit.MILLIS), policy1.status().globalRouting().changedAt());
+
+ // Deployment is set out through a new deployment.xml
+ var applicationPackage2 = new ApplicationPackageBuilder()
+ .region(zone1.region())
+ .region(zone2.region(), false)
+ .endpoint("r0", "c0", zone1.region().value(), zone2.region().value())
+ .endpoint("r1", "c0", zone1.region().value(), zone2.region().value())
+ .build();
+ context.submit(applicationPackage2).deploy();
+ tester.assertTargets(context.instanceId(), EndpointId.of("r0"), 0, zone1);
+ tester.assertTargets(context.instanceId(), EndpointId.of("r1"), 0, zone1);
+
+ // ... back in
+ var applicationPackage3 = new ApplicationPackageBuilder()
+ .region(zone1.region())
+ .region(zone2.region())
+ .endpoint("r0", "c0", zone1.region().value(), zone2.region().value())
+ .endpoint("r1", "c0", zone1.region().value(), zone2.region().value())
+ .build();
+ context.submit(applicationPackage3).deploy();
+ tester.assertTargets(context.instanceId(), EndpointId.of("r0"), 0, zone1, zone2);
+ tester.assertTargets(context.instanceId(), EndpointId.of("r1"), 0, zone1, zone2);
+ }
+
+ @Test
+ public void set_zone_global_endpoint_status() {
+ var tester = new RoutingPoliciesTester();
+ var context1 = tester.newDeploymentContext("tenant1", "app1", "default");
+ var context2 = tester.newDeploymentContext("tenant2", "app2", "default");
+ var contexts = List.of(context1, context2);
+
+ // Deploy applications
+ var applicationPackage = new ApplicationPackageBuilder()
+ .region(zone1.region())
+ .region(zone2.region())
+ .endpoint("default", "c0", zone1.region().value(), zone2.region().value())
+ .build();
+ for (var context : contexts) {
+ tester.provisionLoadBalancers(1, context.instanceId(), zone1, zone2);
+ context.submit(applicationPackage).deploy();
+ tester.assertTargets(context.instanceId(), EndpointId.defaultId(), 0, zone1, zone2);
+ }
+
+ // Set zone out
+ tester.routingPolicies().setGlobalRoutingStatus(zone2, GlobalRouting.Status.out);
+ context1.flushDnsUpdates();
+ tester.assertTargets(context1.instanceId(), EndpointId.defaultId(), 0, zone1);
+ tester.assertTargets(context2.instanceId(), EndpointId.defaultId(), 0, zone1);
+ for (var context : contexts) {
+ var policies = tester.routingPolicies().get(context.instanceId());
+ assertTrue("Global routing status for policy remains " + GlobalRouting.Status.in,
+ policies.values().stream()
+ .map(RoutingPolicy::status)
+ .map(Status::globalRouting)
+ .map(GlobalRouting::status)
+ .allMatch(status -> status == GlobalRouting.Status.in));
+ }
+ var changedAt = tester.controllerTester().clock().instant();
+ var zonePolicy = tester.controllerTester().controller().curator().readZoneRoutingPolicy(zone2);
+ assertEquals(GlobalRouting.Status.out, zonePolicy.globalRouting().status());
+ assertEquals(GlobalRouting.Agent.operator, zonePolicy.globalRouting().agent());
+ assertEquals(changedAt.truncatedTo(ChronoUnit.MILLIS), zonePolicy.globalRouting().changedAt());
+
+ // Setting status per deployment does not affect status as entire zone is out
+ tester.routingPolicies().setGlobalRoutingStatus(context1.deploymentIdIn(zone2), GlobalRouting.Status.in, GlobalRouting.Agent.tenant);
+ context1.flushDnsUpdates();
+ tester.assertTargets(context1.instanceId(), EndpointId.defaultId(), 0, zone1);
+ tester.assertTargets(context2.instanceId(), EndpointId.defaultId(), 0, zone1);
+
+ // Set single deployment out
+ tester.routingPolicies().setGlobalRoutingStatus(context1.deploymentIdIn(zone2), GlobalRouting.Status.out, GlobalRouting.Agent.tenant);
+ context1.flushDnsUpdates();
+
+ // Set zone back in. Deployment set explicitly out, remains out, the rest are in
+ tester.routingPolicies().setGlobalRoutingStatus(zone2, GlobalRouting.Status.in);
+ context1.flushDnsUpdates();
+ tester.assertTargets(context1.instanceId(), EndpointId.defaultId(), 0, zone1);
+ tester.assertTargets(context2.instanceId(), EndpointId.defaultId(), 0, zone1, zone2);
+ }
private static List<LoadBalancer> createLoadBalancers(ZoneId zone, ApplicationId application, int count) {
List<LoadBalancer> loadBalancers = new ArrayList<>();
@@ -372,6 +499,10 @@ public class RoutingPoliciesTest {
this(new DeploymentTester());
}
+ public RoutingPolicies routingPolicies() {
+ return tester.controllerTester().controller().applications().routingPolicies();
+ }
+
public DeploymentContext newDeploymentContext(String tenant, String application, String instance) {
return tester.newDeploymentContext(tenant, application, instance);
}
@@ -391,8 +522,8 @@ public class RoutingPoliciesTest {
}
}
- private Set<RoutingPolicy> policiesOf(ApplicationId instance) {
- return tester.controller().curator().readRoutingPolicies(instance);
+ private Collection<RoutingPolicy> policiesOf(ApplicationId instance) {
+ return tester.controller().curator().readRoutingPolicies(instance).values();
}
private Set<String> recordNames() {
@@ -416,6 +547,21 @@ public class RoutingPoliciesTest {
.collect(Collectors.toList());
}
+ private void assertTargets(ApplicationId application, EndpointId endpointId, int loadBalancerId, ZoneId ...zone) {
+ var prefix = "";
+ if (!endpointId.equals(EndpointId.defaultId())) {
+ prefix = endpointId.id() + ".";
+ }
+ var endpoint = prefix + application.application().value() + "." + application.tenant().value() +
+ ".global.vespa.oath.cloud";
+ var zoneTargets = Arrays.stream(zone)
+ .map(z -> "lb-" + loadBalancerId + "--" + application.serializedForm() + "--" +
+ z.value() + "/dns-zone-1/" + z.value())
+ .collect(Collectors.toSet());
+ assertEquals("Global endpoint " + endpoint + " points to expected zones", zoneTargets,
+ Set.copyOf(aliasDataOf(endpoint)));
+ }
+
}
}
diff --git a/eval/src/tests/ann/sift_benchmark.cpp b/eval/src/tests/ann/sift_benchmark.cpp
index f20df926f24..dcfe1cf9c5c 100644
--- a/eval/src/tests/ann/sift_benchmark.cpp
+++ b/eval/src/tests/ann/sift_benchmark.cpp
@@ -285,6 +285,15 @@ TEST("require that HNSW via NNS api mostly works") {
#endif
+/**
+ * Before running the benchmark the ANN_SIFT1M data set must be downloaded and extracted:
+ * wget ftp://ftp.irisa.fr/local/texmex/corpus/sift.tar.gz
+ * tar -xf sift.tar.gz
+ *
+ * The benchmark program will load the data set from $HOME/sift if no directory is specified.
+ *
+ * More information about the dataset is found here: http://corpus-texmex.irisa.fr/.
+ */
int main(int argc, char **argv) {
TEST_MASTER.init(__FILE__);
std::string sift_dir = ".";
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
index c4a7816ca4c..06deb539df6 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
@@ -215,6 +215,12 @@ public class Flags {
"Takes effect immediately",
ZONE_ID, HOSTNAME);
+ public static final UnboundBooleanFlag GENERATE_L4_ROUTING_CONFIG = defineFeatureFlag(
+ "generate-l4-routing-config", false,
+ "Whether routing nodes should generate L4 routing config",
+ "Takes effect immediately",
+ ZONE_ID, HOSTNAME);
+
/** WARNING: public for testing: All flags should be defined in {@link Flags}. */
public static UnboundBooleanFlag defineFeatureFlag(String flagId, boolean defaultValue, String description,
String modificationEffect, FetchVector.Dimension... dimensions) {
diff --git a/security-utils/src/main/java/com/yahoo/security/X509CertificateUtils.java b/security-utils/src/main/java/com/yahoo/security/X509CertificateUtils.java
index 97b6cc344e1..cefa8ab2f51 100644
--- a/security-utils/src/main/java/com/yahoo/security/X509CertificateUtils.java
+++ b/security-utils/src/main/java/com/yahoo/security/X509CertificateUtils.java
@@ -19,11 +19,16 @@ import java.io.StringReader;
import java.io.StringWriter;
import java.io.UncheckedIOException;
import java.security.GeneralSecurityException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Random;
import static com.yahoo.security.Extension.SUBJECT_ALTERNATIVE_NAMES;
import static java.util.stream.Collectors.toList;
@@ -140,4 +145,20 @@ public class X509CertificateUtils {
}
}
+ public static boolean privateKeyMatchesPublicKey(PrivateKey privateKey, PublicKey publicKey) {
+ byte[] someRandomData = new byte[64];
+ new Random().nextBytes(someRandomData);
+
+ Signature signer = SignatureUtils.createSigner(privateKey);
+ Signature verifier = SignatureUtils.createVerifier(publicKey);
+ try {
+ signer.update(someRandomData);
+ verifier.update(someRandomData);
+ byte[] signature = signer.sign();
+ return verifier.verify(signature);
+ } catch (SignatureException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
}
diff --git a/security-utils/src/test/java/com/yahoo/security/X509CertificateUtilsTest.java b/security-utils/src/test/java/com/yahoo/security/X509CertificateUtilsTest.java
index 76a93028efe..b4eca8328c1 100644
--- a/security-utils/src/test/java/com/yahoo/security/X509CertificateUtilsTest.java
+++ b/security-utils/src/test/java/com/yahoo/security/X509CertificateUtilsTest.java
@@ -17,7 +17,9 @@ import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.Matchers.is;
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 bjorncs
@@ -71,4 +73,18 @@ public class X509CertificateUtilsTest {
assertThat(sans.size(), is(1));
assertThat(sans.get(0), equalTo(san));
}
+
+ @Test
+ public void verifies_matching_cert_and_key() {
+ KeyPair ecKeypairA = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256);
+ KeyPair ecKeypairB = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256);
+ KeyPair rsaKeypairA = KeyUtils.generateKeypair(KeyAlgorithm.RSA, 1024);
+ KeyPair rsaKeypairB = KeyUtils.generateKeypair(KeyAlgorithm.RSA, 1024);
+
+ assertTrue(X509CertificateUtils.privateKeyMatchesPublicKey(ecKeypairA.getPrivate(), ecKeypairA.getPublic()));
+ assertTrue(X509CertificateUtils.privateKeyMatchesPublicKey(rsaKeypairA.getPrivate(), rsaKeypairA.getPublic()));
+
+ assertFalse(X509CertificateUtils.privateKeyMatchesPublicKey(ecKeypairA.getPrivate(), ecKeypairB.getPublic()));
+ assertFalse(X509CertificateUtils.privateKeyMatchesPublicKey(rsaKeypairA.getPrivate(), rsaKeypairB.getPublic()));
+ }
} \ No newline at end of file
diff --git a/zkfacade/abi-spec.json b/zkfacade/abi-spec.json
index 25b652b7312..05fb985dbaf 100644
--- a/zkfacade/abi-spec.json
+++ b/zkfacade/abi-spec.json
@@ -109,4 +109,4 @@
],
"fields": []
}
-}
+} \ No newline at end of file