summaryrefslogtreecommitdiffstats
path: root/controller-server
diff options
context:
space:
mode:
authorØyvind Grønnesby <oyving@verizonmedia.com>2019-07-11 13:45:17 +0200
committerØyvind Grønnesby <oyving@verizonmedia.com>2019-07-11 13:45:17 +0200
commit9549a8005480a3fe61fb087e359a4a442180819f (patch)
treee76ded2ddf9a8127cd780304105313c22640c8dc /controller-server
parent9f3a3b9f6962bd20714b99a046860de1886be600 (diff)
parent1c79079945c56fa91de8427fbc8f2170eec9ed8c (diff)
Merge remote-tracking branch 'origin/master' into olaa/cfg-server-metric-aggregation
Conflicts: configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServer.java controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java
Diffstat (limited to 'controller-server')
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java52
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java255
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java43
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java60
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/AssignedRotation.java80
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java36
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointId.java53
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointList.java17
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/SystemApplication.java39
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/auditlog/AuditLog.java1
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java22
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java202
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java25
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Run.java8
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Step.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/TestConfigSerializer.java82
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/CreateRecord.java5
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/CreateRecords.java5
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/NameServiceForwarder.java16
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/NameServiceQueue.java7
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/NameServiceRequest.java6
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/RemoveRecords.java5
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/BillingMaintainer.java39
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterInfoMaintainer.java45
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterUtilizationMaintainer.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainer.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java38
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CostReportMaintainer.java9
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java5
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DnsMaintainer.java69
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/InfrastructureUpgrader.java31
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunner.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporter.java70
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/NameServiceDispatcher.java1
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgrader.java23
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java42
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPolicies.java198
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPolicyMaintainer.java224
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/SystemUpgrader.java21
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java98
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/AuditLogSerializer.java9
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ConfidenceOverrideSerializer.java7
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java42
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/LogSerializer.java7
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/NameServiceQueueSerializer.java7
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionSerializer.java7
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionStatusSerializer.java7
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializer.java7
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializer.java7
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java7
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/VersionSerializer.java7
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/VersionStatusSerializer.java10
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImpl.java9
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java304
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationChefEnvironment.java43
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/EmptyResponse.java (renamed from controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/EmptyJsonResponse.java)7
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java47
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiHandler.java3
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/cost/CostApiHandler.java6
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/cost/CostCalculator.java8
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java8
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiHandler.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java7
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiHandler.java17
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java12
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepository.java137
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tls/ControllerSslContextFactoryProvider.java34
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/MavenRepositoryClient.java64
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/OsVersionStatus.java25
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java31
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VespaVersion.java7
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/package-info.java5
-rw-r--r--controller-server/src/main/resources/configdefinitions/maven-repository.def15
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java238
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java21
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/EndpointTest.java44
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/auditlog/AuditLoggerTest.java46
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/ApplicationPackageBuilder.java27
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTester.java19
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java41
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalDeploymentTester.java27
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunnerTest.java53
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/TestConfigSerializerTest.java38
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ApplicationCertificateMock.java14
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java156
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryClientMock.java56
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java36
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/RoutingGeneratorMock.java11
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneApiMock.java70
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneFilterMock.java99
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java30
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CostReportMaintainerTest.java12
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DnsMaintainerTest.java141
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporterTest.java106
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgraderTest.java48
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OsVersionStatusUpdaterTest.java3
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainerTest.java26
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPoliciesTest.java (renamed from controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPolicyMaintainerTest.java)121
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/SystemUpgraderTest.java175
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java91
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/VersionStatusSerializerTest.java5
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerControllerTester.java9
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java7
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerCloudTest.java7
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java18
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java302
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelperTest.java21
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-nodes.json2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-reference-2.json2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-reference.json4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-without-change-multiple-deployments.json12
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application.json14
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application1-metering.json34
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application1-recursive.json6
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/delete-with-active-deployments.json2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment-job-accepted.json4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment.json6
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/dev-aws-us-east-2a-runs.json21
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/dev-overview.json31
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/dev-us-east-1.json (renamed from controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/dev-us-west-1.json)6
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/global-rotation-delete.json2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/global-rotation-put.json2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/jobreport-unexpected-completion.json4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/jobreport-unexpected-system-test-completion.json4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/jobs.json14
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/overview.json3
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/prod-us-central-1.json6
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/services.json4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/system-test-job.json2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/tenant-with-application.json4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/us-east-3-log-without-first.json2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiTest.java25
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json9
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/cost/CostApiTest.java9
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiTest.java10
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiTest.java12
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/responses/versions-all-upgraded.json65
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/responses/versions-initial.json65
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/responses/versions-partially-upgraded.json63
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/statuspage/StatusPageProxyApiTest.java4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/application-created.json2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiTest.java34
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiTest.java14
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepositoryTest.java13
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/tls/SecureContainerTest.java4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/MavenRepositoryClientTest.java22
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/VersionStatusTest.java24
-rw-r--r--controller-server/src/test/resources/chef_output.json34
-rw-r--r--controller-server/src/test/resources/job-grandparent.json4
-rw-r--r--controller-server/src/test/resources/job-parent.json9
-rw-r--r--controller-server/src/test/resources/testConfig.json20
-rw-r--r--controller-server/src/test/resources/test_runner_services.xml-cd8
154 files changed, 3504 insertions, 2120 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java
index 84e15deea4c..d8b56502fc3 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java
@@ -11,13 +11,16 @@ 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.api.integration.MetricsService.ApplicationMetrics;
+import com.yahoo.vespa.hosted.controller.api.integration.certificates.ApplicationCertificate;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId;
import com.yahoo.vespa.hosted.controller.api.integration.organization.User;
import com.yahoo.vespa.hosted.controller.application.ApplicationActivity;
+import com.yahoo.vespa.hosted.controller.application.AssignedRotation;
import com.yahoo.vespa.hosted.controller.application.Change;
import com.yahoo.vespa.hosted.controller.application.Deployment;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs;
+import com.yahoo.vespa.hosted.controller.application.EndpointId;
import com.yahoo.vespa.hosted.controller.application.EndpointList;
import com.yahoo.vespa.hosted.controller.application.RotationStatus;
import com.yahoo.vespa.hosted.controller.rotation.RotationId;
@@ -56,8 +59,9 @@ public class Application {
private final OptionalInt majorVersion;
private final ApplicationMetrics metrics;
private final Optional<String> pemDeployKey;
- private final Optional<RotationId> rotation;
+ private final List<AssignedRotation> rotations;
private final Map<HostName, RotationStatus> rotationStatus;
+ private final Optional<ApplicationCertificate> applicationCertificate;
/** Creates an empty application */
public Application(ApplicationId id, Instant now) {
@@ -65,7 +69,7 @@ public class Application {
new DeploymentJobs(OptionalLong.empty(), Collections.emptyList(), Optional.empty(), false),
Change.empty(), Change.empty(), Optional.empty(), Optional.empty(), OptionalInt.empty(),
new ApplicationMetrics(0, 0),
- Optional.empty(), Optional.empty(), Collections.emptyMap());
+ Optional.empty(), Collections.emptyList(), Collections.emptyMap(), Optional.empty());
}
/** Used from persistence layer: Do not use */
@@ -73,18 +77,19 @@ public class Application {
List<Deployment> deployments, DeploymentJobs deploymentJobs, Change change,
Change outstandingChange, Optional<IssueId> ownershipIssueId, Optional<User> owner,
OptionalInt majorVersion, ApplicationMetrics metrics, Optional<String> pemDeployKey,
- Optional<RotationId> rotation, Map<HostName, RotationStatus> rotationStatus) {
+ List<AssignedRotation> rotations, Map<HostName, RotationStatus> rotationStatus,
+ Optional<ApplicationCertificate> applicationCertificate) {
this(id, createdAt, deploymentSpec, validationOverrides,
deployments.stream().collect(Collectors.toMap(Deployment::zone, Function.identity())),
deploymentJobs, change, outstandingChange, ownershipIssueId, owner, majorVersion,
- metrics, pemDeployKey, rotation, rotationStatus);
+ metrics, pemDeployKey, rotations, rotationStatus, applicationCertificate);
}
Application(ApplicationId id, Instant createdAt, DeploymentSpec deploymentSpec, ValidationOverrides validationOverrides,
Map<ZoneId, Deployment> deployments, DeploymentJobs deploymentJobs, Change change,
Change outstandingChange, Optional<IssueId> ownershipIssueId, Optional<User> owner,
OptionalInt majorVersion, ApplicationMetrics metrics, Optional<String> pemDeployKey,
- Optional<RotationId> rotation, Map<HostName, RotationStatus> rotationStatus) {
+ List<AssignedRotation> rotations, Map<HostName, RotationStatus> rotationStatus, Optional<ApplicationCertificate> applicationCertificate) {
this.id = Objects.requireNonNull(id, "id cannot be null");
this.createdAt = Objects.requireNonNull(createdAt, "instant of creation cannot be null");
this.deploymentSpec = Objects.requireNonNull(deploymentSpec, "deploymentSpec cannot be null");
@@ -98,8 +103,9 @@ public class Application {
this.majorVersion = Objects.requireNonNull(majorVersion, "majorVersion cannot be null");
this.metrics = Objects.requireNonNull(metrics, "metrics cannot be null");
this.pemDeployKey = pemDeployKey;
- this.rotation = Objects.requireNonNull(rotation, "rotation cannot be null");
+ this.rotations = List.copyOf(Objects.requireNonNull(rotations, "rotations cannot be null"));
this.rotationStatus = ImmutableMap.copyOf(Objects.requireNonNull(rotationStatus, "rotationStatus cannot be null"));
+ this.applicationCertificate = Objects.requireNonNull(applicationCertificate, "applicationCertificate cannot be null");
}
public ApplicationId id() { return id; }
@@ -195,14 +201,36 @@ public class Application {
}
/** Returns the global rotation id of this, if present */
- public Optional<RotationId> rotation() {
- return rotation;
+ public Optional<RotationId> legacyRotation() {
+ return rotations.stream()
+ .map(AssignedRotation::rotationId)
+ .findFirst();
+ }
+
+ /** Returns all rotations for this application */
+ public List<RotationId> rotations() {
+ return rotations.stream()
+ .map(AssignedRotation::rotationId)
+ .collect(Collectors.toList());
+ }
+
+ /** Returns all assigned rotations for this application */
+ public List<AssignedRotation> assignedRotations() {
+ return rotations;
+ }
+
+ /** Returns the default global endpoints for this in given system - for a given endpoint ID */
+ public EndpointList endpointsIn(SystemName system, EndpointId endpointId) {
+ if (rotations.isEmpty()) return EndpointList.EMPTY;
+ return EndpointList.create(id, endpointId, system);
}
/** Returns the default global endpoints for this in given system */
public EndpointList endpointsIn(SystemName system) {
- if (rotation.isEmpty()) return EndpointList.EMPTY;
- return EndpointList.defaultGlobal(id, system);
+ if (rotations.isEmpty()) return EndpointList.EMPTY;
+ final var endpointStream = rotations.stream()
+ .flatMap(rotation -> EndpointList.create(id, rotation.endpointId(), system).asList().stream());
+ return EndpointList.of(endpointStream);
}
public Optional<String> pemDeployKey() { return pemDeployKey; }
@@ -224,6 +252,10 @@ public class Application {
.orElse(RotationStatus.unknown);
}
+ public Optional<ApplicationCertificate> applicationCertificate() {
+ return applicationCertificate;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
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 b88f1bbb8e1..60fd095eb04 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
@@ -7,7 +7,9 @@ import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.application.api.ValidationId;
import com.yahoo.config.application.api.ValidationOverrides;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.Environment;
+import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.athenz.api.AthenzDomain;
@@ -25,8 +27,11 @@ 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.identifiers.RevisionId;
import com.yahoo.vespa.hosted.controller.api.integration.BuildService;
+import com.yahoo.vespa.hosted.controller.api.integration.certificates.ApplicationCertificate;
+import com.yahoo.vespa.hosted.controller.api.integration.certificates.ApplicationCertificateProvider;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServer;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerException;
+import com.yahoo.vespa.hosted.controller.api.integration.configserver.ContainerEndpoint;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.Log;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.Node;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.NotFoundException;
@@ -36,25 +41,28 @@ import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationV
import com.yahoo.vespa.hosted.controller.api.integration.deployment.ArtifactRepository;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.TesterId;
+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.api.integration.routing.RoutingEndpoint;
import com.yahoo.vespa.hosted.controller.api.integration.routing.RoutingGenerator;
import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
+import com.yahoo.vespa.hosted.controller.application.AssignedRotation;
import com.yahoo.vespa.hosted.controller.application.Deployment;
import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics;
import com.yahoo.vespa.hosted.controller.application.Endpoint;
+import com.yahoo.vespa.hosted.controller.application.EndpointId;
import com.yahoo.vespa.hosted.controller.application.EndpointList;
import com.yahoo.vespa.hosted.controller.application.JobList;
import com.yahoo.vespa.hosted.controller.application.JobStatus;
import com.yahoo.vespa.hosted.controller.application.JobStatus.JobRun;
-import com.yahoo.vespa.hosted.controller.application.RoutingPolicy;
import com.yahoo.vespa.hosted.controller.application.SystemApplication;
import com.yahoo.vespa.hosted.controller.athenz.impl.AthenzFacade;
import com.yahoo.vespa.hosted.controller.concurrent.Once;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentSteps;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentTrigger;
import com.yahoo.vespa.hosted.controller.dns.NameServiceQueue.Priority;
+import com.yahoo.vespa.hosted.controller.maintenance.RoutingPolicies;
import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
import com.yahoo.vespa.hosted.controller.rotation.Rotation;
import com.yahoo.vespa.hosted.controller.rotation.RotationId;
@@ -75,20 +83,23 @@ import java.security.Principal;
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
+import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashMap;
-import java.util.HashSet;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
+import java.util.TreeMap;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import static com.yahoo.vespa.hosted.controller.api.integration.configserver.Node.State.active;
import static com.yahoo.vespa.hosted.controller.api.integration.configserver.Node.State.reserved;
@@ -115,10 +126,12 @@ public class ApplicationController {
private final AccessControl accessControl;
private final ConfigServer configServer;
private final RoutingGenerator routingGenerator;
+ private final RoutingPolicies routingPolicies;
private final Clock clock;
- private final BooleanFlag redirectLegacyDnsFlag;
-
+ private final BooleanFlag useMultipleEndpoints;
private final DeploymentTrigger deploymentTrigger;
+ private final BooleanFlag provisionApplicationCertificate;
+ private final ApplicationCertificateProvider applicationCertificateProvider;
ApplicationController(Controller controller, CuratorDb curator,
AccessControl accessControl, RotationsConfig rotationsConfig,
@@ -130,14 +143,18 @@ public class ApplicationController {
this.accessControl = accessControl;
this.configServer = configServer;
this.routingGenerator = routingGenerator;
+ this.routingPolicies = new RoutingPolicies(controller);
this.clock = clock;
- this.redirectLegacyDnsFlag = Flags.REDIRECT_LEGACY_DNS_NAMES.bindTo(controller.flagSource());
+ this.useMultipleEndpoints = Flags.MULTIPLE_GLOBAL_ENDPOINTS.bindTo(controller.flagSource());
this.artifactRepository = artifactRepository;
this.applicationStore = applicationStore;
this.rotationRepository = new RotationRepository(rotationsConfig, this, curator);
this.deploymentTrigger = new DeploymentTrigger(controller, buildService, clock);
+ this.provisionApplicationCertificate = Flags.PROVISION_APPLICATION_CERTIFICATE.bindTo(controller.flagSource());
+ this.applicationCertificateProvider = controller.applicationCertificateProvider();
+
// Update serialization format of all applications
Once.after(Duration.ofMinutes(1), () -> {
Instant start = clock.instant();
@@ -206,7 +223,7 @@ public class ApplicationController {
return findGlobalEndpoint(deployment).map(endpoint -> {
try {
EndpointStatus status = configServer.getGlobalRotationStatus(deployment, endpoint.upstreamName());
- return Collections.singletonMap(endpoint, status);
+ return Map.of(endpoint, status);
} catch (IOException e) {
throw new UncheckedIOException("Failed to get rotation status of " + deployment, e);
}
@@ -226,8 +243,6 @@ public class ApplicationController {
* @throws IllegalArgumentException if the application already exists
*/
public Application createApplication(ApplicationId id, Optional<Credentials> credentials) {
- if ( ! (id.instance().isDefault())) // TODO: Support instances properly
- throw new IllegalArgumentException("Only the instance name 'default' is supported at the moment");
if (id.instance().isTester())
throw new IllegalArgumentException("'" + id + "' is a tester application!");
try (Lock lock = lock(id)) {
@@ -246,7 +261,7 @@ public class ApplicationController {
if (credentials.isEmpty())
throw new IllegalArgumentException("Could not create '" + id + "': No credentials provided");
- if (id.instance().isDefault()) // Only store the application permits for non-user applications.
+ if ( ! id.instance().isTester()) // Only store the application permits for non-user applications.
accessControl.createApplication(id, credentials.get());
}
LockedApplication application = new LockedApplication(new Application(id, clock.instant()), lock);
@@ -281,8 +296,9 @@ public class ApplicationController {
Version platformVersion;
ApplicationVersion applicationVersion;
ApplicationPackage applicationPackage;
- Set<String> rotationNames = new HashSet<>();
- Set<String> cnames;
+ Set<String> legacyRotations = new LinkedHashSet<>();
+ Set<ContainerEndpoint> endpoints = new LinkedHashSet<>();
+ ApplicationCertificate applicationCertificate;
try (Lock lock = lock(applicationId)) {
LockedApplication application = new LockedApplication(require(applicationId), lock);
@@ -320,13 +336,39 @@ public class ApplicationController {
// TODO: Remove this when all packages are validated upon submission, as in ApplicationApiHandler.submit(...).
verifyApplicationIdentityConfiguration(applicationId.tenant(), applicationPackage, deployingIdentity);
+
// Assign global rotation
- application = withRotation(application, zone);
- Application app = application.get();
- // Include global DNS names
- cnames = app.endpointsIn(controller.system()).asList().stream().map(Endpoint::dnsName).collect(Collectors.toSet());
- // Include rotation ID to ensure that deployment can respond to health checks with rotation ID as Host header
- app.rotation().map(RotationId::asString).ifPresent(cnames::add);
+ if (useMultipleEndpoints.with(FetchVector.Dimension.APPLICATION_ID, application.get().id().serializedForm()).value()) {
+ application = withRotation(application, zone);
+
+ // Include global DNS names
+ Application app = application.get();
+ app.assignedRotations().stream()
+ .filter(assignedRotation -> assignedRotation.regions().contains(zone.region()))
+ .map(assignedRotation -> {
+ return new ContainerEndpoint(
+ assignedRotation.clusterId().value(),
+ Stream.concat(
+ app.endpointsIn(controller.system(), assignedRotation.endpointId()).legacy(false).asList().stream().map(Endpoint::dnsName),
+ app.rotations().stream().map(RotationId::asString)
+ ).collect(Collectors.toList())
+ );
+ })
+ .forEach(endpoints::add);
+ } else {
+ application = withRotationLegacy(application, zone);
+
+ // Add both the names we have in DNS for each endpoint as well as name of the rotation so healthchecks works
+ Application app = application.get();
+ app.endpointsIn(controller.system()).asList().stream().map(Endpoint::dnsName).forEach(legacyRotations::add);
+ app.rotations().stream().map(RotationId::asString).forEach(legacyRotations::add);
+ }
+
+
+ // Get application certificate (provisions a new certificate if missing)
+ application = withApplicationCertificate(application);
+ applicationCertificate = application.get().applicationCertificate().orElse(null);
+
// Update application with information from application package
if ( ! preferOldestVersion
&& ! application.get().deploymentJobs().deployedInternally()
@@ -337,7 +379,7 @@ public class ApplicationController {
// Carry out deployment without holding the application lock.
options = withVersion(platformVersion, options);
- ActivateResult result = deploy(applicationId, applicationPackage, zone, options, rotationNames, cnames);
+ ActivateResult result = deploy(applicationId, applicationPackage, zone, options, legacyRotations, endpoints, applicationCertificate);
lockOrThrow(applicationId, application ->
store(application.withNewDeployment(zone, applicationVersion, platformVersion, clock.instant(),
@@ -393,7 +435,7 @@ public class ApplicationController {
deploySystemApplicationPackage(application, zone, version);
} else {
// Deploy by calling node repository directly
- application.nodeTypes().forEach(nodeType -> configServer().nodeRepository().upgrade(zone, nodeType, version));
+ configServer().nodeRepository().upgrade(zone, application.nodeType(), version);
}
}
@@ -404,7 +446,7 @@ public class ApplicationController {
artifactRepository.getSystemApplicationPackage(application.id(), zone, version)
);
DeployOptions options = withVersion(version, DeployOptions.none());
- return deploy(application.id(), applicationPackage, zone, options, Collections.emptySet(), Collections.emptySet());
+ return deploy(application.id(), applicationPackage, zone, options, Set.of(), Set.of(), /* No application cert */ null);
} else {
throw new RuntimeException("This system application does not have an application package: " + application.id().toShortString());
}
@@ -412,47 +454,106 @@ public class ApplicationController {
/** Deploys the given tester application to the given zone. */
public ActivateResult deployTester(TesterId tester, ApplicationPackage applicationPackage, ZoneId zone, DeployOptions options) {
- return deploy(tester.id(), applicationPackage, zone, options, Collections.emptySet(), Collections.emptySet());
+ return deploy(tester.id(), applicationPackage, zone, options, Set.of(), Set.of(), /* No application cert for tester*/ null);
}
private ActivateResult deploy(ApplicationId application, ApplicationPackage applicationPackage,
ZoneId zone, DeployOptions deployOptions,
- Set<String> rotationNames, Set<String> cnames) {
+ Set<String> legacyRotations, Set<ContainerEndpoint> endpoints, ApplicationCertificate applicationCertificate) {
DeploymentId deploymentId = new DeploymentId(application, zone);
- ConfigServer.PreparedApplication preparedApplication =
- configServer.deploy(deploymentId, deployOptions, cnames, rotationNames,
- applicationPackage.zippedContent());
- return new ActivateResult(new RevisionId(applicationPackage.hash()), preparedApplication.prepareResponse(),
- applicationPackage.zippedContent().length);
+ try {
+ ConfigServer.PreparedApplication preparedApplication =
+ configServer.deploy(deploymentId, deployOptions, legacyRotations, endpoints, applicationCertificate, applicationPackage.zippedContent());
+ return new ActivateResult(new RevisionId(applicationPackage.hash()), preparedApplication.prepareResponse(),
+ applicationPackage.zippedContent().length);
+ } finally {
+ // Even if prepare fails, a load balancer may have been provisioned. Always refresh routing policies so that
+ // any DNS updates can be propagated as early as possible.
+ routingPolicies.refresh(application, zone);
+ }
}
/** Makes sure the application has a global rotation, if eligible. */
- private LockedApplication withRotation(LockedApplication application, ZoneId zone) {
+ private LockedApplication withRotationLegacy(LockedApplication application, ZoneId zone) {
if (zone.environment() == Environment.prod && application.get().deploymentSpec().globalServiceId().isPresent()) {
try (RotationLock rotationLock = rotationRepository.lock()) {
Rotation rotation = rotationRepository.getOrAssignRotation(application.get(), rotationLock);
- application = application.with(rotation.id());
+ application = application.with(createDefaultGlobalIdRotation(application.get(), rotation));
store(application); // store assigned rotation even if deployment fails
- boolean redirectLegacyDns = redirectLegacyDnsFlag.with(FetchVector.Dimension.APPLICATION_ID, application.get().id().serializedForm())
- .value();
-
EndpointList globalEndpoints = application.get()
- .endpointsIn(controller.system())
- .scope(Endpoint.Scope.global);
+ .endpointsIn(controller.system())
+ .scope(Endpoint.Scope.global);
+
globalEndpoints.main().ifPresent(mainEndpoint -> {
registerCname(mainEndpoint.dnsName(), rotation.name());
- if (redirectLegacyDns) {
- globalEndpoints.legacy(true).asList().forEach(endpoint -> registerCname(endpoint.dnsName(), mainEndpoint.dnsName()));
- } else {
- globalEndpoints.legacy(true).asList().forEach(endpoint -> registerCname(endpoint.dnsName(), rotation.name()));
- }
+ globalEndpoints.legacy(true).asList().forEach(endpoint -> registerCname(endpoint.dnsName(), rotation.name()));
});
}
}
return application;
}
+ private List<AssignedRotation> createDefaultGlobalIdRotation(Application application, Rotation rotation) {
+ // This is guaranteed by .withRotationLegacy, but add this to make inspections accept the use of .get() below
+ assert application.deploymentSpec().globalServiceId().isPresent();
+
+ final Set<RegionName> regions = application.deploymentSpec().zones().stream()
+ .filter(zone -> zone.environment().isProduction())
+ .flatMap(zone -> zone.region().stream())
+ .collect(Collectors.toSet());
+
+ final var assignment = new AssignedRotation(
+ ClusterSpec.Id.from(application.deploymentSpec().globalServiceId().get()),
+ EndpointId.default_(),
+ rotation.id(),
+ regions
+ );
+
+ return List.of(assignment);
+ }
+
+ /** Makes sure the application has a global rotation, if eligible. */
+ private LockedApplication withRotation(LockedApplication application, ZoneId zone) {
+ if (zone.environment() == Environment.prod) {
+ try (RotationLock rotationLock = rotationRepository.lock()) {
+ final var rotations = rotationRepository.getOrAssignRotations(application.get(), rotationLock);
+ application = application.with(rotations);
+ store(application); // store assigned rotation even if deployment fails
+ registerAssignedRotationCnames(application.get());
+ }
+ }
+ return application;
+ }
+
+ private void registerAssignedRotationCnames(Application application) {
+ application.assignedRotations().forEach(assignedRotation -> {
+ final var endpoints = application
+ .endpointsIn(controller.system(), assignedRotation.endpointId())
+ .scope(Endpoint.Scope.global);
+
+ final var maybeRotation = rotationRepository.getRotation(assignedRotation.rotationId());
+
+ maybeRotation.ifPresent(rotation -> {
+ endpoints.main().ifPresent(mainEndpoint -> {
+ registerCname(mainEndpoint.dnsName(), rotation.name());
+ });
+ });
+ });
+ }
+
+ private LockedApplication withApplicationCertificate(LockedApplication application) {
+ ApplicationId applicationId = application.get().id();
+
+ // TODO: Verify that the application is deploying to a zone where certificate provisioning is enabled
+ boolean provisionCertificate = provisionApplicationCertificate.with(FetchVector.Dimension.APPLICATION_ID, applicationId.serializedForm()).value();
+ if (provisionCertificate) {
+ application = application.withApplicationCertificate(
+ Optional.of(applicationCertificateProvider.requestCaSignedCertificate(applicationId)));
+ }
+ return application;
+ }
+
private ActivateResult unexpectedDeployment(ApplicationId application, ZoneId zone) {
Log logEntry = new Log();
logEntry.level = "WARNING";
@@ -460,8 +561,8 @@ public class ApplicationController {
logEntry.message = "Ignoring deployment of application '" + application + "' to " + zone +
" as a deployment is not currently expected";
PrepareResponse prepareResponse = new PrepareResponse();
- prepareResponse.log = Collections.singletonList(logEntry);
- prepareResponse.configChangeActions = new ConfigChangeActions(Collections.emptyList(), Collections.emptyList());
+ prepareResponse.log = List.of(logEntry);
+ prepareResponse.configChangeActions = new ConfigChangeActions(List.of(), List.of());
return new ActivateResult(new RevisionId("0"), prepareResponse, 0);
}
@@ -512,24 +613,57 @@ public class ApplicationController {
controller.nameServiceForwarder().createCname(RecordName.from(name), RecordData.fqdn(targetName), Priority.normal);
}
- /** Returns the endpoints of the deployment, or an empty list if the request fails */
- public Optional<List<URI>> getDeploymentEndpoints(DeploymentId deploymentId) {
+ /** Returns the endpoints of the deployment, or empty if the request fails */
+ public List<URI> getDeploymentEndpoints(DeploymentId deploymentId) {
if ( ! get(deploymentId.applicationId())
.map(application -> application.deployments().containsKey(deploymentId.zoneId()))
.orElse(deploymentId.applicationId().instance().isTester()))
throw new NotExistsException("Deployment", deploymentId.toString());
try {
- return Optional.of(ImmutableList.copyOf(routingGenerator.endpoints(deploymentId).stream()
- .map(RoutingEndpoint::endpoint)
- .map(URI::create)
- .iterator()));
+ return ImmutableList.copyOf(routingGenerator.endpoints(deploymentId).stream()
+ .map(RoutingEndpoint::endpoint)
+ .map(URI::create)
+ .iterator());
}
catch (RuntimeException e) {
log.log(Level.WARNING, "Failed to get endpoint information for " + deploymentId + ": "
+ Exceptions.toMessageString(e));
- return Optional.empty();
+ return Collections.emptyList();
+ }
+ }
+
+ /** Returns the non-empty endpoints per cluster in the given deployment, or empty if endpoints can't be found. */
+ public Map<ClusterSpec.Id, URI> clusterEndpoints(DeploymentId id) {
+ if ( ! get(id.applicationId())
+ .map(application -> application.deployments().containsKey(id.zoneId()))
+ .orElse(id.applicationId().instance().isTester()))
+ throw new NotExistsException("Deployment", id.toString());
+
+ // TODO jvenstad: Swap to use routingPolicies first, when this is ready.
+ try {
+ var endpoints = routingGenerator.clusterEndpoints(id);
+ if ( ! endpoints.isEmpty())
+ return endpoints;
+ }
+ catch (RuntimeException e) {
+ log.log(Level.WARNING, "Failed to get endpoint information for " + id + ": " + Exceptions.toMessageString(e));
}
+ return routingPolicies.get(id).stream()
+ .filter(policy -> policy.endpointIn(controller.system()).scope() == Endpoint.Scope.zone)
+ .collect(Collectors.toUnmodifiableMap(policy -> policy.cluster(),
+ policy -> policy.endpointIn(controller.system()).url()));
+ }
+
+ /** Returns all zone-specific cluster endpoints for the given application, in the given zones. */
+ public Map<ZoneId, Map<ClusterSpec.Id, URI>> clusterEndpoints(ApplicationId id, Collection<ZoneId> zones) {
+ Map<ZoneId, Map<ClusterSpec.Id, URI>> deployments = new TreeMap<>(Comparator.comparing(ZoneId::value));
+ for (ZoneId zone : zones) {
+ var endpoints = clusterEndpoints(new DeploymentId(id, zone));
+ if ( ! endpoints.isEmpty())
+ deployments.put(zone, endpoints);
+ }
+ return Collections.unmodifiableMap(deployments);
}
/**
@@ -556,12 +690,23 @@ public class ApplicationController {
// TODO: Make this one transaction when database is moved to ZooKeeper
instances.forEach(id -> lockOrThrow(id, application -> {
if ( ! application.get().deployments().isEmpty())
- throw new IllegalArgumentException("Could not delete '" + application + "': It has active deployments");
+ throw new IllegalArgumentException("Could not delete '" + application + "': It has active deployments in: " +
+ application.get().deployments().keySet().stream().map(ZoneId::toString)
+ .sorted().collect(Collectors.joining(", ")));
curator.removeApplication(id);
applicationStore.removeAll(id);
applicationStore.removeAll(TesterId.of(id));
+ application.get().assignedRotations().forEach(assignedRotation -> {
+ final var endpoints = application.get().endpointsIn(controller.system(), assignedRotation.endpointId());
+ endpoints.asList().stream()
+ .map(Endpoint::dnsName)
+ .forEach(name -> {
+ controller.nameServiceForwarder().removeRecords(Record.Type.CNAME, RecordName.from(name), Priority.normal);
+ });
+ });
+
log.info("Deleted " + application);
}));
@@ -641,9 +786,10 @@ public class ApplicationController {
private LockedApplication deactivate(LockedApplication application, ZoneId zone) {
try {
configServer.deactivate(new DeploymentId(application.get().id(), zone));
- }
- catch (NotFoundException ignored) {
+ } catch (NotFoundException ignored) {
// ok; already gone
+ } finally {
+ routingPolicies.refresh(application.get().id(), zone);
}
return application.withoutDeploymentIn(zone);
}
@@ -703,9 +849,8 @@ public class ApplicationController {
return rotationRepository;
}
- /** Returns all known routing policies for given application */
- public Set<RoutingPolicy> routingPolicies(ApplicationId application) {
- return curator.readRoutingPolicies(application);
+ public RoutingPolicies routingPolicies() {
+ return routingPolicies;
}
/** Sort given list of applications by application ID */
@@ -767,7 +912,7 @@ public class ApplicationController {
if (!"warn".equalsIgnoreCase(log.level) && !"warning".equalsIgnoreCase(log.level)) continue;
warnings.merge(DeploymentMetrics.Warning.all, 1, Integer::sum);
}
- return Collections.unmodifiableMap(warnings);
+ return Map.copyOf(warnings);
}
}
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 fde34e62ae4..08c95d1ecab 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
@@ -8,18 +8,19 @@ import com.yahoo.component.Vtag;
import com.yahoo.config.provision.CloudName;
import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.SystemName;
-import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.vespa.curator.Lock;
import com.yahoo.vespa.flags.FlagSource;
import com.yahoo.vespa.hosted.controller.api.integration.BuildService;
import com.yahoo.vespa.hosted.controller.api.integration.MetricsService;
import com.yahoo.vespa.hosted.controller.api.integration.RunDataStore;
-import com.yahoo.vespa.hosted.controller.api.integration.chef.Chef;
+import com.yahoo.vespa.hosted.controller.api.integration.certificates.ApplicationCertificateProvider;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServer;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationStore;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.ArtifactRepository;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.TesterCloud;
import com.yahoo.vespa.hosted.controller.api.integration.github.GitHub;
+import com.yahoo.vespa.hosted.controller.api.integration.maven.MavenRepository;
import com.yahoo.vespa.hosted.controller.api.integration.organization.Mailer;
import com.yahoo.vespa.hosted.controller.api.integration.routing.RoutingGenerator;
import com.yahoo.vespa.hosted.controller.api.integration.user.Roles;
@@ -77,11 +78,12 @@ public class Controller extends AbstractComponent {
private final ZoneRegistry zoneRegistry;
private final ConfigServer configServer;
private final MetricsService metricsService;
- private final Chef chef;
private final Mailer mailer;
private final AuditLogger auditLogger;
private final FlagSource flagSource;
private final NameServiceForwarder nameServiceForwarder;
+ private final ApplicationCertificateProvider applicationCertificateProvider;
+ private final MavenRepository mavenRepository;
/**
* Creates a controller
@@ -91,24 +93,26 @@ public class Controller extends AbstractComponent {
@Inject
public Controller(CuratorDb curator, RotationsConfig rotationsConfig, GitHub gitHub,
ZoneRegistry zoneRegistry, ConfigServer configServer, MetricsService metricsService,
- RoutingGenerator routingGenerator, Chef chef,
+ RoutingGenerator routingGenerator,
AccessControl accessControl,
ArtifactRepository artifactRepository, ApplicationStore applicationStore, TesterCloud testerCloud,
- BuildService buildService, RunDataStore runDataStore, Mailer mailer, FlagSource flagSource) {
+ BuildService buildService, RunDataStore runDataStore, Mailer mailer, FlagSource flagSource,
+ MavenRepository mavenRepository, ApplicationCertificateProvider applicationCertificateProvider) {
this(curator, rotationsConfig, gitHub, zoneRegistry,
- configServer, metricsService, routingGenerator, chef,
+ configServer, metricsService, routingGenerator,
Clock.systemUTC(), accessControl, artifactRepository, applicationStore, testerCloud,
- buildService, runDataStore, com.yahoo.net.HostName::getLocalhost, mailer, flagSource);
+ buildService, runDataStore, com.yahoo.net.HostName::getLocalhost, mailer, flagSource,
+ mavenRepository, applicationCertificateProvider);
}
public Controller(CuratorDb curator, RotationsConfig rotationsConfig, GitHub gitHub,
ZoneRegistry zoneRegistry, ConfigServer configServer,
MetricsService metricsService,
- RoutingGenerator routingGenerator, Chef chef, Clock clock,
+ RoutingGenerator routingGenerator, Clock clock,
AccessControl accessControl,
ArtifactRepository artifactRepository, ApplicationStore applicationStore, TesterCloud testerCloud,
BuildService buildService, RunDataStore runDataStore, Supplier<String> hostnameSupplier,
- Mailer mailer, FlagSource flagSource) {
+ Mailer mailer, FlagSource flagSource, MavenRepository mavenRepository, ApplicationCertificateProvider applicationCertificateProvider) {
this.hostnameSupplier = Objects.requireNonNull(hostnameSupplier, "HostnameSupplier cannot be null");
this.curator = Objects.requireNonNull(curator, "Curator cannot be null");
@@ -116,11 +120,12 @@ public class Controller extends AbstractComponent {
this.zoneRegistry = Objects.requireNonNull(zoneRegistry, "ZoneRegistry cannot be null");
this.configServer = Objects.requireNonNull(configServer, "ConfigServer cannot be null");
this.metricsService = Objects.requireNonNull(metricsService, "MetricsService cannot be null");
- this.chef = Objects.requireNonNull(chef, "Chef cannot be null");
this.clock = Objects.requireNonNull(clock, "Clock cannot be null");
this.mailer = Objects.requireNonNull(mailer, "Mailer cannot be null");
this.flagSource = Objects.requireNonNull(flagSource, "FlagSource cannot be null");
this.nameServiceForwarder = new NameServiceForwarder(curator);
+ this.applicationCertificateProvider = Objects.requireNonNull(applicationCertificateProvider);
+ this.mavenRepository = Objects.requireNonNull(mavenRepository, "MavenRepository cannot be null");
jobController = new JobController(this, runDataStore, Objects.requireNonNull(testerCloud));
applicationController = new ApplicationController(this, curator, accessControl,
@@ -163,9 +168,9 @@ public class Controller extends AbstractComponent {
public ZoneRegistry zoneRegistry() { return zoneRegistry; }
- public NameServiceForwarder nameServiceForwarder() {
- return nameServiceForwarder;
- }
+ public NameServiceForwarder nameServiceForwarder() { return nameServiceForwarder; }
+
+ public MavenRepository mavenRepository() { return mavenRepository; }
public ApplicationView getApplicationView(String tenantName, String applicationName, String instanceName,
String environment, String region) {
@@ -286,10 +291,6 @@ public class Controller extends AbstractComponent {
return zoneRegistry.system();
}
- public Chef chefClient() {
- return chef;
- }
-
public CuratorDb curator() {
return curator;
}
@@ -298,6 +299,10 @@ public class Controller extends AbstractComponent {
return auditLogger;
}
+ public ApplicationCertificateProvider applicationCertificateProvider() {
+ return applicationCertificateProvider;
+ }
+
/** Returns all other roles the given tenant role implies. */
public Set<Role> impliedRoles(TenantRole role) {
return Stream.concat(Roles.tenantRoles(role.tenant()).stream(),
@@ -315,8 +320,8 @@ public class Controller extends AbstractComponent {
}
private Set<CloudName> clouds() {
- return zoneRegistry.zones().all().ids().stream()
- .map(ZoneId::cloud)
+ return zoneRegistry.zones().all().zones().stream()
+ .map(ZoneApi::getCloudName)
.collect(Collectors.toUnmodifiableSet());
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java
index d1a5360625c..1e032ab25ea 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java
@@ -11,11 +11,13 @@ import com.yahoo.vespa.curator.Lock;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.ClusterMetrics;
import com.yahoo.vespa.hosted.controller.api.integration.MetricsService;
import com.yahoo.vespa.hosted.controller.api.integration.MetricsService.ApplicationMetrics;
+import com.yahoo.vespa.hosted.controller.api.integration.certificates.ApplicationCertificate;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId;
import com.yahoo.vespa.hosted.controller.api.integration.organization.User;
import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.vespa.hosted.controller.application.AssignedRotation;
import com.yahoo.vespa.hosted.controller.application.Change;
import com.yahoo.vespa.hosted.controller.application.ClusterInfo;
import com.yahoo.vespa.hosted.controller.application.ClusterUtilization;
@@ -57,8 +59,9 @@ public class LockedApplication {
private final OptionalInt majorVersion;
private final ApplicationMetrics metrics;
private final Optional<String> pemDeployKey;
- private final Optional<RotationId> rotation;
+ private final List<AssignedRotation> rotations;
private final Map<HostName, RotationStatus> rotationStatus;
+ private final Optional<ApplicationCertificate> applicationCertificate;
/**
* Used to create a locked application
@@ -72,7 +75,7 @@ public class LockedApplication {
application.deployments(),
application.deploymentJobs(), application.change(), application.outstandingChange(),
application.ownershipIssueId(), application.owner(), application.majorVersion(), application.metrics(),
- application.pemDeployKey(), application.rotation(), application.rotationStatus());
+ application.pemDeployKey(), application.assignedRotations(), application.rotationStatus(), application.applicationCertificate());
}
private LockedApplication(Lock lock, ApplicationId id, Instant createdAt,
@@ -80,7 +83,7 @@ public class LockedApplication {
Map<ZoneId, Deployment> deployments, DeploymentJobs deploymentJobs, Change change,
Change outstandingChange, Optional<IssueId> ownershipIssueId, Optional<User> owner,
OptionalInt majorVersion, ApplicationMetrics metrics, Optional<String> pemDeployKey,
- Optional<RotationId> rotation, Map<HostName, RotationStatus> rotationStatus) {
+ List<AssignedRotation> rotations, Map<HostName, RotationStatus> rotationStatus, Optional<ApplicationCertificate> applicationCertificate) {
this.lock = lock;
this.id = id;
this.createdAt = createdAt;
@@ -95,43 +98,44 @@ public class LockedApplication {
this.majorVersion = majorVersion;
this.metrics = metrics;
this.pemDeployKey = pemDeployKey;
- this.rotation = rotation;
+ this.rotations = rotations;
this.rotationStatus = rotationStatus;
+ this.applicationCertificate = applicationCertificate;
}
/** Returns a read-only copy of this */
public Application get() {
return new Application(id, createdAt, deploymentSpec, validationOverrides, deployments, deploymentJobs, change,
outstandingChange, ownershipIssueId, owner, majorVersion, metrics, pemDeployKey,
- rotation, rotationStatus);
+ rotations, rotationStatus, applicationCertificate);
}
public LockedApplication withBuiltInternally(boolean builtInternally) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
deploymentJobs.withBuiltInternally(builtInternally), change, outstandingChange,
ownershipIssueId, owner, majorVersion, metrics, pemDeployKey,
- rotation, rotationStatus);
+ rotations, rotationStatus, applicationCertificate);
}
public LockedApplication withProjectId(OptionalLong projectId) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
deploymentJobs.withProjectId(projectId), change, outstandingChange,
ownershipIssueId, owner, majorVersion, metrics, pemDeployKey,
- rotation, rotationStatus);
+ rotations, rotationStatus, applicationCertificate);
}
public LockedApplication withDeploymentIssueId(IssueId issueId) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
deploymentJobs.with(issueId), change, outstandingChange,
ownershipIssueId, owner, majorVersion, metrics, pemDeployKey,
- rotation, rotationStatus);
+ rotations, rotationStatus, applicationCertificate);
}
public LockedApplication withJobPause(JobType jobType, OptionalLong pausedUntil) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
deploymentJobs.withPause(jobType, pausedUntil), change, outstandingChange,
ownershipIssueId, owner, majorVersion, metrics, pemDeployKey,
- rotation, rotationStatus);
+ rotations, rotationStatus, applicationCertificate);
}
public LockedApplication withJobCompletion(long projectId, JobType jobType, JobStatus.JobRun completion,
@@ -139,14 +143,14 @@ public class LockedApplication {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
deploymentJobs.withCompletion(projectId, jobType, completion, jobError),
change, outstandingChange, ownershipIssueId, owner, majorVersion, metrics,
- pemDeployKey, rotation, rotationStatus);
+ pemDeployKey, rotations, rotationStatus, applicationCertificate);
}
public LockedApplication withJobTriggering(JobType jobType, JobStatus.JobRun job) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
deploymentJobs.withTriggering(jobType, job), change, outstandingChange,
ownershipIssueId, owner, majorVersion, metrics, pemDeployKey,
- rotation, rotationStatus);
+ rotations, rotationStatus, applicationCertificate);
}
public LockedApplication withNewDeployment(ZoneId zone, ApplicationVersion applicationVersion, Version version,
@@ -198,45 +202,45 @@ public class LockedApplication {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
deploymentJobs.without(jobType), change, outstandingChange,
ownershipIssueId, owner, majorVersion, metrics, pemDeployKey,
- rotation, rotationStatus);
+ rotations, rotationStatus, applicationCertificate);
}
public LockedApplication with(DeploymentSpec deploymentSpec) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
deploymentJobs, change, outstandingChange,
ownershipIssueId, owner, majorVersion, metrics, pemDeployKey,
- rotation, rotationStatus);
+ rotations, rotationStatus, applicationCertificate);
}
public LockedApplication with(ValidationOverrides validationOverrides) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
deploymentJobs, change, outstandingChange, ownershipIssueId, owner, majorVersion,
- metrics, pemDeployKey, rotation, rotationStatus);
+ metrics, pemDeployKey, rotations, rotationStatus, applicationCertificate);
}
public LockedApplication withChange(Change change) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
deploymentJobs, change, outstandingChange, ownershipIssueId, owner, majorVersion,
- metrics, pemDeployKey, rotation, rotationStatus);
+ metrics, pemDeployKey, rotations, rotationStatus, applicationCertificate);
}
public LockedApplication withOutstandingChange(Change outstandingChange) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
deploymentJobs, change, outstandingChange, ownershipIssueId, owner, majorVersion,
- metrics, pemDeployKey, rotation, rotationStatus);
+ metrics, pemDeployKey, rotations, rotationStatus, applicationCertificate);
}
public LockedApplication withOwnershipIssueId(IssueId issueId) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
deploymentJobs, change, outstandingChange, Optional.ofNullable(issueId), owner,
- majorVersion, metrics, pemDeployKey, rotation, rotationStatus);
+ majorVersion, metrics, pemDeployKey, rotations, rotationStatus, applicationCertificate);
}
public LockedApplication withOwner(User owner) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
deploymentJobs, change, outstandingChange, ownershipIssueId,
Optional.ofNullable(owner), majorVersion, metrics, pemDeployKey,
- rotation, rotationStatus);
+ rotations, rotationStatus, applicationCertificate);
}
/** Set a major version for this, or set to null to remove any major version override */
@@ -244,31 +248,31 @@ public class LockedApplication {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
deploymentJobs, change, outstandingChange, ownershipIssueId, owner,
majorVersion == null ? OptionalInt.empty() : OptionalInt.of(majorVersion),
- metrics, pemDeployKey, rotation, rotationStatus);
+ metrics, pemDeployKey, rotations, rotationStatus, applicationCertificate);
}
public LockedApplication with(MetricsService.ApplicationMetrics metrics) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
deploymentJobs, change, outstandingChange, ownershipIssueId, owner, majorVersion,
- metrics, pemDeployKey, rotation, rotationStatus);
+ metrics, pemDeployKey, rotations, rotationStatus, applicationCertificate);
}
public LockedApplication withPemDeployKey(String pemDeployKey) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
deploymentJobs, change, outstandingChange, ownershipIssueId, owner, majorVersion,
- metrics, Optional.ofNullable(pemDeployKey), rotation, rotationStatus);
+ metrics, Optional.ofNullable(pemDeployKey), rotations, rotationStatus, applicationCertificate);
}
- public LockedApplication with(RotationId rotation) {
+ public LockedApplication with(List<AssignedRotation> assignedRotations) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
deploymentJobs, change, outstandingChange, ownershipIssueId, owner, majorVersion,
- metrics, pemDeployKey, Optional.of(rotation), rotationStatus);
+ metrics, pemDeployKey, assignedRotations, rotationStatus, applicationCertificate);
}
public LockedApplication withRotationStatus(Map<HostName, RotationStatus> rotationStatus) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
deploymentJobs, change, outstandingChange, ownershipIssueId, owner, majorVersion,
- metrics, pemDeployKey, rotation, rotationStatus);
+ metrics, pemDeployKey, rotations, rotationStatus, applicationCertificate);
}
public LockedApplication with(ZoneId zoneId, List<ClusterMetrics> clusterMetrics) {
@@ -277,6 +281,12 @@ public class LockedApplication {
return with(deployment.withClusterMetrics(clusterMetrics));
}
+ public LockedApplication withApplicationCertificate(Optional<ApplicationCertificate> applicationCertificate) {
+ return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
+ deploymentJobs, change, outstandingChange, ownershipIssueId, owner, majorVersion,
+ metrics, pemDeployKey, rotations, rotationStatus, applicationCertificate);
+ }
+
/** Don't expose non-leaf sub-objects. */
private LockedApplication with(Deployment deployment) {
Map<ZoneId, Deployment> deployments = new LinkedHashMap<>(this.deployments);
@@ -287,7 +297,7 @@ public class LockedApplication {
private LockedApplication with(Map<ZoneId, Deployment> deployments) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments,
deploymentJobs, change, outstandingChange, ownershipIssueId, owner, majorVersion,
- metrics, pemDeployKey, rotation, rotationStatus);
+ metrics, pemDeployKey, rotations, rotationStatus, applicationCertificate);
}
@Override
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/AssignedRotation.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/AssignedRotation.java
new file mode 100644
index 00000000000..ec13066d069
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/AssignedRotation.java
@@ -0,0 +1,80 @@
+package com.yahoo.vespa.hosted.controller.application;
+
+import com.yahoo.config.provision.ClusterSpec;
+import com.yahoo.config.provision.RegionName;
+import com.yahoo.vespa.hosted.controller.rotation.RotationId;
+
+import java.util.Collection;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Contains the tuple of [clusterId, endpointId, rotationId, regions[]], to keep track
+ * of which services have assigned which rotations under which name.
+ *
+ * @author ogronnesby
+ */
+public class AssignedRotation {
+ private final ClusterSpec.Id clusterId;
+ private final EndpointId endpointId;
+ private final RotationId rotationId;
+ private final Set<RegionName> regions;
+
+ public AssignedRotation(ClusterSpec.Id clusterId, EndpointId endpointId, RotationId rotationId, Set<RegionName> regions) {
+ this.clusterId = requireNonEmpty(clusterId, clusterId.value(), "clusterId");
+ this.endpointId = Objects.requireNonNull(endpointId);
+ this.rotationId = Objects.requireNonNull(rotationId);
+ this.regions = Set.copyOf(Objects.requireNonNull(regions));
+ }
+
+ public ClusterSpec.Id clusterId() { return clusterId; }
+ public EndpointId endpointId() { return endpointId; }
+ public RotationId rotationId() { return rotationId; }
+ public Set<RegionName> regions() { return regions; }
+
+ @Override
+ public String toString() {
+ return "AssignedRotation{" +
+ "clusterId=" + clusterId +
+ ", endpointId='" + endpointId + '\'' +
+ ", rotationId=" + rotationId +
+ ", regions=" + regions +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ AssignedRotation that = (AssignedRotation) o;
+ return clusterId.equals(that.clusterId) &&
+ endpointId.equals(that.endpointId) &&
+ rotationId.equals(that.rotationId) &&
+ regions.equals(that.regions);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(clusterId, endpointId, rotationId, regions);
+ }
+
+ private static <T> T requireNonEmpty(T object, String value, String field) {
+ Objects.requireNonNull(object);
+ Objects.requireNonNull(value);
+ if (value.isEmpty()) {
+ throw new IllegalArgumentException("Field '" + field + "' was empty");
+ }
+ return object;
+ }
+
+ /** Convenience method intended for tests */
+ public static AssignedRotation fromStrings(String clusterId, String endpointId, String rotationId, Collection<String> regions) {
+ return new AssignedRotation(
+ new ClusterSpec.Id(clusterId),
+ new EndpointId(endpointId),
+ new RotationId(rotationId),
+ regions.stream().map(RegionName::from).collect(Collectors.toSet())
+ );
+ }
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java
index ce7af03aa7e..5dccd5c8120 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java
@@ -21,6 +21,7 @@ public class Endpoint {
public static final String YAHOO_DNS_SUFFIX = ".vespa.yahooapis.com";
public static final String OATH_DNS_SUFFIX = ".vespa.oath.cloud";
public static final String PUBLIC_DNS_SUFFIX = ".public.vespa.oath.cloud";
+ public static final String PUBLIC_CD_DNS_SUFFIX = ".public-cd.vespa.oath.cloud";
private final URI url;
private final Scope scope;
@@ -118,7 +119,7 @@ public class Endpoint {
private static String separator(SystemName system, boolean directRouting, boolean tls) {
if (!tls) return ".";
if (directRouting) return ".";
- if (isPublic(system)) return ".";
+ if (system.isPublic()) return ".";
return "--";
}
@@ -140,8 +141,8 @@ public class Endpoint {
}
private static String systemPart(SystemName system, String separator) {
- if (system == SystemName.main || isPublic(system)) return "";
- return system.name() + separator;
+ if (!system.isCd()) return "";
+ return system.value() + separator;
}
private static String dnsSuffix(SystemName system, boolean legacy) {
@@ -151,16 +152,13 @@ public class Endpoint {
if (legacy) return YAHOO_DNS_SUFFIX;
return OATH_DNS_SUFFIX;
case Public:
- case vaas:
return PUBLIC_DNS_SUFFIX;
+ case PublicCd:
+ return PUBLIC_CD_DNS_SUFFIX;
default: throw new IllegalArgumentException("No DNS suffix declared for system " + system);
}
}
- private static boolean isPublic(SystemName system) { // TODO: Remove and inline once we're down to one
- return system == SystemName.Public || system == SystemName.vaas;
- }
-
/** An endpoint's scope */
public enum Scope {
@@ -219,6 +217,7 @@ public class Endpoint {
private ZoneId zone;
private ClusterSpec.Id cluster;
private RotationName rotation;
+ private EndpointId endpointId;
private Port port;
private boolean legacy = false;
private boolean directRouting = false;
@@ -229,8 +228,8 @@ public class Endpoint {
/** Sets the cluster and zone target of this */
public EndpointBuilder target(ClusterSpec.Id cluster, ZoneId zone) {
- if (rotation != null) {
- throw new IllegalArgumentException("Cannot set both cluster and rotation target");
+ if (rotation != null || endpointId != null) {
+ throw new IllegalArgumentException("Cannot set multiple target types");
}
this.cluster = cluster;
this.zone = zone;
@@ -239,13 +238,22 @@ public class Endpoint {
/** Sets the rotation target of this */
public EndpointBuilder target(RotationName rotation) {
- if (cluster != null && zone != null) {
- throw new IllegalArgumentException("Cannot set both cluster and rotation target");
+ if ((cluster != null && zone != null) || endpointId != null) {
+ throw new IllegalArgumentException("Cannot set multiple target types");
}
this.rotation = rotation;
return this;
}
+ /** Sets the endpoint ID as defines in deployments.xml */
+ public EndpointBuilder named(EndpointId endpointId) {
+ if (rotation != null || cluster != null || zone != null) {
+ throw new IllegalArgumentException("Cannot set multiple target types");
+ }
+ this.endpointId = endpointId;
+ return this;
+ }
+
/** Sets the port of this */
public EndpointBuilder on(Port port) {
this.port = port;
@@ -271,10 +279,12 @@ public class Endpoint {
name = cluster.value();
} else if (rotation != null) {
name = rotation.value();
+ } else if (endpointId != null) {
+ name = endpointId.id();
} else {
throw new IllegalArgumentException("Must set either cluster or rotation target");
}
- if (isPublic(system) && !directRouting) {
+ if (system.isPublic() && !directRouting) {
throw new IllegalArgumentException("Public system only supports direct routing endpoints");
}
if (directRouting && !port.isDefault()) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointId.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointId.java
new file mode 100644
index 00000000000..13c242c7b5f
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointId.java
@@ -0,0 +1,53 @@
+package com.yahoo.vespa.hosted.controller.application;
+
+import java.util.Objects;
+
+/**
+ * A type to represent the ID of an endpoint. This is typically the first part of
+ * an endpoint name.
+ *
+ * @author ogronnesby
+ */
+public class EndpointId {
+ private static final EndpointId DEFAULT = new EndpointId("default");
+
+ private final String id;
+
+ public EndpointId(String id) {
+ this.id = requireNotEmpty(id);
+ }
+
+ public String id() { return id; }
+
+ @Override
+ public String toString() {
+ return "EndpointId{" +
+ "id='" + id + '\'' +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ EndpointId that = (EndpointId) o;
+ return Objects.equals(id, that.id);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id);
+ }
+
+ private static String requireNotEmpty(String input) {
+ Objects.requireNonNull(input);
+ if (input.isEmpty()) {
+ throw new IllegalArgumentException("The value EndpointId was empty");
+ }
+ return input;
+ }
+
+ public static EndpointId default_() { return DEFAULT; }
+
+ public static EndpointId of(String id) { return new EndpointId(id); }
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointList.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointList.java
index 0c04a1f099c..d9aea783880 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointList.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointList.java
@@ -25,13 +25,6 @@ public class EndpointList {
private final List<Endpoint> endpoints;
private EndpointList(List<Endpoint> endpoints) {
- long mainEndpoints = endpoints.stream()
- .filter(endpoint -> endpoint.scope() == Endpoint.Scope.global)
- .filter(Predicate.not(Endpoint::directRouting))
- .filter(Predicate.not(Endpoint::legacy)).count();
- if (mainEndpoints > 1) {
- throw new IllegalArgumentException("Can have only 1 non-legacy global endpoint, got " + endpoints);
- }
if (endpoints.stream().distinct().count() != endpoints.size()) {
throw new IllegalArgumentException("Expected all endpoints to be distinct, got " + endpoints);
}
@@ -67,16 +60,14 @@ public class EndpointList {
}
/** Returns the default global endpoints in given system. Default endpoints are served by a pre-provisioned routing layer */
- public static EndpointList defaultGlobal(ApplicationId application, SystemName system) {
- // Rotation name is always default in the routing layer
- RotationName rotation = RotationName.from("default");
+ public static EndpointList create(ApplicationId application, EndpointId endpointId, SystemName system) {
switch (system) {
case cd:
case main:
return new EndpointList(List.of(
- Endpoint.of(application).target(rotation).on(Port.plain(4080)).legacy().in(system),
- Endpoint.of(application).target(rotation).on(Port.tls(4443)).legacy().in(system),
- Endpoint.of(application).target(rotation).on(Port.tls(4443)).in(system)
+ Endpoint.of(application).named(endpointId).on(Port.plain(4080)).legacy().in(system),
+ Endpoint.of(application).named(endpointId).on(Port.tls(4443)).legacy().in(system),
+ Endpoint.of(application).named(endpointId).on(Port.tls(4443)).in(system)
));
}
return EMPTY;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/SystemApplication.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/SystemApplication.java
index 708133d2a07..0d6da51c492 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/SystemApplication.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/SystemApplication.java
@@ -1,6 +1,7 @@
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.application;
+import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.NodeType;
import com.yahoo.config.provision.zone.ZoneId;
@@ -9,8 +10,7 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ServiceConvergence;
import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
+import java.util.Optional;
/**
* This represents a system-level application in hosted Vespa. All infrastructure nodes in a hosted Vespa zones are
@@ -21,25 +21,18 @@ import java.util.stream.Collectors;
public enum SystemApplication {
configServerHost(ApplicationId.from("hosted-vespa", "configserver-host", "default"), NodeType.confighost),
+ configServer(ApplicationId.from("hosted-vespa", "zone-config-servers", "default"), NodeType.config),
proxyHost(ApplicationId.from("hosted-vespa", "proxy-host", "default"), NodeType.proxyhost),
- configServer(ApplicationId.from("hosted-vespa", "zone-config-servers", "default"), NodeType.config, configServerHost),
- zone(ApplicationId.from("hosted-vespa", "routing", "default"), Set.of(NodeType.proxy, NodeType.host),
- configServerHost, proxyHost, configServer);
+ proxy(ApplicationId.from("hosted-vespa", "routing", "default"), NodeType.proxy, proxyHost, configServer),
+ tenantHost(ApplicationId.from("hosted-vespa", "tenant-host", "default"), NodeType.host);
private final ApplicationId id;
- private final Set<NodeType> nodeTypes;
+ private final NodeType nodeType;
private final List<SystemApplication> dependencies;
SystemApplication(ApplicationId id, NodeType nodeType, SystemApplication... dependencies) {
- this(id, Set.of(nodeType), dependencies);
- }
-
- SystemApplication(ApplicationId id, Set<NodeType> nodeTypes, SystemApplication... dependencies) {
- if (nodeTypes.isEmpty()) {
- throw new IllegalArgumentException("Node types must be non-empty");
- }
this.id = id;
- this.nodeTypes = Set.copyOf(nodeTypes);
+ this.nodeType = nodeType;
this.dependencies = List.of(dependencies);
}
@@ -47,9 +40,9 @@ public enum SystemApplication {
return id;
}
- /** The node type(s) that are implicitly allocated to this */
- public Set<NodeType> nodeTypes() {
- return nodeTypes;
+ /** The node type that is implicitly allocated to this */
+ public NodeType nodeType() {
+ return nodeType;
}
/** Returns the system applications that should upgrade before this */
@@ -57,22 +50,22 @@ public enum SystemApplication {
/** Returns whether this system application has an application package */
public boolean hasApplicationPackage() {
- return this == zone;
+ return this == proxy;
}
/** Returns whether config for this application has converged in given zone */
- public boolean configConvergedIn(ZoneId zone, Controller controller) {
+ public boolean configConvergedIn(ZoneId zone, Controller controller, Optional<Version> version) {
if (!hasApplicationPackage()) {
return true;
}
- return controller.configServer().serviceConvergence(new DeploymentId(id(), zone))
+ return controller.configServer().serviceConvergence(new DeploymentId(id(), zone), version)
.map(ServiceConvergence::converged)
.orElse(false);
}
/** Returns the node types of this that should receive OS upgrades */
- public Set<NodeType> nodeTypesWithUpgradableOs() {
- return nodeTypes().stream().filter(NodeType::isDockerHost).collect(Collectors.toSet());
+ public boolean isEligibleForOsUpgrades() {
+ return nodeType.isDockerHost();
}
/** All known system applications */
@@ -82,7 +75,7 @@ public enum SystemApplication {
@Override
public String toString() {
- return String.format("system application %s of type %s", id, nodeTypes);
+ return String.format("system application %s of type %s", id, nodeType);
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/auditlog/AuditLog.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/auditlog/AuditLog.java
index c467a4a0acd..aefe8ae7b48 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/auditlog/AuditLog.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/auditlog/AuditLog.java
@@ -109,6 +109,7 @@ public class AuditLog {
public enum Method {
POST,
PATCH,
+ PUT,
DELETE
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java
index a8130d60cc5..b4fe8ea2971 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java
@@ -92,7 +92,7 @@ public class DeploymentTrigger {
* trigger next.
*/
public void notifyOfCompletion(JobReport report) {
- log.log(LogLevel.INFO, String.format("Notified of %s for %s of %s (%d)",
+ log.log(LogLevel.DEBUG, String.format("Notified of %s for %s of %s (%d)",
report.jobError().map(e -> e.toString() + " error")
.orElse("success"),
report.jobType(),
@@ -124,14 +124,16 @@ public class DeploymentTrigger {
}
}
else {
- triggering = application.get().deploymentJobs().statusOf(report.jobType())
- .filter(job -> job.lastTriggered().isPresent()
- && job.lastCompleted()
- .map(completion -> ! completion.at().isAfter(job.lastTriggered().get().at()))
- .orElse(true))
- .orElseThrow(() -> new IllegalStateException("Notified of completion of " + report.jobType().jobName() + " for " +
- report.applicationId() + ", but that has neither been triggered nor deployed"))
- .lastTriggered().get();
+ Optional<JobStatus> status = application.get().deploymentJobs().statusOf(report.jobType());
+ triggering = status.filter(job -> job.lastTriggered().isPresent()
+ && job.lastCompleted()
+ .map(completion -> ! completion.at().isAfter(job.lastTriggered().get().at()))
+ .orElse(true))
+ .orElseThrow(() -> new IllegalStateException("Notified of completion of " + report.jobType().jobName() + " for " +
+ report.applicationId() + ", but that has not been triggered; last was " +
+ status.flatMap(job -> job.lastTriggered().map(run -> run.at().toString()))
+ .orElse("never")))
+ .lastTriggered().get();
}
application = application.withJobCompletion(report.projectId(),
report.jobType(),
@@ -181,7 +183,7 @@ public class DeploymentTrigger {
* the project id is removed from the application owning the job, to prevent further trigger attempts.
*/
public boolean trigger(Job job) {
- log.log(LogLevel.INFO, String.format("Triggering %s: %s", job, job.triggering));
+ log.log(LogLevel.DEBUG, String.format("Triggering %s: %s", job, job.triggering));
try {
applications().lockOrThrow(job.applicationId(), application -> {
if (application.get().deploymentJobs().deployedInternally())
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 472a0c5fb7e..ce0e7c0dbab 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
@@ -11,13 +11,11 @@ import com.yahoo.config.application.api.Notifications.When;
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.SystemName;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.io.IOUtils;
import com.yahoo.log.LogLevel;
-import com.yahoo.slime.Cursor;
-import com.yahoo.slime.Slime;
-import com.yahoo.vespa.config.SlimeUtils;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.api.ActivateResult;
@@ -28,7 +26,6 @@ import com.yahoo.vespa.hosted.controller.api.integration.LogEntry;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerException;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.Node;
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.ApplicationVersion;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.RunId;
@@ -41,7 +38,6 @@ import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobReport;
import com.yahoo.yolean.Exceptions;
import java.io.ByteArrayOutputStream;
-import java.io.IOException;
import java.io.PrintStream;
import java.io.UncheckedIOException;
import java.net.URI;
@@ -57,7 +53,6 @@ import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
-import java.util.stream.Stream;
import static com.yahoo.config.application.api.Notifications.Role.author;
import static com.yahoo.config.application.api.Notifications.When.failing;
@@ -66,6 +61,7 @@ import static com.yahoo.log.LogLevel.DEBUG;
import static com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerException.ErrorCode.ACTIVATION_CONFLICT;
import static com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerException.ErrorCode.APPLICATION_LOCK_FAILURE;
import static com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerException.ErrorCode.BAD_REQUEST;
+import static com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerException.ErrorCode.CERTIFICATE_NOT_READY;
import static com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerException.ErrorCode.INVALID_APPLICATION_PACKAGE;
import static com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerException.ErrorCode.OUT_OF_CAPACITY;
import static com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerException.ErrorCode.PARENT_HOST_NOT_READY;
@@ -99,10 +95,12 @@ public class InternalStepRunner implements StepRunner {
static final Duration installationTimeout = Duration.ofMinutes(150);
private final Controller controller;
+ private final TestConfigSerializer testConfigSerializer;
private final DeploymentFailureMails mails;
public InternalStepRunner(Controller controller) {
this.controller = controller;
+ this.testConfigSerializer = new TestConfigSerializer(controller.system());
this.mails = new DeploymentFailureMails(controller.zoneRegistry());
}
@@ -234,7 +232,8 @@ public class InternalStepRunner implements StepRunner {
if ( e.getErrorCode() == OUT_OF_CAPACITY && type.isTest()
|| e.getErrorCode() == ACTIVATION_CONFLICT
|| e.getErrorCode() == APPLICATION_LOCK_FAILURE
- || e.getErrorCode() == PARENT_HOST_NOT_READY) {
+ || e.getErrorCode() == PARENT_HOST_NOT_READY
+ || e.getErrorCode() == CERTIFICATE_NOT_READY) {
logger.log("Will retry, because of '" + e.getErrorCode() + "' deploying:\n" + e.getMessage());
return Optional.empty();
}
@@ -268,9 +267,15 @@ public class InternalStepRunner implements StepRunner {
logger.log("Checking installation of " + platform + " and " + application.id() + " ...");
if ( nodesConverged(id.application(), id.type(), platform, logger)
- && servicesConverged(id.application(), id.type(), logger)) {
- logger.log("Installation succeeded!");
- return Optional.of(running);
+ && servicesConverged(id.application(), id.type(), platform, logger)) {
+ if (endpointsAvailable(id.application(), id.type().zone(controller.system()), logger)) {
+ logger.log("Installation succeeded!");
+ return Optional.of(running);
+ }
+ else if (timedOut(deployment.get(), endpointTimeout)) {
+ logger.log(WARNING, "Endpoints failed to show up within " + endpointTimeout.toMinutes() + " minutes!");
+ return Optional.of(error);
+ }
}
if (timedOut(deployment.get(), installationTimeout)) {
@@ -292,9 +297,15 @@ public class InternalStepRunner implements StepRunner {
Version platform = controller.jobController().run(id).get().versions().targetPlatform();
logger.log("Checking installation of tester container ...");
if ( nodesConverged(id.tester().id(), id.type(), platform, logger)
- && servicesConverged(id.tester().id(), id.type(), logger)) {
- logger.log("Tester container successfully installed!");
- return Optional.of(running);
+ && servicesConverged(id.tester().id(), id.type(), platform, logger)) {
+ if (endpointsAvailable(id.tester().id(), id.type().zone(controller.system()), logger)) {
+ logger.log("Tester container successfully installed!");
+ return Optional.of(running);
+ }
+ else if (timedOut(deployment.get(), endpointTimeout)) {
+ logger.log(WARNING, "Tester failed to show up within " + endpointTimeout.toMinutes() + " minutes!");
+ return Optional.of(error);
+ }
}
if (timedOut(deployment.get(), installationTimeout)) {
@@ -306,6 +317,27 @@ public class InternalStepRunner implements StepRunner {
return Optional.empty();
}
+ private boolean endpointsAvailable(ApplicationId id, ZoneId zoneId, DualLogger logger) {
+ logger.log("Attempting to find deployment endpoints ...");
+ var endpoints = controller.applications().clusterEndpoints(id, Set.of(zoneId));
+ if ( ! endpoints.containsKey(zoneId)) {
+ logger.log("Endpoints not yet ready.");
+ return false;
+ }
+ logEndpoints(endpoints, logger);
+ return true;
+ }
+
+ private void logEndpoints(Map<ZoneId, Map<ClusterSpec.Id, URI>> endpoints, DualLogger logger) {
+ List<String> messages = new ArrayList<>();
+ messages.add("Found endpoints:");
+ endpoints.forEach((zone, uris) -> {
+ messages.add("- " + zone);
+ uris.forEach((cluster, uri) -> messages.add(" |-- " + uri + " (" + cluster + ")"));
+ });
+ logger.log(messages);
+ }
+
private boolean nodesConverged(ApplicationId id, JobType type, Version target, DualLogger logger) {
List<Node> nodes = controller.configServer().nodeRepository().list(type.zone(controller.system()), id, ImmutableSet.of(active, reserved));
List<String> statuses = nodes.stream()
@@ -325,9 +357,10 @@ public class InternalStepRunner implements StepRunner {
&& node.rebootGeneration() >= node.wantedRebootGeneration());
}
- private boolean servicesConverged(ApplicationId id, JobType type, DualLogger logger) {
- Optional<ServiceConvergence> convergence = controller.configServer().serviceConvergence(new DeploymentId(id, type.zone(controller.system())));
- if ( ! convergence.isPresent()) {
+ private boolean servicesConverged(ApplicationId id, JobType type, Version platform, DualLogger logger) {
+ var convergence = controller.configServer().serviceConvergence(new DeploymentId(id, type.zone(controller.system())),
+ Optional.of(platform));
+ if (convergence.isEmpty()) {
logger.log("Config status not currently available -- will retry.");
return false;
}
@@ -341,6 +374,8 @@ public class InternalStepRunner implements StepRunner {
serviceStatus.currentGeneration() == -1 ? "not started!" : Long.toString(serviceStatus.currentGeneration())))
.collect(Collectors.toList());
logger.log(statuses);
+ if (statuses.isEmpty())
+ logger.log("All services on wanted config generation.");
return convergence.get().converged();
}
@@ -352,45 +387,34 @@ public class InternalStepRunner implements StepRunner {
return Optional.of(aborted);
}
- Set<ZoneId> zones = testedZoneAndProductionZones(id);
+ Set<ZoneId> zones = controller.jobController().testedZoneAndProductionZones(id.application(), id.type());
logger.log("Attempting to find endpoints ...");
- Map<ZoneId, List<URI>> endpoints = deploymentEndpoints(id.application(), zones);
- List<String> messages = new ArrayList<>();
- messages.add("Found endpoints");
- endpoints.forEach((zone, uris) -> {
- messages.add("- " + zone);
- uris.forEach(uri -> messages.add(" |-- " + uri));
- });
- logger.log(messages);
- if ( ! endpoints.containsKey(id.type().zone(controller.system()))) {
- if (timedOut(deployment.get(), endpointTimeout)) {
- logger.log(WARNING, "Endpoints failed to show up within " + endpointTimeout.toMinutes() + " minutes!");
- return Optional.of(error);
- }
-
- logger.log("Endpoints for the deployment to test are not yet ready.");
- return Optional.empty();
+ var endpoints = controller.applications().clusterEndpoints(id.application(), zones);
+ if ( ! endpoints.containsKey(id.type().zone(controller.system())) && timedOut(deployment.get(), endpointTimeout)) {
+ logger.log(WARNING, "Endpoints for the deployment to test vanished again, while it was still active!");
+ return Optional.of(error);
}
-
- Map<ZoneId, List<String>> clusters = listClusters(id.application(), zones);
+ logEndpoints(endpoints, logger);
Optional<URI> testerEndpoint = controller.jobController().testerEndpoint(id);
- if (testerEndpoint.isPresent() && controller.jobController().cloud().ready(testerEndpoint.get())) {
+ if (testerEndpoint.isEmpty() && timedOut(deployment.get(), endpointTimeout)) {
+ logger.log(WARNING, "Endpoints for the tester container vanished again, while it was still active!");
+ return Optional.of(error);
+ }
+
+ if (controller.jobController().cloud().ready(testerEndpoint.get())) {
logger.log("Starting tests ...");
controller.jobController().cloud().startTests(testerEndpoint.get(),
TesterCloud.Suite.of(id.type()),
- testConfig(id.application(), id.type().zone(controller.system()),
- controller.system(), endpoints, clusters));
+ testConfigSerializer.configJson(id.application(),
+ id.type(),
+ endpoints,
+ listClusters(id.application(), zones)));
return Optional.of(running);
}
- if (timedOut(deployment.get(), endpointTimeout)) {
- logger.log(WARNING, "Endpoint for tester failed to show up within " + endpointTimeout.toMinutes() + " minutes of real deployment!");
- return Optional.of(error);
- }
-
- logger.log("Endpoints of tester container not yet available.");
+ logger.log("Tester container not yet ready.");
return Optional.empty();
}
@@ -430,7 +454,7 @@ public class InternalStepRunner implements StepRunner {
private Optional<RunStatus> copyVespaLogs(RunId id, DualLogger logger) {
ZoneId zone = id.type().zone(controller.system());
- if (controller.applications().require(id.application()).deployments().containsKey(zone))
+ if (deployment(id.application(), id.type()).isPresent())
try {
logger.log("Copying Vespa log from nodes of " + id.application() + " in " + zone + " ...");
List<LogEntry> entries = new ArrayList<>();
@@ -451,20 +475,33 @@ public class InternalStepRunner implements StepRunner {
}
catch (Exception e) {
logger.log(INFO, "Failure getting vespa logs for " + id, e);
+ return Optional.of(error);
}
- return Optional.of(running); // Don't let failure here stop cleanup.
+ return Optional.of(running);
}
private Optional<RunStatus> deactivateReal(RunId id, DualLogger logger) {
- logger.log("Deactivating deployment of " + id.application() + " in " + id.type().zone(controller.system()) + " ...");
- controller.applications().deactivate(id.application(), id.type().zone(controller.system()));
- return Optional.of(running);
+ try {
+ logger.log("Deactivating deployment of " + id.application() + " in " + id.type().zone(controller.system()) + " ...");
+ controller.applications().deactivate(id.application(), id.type().zone(controller.system()));
+ return Optional.of(running);
+ }
+ catch (RuntimeException e) {
+ logger.log(WARNING, "Failed deleting application " + id.application(), e);
+ return Optional.of(error);
+ }
}
private Optional<RunStatus> deactivateTester(RunId id, DualLogger logger) {
- logger.log("Deactivating tester of " + id.application() + " in " + id.type().zone(controller.system()) + " ...");
- controller.jobController().deactivateTester(id.tester(), id.type());
- return Optional.of(running);
+ try {
+ logger.log("Deactivating tester of " + id.application() + " in " + id.type().zone(controller.system()) + " ...");
+ controller.jobController().deactivateTester(id.tester(), id.type());
+ return Optional.of(running);
+ }
+ catch (RuntimeException e) {
+ logger.log(WARNING, "Failed deleting tester of " + id.application(), e);
+ return Optional.of(error);
+ }
}
private Optional<RunStatus> report(RunId id, DualLogger logger) {
@@ -481,7 +518,8 @@ public class InternalStepRunner implements StepRunner {
});
}
catch (IllegalStateException e) {
- logger.log(INFO, "Job '" + id.type() + "'no longer supposed to run?:", e);
+ logger.log(INFO, "Job '" + id.type() + "' no longer supposed to run?", e);
+ return Optional.of(error);
}
return Optional.of(running);
}
@@ -526,6 +564,7 @@ public class InternalStepRunner implements StepRunner {
/** Returns the real application with the given id. */
private Application application(ApplicationId id) {
+ controller.applications().lockOrThrow(id, __ -> { }); // Memory fence.
return controller.applications().require(id);
}
@@ -571,23 +610,6 @@ public class InternalStepRunner implements StepRunner {
throw new IllegalStateException("No step deploys to the zone this run is for!");
}
- /** Returns a stream containing the zone of the deployment tested in the given run, and all production zones for the application. */
- private Set<ZoneId> testedZoneAndProductionZones(RunId id) {
- return Stream.concat(Stream.of(id.type().zone(controller.system())),
- application(id.application()).productionDeployments().keySet().stream())
- .collect(Collectors.toSet());
- }
-
- /** Returns all endpoints for all current deployments of the given real application. */
- private Map<ZoneId, List<URI>> deploymentEndpoints(ApplicationId id, Iterable<ZoneId> zones) {
- ImmutableMap.Builder<ZoneId, List<URI>> deployments = ImmutableMap.builder();
- for (ZoneId zone : zones)
- controller.applications().getDeploymentEndpoints(new DeploymentId(id, zone))
- .filter(endpoints -> ! endpoints.isEmpty())
- .ifPresent(endpoints -> deployments.put(zone, endpoints));
- return deployments.build();
- }
-
/** Returns all content clusters in all current deployments of the given real application. */
private Map<ZoneId, List<String>> listClusters(ApplicationId id, Iterable<ZoneId> zones) {
ImmutableMap.Builder<ZoneId, List<String>> clusters = ImmutableMap.builder();
@@ -603,12 +625,12 @@ public class InternalStepRunner implements StepRunner {
String flavor = testerFlavor.orElse("d-1-4-50");
int memoryGb = Integer.parseInt(flavor.split("-")[2]); // Memory available in tester container.
int jdiscMemoryPercentage = (int) Math.ceil(200.0 / memoryGb); // 2Gb memory for tester application (excessive?).
- int testMemoryMb = 768 * (memoryGb - 2); // Memory allocated to Surefire running tests. ≥25% left for other stuff.
+ int testMemoryMb = 512 * (memoryGb - 2); // Memory allocated to Surefire running tests. ≥25% left for other stuff.
String servicesXml =
"<?xml version='1.0' encoding='UTF-8'?>\n" +
"<services xmlns:deploy='vespa' version='1.0'>\n" +
- " <container version='1.0' id='default'>\n" +
+ " <container version='1.0' id='tester'>\n" +
"\n" +
" <component id=\"com.yahoo.vespa.hosted.testrunner.TestRunner\" bundle=\"vespa-testrunner-components\">\n" +
" <config name=\"com.yahoo.vespa.hosted.testrunner.test-runner\">\n" +
@@ -646,7 +668,9 @@ public class InternalStepRunner implements StepRunner {
" </filtering>\n" +
" </http>\n" +
"\n" +
- " <nodes count=\"1\" flavor=\"" + flavor + "\" allocated-memory=\"" + jdiscMemoryPercentage + "%\" />\n" +
+ " <nodes count=\"1\" flavor=\"" + flavor + "\">\n" +
+ " <jvm allocated-memory=\"" + jdiscMemoryPercentage + "%\" />\n" +
+ " </nodes>\n" +
" </container>\n" +
"</services>\n";
@@ -664,38 +688,6 @@ public class InternalStepRunner implements StepRunner {
return deploymentSpec.getBytes(StandardCharsets.UTF_8);
}
- /** Returns the config for the tests to run for the given job. */
- private static byte[] testConfig(ApplicationId id, ZoneId testerZone, SystemName system,
- Map<ZoneId, List<URI>> deployments, Map<ZoneId, List<String>> clusters) {
- Slime slime = new Slime();
- Cursor root = slime.setObject();
-
- root.setString("application", id.serializedForm());
- root.setString("zone", testerZone.value());
- root.setString("system", system.name());
-
- Cursor endpointsObject = root.setObject("endpoints");
- deployments.forEach((zone, endpoints) -> {
- Cursor endpointArray = endpointsObject.setArray(zone.value());
- for (URI endpoint : endpoints)
- endpointArray.addString(endpoint.toString());
- });
-
- Cursor clustersObject = root.setObject("clusters");
- clusters.forEach((zone, clusterList) -> {
- Cursor clusterArray = clustersObject.setArray(zone.value());
- for (String cluster : clusterList)
- clusterArray.addString(cluster);
- });
-
- try {
- return SlimeUtils.toJsonBytes(slime);
- }
- catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- }
-
/** Logger which logs to a {@link JobController}, as well as to the parent class' {@link Logger}. */
private class DualLogger {
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 245af60d0ad..c644af2e554 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
@@ -4,6 +4,7 @@ package com.yahoo.vespa.hosted.controller.deployment;
import com.google.common.collect.ImmutableMap;
import com.yahoo.component.Version;
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.Application;
import com.yahoo.vespa.hosted.controller.Controller;
@@ -41,6 +42,7 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;
import java.util.logging.Level;
+import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.google.common.collect.ImmutableList.copyOf;
@@ -72,6 +74,8 @@ public class JobController {
private final TesterCloud cloud;
private final Badges badges;
+ private AtomicReference<Consumer<Run>> runner = new AtomicReference<>(__ -> { });
+
public JobController(Controller controller, RunDataStore runDataStore, TesterCloud testerCloud) {
this.controller = controller;
this.curator = controller.curator();
@@ -82,6 +86,7 @@ public class JobController {
public TesterCloud cloud() { return cloud; }
public int historyLength() { return historyLength; }
+ public void setRunner(Consumer<Run> runner) { this.runner.set(runner); }
/** Rewrite all job data with the newest format. */
public void updateStorage() {
@@ -317,12 +322,16 @@ public class JobController {
ApplicationVersion.unknown,
Optional.empty(),
Optional.empty()));
+
+ runner.get().accept(last(id, type).get());
});
}
/** Aborts a run and waits for it complete. */
private void abortAndWait(RunId id) {
abort(id);
+ runner.get().accept(last(id.application(), id.type()).get());
+
while ( ! last(id.application(), id.type()).get().hasEnded()) {
try {
Thread.sleep(100);
@@ -399,9 +408,19 @@ public class JobController {
/** Returns a URI of the tester endpoint retrieved from the routing generator, provided it matches an expected form. */
Optional<URI> testerEndpoint(RunId id) {
- ApplicationId tester = id.tester().id();
- return controller.applications().getDeploymentEndpoints(new DeploymentId(tester, id.type().zone(controller.system())))
- .flatMap(uris -> uris.stream().findAny());
+ 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()
+ .findAny()
+ .map(policy -> policy.endpointIn(controller.system()).url()));
+ }
+
+ /** Returns a set containing the zone of the deployment tested in the given run, and all production zones for the application. */
+ public Set<ZoneId> testedZoneAndProductionZones(ApplicationId id, JobType type) {
+ return Stream.concat(Stream.of(type.zone(controller.system())),
+ controller.applications().require(id).productionDeployments().keySet().stream())
+ .collect(Collectors.toSet());
}
// TODO jvenstad: Find a more appropriate way of doing this, at least when this is the only build service.
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Run.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Run.java
index 0cb400004aa..f052c7d91ab 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Run.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Run.java
@@ -14,6 +14,7 @@ import java.util.Optional;
import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.aborted;
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.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.Objects.requireNonNull;
@@ -173,15 +174,14 @@ public class Run {
.iterator());
}
- /** Returns the list of not-yet-succeeded run-always steps whose run-always prerequisites have all succeeded. */
+ /** Returns the list of not-yet-run run-always steps whose run-always prerequisites have all run. */
private List<Step> forcedSteps() {
return ImmutableList.copyOf(steps.entrySet().stream()
- .filter(entry -> entry.getValue() != succeeded
+ .filter(entry -> entry.getValue() == unfinished
&& JobProfile.of(id.type()).alwaysRun().contains(entry.getKey())
&& entry.getKey().prerequisites().stream()
.filter(JobProfile.of(id.type()).alwaysRun()::contains)
- .allMatch(step -> steps.get(step) == null
- || steps.get(step) == succeeded))
+ .allMatch(step -> steps.get(step) != unfinished))
.map(Map.Entry::getKey)
.iterator());
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Step.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Step.java
index 051a99074d1..a5f9ef86da4 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Step.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Step.java
@@ -15,7 +15,7 @@ import java.util.List;
* only the prerequisites of a step which are included in a run's profile will be considered.
* Under normal circumstances, a step will run only after each of its prerequisites have succeeded.
* When a run has failed, however, each of the always-run steps of the run's profile will be run,
- * again in a topological order, and again requiring success of all their always-run prerequisites.
+ * again in a topological order, and requiring all their always-run prerequisites to have run.
*
* 2. A step will never run concurrently with its prerequisites. This is to ensure, e.g., that relevant
* information from a failed run is stored, and that deployment does not occur after deactivation.
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/TestConfigSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/TestConfigSerializer.java
new file mode 100644
index 00000000000..e79692d34ed
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/TestConfigSerializer.java
@@ -0,0 +1,82 @@
+package com.yahoo.vespa.hosted.controller.deployment;
+
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.ClusterSpec;
+import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.slime.Cursor;
+import com.yahoo.slime.Slime;
+import com.yahoo.vespa.config.SlimeUtils;
+import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Serializes config for integration tests against Vespa deployments.
+ *
+ * @author jonmv
+ */
+public class TestConfigSerializer {
+
+ private final SystemName system;
+
+ public TestConfigSerializer(SystemName system) {
+ this.system = system;
+ }
+
+ public Slime configSlime(ApplicationId id,
+ JobType type,
+ Map<ZoneId, Map<ClusterSpec.Id, URI>> deployments,
+ Map<ZoneId, List<String>> clusters) {
+ Slime slime = new Slime();
+ Cursor root = slime.setObject();
+
+ root.setString("application", id.serializedForm());
+ root.setString("zone", type.zone(system).value());
+ root.setString("system", system.value());
+
+ Cursor endpointsObject = root.setObject("endpoints"); // TODO jvenstad: remove.
+ deployments.forEach((zone, endpoints) -> {
+ Cursor endpointArray = endpointsObject.setArray(zone.value());
+ for (URI endpoint : endpoints.values())
+ endpointArray.addString(endpoint.toString());
+ });
+
+ Cursor zoneEndpointsObject = root.setObject("zoneEndpoints");
+ deployments.forEach((zone, endpoints) -> {
+ Cursor clusterEndpointsObject = zoneEndpointsObject.setObject(zone.value());
+ endpoints.forEach((cluster, endpoint) -> {
+ clusterEndpointsObject.setString(cluster.value(), endpoint.toString());
+ });
+ });
+
+ if ( ! clusters.isEmpty()) {
+ Cursor clustersObject = root.setObject("clusters");
+ clusters.forEach((zone, clusterList) -> {
+ Cursor clusterArray = clustersObject.setArray(zone.value());
+ for (String cluster : clusterList)
+ clusterArray.addString(cluster);
+ });
+ }
+
+ return slime;
+ }
+
+ /** Returns the config for the tests to run for the given job. */
+ public byte[] configJson(ApplicationId id,
+ JobType type,
+ Map<ZoneId, Map<ClusterSpec.Id, URI>> deployments,
+ Map<ZoneId, List<String>> clusters) {
+ try {
+ return SlimeUtils.toJsonBytes(configSlime(id, type, deployments, clusters));
+ }
+ catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/CreateRecord.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/CreateRecord.java
index b17ca52d835..3cd3d969731 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/CreateRecord.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/CreateRecord.java
@@ -43,11 +43,6 @@ public class CreateRecord implements NameServiceRequest {
}
@Override
- public List<Record> affectedRecords() {
- return List.of(record);
- }
-
- @Override
public String toString() {
return "create record " + record;
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/CreateRecords.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/CreateRecords.java
index ec943676962..9dd02735638 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/CreateRecords.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/CreateRecords.java
@@ -52,11 +52,6 @@ public class CreateRecords implements NameServiceRequest {
}
@Override
- public List<Record> affectedRecords() {
- return records();
- }
-
- @Override
public String toString() {
return "create records " + records();
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/NameServiceForwarder.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/NameServiceForwarder.java
index f4bea8b1083..4e461534cc0 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/NameServiceForwarder.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/NameServiceForwarder.java
@@ -39,24 +39,24 @@ public class NameServiceForwarder {
}
/** Create or update a CNAME record with given name and data */
- public Record createCname(RecordName name, RecordData canonicalName, NameServiceQueue.Priority priority) {
- return forward(new CreateRecord(new Record(Record.Type.CNAME, name, canonicalName)), priority).affectedRecords().get(0);
+ public void createCname(RecordName name, RecordData canonicalName, NameServiceQueue.Priority priority) {
+ forward(new CreateRecord(new Record(Record.Type.CNAME, name, canonicalName)), priority);
}
/** Create or update an ALIAS record with given name and targets */
- public List<Record> createAlias(RecordName name, Set<AliasTarget> targets, NameServiceQueue.Priority priority) {
+ public void createAlias(RecordName name, Set<AliasTarget> targets, NameServiceQueue.Priority priority) {
var records = targets.stream().map(AliasTarget::asData)
.map(data -> new Record(Record.Type.ALIAS, name, data))
.collect(Collectors.toList());
- return forward(new CreateRecords(records), priority).affectedRecords();
+ forward(new CreateRecords(records), priority);
}
/** Create or update a TXT record with given name and data */
- public List<Record> createTxt(RecordName name, List<RecordData> txtData, NameServiceQueue.Priority priority) {
+ public void createTxt(RecordName name, List<RecordData> txtData, NameServiceQueue.Priority priority) {
var records = txtData.stream()
.map(data -> new Record(Record.Type.TXT, name, data))
.collect(Collectors.toList());
- return forward(new CreateRecords(records), priority).affectedRecords();
+ forward(new CreateRecords(records), priority);
}
/** Remove all records of given type and name */
@@ -69,7 +69,7 @@ public class NameServiceForwarder {
forward(new RemoveRecords(type, data), priority);
}
- private NameServiceRequest forward(NameServiceRequest request, NameServiceQueue.Priority priority) {
+ private void forward(NameServiceRequest request, NameServiceQueue.Priority priority) {
try (Lock lock = db.lockNameServiceQueue()) {
NameServiceQueue queue = db.readNameServiceQueue();
var queued = queue.requests().size();
@@ -78,9 +78,9 @@ public class NameServiceForwarder {
"requests. This likely means that the name service is not successfully " +
"executing requests");
}
+ log.log(LogLevel.INFO, "Queueing name service request: " + request);
db.writeNameServiceQueue(queue.with(request, priority).last(maxQueuedRequests));
}
- return request;
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/NameServiceQueue.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/NameServiceQueue.java
index cc49e589cbb..4768577aa7b 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/NameServiceQueue.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/NameServiceQueue.java
@@ -72,8 +72,9 @@ public class NameServiceQueue {
if (requests.isEmpty()) return this;
var queue = new NameServiceQueue(requests);
- for (var i = 0; i < n && !queue.requests.isEmpty(); i++) {
+ for (int i = 0; i < n && !queue.requests.isEmpty(); i++) {
var request = queue.requests.peek();
+ log.log(LogLevel.INFO, "Dispatching name service request: " + request);
try {
request.dispatchTo(nameService);
queue.requests.poll();
@@ -97,11 +98,13 @@ public class NameServiceQueue {
/** Priority of a request added to this */
public enum Priority {
+
/** Default priority. Request will be delivered in FIFO order */
normal,
- /**Request is queued before others. Useful for code that needs to act on effects of a request */
+ /** Request is queued first. Useful for code that needs to act on effects of a request */
high
+
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/NameServiceRequest.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/NameServiceRequest.java
index a01719ccc88..65076694160 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/NameServiceRequest.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/NameServiceRequest.java
@@ -2,9 +2,6 @@
package com.yahoo.vespa.hosted.controller.dns;
import com.yahoo.vespa.hosted.controller.api.integration.dns.NameService;
-import com.yahoo.vespa.hosted.controller.api.integration.dns.Record;
-
-import java.util.List;
/**
* Interface for requests to a {@link NameService}.
@@ -16,7 +13,4 @@ public interface NameServiceRequest {
/** Send this to given name service */
void dispatchTo(NameService nameService);
- /** Returns the records affected by executing this */
- List<Record> affectedRecords();
-
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/RemoveRecords.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/RemoveRecords.java
index b721f66e452..ddc4d157afd 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/RemoveRecords.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/RemoveRecords.java
@@ -61,11 +61,6 @@ public class RemoveRecords implements NameServiceRequest {
}
@Override
- public List<Record> affectedRecords() {
- return List.of();
- }
-
- @Override
public String toString() {
return "remove records of type " + type + ", by " +
name.map(n -> "name " + n).orElse("") +
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/BillingMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/BillingMaintainer.java
new file mode 100644
index 00000000000..c6956293adf
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/BillingMaintainer.java
@@ -0,0 +1,39 @@
+// 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.provision.SystemName;
+import com.yahoo.vespa.hosted.controller.Controller;
+import com.yahoo.vespa.hosted.controller.api.integration.organization.Billing;
+import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
+
+import java.time.Duration;
+import java.util.EnumSet;
+
+/**
+ * @author olaa
+ */
+public class BillingMaintainer extends Maintainer {
+
+ private final Billing billing;
+
+ public BillingMaintainer(Controller controller, Duration interval, JobControl jobControl, Billing billing) {
+ super(controller, interval, jobControl, BillingMaintainer.class.getSimpleName(), EnumSet.of(SystemName.cd));
+ this.billing = billing;
+ }
+
+ @Override
+ public void maintain() {
+ controller().tenants().asList()
+ .stream()
+ .filter(tenant -> tenant instanceof CloudTenant)
+ .map(tenant -> (CloudTenant) tenant)
+ .forEach(cloudTenant -> controller().applications().asList(cloudTenant.name())
+ .stream()
+ .forEach( application -> {
+ billing.handleBilling(application.id(), cloudTenant.billingInfo().customerId());
+ })
+ );
+ }
+}
+
+
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterInfoMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterInfoMaintainer.java
index c9b07ada854..d9aaef6ef3b 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterInfoMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterInfoMaintainer.java
@@ -5,14 +5,12 @@ import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.vespa.hosted.controller.Application;
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.NodeRepository;
import com.yahoo.vespa.hosted.controller.api.integration.noderepository.NodeList;
-import com.yahoo.vespa.hosted.controller.api.integration.noderepository.NodeRepositoryClientInterface;
import com.yahoo.vespa.hosted.controller.api.integration.noderepository.NodeRepositoryNode;
-import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.application.ClusterInfo;
import com.yahoo.vespa.hosted.controller.application.Deployment;
-import java.io.IOException;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
@@ -22,8 +20,8 @@ import java.util.logging.Logger;
import java.util.stream.Collectors;
/**
- * Maintain info about hardware, hostnames and cluster specifications.
- * <p>
+ * Maintains information about hardware, hostnames and cluster specifications.
+ *
* This is used to calculate cost metrics for the application api.
*
* @author smorgrav
@@ -33,26 +31,26 @@ public class ClusterInfoMaintainer extends Maintainer {
private static final Logger log = Logger.getLogger(ClusterInfoMaintainer.class.getName());
private final Controller controller;
- private final NodeRepositoryClientInterface nodeRepositoryClient;
+ private final NodeRepository nodeRepository;
ClusterInfoMaintainer(Controller controller, Duration duration, JobControl jobControl,
- NodeRepositoryClientInterface nodeRepositoryClient) {
+ NodeRepository nodeRepository) {
super(controller, duration, jobControl);
this.controller = controller;
- this.nodeRepositoryClient = nodeRepositoryClient;
+ this.nodeRepository = nodeRepository;
}
- private static String clusterid(NodeRepositoryNode node) {
+ private static String clusterId(NodeRepositoryNode node) {
return node.getMembership().clusterid;
}
- private Map<ClusterSpec.Id, ClusterInfo> getClusterInfo(NodeList nodes, ZoneId zone) {
+ private Map<ClusterSpec.Id, ClusterInfo> getClusterInfo(NodeList nodes) {
Map<ClusterSpec.Id, ClusterInfo> infoMap = new HashMap<>();
// Group nodes by clusterid
Map<String, List<NodeRepositoryNode>> clusters = nodes.nodes().stream()
.filter(node -> node.getMembership() != null)
- .collect(Collectors.groupingBy(ClusterInfoMaintainer::clusterid));
+ .collect(Collectors.groupingBy(ClusterInfoMaintainer::clusterId));
// For each cluster - get info
for (String id : clusters.keySet()) {
@@ -65,20 +63,11 @@ public class ClusterInfoMaintainer extends Maintainer {
double cpu = 0;
double mem = 0;
double disk = 0;
- // TODO: This code was never run. Reenable when flavours are available from a FlavorRegistry or something, or remove.
- /*if (zone.nodeFlavors().isPresent()) {
- Optional<Flavor> flavorOptional = zone.nodeFlavors().get().getFlavor(node.flavor);
- if ((flavorOptional.isPresent())) {
- Flavor flavor = flavorOptional.get();
- cpu = flavor.getMinCpuCores();
- mem = flavor.getMinMainMemoryAvailableGb();
- disk = flavor.getMinMainMemoryAvailableGb();
- }
- }*/
// Add to map
List<String> hostnames = clusterNodes.stream().map(NodeRepositoryNode::getHostname).collect(Collectors.toList());
- ClusterInfo inf = new ClusterInfo(node.getFlavor(), node.getCost(), cpu, mem, disk,
+ int cost = node.getCost() == null ? 0 : node.getCost(); // Cost is not guaranteed to be defined for all flavors
+ ClusterInfo inf = new ClusterInfo(node.getFlavor(), cost, cpu, mem, disk,
ClusterSpec.Type.from(node.getMembership().clustertype), hostnames);
infoMap.put(new ClusterSpec.Id(id), inf);
}
@@ -92,16 +81,12 @@ public class ClusterInfoMaintainer extends Maintainer {
for (Deployment deployment : application.deployments().values()) {
DeploymentId deploymentId = new DeploymentId(application.id(), deployment.zone());
try {
- NodeList nodes = nodeRepositoryClient.listNodes(deploymentId.zoneId(),
- deploymentId.applicationId().tenant().value(),
- deploymentId.applicationId().application().value(),
- deploymentId.applicationId().instance().value());
- Map<ClusterSpec.Id, ClusterInfo> clusterInfo = getClusterInfo(nodes, deployment.zone());
+ NodeList nodes = nodeRepository.listNodes(deploymentId.zoneId(), deploymentId.applicationId());
+ Map<ClusterSpec.Id, ClusterInfo> clusterInfo = getClusterInfo(nodes);
controller().applications().lockIfPresent(application.id(), lockedApplication ->
controller.applications().store(lockedApplication.withClusterInfo(deployment.zone(), clusterInfo)));
- }
- catch (IOException | IllegalArgumentException e) {
- log.log(Level.WARNING, "Failing getting cluster info of for " + deploymentId, e);
+ } catch (Exception e) {
+ log.log(Level.WARNING, "Failing getting cluster information for " + deploymentId, e);
}
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterUtilizationMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterUtilizationMaintainer.java
index 7955505a2b0..7cf710623ea 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterUtilizationMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ClusterUtilizationMaintainer.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.controller.maintenance;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ClusterSpec;
+import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
@@ -13,6 +14,7 @@ import com.yahoo.vespa.hosted.controller.application.Deployment;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
+import java.util.function.Predicate;
/**
* Fetch utilization metrics and update applications with this data.
@@ -24,7 +26,7 @@ public class ClusterUtilizationMaintainer extends Maintainer {
private final Controller controller;
public ClusterUtilizationMaintainer(Controller controller, Duration duration, JobControl jobControl) {
- super(controller, duration, jobControl);
+ super(controller, duration, jobControl, null, SystemName.allOf(Predicate.not(SystemName::isPublic)));
this.controller = controller;
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainer.java
index 7dfbc135df9..0080d7c23c2 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainer.java
@@ -11,9 +11,9 @@ import com.yahoo.vespa.hosted.controller.tenant.Tenant;
import com.yahoo.yolean.Exceptions;
import java.time.Duration;
-import java.util.EnumSet;
import java.util.Objects;
import java.util.Optional;
+import java.util.function.Predicate;
import java.util.logging.Logger;
/**
@@ -28,7 +28,7 @@ public class ContactInformationMaintainer extends Maintainer {
private final ContactRetriever contactRetriever;
public ContactInformationMaintainer(Controller controller, Duration interval, JobControl jobControl, ContactRetriever contactRetriever) {
- super(controller, interval, jobControl, null, EnumSet.of(SystemName.cd, SystemName.main));
+ super(controller, interval, jobControl, null, SystemName.allOf(Predicate.not(SystemName::isPublic)));
this.contactRetriever = Objects.requireNonNull(contactRetriever, "organization must be non-null");
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java
index aec4c7a915c..116ea532a11 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java
@@ -2,17 +2,17 @@
package com.yahoo.vespa.hosted.controller.maintenance;
import com.yahoo.component.AbstractComponent;
+import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.jdisc.Metric;
import com.yahoo.vespa.hosted.controller.Controller;
-import com.yahoo.vespa.hosted.controller.api.integration.organization.ContactRetriever;
-import com.yahoo.vespa.hosted.controller.api.integration.resource.ResourceSnapshotConsumer;
-import com.yahoo.vespa.hosted.controller.authority.config.ApiAuthorityConfig;
-import com.yahoo.vespa.hosted.controller.api.integration.chef.Chef;
+import com.yahoo.vespa.hosted.controller.api.integration.configserver.NodeRepository;
import com.yahoo.vespa.hosted.controller.api.integration.dns.NameService;
-import com.yahoo.vespa.hosted.controller.api.integration.noderepository.NodeRepositoryClientInterface;
+import com.yahoo.vespa.hosted.controller.api.integration.organization.Billing;
+import com.yahoo.vespa.hosted.controller.api.integration.organization.ContactRetriever;
import com.yahoo.vespa.hosted.controller.api.integration.organization.DeploymentIssues;
import com.yahoo.vespa.hosted.controller.api.integration.organization.OwnershipIssues;
-import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.vespa.hosted.controller.api.integration.resource.ResourceSnapshotConsumer;
+import com.yahoo.vespa.hosted.controller.authority.config.ApiAuthorityConfig;
import com.yahoo.vespa.hosted.controller.maintenance.config.MaintainerConfig;
import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
import com.yahoo.vespa.hosted.controller.restapi.cost.CostReportConsumer;
@@ -46,49 +46,48 @@ public class ControllerMaintenance extends AbstractComponent {
private final ClusterUtilizationMaintainer clusterUtilizationMaintainer;
private final DeploymentMetricsMaintainer deploymentMetricsMaintainer;
private final ApplicationOwnershipConfirmer applicationOwnershipConfirmer;
- private final DnsMaintainer dnsMaintainer;
private final SystemUpgrader systemUpgrader;
private final List<OsUpgrader> osUpgraders;
private final OsVersionStatusUpdater osVersionStatusUpdater;
private final JobRunner jobRunner;
private final ContactInformationMaintainer contactInformationMaintainer;
private final CostReportMaintainer costReportMaintainer;
- private final RoutingPolicyMaintainer routingPolicyMaintainer;
private final ResourceMeterMaintainer resourceMeterMaintainer;
private final NameServiceDispatcher nameServiceDispatcher;
+ private final BillingMaintainer billingMaintainer;
@SuppressWarnings("unused") // instantiated by Dependency Injection
public ControllerMaintenance(MaintainerConfig maintainerConfig, ApiAuthorityConfig apiAuthorityConfig, Controller controller, CuratorDb curator,
- JobControl jobControl, Metric metric, Chef chefClient,
+ JobControl jobControl, Metric metric,
DeploymentIssues deploymentIssues, OwnershipIssues ownershipIssues,
- NameService nameService, NodeRepositoryClientInterface nodeRepositoryClient,
+ NameService nameService, NodeRepository nodeRepository,
ContactRetriever contactRetriever,
CostReportConsumer reportConsumer,
ResourceSnapshotConsumer resourceSnapshotConsumer,
+ Billing billing,
SelfHostedCostConfig selfHostedCostConfig) {
Duration maintenanceInterval = Duration.ofMinutes(maintainerConfig.intervalMinutes());
this.jobControl = jobControl;
deploymentExpirer = new DeploymentExpirer(controller, maintenanceInterval, jobControl);
deploymentIssueReporter = new DeploymentIssueReporter(controller, deploymentIssues, maintenanceInterval, jobControl);
- metricsReporter = new MetricsReporter(controller, metric, chefClient, jobControl, controller.system());
+ metricsReporter = new MetricsReporter(controller, metric, jobControl);
outstandingChangeDeployer = new OutstandingChangeDeployer(controller, Duration.ofMinutes(1), jobControl);
versionStatusUpdater = new VersionStatusUpdater(controller, Duration.ofMinutes(1), jobControl);
upgrader = new Upgrader(controller, maintenanceInterval, jobControl, curator);
readyJobsTrigger = new ReadyJobsTrigger(controller, Duration.ofMinutes(1), jobControl);
- clusterInfoMaintainer = new ClusterInfoMaintainer(controller, Duration.ofHours(2), jobControl, nodeRepositoryClient);
+ clusterInfoMaintainer = new ClusterInfoMaintainer(controller, Duration.ofHours(2), jobControl, nodeRepository);
clusterUtilizationMaintainer = new ClusterUtilizationMaintainer(controller, Duration.ofHours(2), jobControl);
deploymentMetricsMaintainer = new DeploymentMetricsMaintainer(controller, Duration.ofMinutes(5), jobControl);
applicationOwnershipConfirmer = new ApplicationOwnershipConfirmer(controller, Duration.ofHours(12), jobControl, ownershipIssues);
- dnsMaintainer = new DnsMaintainer(controller, Duration.ofMinutes(5), jobControl);
systemUpgrader = new SystemUpgrader(controller, Duration.ofMinutes(1), jobControl);
jobRunner = new JobRunner(controller, Duration.ofMinutes(2), jobControl);
osUpgraders = osUpgraders(controller, jobControl);
osVersionStatusUpdater = new OsVersionStatusUpdater(controller, maintenanceInterval, jobControl);
contactInformationMaintainer = new ContactInformationMaintainer(controller, Duration.ofHours(12), jobControl, contactRetriever);
- costReportMaintainer = new CostReportMaintainer(controller, Duration.ofHours(2), reportConsumer, jobControl, nodeRepositoryClient, Clock.systemUTC(), selfHostedCostConfig);
- routingPolicyMaintainer = new RoutingPolicyMaintainer(controller, Duration.ofMinutes(5), jobControl, curator);
- resourceMeterMaintainer = new ResourceMeterMaintainer(controller, Duration.ofMinutes(60), jobControl, nodeRepositoryClient, Clock.systemUTC(), metric, resourceSnapshotConsumer);
+ costReportMaintainer = new CostReportMaintainer(controller, Duration.ofHours(2), reportConsumer, jobControl, nodeRepository, Clock.systemUTC(), selfHostedCostConfig);
+ resourceMeterMaintainer = new ResourceMeterMaintainer(controller, Duration.ofMinutes(60), jobControl, nodeRepository, Clock.systemUTC(), metric, resourceSnapshotConsumer);
nameServiceDispatcher = new NameServiceDispatcher(controller, Duration.ofSeconds(10), jobControl, nameService);
+ billingMaintainer = new BillingMaintainer(controller, Duration.ofDays(3), jobControl, billing);
}
public Upgrader upgrader() { return upgrader; }
@@ -109,22 +108,21 @@ public class ControllerMaintenance extends AbstractComponent {
clusterInfoMaintainer.deconstruct();
deploymentMetricsMaintainer.deconstruct();
applicationOwnershipConfirmer.deconstruct();
- dnsMaintainer.deconstruct();
systemUpgrader.deconstruct();
osUpgraders.forEach(Maintainer::deconstruct);
osVersionStatusUpdater.deconstruct();
jobRunner.deconstruct();
contactInformationMaintainer.deconstruct();
costReportMaintainer.deconstruct();
- routingPolicyMaintainer.deconstruct();
resourceMeterMaintainer.deconstruct();
nameServiceDispatcher.deconstruct();
+ billingMaintainer.deconstruct();
}
/** Create one OS upgrader per cloud found in the zone registry of controller */
private static List<OsUpgrader> osUpgraders(Controller controller, JobControl jobControl) {
- return controller.zoneRegistry().zones().controllerUpgraded().ids().stream()
- .map(ZoneId::cloud)
+ return controller.zoneRegistry().zones().controllerUpgraded().zones().stream()
+ .map(ZoneApi::getCloudName)
.distinct()
.sorted()
.map(cloud -> new OsUpgrader(controller, Duration.ofMinutes(1), jobControl, cloud))
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CostReportMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CostReportMaintainer.java
index 2b26e93aeb8..f3dac2be22e 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CostReportMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CostReportMaintainer.java
@@ -5,14 +5,15 @@ import com.google.inject.Inject;
import com.yahoo.config.provision.CloudName;
import com.yahoo.config.provision.SystemName;
import com.yahoo.vespa.hosted.controller.Controller;
-import com.yahoo.vespa.hosted.controller.api.integration.noderepository.NodeRepositoryClientInterface;
+import com.yahoo.vespa.hosted.controller.api.integration.configserver.NodeRepository;
import com.yahoo.vespa.hosted.controller.restapi.cost.CostCalculator;
import com.yahoo.vespa.hosted.controller.restapi.cost.CostReportConsumer;
import com.yahoo.vespa.hosted.controller.restapi.cost.config.SelfHostedCostConfig;
import java.time.Clock;
import java.time.Duration;
-import java.util.*;
+import java.util.EnumSet;
+import java.util.Objects;
import java.util.logging.Logger;
/**
@@ -26,7 +27,7 @@ public class CostReportMaintainer extends Maintainer {
private static final Logger log = Logger.getLogger(CostReportMaintainer.class.getName());
private final CostReportConsumer consumer;
- private final NodeRepositoryClientInterface nodeRepository;
+ private final NodeRepository nodeRepository;
private final Clock clock;
private final SelfHostedCostConfig selfHostedCostConfig;
@@ -35,7 +36,7 @@ public class CostReportMaintainer extends Maintainer {
public CostReportMaintainer(Controller controller, Duration interval,
CostReportConsumer consumer,
JobControl jobControl,
- NodeRepositoryClientInterface nodeRepository,
+ NodeRepository nodeRepository,
Clock clock,
SelfHostedCostConfig selfHostedCostConfig) {
super(controller, interval, jobControl, "CostReportMaintainer", EnumSet.of(SystemName.main));
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java
index 3e07d0879ea..1d9f76cddcd 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.controller.maintenance;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.HostName;
+import com.yahoo.config.provision.SystemName;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.ApplicationController;
import com.yahoo.vespa.hosted.controller.Controller;
@@ -22,6 +23,7 @@ import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -41,7 +43,8 @@ public class DeploymentMetricsMaintainer extends Maintainer {
private final ApplicationController applications;
public DeploymentMetricsMaintainer(Controller controller, Duration duration, JobControl jobControl) {
- super(controller, duration, jobControl);
+ super(controller, duration, jobControl, DeploymentMetricsMaintainer.class.getSimpleName(),
+ SystemName.allOf(Predicate.not(SystemName::isPublic)));
this.applications = controller.applications();
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DnsMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DnsMaintainer.java
deleted file mode 100644
index 7e0032f03c5..00000000000
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DnsMaintainer.java
+++ /dev/null
@@ -1,69 +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.hosted.controller.maintenance;
-
-import com.yahoo.vespa.hosted.controller.Controller;
-import com.yahoo.vespa.hosted.controller.api.integration.dns.NameService;
-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.dns.NameServiceQueue.Priority;
-import com.yahoo.vespa.hosted.controller.rotation.Rotation;
-import com.yahoo.vespa.hosted.controller.rotation.RotationId;
-import com.yahoo.vespa.hosted.controller.rotation.RotationLock;
-import com.yahoo.vespa.hosted.controller.rotation.RotationRepository;
-
-import java.time.Duration;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/**
- * Performs DNS maintenance tasks such as removing DNS aliases for unassigned rotations.
- *
- * @author mpolden
- */
-public class DnsMaintainer extends Maintainer {
-
- private final AtomicInteger rotationIndex = new AtomicInteger(0);
-
- public DnsMaintainer(Controller controller, Duration interval, JobControl jobControl) {
- super(controller, interval, jobControl);
- }
-
- private RotationRepository rotationRepository() {
- return controller().applications().rotationRepository();
- }
-
- @Override
- protected void maintain() {
- try (RotationLock lock = rotationRepository().lock()) {
- Map<RotationId, Rotation> unassignedRotations = rotationRepository().availableRotations(lock);
- rotationToCheckOf(unassignedRotations.values()).ifPresent(this::removeCname);
- }
- }
-
- /** Remove CNAME(s) for unassigned rotation */
- private void removeCname(Rotation rotation) {
- // When looking up CNAME by data, the data must be a FQDN
- controller().nameServiceForwarder().removeRecords(Record.Type.CNAME, RecordData.fqdn(rotation.name()), Priority.normal);
- }
-
- /**
- * Returns the rotation that should be checked in this run. We check only one rotation per run to avoid running into
- * rate limits that may be imposed by the {@link NameService} implementation.
- */
- private Optional<Rotation> rotationToCheckOf(Collection<Rotation> rotations) {
- if (rotations.isEmpty()) return Optional.empty();
- List<Rotation> rotationList = new ArrayList<>(rotations);
- int index = rotationIndex.getAndUpdate((i) -> {
- if (i < rotationList.size() - 1) {
- return ++i;
- }
- return 0;
- });
- return Optional.of(rotationList.get(index));
- }
-
-}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/InfrastructureUpgrader.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/InfrastructureUpgrader.java
index 0333711ae39..b8bb9a7ef79 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/InfrastructureUpgrader.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/InfrastructureUpgrader.java
@@ -3,10 +3,10 @@ package com.yahoo.vespa.hosted.controller.maintenance;
import com.yahoo.component.Version;
import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.zone.UpgradePolicy;
+import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.Node;
-import com.yahoo.config.provision.zone.UpgradePolicy;
-import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.application.SystemApplication;
import com.yahoo.yolean.Exceptions;
@@ -42,9 +42,9 @@ public abstract class InfrastructureUpgrader extends Maintainer {
/** Deploy a list of system applications until they converge on the given version */
private void upgradeAll(Version target, List<SystemApplication> applications) {
- for (List<ZoneId> zones : upgradePolicy.asList()) {
+ for (List<ZoneApi> zones : upgradePolicy.asList()) {
boolean converged = true;
- for (ZoneId zone : zones) {
+ for (ZoneApi zone : zones) {
try {
converged &= upgradeAll(target, applications, zone);
} catch (UnreachableNodeRepositoryException e) {
@@ -62,39 +62,44 @@ public abstract class InfrastructureUpgrader extends Maintainer {
}
/** Returns whether all applications have converged to the target version in zone */
- private boolean upgradeAll(Version target, List<SystemApplication> applications, ZoneId zone) {
+ private boolean upgradeAll(Version target, List<SystemApplication> applications, ZoneApi zone) {
boolean converged = true;
for (SystemApplication application : applications) {
if (convergedOn(target, application.dependencies(), zone)) {
- upgrade(target, application, zone);
+ boolean currentAppConverged = convergedOn(target, application, zone);
+ // In dynamically provisioned zones there may be no tenant hosts at the time of upgrade, so we
+ // should always set the target version.
+ if (application == SystemApplication.tenantHost || !currentAppConverged) {
+ upgrade(target, application, zone);
+ }
+ converged &= currentAppConverged;
}
- converged &= convergedOn(target, application, zone);
}
return converged;
}
- private boolean convergedOn(Version target, List<SystemApplication> applications, ZoneId zone) {
+ private boolean convergedOn(Version target, List<SystemApplication> applications, ZoneApi zone) {
return applications.stream().allMatch(application -> convergedOn(target, application, zone));
}
/** Upgrade component to target version. Implementation should be idempotent */
- protected abstract void upgrade(Version target, SystemApplication application, ZoneId zone);
+ protected abstract void upgrade(Version target, SystemApplication application, ZoneApi zone);
/** Returns whether application has converged to target version in zone */
- protected abstract boolean convergedOn(Version target, SystemApplication application, ZoneId zone);
+ protected abstract boolean convergedOn(Version target, SystemApplication application, ZoneApi zone);
/** Returns the target version for the component upgraded by this, if any */
protected abstract Optional<Version> targetVersion();
/** Returns whether the upgrader should require given node to upgrade */
- protected abstract boolean requireUpgradeOf(Node node, SystemApplication application, ZoneId zone);
+ protected abstract boolean requireUpgradeOf(Node node, SystemApplication application, ZoneApi zone);
/** Find the minimum value of a version field in a zone */
- protected final Optional<Version> minVersion(ZoneId zone, SystemApplication application, Function<Node, Version> versionField) {
+ protected final Optional<Version> minVersion(ZoneApi zone, SystemApplication application, Function<Node, Version> versionField) {
try {
return controller().configServer()
.nodeRepository()
- .list(zone, application.id())
+ .list(zone.getId(), application.id())
.stream()
.filter(node -> requireUpgradeOf(node, application, zone))
.map(versionField)
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunner.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunner.java
index 47e17b34d2a..f13c31de5d7 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunner.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunner.java
@@ -34,7 +34,6 @@ public class JobRunner extends Maintainer {
private final ExecutorService executors;
private final StepRunner runner;
- @Inject
public JobRunner(Controller controller, Duration duration, JobControl jobControl) {
this(controller, duration, jobControl, Executors.newFixedThreadPool(32), new InternalStepRunner(controller));
}
@@ -43,6 +42,7 @@ public class JobRunner extends Maintainer {
public JobRunner(Controller controller, Duration duration, JobControl jobControl, ExecutorService executors, StepRunner runner) {
super(controller, duration, jobControl);
this.jobs = controller.jobController();
+ this.jobs.setRunner(this::advance);
this.executors = executors;
this.runner = runner;
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporter.java
index 449b8c51acd..c7b76696d84 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporter.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporter.java
@@ -3,14 +3,9 @@ package com.yahoo.vespa.hosted.controller.maintenance;
import com.google.common.collect.ImmutableMap;
import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.config.provision.SystemName;
import com.yahoo.jdisc.Metric;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
-import com.yahoo.vespa.hosted.controller.api.integration.chef.AttributeMapping;
-import com.yahoo.vespa.hosted.controller.api.integration.chef.Chef;
-import com.yahoo.vespa.hosted.controller.api.integration.chef.rest.PartialNode;
-import com.yahoo.vespa.hosted.controller.api.integration.chef.rest.PartialNodeResult;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.application.ApplicationList;
import com.yahoo.vespa.hosted.controller.application.Deployment;
@@ -24,10 +19,8 @@ import java.time.Duration;
import java.time.Instant;
import java.util.Collection;
import java.util.Collections;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Optional;
import java.util.stream.Collectors;
/**
@@ -36,7 +29,6 @@ import java.util.stream.Collectors;
*/
public class MetricsReporter extends Maintainer {
- public static final String CONVERGENCE_METRIC = "seconds.since.last.chef.convergence";
public static final String DEPLOYMENT_FAIL_METRIC = "deployment.failurePercentage";
public static final String DEPLOYMENT_AVERAGE_DURATION = "deployment.averageDuration";
public static final String DEPLOYMENT_FAILING_UPGRADES = "deployment.failingUpgrades";
@@ -46,27 +38,16 @@ public class MetricsReporter extends Maintainer {
public static final String NAME_SERVICE_REQUESTS_QUEUED = "dns.queuedRequests";
private final Metric metric;
- private final Chef chefClient;
private final Clock clock;
- private final SystemName system;
- public MetricsReporter(Controller controller, Metric metric, Chef chefClient, JobControl jobControl,
- SystemName system) {
- this(controller, metric, chefClient, Clock.systemUTC(), jobControl, system);
- }
-
- public MetricsReporter(Controller controller, Metric metric, Chef chefClient, Clock clock,
- JobControl jobControl, SystemName system) {
+ public MetricsReporter(Controller controller, Metric metric, JobControl jobControl) {
super(controller, Duration.ofMinutes(1), jobControl); // use fixed rate for metrics
this.metric = metric;
- this.chefClient = chefClient;
- this.clock = clock;
- this.system = system;
+ this.clock = controller.clock();
}
@Override
public void maintain() {
- reportChefMetrics();
reportDeploymentMetrics();
reportRemainingRotations();
reportQueuedNameServiceRequests();
@@ -79,49 +60,6 @@ public class MetricsReporter extends Maintainer {
}
}
- private void reportChefMetrics() {
- String query = "chef_environment:hosted*";
- if (system == SystemName.cd) {
- query += " AND hosted_system:" + system;
- }
- PartialNodeResult nodeResult = chefClient.partialSearchNodes(query,
- List.of(
- AttributeMapping.simpleMapping("fqdn"),
- AttributeMapping.simpleMapping("ohai_time"),
- AttributeMapping.deepMapping("tenant", List.of("hosted", "owner", "tenant")),
- AttributeMapping.deepMapping("application", List.of("hosted", "owner", "application")),
- AttributeMapping.deepMapping("instance", List.of("hosted", "owner", "instance")),
- AttributeMapping.deepMapping("environment", List.of("hosted", "environment")),
- AttributeMapping.deepMapping("region", List.of("hosted", "region")),
- AttributeMapping.deepMapping("system", List.of("hosted", "system"))
- ));
-
- // The above search will return a correct list if the system is CD. However for main, it will
- // return all nodes, since system==nil for main
- keepNodesWithSystem(nodeResult, system);
-
- Instant instant = clock.instant();
- for (PartialNode node : nodeResult.rows) {
- String hostname = node.getFqdn();
- long secondsSinceConverge = Duration.between(Instant.ofEpochSecond(node.getOhaiTime().longValue()), instant).getSeconds();
- Map<String, String> dimensions = new HashMap<>();
- dimensions.put("host", hostname);
- dimensions.put("system", node.getValue("system").orElse("main"));
- Optional<String> environment = node.getValue("environment");
- Optional<String> region = node.getValue("region");
-
- if (environment.isPresent() && region.isPresent()) {
- dimensions.put("zone", String.format("%s.%s", environment.get(), region.get()));
- }
-
- node.getValue("tenant").ifPresent(tenant -> dimensions.put("tenantName", tenant));
- Optional<String> application = node.getValue("application");
- application.ifPresent(app -> dimensions.put("app", String.format("%s.%s", app, node.getValue("instance").orElse("default"))));
- Metric.Context context = metric.createContext(dimensions);
- metric.set(CONVERGENCE_METRIC, secondsSinceConverge, context);
- }
- }
-
private void reportDeploymentMetrics() {
ApplicationList applications = ApplicationList.from(controller().applications().asList())
.hasProductionDeployment();
@@ -210,10 +148,6 @@ public class MetricsReporter extends Maintainer {
.max(Integer::compareTo)
.orElse(0);
}
-
- private static void keepNodesWithSystem(PartialNodeResult nodeResult, SystemName system) {
- nodeResult.rows.removeIf(node -> !system.name().equals(node.getValue("system").orElse("main")));
- }
private static Map<String, String> dimensions(ApplicationId application) {
return ImmutableMap.of(
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/NameServiceDispatcher.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/NameServiceDispatcher.java
index 8878ac9bd5b..d4dc068c71f 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/NameServiceDispatcher.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/NameServiceDispatcher.java
@@ -41,6 +41,7 @@ public class NameServiceDispatcher extends Maintainer {
try (Lock lock = db.lockNameServiceQueue()) {
NameServiceQueue queue = db.readNameServiceQueue();
NameServiceQueue remaining = queue.dispatchTo(nameService, requestCount);
+ if (queue == remaining) return; // Queue unchanged
db.writeNameServiceQueue(remaining);
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgrader.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgrader.java
index 3b521657f15..8845f4c652f 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgrader.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgrader.java
@@ -4,9 +4,9 @@ package com.yahoo.vespa.hosted.controller.maintenance;
import com.google.common.collect.ImmutableSet;
import com.yahoo.component.Version;
import com.yahoo.config.provision.CloudName;
+import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.Node;
-import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.application.SystemApplication;
import com.yahoo.vespa.hosted.controller.versions.OsVersion;
@@ -38,23 +38,22 @@ public class OsUpgrader extends InfrastructureUpgrader {
}
@Override
- protected void upgrade(Version target, SystemApplication application, ZoneId zone) {
- if (wantedVersion(zone, application, target).equals(target)) {
+ protected void upgrade(Version target, SystemApplication application, ZoneApi zone) {
+ if (!application.isEligibleForOsUpgrades() || wantedVersion(zone, application, target).equals(target)) {
return;
}
- log.info(String.format("Upgrading OS of %s to version %s in %s", application.id(), target, zone));
- application.nodeTypesWithUpgradableOs().forEach(nodeType -> controller().configServer().nodeRepository()
- .upgradeOs(zone, nodeType, target));
+ log.info(String.format("Upgrading OS of %s to version %s in %s in cloud %s", application.id(), target, zone.getId(), zone.getCloudName()));
+ controller().configServer().nodeRepository().upgradeOs(zone.getId(), application.nodeType(), target);
}
@Override
- protected boolean convergedOn(Version target, SystemApplication application, ZoneId zone) {
+ protected boolean convergedOn(Version target, SystemApplication application, ZoneApi zone) {
return currentVersion(zone, application, target).equals(target);
}
@Override
- protected boolean requireUpgradeOf(Node node, SystemApplication application, ZoneId zone) {
- return cloud.equals(zone.cloud()) && eligibleForUpgrade(node, application);
+ protected boolean requireUpgradeOf(Node node, SystemApplication application, ZoneApi zone) {
+ return cloud.equals(zone.getCloudName()) && eligibleForUpgrade(node, application);
}
@Override
@@ -66,18 +65,18 @@ public class OsUpgrader extends InfrastructureUpgrader {
.map(OsVersion::version);
}
- private Version currentVersion(ZoneId zone, SystemApplication application, Version defaultVersion) {
+ private Version currentVersion(ZoneApi zone, SystemApplication application, Version defaultVersion) {
return minVersion(zone, application, Node::currentOsVersion).orElse(defaultVersion);
}
- private Version wantedVersion(ZoneId zone, SystemApplication application, Version defaultVersion) {
+ private Version wantedVersion(ZoneApi zone, SystemApplication application, Version defaultVersion) {
return minVersion(zone, application, Node::wantedOsVersion).orElse(defaultVersion);
}
/** Returns whether node in application should be upgraded by this */
public static boolean eligibleForUpgrade(Node node, SystemApplication application) {
return upgradableNodeStates.contains(node.state()) &&
- application.nodeTypesWithUpgradableOs().contains(node.type());
+ application.isEligibleForOsUpgrades();
}
private static String name(CloudName cloud) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java
index 5ed7a14836e..5e2ba48da3f 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java
@@ -1,25 +1,28 @@
// 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.provision.ApplicationId;
+import com.yahoo.config.provision.CloudName;
import com.yahoo.config.provision.SystemName;
import com.yahoo.jdisc.Metric;
import com.yahoo.vespa.hosted.controller.Controller;
-import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.vespa.hosted.controller.api.integration.configserver.NodeRepository;
import com.yahoo.vespa.hosted.controller.api.integration.noderepository.NodeOwner;
-import com.yahoo.vespa.hosted.controller.api.integration.noderepository.NodeRepositoryClientInterface;
import com.yahoo.vespa.hosted.controller.api.integration.noderepository.NodeRepositoryNode;
+import com.yahoo.vespa.hosted.controller.api.integration.noderepository.NodeState;
+import com.yahoo.vespa.hosted.controller.api.integration.resource.ResourceAllocation;
import com.yahoo.vespa.hosted.controller.api.integration.resource.ResourceSnapshot;
import com.yahoo.vespa.hosted.controller.api.integration.resource.ResourceSnapshotConsumer;
-import com.yahoo.vespa.hosted.controller.api.integration.resource.ResourceAllocation;
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
-import java.util.*;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
-import static com.yahoo.yolean.Exceptions.uncheck;
-
/**
* Creates a ResourceSnapshot per application, which is then passed on to a ResourceSnapshotConsumer
* TODO: Write JSON blob of node repo somewhere
@@ -30,7 +33,7 @@ public class ResourceMeterMaintainer extends Maintainer {
private final Clock clock;
private final Metric metric;
- private final NodeRepositoryClientInterface nodeRepository;
+ private final NodeRepository nodeRepository;
private final ResourceSnapshotConsumer resourceSnapshotConsumer;
private static final String metering_last_reported = "metering_last_reported";
@@ -40,11 +43,11 @@ public class ResourceMeterMaintainer extends Maintainer {
public ResourceMeterMaintainer(Controller controller,
Duration interval,
JobControl jobControl,
- NodeRepositoryClientInterface nodeRepository,
+ NodeRepository nodeRepository,
Clock clock,
Metric metric,
ResourceSnapshotConsumer resourceSnapshotConsumer) {
- super(controller, interval, jobControl, ResourceMeterMaintainer.class.getSimpleName(), Set.of(SystemName.cd));
+ super(controller, interval, jobControl, null, SystemName.allOf(Predicate.not(SystemName::isPublic)));
this.clock = clock;
this.nodeRepository = nodeRepository;
this.metric = metric;
@@ -77,24 +80,19 @@ public class ResourceMeterMaintainer extends Maintainer {
private List<NodeRepositoryNode> getNodes() {
return controller().zoneRegistry().zones()
- .reachable().ids().stream()
- .flatMap(zoneId -> uncheck(() -> nodeRepository.listNodes(zoneId, true).nodes().stream()))
+ .ofCloud(CloudName.from("aws"))
+ .reachable().zones().stream()
+ .flatMap(zone -> nodeRepository.listNodes(zone.getId()).nodes().stream())
.filter(node -> node.getOwner() != null && !node.getOwner().getTenant().equals("hosted-vespa"))
+ .filter(node -> node.getState() == NodeState.active)
.collect(Collectors.toList());
}
private Map<ApplicationId, ResourceAllocation> getResourceAllocationByApplication(List<NodeRepositoryNode> nodes) {
- Map<ApplicationId, List<NodeRepositoryNode>> applicationNodes = new HashMap<>();
-
- nodes.stream().forEach(node -> applicationNodes.computeIfAbsent(applicationIdFromNodeOwner(node.getOwner()), n -> new ArrayList<>()).add(node));
-
- return applicationNodes.entrySet().stream()
- .collect(
- Collectors.toMap(
- entry -> entry.getKey(),
- entry -> ResourceAllocation.from(entry.getValue())
- )
- );
+ return nodes.stream()
+ .collect(Collectors.groupingBy(
+ node -> applicationIdFromNodeOwner(node.getOwner()),
+ Collectors.collectingAndThen(Collectors.toList(), ResourceAllocation::from)));
}
private ApplicationId applicationIdFromNodeOwner(NodeOwner owner) {
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
new file mode 100644
index 00000000000..4a98cb49227
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPolicies.java
@@ -0,0 +1,198 @@
+// 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.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.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.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 application */
+ 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, ZoneId zone) {
+ // TODO: Use this to decide how apply routing policies for shared routing layer
+ if (!controller.zoneRegistry().zones().directlyRouted().ids().contains(zone)) return;
+ var lbs = new LoadBalancers(application, zone, controller.applications().configServer()
+ .getLoadBalancers(application, zone));
+ 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(LoadBalancers 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().rotation(),
+ 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 */
+ private void storePoliciesOf(LoadBalancers loadBalancers, @SuppressWarnings("unused") Lock lock) {
+ Set<RoutingPolicy> policies = new LinkedHashSet<>(get(loadBalancers.application));
+ for (LoadBalancer loadBalancer : loadBalancers.list) {
+ RoutingPolicy policy = createPolicy(loadBalancers.application, loadBalancers.zone, loadBalancer);
+ if (!policies.add(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) {
+ RoutingPolicy routingPolicy = new RoutingPolicy(application, loadBalancer.cluster(), zone,
+ loadBalancer.hostname(), loadBalancer.dnsZone(),
+ loadBalancer.rotations());
+ RecordName name = RecordName.from(routingPolicy.endpointIn(controller.system()).dnsName());
+ RecordData 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(LoadBalancers 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(LoadBalancers loadBalancers, @SuppressWarnings("unused") Lock lock) {
+ var zonePolicies = get(loadBalancers.application, loadBalancers.zone);
+ var removalCandidates = routingTableFrom(zonePolicies).keySet();
+ var activeRoutingIds = routingIdsFrom(loadBalancers.list);
+ removalCandidates.removeAll(activeRoutingIds);
+ for (var id : removalCandidates) {
+ Endpoint endpoint = RoutingPolicy.endpointOf(id.application(), id.rotation(), 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(List<LoadBalancer> loadBalancers) {
+ Set<RoutingId> routingIds = new LinkedHashSet<>();
+ for (var loadBalancer : loadBalancers) {
+ for (var rotation : loadBalancer.rotations()) {
+ routingIds.add(new RoutingId(loadBalancer.application(), rotation));
+ }
+ }
+ 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.rotations()) {
+ var id = new RoutingId(policy.owner(), rotation);
+ routingTable.putIfAbsent(id, new ArrayList<>());
+ routingTable.get(id).add(policy);
+ }
+ }
+ return routingTable;
+ }
+
+ /** Load balancers for a particular deployment */
+ private static class LoadBalancers {
+
+ private final ApplicationId application;
+ private final ZoneId zone;
+ private final List<LoadBalancer> list;
+
+ private LoadBalancers(ApplicationId application, ZoneId zone, List<LoadBalancer> list) {
+ this.application = application;
+ this.zone = zone;
+ this.list = list;
+ }
+
+ }
+
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPolicyMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPolicyMaintainer.java
deleted file mode 100644
index 0ddc24147ee..00000000000
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPolicyMaintainer.java
+++ /dev/null
@@ -1,224 +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.provision.ApplicationId;
-import com.yahoo.config.provision.RotationName;
-import com.yahoo.config.provision.zone.ZoneId;
-import com.yahoo.log.LogLevel;
-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.RoutingId;
-import com.yahoo.vespa.hosted.controller.application.RoutingPolicy;
-import com.yahoo.vespa.hosted.controller.dns.NameServiceForwarder;
-import com.yahoo.vespa.hosted.controller.dns.NameServiceQueue.Priority;
-import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
-
-import java.time.Duration;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.logging.Logger;
-import java.util.stream.Collectors;
-
-/**
- * Maintains routing policies and their DNS records for all exclusive load balancers in this system.
- *
- * @author mortent
- * @author mpolden
- */
-public class RoutingPolicyMaintainer extends Maintainer {
-
- private static final Logger log = Logger.getLogger(RoutingPolicyMaintainer.class.getName());
-
- private final NameServiceForwarder nameServiceForwarder;
- private final CuratorDb db;
-
- public RoutingPolicyMaintainer(Controller controller,
- Duration interval,
- JobControl jobControl,
- CuratorDb db) {
- super(controller, interval, jobControl);
- this.nameServiceForwarder = controller.nameServiceForwarder();
- this.db = db;
- // Update serialized format
- try (Lock lock = db.lockRoutingPolicies()) {
- for (var policy : db.readRoutingPolicies().entrySet()) {
- db.writeRoutingPolicies(policy.getKey(), policy.getValue());
- }
- }
- }
-
- @Override
- protected void maintain() {
- Map<DeploymentId, List<LoadBalancer>> loadBalancers = findLoadBalancers();
- removeObsoleteEndpointsFromDns(loadBalancers);
- storePolicies(loadBalancers);
- removeObsoletePolicies(loadBalancers);
- registerEndpointsInDns();
- }
-
- /** Find all exclusive load balancers in this system, grouped by deployment */
- private Map<DeploymentId, List<LoadBalancer>> findLoadBalancers() {
- Map<DeploymentId, List<LoadBalancer>> result = new LinkedHashMap<>();
- for (ZoneId zone : controller().zoneRegistry().zones().controllerUpgraded().ids()) {
- List<LoadBalancer> loadBalancers = controller().applications().configServer().getLoadBalancers(zone);
- for (LoadBalancer loadBalancer : loadBalancers) {
- DeploymentId deployment = new DeploymentId(loadBalancer.application(), zone);
- result.compute(deployment, (k, existing) -> {
- if (existing == null) {
- existing = new ArrayList<>();
- }
- existing.add(loadBalancer);
- return existing;
- });
- }
- }
- return Collections.unmodifiableMap(result);
- }
-
- /** Create global endpoints for all current routing policies */
- private void registerEndpointsInDns() {
- try (Lock lock = db.lockRoutingPolicies()) {
- Map<RoutingId, List<RoutingPolicy>> routingTable = routingTableFrom(db.readRoutingPolicies());
-
- // Create DNS record for each routing ID
- for (Map.Entry<RoutingId, List<RoutingPolicy>> route : routingTable.entrySet()) {
- Endpoint endpoint = RoutingPolicy.endpointOf(route.getKey().application(), route.getKey().rotation(),
- controller().system());
- Set<AliasTarget> targets = route.getValue()
- .stream()
- .filter(policy -> policy.dnsZone().isPresent())
- .map(policy -> new AliasTarget(policy.canonicalName(),
- policy.dnsZone().get(),
- policy.zone()))
- .collect(Collectors.toSet());
- try {
- nameServiceForwarder.createAlias(RecordName.from(endpoint.dnsName()), targets, Priority.normal);
- } catch (Exception e) {
- log.log(LogLevel.WARNING, "Failed to create or update DNS record for global rotation " +
- endpoint.dnsName() + ". Retrying in " + maintenanceInterval(), e);
- }
- }
- }
- }
-
- /** Store routing policies for all load balancers */
- private void storePolicies(Map<DeploymentId, List<LoadBalancer>> loadBalancers) {
- for (Map.Entry<DeploymentId, List<LoadBalancer>> entry : loadBalancers.entrySet()) {
- ApplicationId application = entry.getKey().applicationId();
- ZoneId zone = entry.getKey().zoneId();
- try (Lock lock = db.lockRoutingPolicies()) {
- Set<RoutingPolicy> policies = new LinkedHashSet<>(db.readRoutingPolicies(application));
- for (LoadBalancer loadBalancer : entry.getValue()) {
- try {
- RoutingPolicy policy = storePolicy(application, zone, loadBalancer);
- if (!policies.add(policy)) {
- policies.remove(policy);
- policies.add(policy);
- }
- } catch (Exception e) {
- log.log(LogLevel.WARNING, "Failed to create or update DNS record for load balancer " +
- loadBalancer.hostname() + ". Retrying in " + maintenanceInterval(),
- e);
- }
- }
- db.writeRoutingPolicies(application, policies);
- }
- }
- }
-
- /** Store policy for given load balancer and request a CNAME for it */
- private RoutingPolicy storePolicy(ApplicationId application, ZoneId zone, LoadBalancer loadBalancer) {
- RoutingPolicy routingPolicy = new RoutingPolicy(application, loadBalancer.cluster(), zone,
- loadBalancer.hostname(), loadBalancer.dnsZone(),
- loadBalancer.rotations());
- RecordName name = RecordName.from(routingPolicy.endpointIn(controller().system()).dnsName());
- RecordData data = RecordData.fqdn(loadBalancer.hostname().value());
- nameServiceForwarder.createCname(name, data, Priority.normal);
- return routingPolicy;
- }
-
- /** Remove obsolete policies and their CNAME records */
- private void removeObsoletePolicies(Map<DeploymentId, List<LoadBalancer>> loadBalancers) {
- try (Lock lock = db.lockRoutingPolicies()) {
- var allPolicies = new HashMap<>(db.readRoutingPolicies());
- var removalCandidates = allPolicies.values().stream()
- .flatMap(Collection::stream)
- .collect(Collectors.toSet());
- var activeLoadBalancers = loadBalancers.values().stream()
- .flatMap(Collection::stream)
- .map(LoadBalancer::hostname)
- .collect(Collectors.toSet());
- // Keep active load balancers by removing them from candidates
- removalCandidates.removeIf(policy -> activeLoadBalancers.contains(policy.canonicalName()));
- for (var policy : removalCandidates) {
- var dnsName = policy.endpointIn(controller().system()).dnsName();
- nameServiceForwarder.removeRecords(Record.Type.CNAME, RecordName.from(dnsName), Priority.normal);
- // Remove stale policy from curator
- var updatedPolicies = new LinkedHashSet<>(allPolicies.getOrDefault(policy.owner(), Set.of()));
- updatedPolicies.remove(policy);
- allPolicies.put(policy.owner(), updatedPolicies);
- db.writeRoutingPolicies(policy.owner(), updatedPolicies);
- }
- }
- }
-
- /** Remove DNS for global endpoints not referenced by given load balancers */
- private void removeObsoleteEndpointsFromDns(Map<DeploymentId, List<LoadBalancer>> loadBalancers) {
- try (Lock lock = db.lockRoutingPolicies()) {
- Set<RoutingId> removalCandidates = routingTableFrom(db.readRoutingPolicies()).keySet();
- Set<RoutingId> activeRoutingIds = routingIdsFrom(loadBalancers);
- removalCandidates.removeAll(activeRoutingIds);
- for (RoutingId id : removalCandidates) {
- Endpoint endpoint = RoutingPolicy.endpointOf(id.application(), id.rotation(), controller().system());
- nameServiceForwarder.removeRecords(Record.Type.ALIAS, RecordName.from(endpoint.dnsName()), Priority.normal);
- }
- }
- }
-
- /** Compute routing IDs from given load balancers */
- private static Set<RoutingId> routingIdsFrom(Map<DeploymentId, List<LoadBalancer>> loadBalancers) {
- Set<RoutingId> routingIds = new LinkedHashSet<>();
- for (List<LoadBalancer> values : loadBalancers.values()) {
- for (LoadBalancer loadBalancer : values) {
- for (RotationName rotation : loadBalancer.rotations()) {
- routingIds.add(new RoutingId(loadBalancer.application(), rotation));
- }
- }
- }
- return Collections.unmodifiableSet(routingIds);
- }
-
- /** Compute a routing table from given policies */
- private static Map<RoutingId, List<RoutingPolicy>> routingTableFrom(Map<ApplicationId, Set<RoutingPolicy>> routingPolicies) {
- var flattenedPolicies = routingPolicies.values().stream().flatMap(Collection::stream).collect(Collectors.toSet());
- var routingTable = new LinkedHashMap<RoutingId, List<RoutingPolicy>>();
- for (var policy : flattenedPolicies) {
- for (var rotation : policy.rotations()) {
- var id = new RoutingId(policy.owner(), rotation);
- routingTable.compute(id, (k, policies) -> {
- if (policies == null) {
- policies = new ArrayList<>();
- }
- policies.add(policy);
- return policies;
- });
- }
- }
- return routingTable;
- }
-
-}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/SystemUpgrader.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/SystemUpgrader.java
index 62d401cf478..156e8d0d242 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/SystemUpgrader.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/SystemUpgrader.java
@@ -2,7 +2,7 @@
package com.yahoo.vespa.hosted.controller.maintenance;
import com.yahoo.component.Version;
-import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.Node;
import com.yahoo.vespa.hosted.controller.application.SystemApplication;
@@ -30,23 +30,26 @@ public class SystemUpgrader extends InfrastructureUpgrader {
}
@Override
- protected void upgrade(Version target, SystemApplication application, ZoneId zone) {
+ protected void upgrade(Version target, SystemApplication application, ZoneApi zone) {
if (minVersion(zone, application, Node::wantedVersion).map(target::isAfter)
.orElse(true)) {
- log.info(String.format("Deploying %s version %s in %s", application.id(), target, zone));
- controller().applications().deploy(application, zone, target);
+ log.info(String.format("Deploying %s version %s in %s", application.id(), target, zone.getId()));
+ controller().applications().deploy(application, zone.getId(), target);
}
}
@Override
- protected boolean convergedOn(Version target, SystemApplication application, ZoneId zone) {
- return minVersion(zone, application, Node::currentVersion).map(target::equals)
- .orElse(true)
- && application.configConvergedIn(zone, controller());
+ protected boolean convergedOn(Version target, SystemApplication application, ZoneApi zone) {
+ Optional<Version> minVersion = minVersion(zone, application, Node::currentVersion);
+ // Skip application convergence check if there are no nodes belonging to the application in the zone
+ if (minVersion.isEmpty()) return true;
+
+ return minVersion.get().equals(target)
+ && application.configConvergedIn(zone.getId(), controller(), Optional.of(target));
}
@Override
- protected boolean requireUpgradeOf(Node node, SystemApplication application, ZoneId zone) {
+ protected boolean requireUpgradeOf(Node node, SystemApplication application, ZoneApi zone) {
return eligibleForUpgrade(node);
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java
index 5c4adb46f6b..74cea37f3a7 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java
@@ -16,12 +16,14 @@ import com.yahoo.vespa.config.SlimeUtils;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.ClusterMetrics;
import com.yahoo.vespa.hosted.controller.api.integration.MetricsService.ApplicationMetrics;
+import com.yahoo.vespa.hosted.controller.api.integration.certificates.ApplicationCertificate;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.SourceRevision;
import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId;
import com.yahoo.vespa.hosted.controller.api.integration.organization.User;
import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.vespa.hosted.controller.application.AssignedRotation;
import com.yahoo.vespa.hosted.controller.application.Change;
import com.yahoo.vespa.hosted.controller.application.ClusterInfo;
import com.yahoo.vespa.hosted.controller.application.ClusterUtilization;
@@ -30,6 +32,7 @@ import com.yahoo.vespa.hosted.controller.application.DeploymentActivity;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobError;
import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics;
+import com.yahoo.vespa.hosted.controller.application.EndpointId;
import com.yahoo.vespa.hosted.controller.application.JobStatus;
import com.yahoo.vespa.hosted.controller.application.RotationStatus;
import com.yahoo.vespa.hosted.controller.rotation.RotationId;
@@ -39,6 +42,8 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -46,6 +51,7 @@ import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.OptionalLong;
import java.util.TreeMap;
+import java.util.stream.Collectors;
/**
* Serializes {@link Application} to/from slime.
@@ -55,6 +61,13 @@ import java.util.TreeMap;
*/
public class ApplicationSerializer {
+ // 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.
+
// Application fields
private final String idField = "id";
private final String createdAtField = "createdAt";
@@ -71,8 +84,14 @@ public class ApplicationSerializer {
private final String writeQualityField = "writeQuality";
private final String queryQualityField = "queryQuality";
private final String pemDeployKeyField = "pemDeployKey";
- private final String rotationField = "rotation";
+ private final String assignedRotationsField = "assignedRotations";
+ private final String assignedRotationEndpointField = "endpointId";
+ private final String assignedRotationClusterField = "clusterId";
+ private final String assignedRotationRotationField = "rotationId";
+ private final String rotationsField = "endpoints";
+ private final String deprecatedRotationField = "rotation";
private final String rotationStatusField = "rotationStatus";
+ private final String applicationCertificateField = "applicationCertificate";
// Deployment fields
private final String zoneField = "zone";
@@ -164,8 +183,11 @@ public class ApplicationSerializer {
root.setDouble(queryQualityField, application.metrics().queryServiceQuality());
root.setDouble(writeQualityField, application.metrics().writeServiceQuality());
application.pemDeployKey().ifPresent(pemDeployKey -> root.setString(pemDeployKeyField, pemDeployKey));
- application.rotation().ifPresent(rotation -> root.setString(rotationField, rotation.asString()));
+ application.legacyRotation().ifPresent(rotation -> root.setString(deprecatedRotationField, rotation.asString()));
+ rotationsToSlime(application.assignedRotations(), root, rotationsField);
+ assignedRotationsToSlime(application.assignedRotations(), root, assignedRotationsField);
toSlime(application.rotationStatus(), root.setArray(rotationStatusField));
+ application.applicationCertificate().ifPresent(cert -> root.setString(applicationCertificateField, cert.secretsKeyNamePrefix()));
return slime;
}
@@ -336,6 +358,21 @@ public class ApplicationSerializer {
return clusterMetricsList;
}
+ private void rotationsToSlime(List<AssignedRotation> rotations, Cursor parent, String fieldName) {
+ final var rotationsArray = parent.setArray(fieldName);
+ rotations.forEach(rot -> rotationsArray.addString(rot.rotationId().asString()));
+ }
+
+ private void assignedRotationsToSlime(List<AssignedRotation> rotations, Cursor parent, String fieldName) {
+ final var rotationsArray = parent.setArray(fieldName);
+ for (var rotation : rotations) {
+ final var object = rotationsArray.addObject();
+ object.setString(assignedRotationEndpointField, rotation.endpointId().id());
+ object.setString(assignedRotationRotationField, rotation.rotationId().asString());
+ object.setString(assignedRotationClusterField, rotation.clusterId().value());
+ }
+ }
+
// ------------------ Deserialization
public Application fromSlime(Slime slime) {
@@ -355,12 +392,13 @@ public class ApplicationSerializer {
ApplicationMetrics metrics = new ApplicationMetrics(root.field(queryQualityField).asDouble(),
root.field(writeQualityField).asDouble());
Optional<String> pemDeployKey = optionalString(root.field(pemDeployKeyField));
- Optional<RotationId> rotation = rotationFromSlime(root.field(rotationField));
+ List<AssignedRotation> assignedRotations = assignedRotationsFromSlime(deploymentSpec, root);
Map<HostName, RotationStatus> rotationStatus = rotationStatusFromSlime(root.field(rotationStatusField));
+ Optional<ApplicationCertificate> applicationCertificate = optionalString(root.field(applicationCertificateField)).map(ApplicationCertificate::new);
return new Application(id, createdAt, deploymentSpec, validationOverrides, deployments, deploymentJobs,
deploying, outstandingChange, ownershipIssueId, owner, majorVersion, metrics,
- pemDeployKey, rotation, rotationStatus);
+ pemDeployKey, assignedRotations, rotationStatus, applicationCertificate);
}
private List<Deployment> deploymentsFromSlime(Inspector array) {
@@ -541,7 +579,57 @@ public class ApplicationSerializer {
Instant.ofEpochMilli(object.field(atField).asLong())));
}
- private Optional<RotationId> rotationFromSlime(Inspector field) {
+ private List<AssignedRotation> assignedRotationsFromSlime(DeploymentSpec deploymentSpec, Inspector root) {
+ final var assignedRotations = new LinkedHashMap<EndpointId, AssignedRotation>();
+
+ // Add the legacy rotation field to the set - this needs to be first
+ // TODO: Remove when we retire the rotations field
+ final var legacyRotation = legacyRotationFromSlime(root.field(deprecatedRotationField));
+ if (legacyRotation.isPresent() && deploymentSpec.globalServiceId().isPresent()) {
+ final var clusterId = new ClusterSpec.Id(deploymentSpec.globalServiceId().get());
+ final var regions = deploymentSpec.zones().stream().flatMap(zone -> zone.region().stream()).collect(Collectors.toSet());
+ assignedRotations.putIfAbsent(EndpointId.default_(), new AssignedRotation(clusterId, EndpointId.default_(), legacyRotation.get(), regions));
+ }
+
+ // Now add the same entries from "stupid" list of rotations
+ // TODO: Remove when we retire the rotations field
+ final var rotations = rotationListFromSlime(root.field(rotationsField));
+ for (var rotation : rotations) {
+ final var regions = deploymentSpec.zones().stream().flatMap(zone -> zone.region().stream()).collect(Collectors.toSet());
+ if (deploymentSpec.globalServiceId().isPresent()) {
+ final var clusterId = new ClusterSpec.Id(deploymentSpec.globalServiceId().get());
+ assignedRotations.putIfAbsent(EndpointId.default_(), new AssignedRotation(clusterId, EndpointId.default_(), rotation, regions));
+ }
+ }
+
+ // Last - add the actual entries we want. Do _not_ remove this during clean-up
+ root.field(assignedRotationsField).traverse((ArrayTraverser) (idx, inspector) -> {
+ final var clusterId = new ClusterSpec.Id(inspector.field(assignedRotationClusterField).asString());
+ final var endpointId = EndpointId.of(inspector.field(assignedRotationEndpointField).asString());
+ final var rotationId = new RotationId(inspector.field(assignedRotationRotationField).asString());
+ final var regions = deploymentSpec.endpoints().stream()
+ .filter(endpoint -> endpoint.endpointId().equals(endpointId.id()))
+ .flatMap(endpoint -> endpoint.regions().stream())
+ .collect(Collectors.toSet());
+ assignedRotations.putIfAbsent(endpointId, new AssignedRotation(clusterId, endpointId, rotationId, regions));
+ });
+
+ return List.copyOf(assignedRotations.values());
+ }
+
+ private List<RotationId> rotationListFromSlime(Inspector field) {
+ final var rotations = new ArrayList<RotationId>();
+
+ field.traverse((ArrayTraverser) (idx, inspector) -> {
+ final var rotation = new RotationId(inspector.asString());
+ rotations.add(rotation);
+ });
+
+ return rotations;
+ }
+
+ // TODO: Remove after June 2019 once the 'rotation' field is gone from storage
+ private Optional<RotationId> legacyRotationFromSlime(Inspector field) {
return field.valid() ? optionalString(field).map(RotationId::new) : Optional.empty();
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/AuditLogSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/AuditLogSerializer.java
index 5bcb155efcb..d18e561ce5d 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/AuditLogSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/AuditLogSerializer.java
@@ -19,6 +19,13 @@ import java.util.function.Function;
*/
public class AuditLogSerializer {
+ // 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 entriesField = "entries";
private static final String atField = "at";
private static final String principalField = "principal";
@@ -60,6 +67,7 @@ public class AuditLogSerializer {
switch (method) {
case POST: return "POST";
case PATCH: return "PATCH";
+ case PUT: return "PUT";
case DELETE: return "DELETE";
default: throw new IllegalArgumentException("No serialization defined for method " + method);
}
@@ -69,6 +77,7 @@ public class AuditLogSerializer {
switch (field.asString()) {
case "POST": return AuditLog.Entry.Method.POST;
case "PATCH": return AuditLog.Entry.Method.PATCH;
+ case "PUT": return AuditLog.Entry.Method.PUT;
case "DELETE": return AuditLog.Entry.Method.DELETE;
default: throw new IllegalArgumentException("Unknown serialized value '" + field.asString() + "'");
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ConfidenceOverrideSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ConfidenceOverrideSerializer.java
index a87875da104..2cb981aac03 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ConfidenceOverrideSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ConfidenceOverrideSerializer.java
@@ -18,6 +18,13 @@ import java.util.Map;
*/
public class ConfidenceOverrideSerializer {
+ // 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 overridesField = "overrides";
public Slime toSlime(Map<Version, Confidence> overrides) {
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 a2e5a0c78f7..d704d701cf0 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
@@ -106,6 +106,10 @@ public class CuratorDb {
CuratorDb(Curator curator, Duration tryLockTimeout) {
this.curator = curator;
this.tryLockTimeout = tryLockTimeout;
+
+ // TODO: Remove after 7.60
+ curator.delete(root.append("openStackServerPool"));
+ curator.delete(root.append("vespaServerPool"));
}
/** Returns all hosts configured to be part of this ZooKeeper cluster */
@@ -168,16 +172,6 @@ public class CuratorDb {
return lock(lockPath(provisionStateId), Duration.ofSeconds(1));
}
- @SuppressWarnings("unused") // Called by internal code
- public Lock lockVespaServerPool() {
- return lock(lockRoot.append("vespaServerPoolLock"), Duration.ofSeconds(1));
- }
-
- @SuppressWarnings("unused") // Called by internal code
- public Lock lockOpenStackServerPool() {
- return lock(lockRoot.append("openStackServerPoolLock"), Duration.ofSeconds(1));
- }
-
public Lock lockOsVersions() {
return lock(lockRoot.append("osTargetVersion"), defaultLockTimeout);
}
@@ -469,26 +463,6 @@ public class CuratorDb {
return curator.getChildren(provisionStatePath());
}
- @SuppressWarnings("unused")
- public Optional<byte[]> readVespaServerPool() {
- return curator.getData(vespaServerPoolPath());
- }
-
- @SuppressWarnings("unused")
- public void writeVespaServerPool(byte[] data) {
- curator.set(vespaServerPoolPath(), data);
- }
-
- @SuppressWarnings("unused")
- public Optional<byte[]> readOpenStackServerPool() {
- return curator.getData(openStackServerPoolPath());
- }
-
- @SuppressWarnings("unused")
- public void writeOpenStackServerPool(byte[] data) {
- curator.set(openStackServerPoolPath(), data);
- }
-
// -------------- Routing policies ----------------------------------------
public void writeRoutingPolicies(ApplicationId application, Set<RoutingPolicy> policies) {
@@ -589,14 +563,6 @@ public class CuratorDb {
return provisionStatePath().append(provisionId);
}
- private static Path vespaServerPoolPath() {
- return root.append("vespaServerPool");
- }
-
- private static Path openStackServerPoolPath() {
- return root.append("openStackServerPool");
- }
-
private static Path tenantPath(TenantName name) {
return tenantRoot.append(name.value());
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/LogSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/LogSerializer.java
index 40781ac6e92..418038d4f1e 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/LogSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/LogSerializer.java
@@ -27,6 +27,13 @@ import java.util.stream.Collectors;
*/
class LogSerializer {
+ // 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 idField = "id";
private static final String typeField = "type";
private static final String timestampField = "at";
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/NameServiceQueueSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/NameServiceQueueSerializer.java
index 3dfb5ffe5f8..e3dedd65e68 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/NameServiceQueueSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/NameServiceQueueSerializer.java
@@ -24,6 +24,13 @@ import java.util.ArrayList;
*/
public class NameServiceQueueSerializer {
+ // 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 requestsField = "requests";
private static final String requestType = "requestType";
private static final String recordsField = "records";
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionSerializer.java
index 21f8b1bcb80..d68e24a27ea 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionSerializer.java
@@ -20,6 +20,13 @@ import java.util.TreeSet;
*/
public class OsVersionSerializer {
+ // 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 versionsField = "versions";
private static final String versionField = "version";
private static final String cloudField = "cloud";
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionStatusSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionStatusSerializer.java
index 3e3c0df1673..88805f54d65 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionStatusSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionStatusSerializer.java
@@ -26,6 +26,13 @@ import java.util.TreeMap;
*/
public class OsVersionStatusSerializer {
+ // 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 versionsField = "versions";
private static final String versionField = "version";
private static final String nodesField = "nodes";
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 a9c6c54a44a..9cfce8dc16a 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
@@ -24,6 +24,13 @@ import java.util.function.Function;
*/
public class RoutingPolicySerializer {
+ // 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 routingPoliciesField = "routingPolicies";
private static final String clusterField = "cluster";
private static final String canonicalNameField = "canonicalName";
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializer.java
index f29af1055d0..1c95c9766f5 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializer.java
@@ -56,6 +56,13 @@ import static java.util.Comparator.comparing;
*/
class RunSerializer {
+ // 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 stepsField = "steps";
private static final String applicationField = "id";
private static final String jobTypeField = "type";
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java
index 56e80068908..3a4e6c3954c 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java
@@ -29,6 +29,13 @@ import java.util.Optional;
*/
public class TenantSerializer {
+ // 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 nameField = "name";
private static final String typeField = "type";
private static final String athenzDomainField = "athenzDomain";
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/VersionSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/VersionSerializer.java
index 5edae803fdb..e5897963254 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/VersionSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/VersionSerializer.java
@@ -13,6 +13,13 @@ import com.yahoo.slime.Slime;
*/
public class VersionSerializer {
+ // 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 versionField = "version";
public Slime toSlime(Version version) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/VersionStatusSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/VersionStatusSerializer.java
index 72d38bbee5f..207a5f8dcf9 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/VersionStatusSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/VersionStatusSerializer.java
@@ -27,6 +27,13 @@ import java.util.Set;
*/
public class VersionStatusSerializer {
+ // 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.
+
// VersionStatus fields
private static final String versionsField = "versions";
@@ -35,6 +42,7 @@ public class VersionStatusSerializer {
private static final String committedAtField = "releasedAt";
private static final String isControllerVersionField = "isCurrentControllerVersion";
private static final String isSystemVersionField = "isCurrentSystemVersion";
+ private static final String isReleasedField = "isReleased";
private static final String deploymentStatisticsField = "deploymentStatistics";
private static final String confidenceField = "confidence";
private static final String configServersField = "configServerHostnames";
@@ -66,6 +74,7 @@ public class VersionStatusSerializer {
object.setLong(committedAtField, version.committedAt().toEpochMilli());
object.setBool(isControllerVersionField, version.isControllerVersion());
object.setBool(isSystemVersionField, version.isSystemVersion());
+ object.setBool(isReleasedField, version.isReleased());
deploymentStatisticsToSlime(version.statistics(), object.setObject(deploymentStatisticsField));
object.setString(confidenceField, version.confidence().name());
configServersToSlime(version.systemApplicationHostnames(), object.setArray(configServersField));
@@ -98,6 +107,7 @@ public class VersionStatusSerializer {
Instant.ofEpochMilli(object.field(committedAtField).asLong()),
object.field(isControllerVersionField).asBool(),
object.field(isSystemVersionField).asBool(),
+ object.field(isReleasedField).valid() ? object.field(isReleasedField).asBool() : true,
configServersFromSlime(object.field(configServersField)),
VespaVersion.Confidence.valueOf(object.field(confidenceField).asString())
);
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImpl.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImpl.java
index 01d9a01a316..73a029ad3b3 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImpl.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImpl.java
@@ -5,14 +5,15 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.inject.Inject;
import com.yahoo.config.provision.Environment;
+import com.yahoo.config.provision.zone.ZoneApi;
+import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.config.provision.zone.ZoneList;
import com.yahoo.jdisc.http.HttpRequest.Method;
import com.yahoo.log.LogLevel;
import com.yahoo.vespa.athenz.api.AthenzIdentity;
import com.yahoo.vespa.athenz.identity.ServiceIdentityProvider;
import com.yahoo.vespa.athenz.tls.AthenzIdentityVerifier;
import com.yahoo.vespa.athenz.utils.AthenzIdentities;
-import com.yahoo.config.provision.zone.ZoneId;
-import com.yahoo.config.provision.zone.ZoneList;
import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry;
import org.apache.http.Header;
import org.apache.http.client.config.RequestConfig;
@@ -114,9 +115,9 @@ public class ConfigServerRestExecutorImpl implements ConfigServerRestExecutor {
if ( ! environmentName.isEmpty())
zones = zones.in(Environment.from(environmentName));
- for (ZoneId zoneId : zones.ids()) {
+ for (ZoneApi zone : zones.zones()) {
responseStructure.uris.add(proxyRequest.getScheme() + "://" + proxyRequest.getControllerPrefix() +
- zoneId.environment().name() + "/" + zoneId.region().value());
+ zone.getEnvironment().value() + "/" + zone.getRegionName().value());
}
JsonNode node = mapper.valueToTree(responseStructure);
return new ProxyResponse(proxyRequest, node.toString(), 200, Optional.empty(), "application/json");
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
index d32b3f009f4..868272e5051 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
@@ -7,15 +7,13 @@ import com.google.common.collect.ImmutableSet;
import com.google.inject.Inject;
import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.Environment;
-import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.TenantName;
+import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.container.jdisc.LoggingRequestHandler;
import com.yahoo.io.IOUtils;
-import com.yahoo.log.LogLevel;
import com.yahoo.restapi.Path;
import com.yahoo.slime.Cursor;
import com.yahoo.slime.Inspector;
@@ -31,9 +29,7 @@ import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.NotExistsException;
import com.yahoo.vespa.hosted.controller.api.ActivateResult;
-import com.yahoo.vespa.hosted.controller.api.application.v4.ApplicationResource;
import com.yahoo.vespa.hosted.controller.api.application.v4.EnvironmentResource;
-import com.yahoo.vespa.hosted.controller.api.application.v4.TenantResource;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.DeployOptions;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.EndpointStatus;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.configserverbindings.RefeedAction;
@@ -50,7 +46,6 @@ 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.api.integration.deployment.SourceRevision;
import com.yahoo.vespa.hosted.controller.api.integration.routing.RoutingEndpoint;
-import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
import com.yahoo.vespa.hosted.controller.application.Change;
import com.yahoo.vespa.hosted.controller.application.ClusterCost;
@@ -66,11 +61,11 @@ import com.yahoo.vespa.hosted.controller.application.RoutingPolicy;
import com.yahoo.vespa.hosted.controller.application.SystemApplication;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentTrigger;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentTrigger.ChangesToCancel;
+import com.yahoo.vespa.hosted.controller.deployment.TestConfigSerializer;
import com.yahoo.vespa.hosted.controller.restapi.ErrorResponse;
import com.yahoo.vespa.hosted.controller.restapi.MessageResponse;
import com.yahoo.vespa.hosted.controller.restapi.ResourceResponse;
import com.yahoo.vespa.hosted.controller.restapi.SlimeJsonResponse;
-import com.yahoo.vespa.hosted.controller.restapi.StringResponse;
import com.yahoo.vespa.hosted.controller.security.AccessControlRequests;
import com.yahoo.vespa.hosted.controller.security.Credentials;
import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant;
@@ -95,6 +90,8 @@ import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.Base64;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -102,6 +99,7 @@ import java.util.Scanner;
import java.util.Set;
import java.util.StringJoiner;
import java.util.logging.Level;
+import java.util.stream.Collectors;
import static java.util.stream.Collectors.joining;
@@ -174,15 +172,29 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
if (path.matches("/application/v4/user")) return authenticatedUser(request);
if (path.matches("/application/v4/tenant")) return tenants(request);
if (path.matches("/application/v4/tenant/{tenant}")) return tenant(path.get("tenant"), request);
- if (path.matches("/application/v4/tenant/{tenant}/application")) return applications(path.get("tenant"), request);
- if (path.matches("/application/v4/tenant/{tenant}/application/{application}")) return application(path.get("tenant"), path.get("application"), request);
- if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying")) return deploying(path.get("tenant"), path.get("application"), request);
- if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying/pin")) return deploying(path.get("tenant"), path.get("application"), request);
+ if (path.matches("/application/v4/tenant/{tenant}/application")) return applications(path.get("tenant"), Optional.empty(), request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}")) return application(path.get("tenant"), path.get("application"), "default", request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying")) return deploying(path.get("tenant"), path.get("application"), "default", request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying/pin")) return deploying(path.get("tenant"), path.get("application"), "default", request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/metering")) return metering(path.get("tenant"), path.get("application"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/nodes")) return nodes(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"));
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/logs")) return logs(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request.propertyMap());
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance")) return applications(path.get("tenant"), Optional.of(path.get("application")), request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}")) return application(path.get("tenant"), path.get("application"), path.get("instance"), request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/deploying")) return deploying(path.get("tenant"), path.get("application"), path.get("instance"), request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/deploying/pin")) return deploying(path.get("tenant"), path.get("application"), path.get("instance"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/job")) return JobControllerApiHandlerHelper.jobTypeResponse(controller, appIdFromPath(path), request.getUri());
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/job/{jobtype}")) return JobControllerApiHandlerHelper.runResponse(controller.jobController().runs(appIdFromPath(path), jobTypeFromPath(path)), request.getUri());
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/job/{jobtype}/test-config")) return testConfig(appIdFromPath(path), jobTypeFromPath(path));
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/job/{jobtype}/run/{number}")) return JobControllerApiHandlerHelper.runDetailsResponse(controller.jobController(), runIdFromPath(path), request.getProperty("after"));
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}")) return deployment(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}")) return deployment(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/suspended")) return suspended(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/service")) return services(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/service/{service}/{*}")) return service(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), path.get("service"), path.getRest(), request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/global-rotation")) return rotationStatus(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"));
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/global-rotation/override")) return getGlobalRotationOverride(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"));
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}")) return deployment(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}")) return deployment(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/suspended")) return suspended(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/service")) return services(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request);
@@ -195,53 +207,65 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
private HttpResponse handlePUT(Path path, HttpRequest request) {
if (path.matches("/application/v4/user")) return createUser(request);
if (path.matches("/application/v4/tenant/{tenant}")) return updateTenant(path.get("tenant"), request);
- if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/global-rotation/override"))
- return setGlobalRotationOverride(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), false, request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/global-rotation/override")) return setGlobalRotationOverride(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), false, request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/global-rotation/override")) return setGlobalRotationOverride(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), false, request);
return ErrorResponse.notFoundError("Nothing at " + path);
}
private HttpResponse handlePOST(Path path, HttpRequest request) {
if (path.matches("/application/v4/tenant/{tenant}")) return createTenant(path.get("tenant"), request);
- if (path.matches("/application/v4/tenant/{tenant}/application/{application}")) return createApplication(path.get("tenant"), path.get("application"), request);
- if (path.matches("/application/v4/tenant/{tenant}/application/{application}/promote")) return promoteApplication(path.get("tenant"), path.get("application"), request);
- if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying/platform")) return deployPlatform(path.get("tenant"), path.get("application"), false, request);
- if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying/pin")) return deployPlatform(path.get("tenant"), path.get("application"), true, request);
- if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying/application")) return deployApplication(path.get("tenant"), path.get("application"), request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}")) return createApplication(path.get("tenant"), path.get("application"), "default", request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying/platform")) return deployPlatform(path.get("tenant"), path.get("application"), "default", false, request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying/pin")) return deployPlatform(path.get("tenant"), path.get("application"), "default", true, request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying/application")) return deployApplication(path.get("tenant"), path.get("application"), "default", request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/jobreport")) return notifyJobCompletion(path.get("tenant"), path.get("application"), request);
- if (path.matches("/application/v4/tenant/{tenant}/application/{application}/submit")) return submit(path.get("tenant"), path.get("application"), request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/submit")) return submit(path.get("tenant"), path.get("application"), "default", request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}")) return createApplication(path.get("tenant"), path.get("application"), path.get("instance"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/deploy/{jobtype}")) return jobDeploy(appIdFromPath(path), jobTypeFromPath(path), request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/deploying/platform")) return deployPlatform(path.get("tenant"), path.get("application"), path.get("instance"), false, request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/deploying/pin")) return deployPlatform(path.get("tenant"), path.get("application"), path.get("instance"), true, request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/deploying/application")) return deployApplication(path.get("tenant"), path.get("application"), path.get("instance"), request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{ignored}/jobreport")) return notifyJobCompletion(path.get("tenant"), path.get("application"), request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/submit")) return submit(path.get("tenant"), path.get("application"), path.get("instance"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/job/{jobtype}")) return trigger(appIdFromPath(path), jobTypeFromPath(path), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/job/{jobtype}/pause")) return pause(appIdFromPath(path), jobTypeFromPath(path));
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}")) return deploy(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/deploy")) return deploy(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request); // legacy synonym of the above
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/restart")) return restart(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}")) return deploy(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/deploy")) return deploy(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request); // legacy synonym of the above
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/restart")) return restart(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request);
- if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/promote")) return promoteApplicationDeployment(path.get("tenant"), path.get("application"), path.get("environment"), path.get("region"), path.get("instance"), request);
return ErrorResponse.notFoundError("Nothing at " + path);
}
private HttpResponse handlePATCH(Path path, HttpRequest request) {
- if (path.matches("/application/v4/tenant/{tenant}/application/{application}"))
- return patchApplication(path.get("tenant"), path.get("application"), request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}")) return patchApplication(path.get("tenant"), path.get("application"), "default", request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}")) return patchApplication(path.get("tenant"), path.get("application"), path.get("instance"), request);
return ErrorResponse.notFoundError("Nothing at " + path);
}
private HttpResponse handleDELETE(Path path, HttpRequest request) {
if (path.matches("/application/v4/tenant/{tenant}")) return deleteTenant(path.get("tenant"), request);
- if (path.matches("/application/v4/tenant/{tenant}/application/{application}")) return deleteApplication(path.get("tenant"), path.get("application"), request);
- if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying")) return cancelDeploy(path.get("tenant"), path.get("application"), "all");
- if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying/{choice}")) return cancelDeploy(path.get("tenant"), path.get("application"), path.get("choice"));
- if (path.matches("/application/v4/tenant/{tenant}/application/{application}/submit")) return JobControllerApiHandlerHelper.unregisterResponse(controller.jobController(), path.get("tenant"), path.get("application"));
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}")) return deleteApplication(path.get("tenant"), path.get("application"), "default", request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying")) return cancelDeploy(path.get("tenant"), path.get("application"), "default", "all");
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying/{choice}")) return cancelDeploy(path.get("tenant"), path.get("application"), "default", path.get("choice"));
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/submit")) return JobControllerApiHandlerHelper.unregisterResponse(controller.jobController(), path.get("tenant"), path.get("application"), "default");
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}")) return deleteApplication(path.get("tenant"), path.get("application"), path.get("instance"), request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/deploying")) return cancelDeploy(path.get("tenant"), path.get("application"), path.get("instance"), "all");
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/deploying/{choice}")) return cancelDeploy(path.get("tenant"), path.get("application"), path.get("instance"), path.get("choice"));
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/submit")) return JobControllerApiHandlerHelper.unregisterResponse(controller.jobController(), path.get("tenant"), path.get("application"), path.get("instance"));
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/job/{jobtype}")) return JobControllerApiHandlerHelper.abortJobResponse(controller.jobController(), appIdFromPath(path), jobTypeFromPath(path));
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}")) return deactivate(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/global-rotation/override")) return setGlobalRotationOverride(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), true, request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}")) return deactivate(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request);
- if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/global-rotation/override"))
- return setGlobalRotationOverride(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), true, request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/global-rotation/override")) return setGlobalRotationOverride(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), true, request);
return ErrorResponse.notFoundError("Nothing at " + path);
}
private HttpResponse handleOPTIONS() {
// We implement this to avoid redirect loops on OPTIONS requests from browsers, but do not really bother
// spelling out the methods supported at each path, which we should
- EmptyJsonResponse response = new EmptyJsonResponse();
+ EmptyResponse response = new EmptyResponse();
response.headers().put("Allow", "GET,PUT,POST,PATCH,DELETE,OPTIONS");
return response;
}
@@ -299,25 +323,28 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
return new SlimeJsonResponse(slime);
}
- private HttpResponse applications(String tenantName, HttpRequest request) {
+ private HttpResponse applications(String tenantName, Optional<String> applicationName, HttpRequest request) {
TenantName tenant = TenantName.from(tenantName);
Slime slime = new Slime();
Cursor array = slime.setArray();
- for (Application application : controller.applications().asList(tenant))
+ for (Application application : controller.applications().asList(tenant)) {
+ if (applicationName.isPresent() && ! application.id().application().toString().equals(applicationName.get()))
+ continue;
toSlime(application, array.addObject(), request);
+ }
return new SlimeJsonResponse(slime);
}
- private HttpResponse application(String tenantName, String applicationName, HttpRequest request) {
+ private HttpResponse application(String tenantName, String applicationName, String instanceName, HttpRequest request) {
Slime slime = new Slime();
- toSlime(slime.setObject(), getApplication(tenantName, applicationName), request);
+ toSlime(slime.setObject(), getApplication(tenantName, applicationName, instanceName), request);
return new SlimeJsonResponse(slime);
}
- private HttpResponse patchApplication(String tenantName, String applicationName, HttpRequest request) {
+ private HttpResponse patchApplication(String tenantName, String applicationName, String instanceName, HttpRequest request) {
Inspector requestObject = toSlime(request.getData()).get();
StringJoiner messageBuilder = new StringJoiner("\n").setEmptyValue("No applicable changes.");
- controller.applications().lockOrThrow(ApplicationId.from(tenantName, applicationName, "default"), application -> {
+ controller.applications().lockOrThrow(ApplicationId.from(tenantName, applicationName, instanceName), application -> {
Inspector majorVersionField = requestObject.field("majorVersion");
if (majorVersionField.valid()) {
Integer majorVersion = majorVersionField.asLong() == 0 ? null : (int) majorVersionField.asLong();
@@ -337,8 +364,8 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
return new MessageResponse(messageBuilder.toString());
}
- private Application getApplication(String tenantName, String applicationName) {
- ApplicationId applicationId = ApplicationId.from(tenantName, applicationName, "default");
+ private Application getApplication(String tenantName, String applicationName, String instanceName) {
+ ApplicationId applicationId = ApplicationId.from(tenantName, applicationName, instanceName);
return controller.applications().get(applicationId)
.orElseThrow(() -> new NotExistsException(applicationId + " not found"));
}
@@ -482,7 +509,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
});
// Compile version. The version that should be used when building an application
- object.setString("compileVersion", controller.applications().oldestInstalledPlatform(application.id()).toFullString());
+ object.setString("compileVersion", compileVersion(application.id()).toFullString());
application.majorVersion().ifPresent(majorVersion -> object.setLong("majorVersion", majorVersion));
@@ -496,10 +523,11 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
.map(URI::toString)
.forEach(globalRotationsArray::addString);
- application.rotation().ifPresent(rotation -> object.setString("rotationId", rotation.asString()));
+
+ application.rotations().stream().findFirst().ifPresent(rotation -> object.setString("rotationId", rotation.asString()));
// Per-cluster rotations
- Set<RoutingPolicy> routingPolicies = controller.applications().routingPolicies(application.id());
+ Set<RoutingPolicy> routingPolicies = controller.applications().routingPolicies().get(application.id());
for (RoutingPolicy policy : routingPolicies) {
policy.rotationEndpointsIn(controller.system()).asList().stream()
.map(Endpoint::url)
@@ -515,7 +543,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
for (Deployment deployment : deployments) {
Cursor deploymentObject = instancesArray.addObject();
- if (application.rotation().isPresent() && deployment.zone().environment() == Environment.prod) {
+ if ((! application.rotations().isEmpty()) && deployment.zone().environment() == Environment.prod) {
toSlime(application.rotationStatus(deployment), deploymentObject);
}
@@ -528,7 +556,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
deploymentObject.setString("url", withPath(request.getUri().getPath() +
"/environment/" + deployment.zone().environment().value() +
"/region/" + deployment.zone().region().value() +
- "/instance/" + application.id().instance().value(),
+ ( request.getUri().getPath().contains("/instance/") ? "" : "/instance/" + application.id().instance().value()),
request.getUri()).toString());
}
}
@@ -585,7 +613,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
// Add endpoint(s) defined by routing policies
var endpointArray = response.setArray("endpoints");
- for (var policy : controller.applications().routingPolicies(deploymentId.applicationId())) {
+ for (var policy : controller.applications().routingPolicies().get(deploymentId)) {
Cursor endpointObject = endpointArray.addObject();
Endpoint endpoint = policy.endpointIn(controller.system());
endpointObject.setString("cluster", policy.cluster().value());
@@ -598,7 +626,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
// ask the routing layer here
Cursor serviceUrlArray = response.setArray("serviceUrls");
controller.applications().getDeploymentEndpoints(deploymentId)
- .ifPresent(endpoints -> endpoints.forEach(endpoint -> serviceUrlArray.addString(endpoint.toString())));
+ .forEach(endpoint -> serviceUrlArray.addString(endpoint.toString()));
response.setString("nodes", withPath("/zone/v2/" + deploymentId.zoneId().environment() + "/" + deploymentId.zoneId().region() + "/nodes/v2/node/?&recursive=true&application=" + deploymentId.applicationId().tenant() + "." + deploymentId.applicationId().application() + "." + deploymentId.applicationId().instance(), request.getUri()).toString());
response.setString("yamasUrl", monitoringSystemUri(deploymentId).toString());
@@ -659,6 +687,30 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
return controller.zoneRegistry().getMonitoringSystemUri(deploymentId);
}
+ /**
+ * Returns a non-broken, released version at least as old as the oldest platform the given application is on.
+ *
+ * If no known version is applicable, the newest version at least as old as the oldest platform is selected,
+ * among all versions released for this system. If no such versions exists, throws an IllegalStateException.
+ */
+ private Version compileVersion(ApplicationId id) {
+ Version oldestPlatform = controller.applications().oldestInstalledPlatform(id);
+ return controller.versionStatus().versions().stream()
+ .filter(version -> version.confidence().equalOrHigherThan(VespaVersion.Confidence.low))
+ .filter(VespaVersion::isReleased)
+ .map(VespaVersion::versionNumber)
+ .filter(version -> ! version.isAfter(oldestPlatform))
+ .max(Comparator.naturalOrder())
+ .orElseGet(() -> controller.mavenRepository().metadata().versions().stream()
+ .filter(version -> ! version.isAfter(oldestPlatform))
+ .filter(version -> ! controller.versionStatus().versions().stream()
+ .map(VespaVersion::versionNumber)
+ .collect(Collectors.toSet()).contains(version))
+ .max(Comparator.naturalOrder())
+ .orElseThrow(() -> new IllegalStateException("No available releases of " +
+ controller.mavenRepository().artifactId())));
+ }
+
private HttpResponse setGlobalRotationOverride(String tenantName, String applicationName, String instanceName, String environment, String region, boolean inService, HttpRequest request) {
Application application = controller.applications().require(ApplicationId.from(tenantName, applicationName, instanceName));
ZoneId zone = ZoneId.from(environment, region);
@@ -707,7 +759,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
ApplicationId applicationId = ApplicationId.from(tenantName, applicationName, instanceName);
Application application = controller.applications().require(applicationId);
ZoneId zone = ZoneId.from(environment, region);
- if (!application.rotation().isPresent()) {
+ if (application.rotations().isEmpty()) {
throw new NotExistsException("global rotation does not exist for " + application);
}
Deployment deployment = application.deployments().get(zone);
@@ -721,11 +773,50 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
return new SlimeJsonResponse(slime);
}
- private HttpResponse deploying(String tenant, String application, HttpRequest request) {
- Application app = controller.applications().require(ApplicationId.from(tenant, application, "default"));
+ private HttpResponse metering(String tenant, String application, HttpRequest request) {
Slime slime = new Slime();
Cursor root = slime.setObject();
- if (!app.change().isEmpty()) {
+
+ Cursor currentRate = root.setObject("currentrate");
+ currentRate.setDouble("cpu", 0);
+ currentRate.setDouble("mem", 0);
+ currentRate.setDouble("disk", 0);
+
+ Cursor thismonth = root.setObject("thismonth");
+ thismonth.setDouble("cpu", 0);
+ thismonth.setDouble("mem", 0);
+ thismonth.setDouble("disk", 0);
+
+ Cursor lastmonth = root.setObject("lastmonth");
+ lastmonth.setDouble("cpu", 0);
+ lastmonth.setDouble("mem", 0);
+ lastmonth.setDouble("disk", 0);
+
+ Cursor details = root.setObject("details");
+
+ Cursor detailsCpu = details.setObject("cpu");
+ Cursor detailsCpuDummyApp = detailsCpu.setObject("dummy");
+ Cursor detailsCpuDummyData = detailsCpuDummyApp.setArray("data");
+
+ // The data array should be filled with objects like: { unixms: <number>, valur: <number }
+
+ Cursor detailsMem = details.setObject("mem");
+ Cursor detailsMemDummyApp = detailsMem.setObject("dummy");
+ Cursor detailsMemDummyData = detailsMemDummyApp.setArray("data");
+
+ Cursor detailsDisk = details.setObject("disk");
+ Cursor detailsDiskDummyApp = detailsDisk.setObject("dummy");
+ Cursor detailsDiskDummyData = detailsDiskDummyApp.setArray("data");
+
+
+ return new SlimeJsonResponse(slime);
+ }
+
+ private HttpResponse deploying(String tenant, String application, String instance, HttpRequest request) {
+ Application app = controller.applications().require(ApplicationId.from(tenant, application, instance));
+ Slime slime = new Slime();
+ Cursor root = slime.setObject();
+ if ( ! app.change().isEmpty()) {
app.change().platform().ifPresent(version -> root.setString("platform", version.toString()));
app.change().application().ifPresent(applicationVersion -> root.setString("application", applicationVersion.id()));
root.setBool("pinned", app.change().isPinned());
@@ -800,9 +891,9 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
return tenant(controller.tenants().require(TenantName.from(tenantName)), request);
}
- private HttpResponse createApplication(String tenantName, String applicationName, HttpRequest request) {
+ private HttpResponse createApplication(String tenantName, String applicationName, String instanceName, HttpRequest request) {
Inspector requestObject = toSlime(request.getData()).get();
- ApplicationId id = ApplicationId.from(tenantName, applicationName, "default");
+ ApplicationId id = ApplicationId.from(tenantName, applicationName, instanceName);
try {
Optional<Credentials> credentials = controller.tenants().require(id.tenant()).type() == Tenant.Type.user
? Optional.empty()
@@ -822,10 +913,10 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
}
/** Trigger deployment of the given Vespa version if a valid one is given, e.g., "7.8.9". */
- private HttpResponse deployPlatform(String tenantName, String applicationName, boolean pin, HttpRequest request) {
+ private HttpResponse deployPlatform(String tenantName, String applicationName, String instanceName, boolean pin, HttpRequest request) {
request = controller.auditLogger().log(request);
String versionString = readToString(request.getData());
- ApplicationId id = ApplicationId.from(tenantName, applicationName, "default");
+ ApplicationId id = ApplicationId.from(tenantName, applicationName, instanceName);
StringBuilder response = new StringBuilder();
controller.applications().lockOrThrow(id, application -> {
Version version = Version.fromString(versionString);
@@ -833,12 +924,12 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
version = controller.systemVersion();
if ( ! systemHasVersion(version))
throw new IllegalArgumentException("Cannot trigger deployment of version '" + version + "': " +
- "Version is not active in this system. " +
- "Active versions: " + controller.versionStatus().versions()
- .stream()
- .map(VespaVersion::versionNumber)
- .map(Version::toString)
- .collect(joining(", ")));
+ "Version is not active in this system. " +
+ "Active versions: " + controller.versionStatus().versions()
+ .stream()
+ .map(VespaVersion::versionNumber)
+ .map(Version::toString)
+ .collect(joining(", ")));
Change change = Change.of(version);
if (pin)
change = change.withPin();
@@ -850,9 +941,9 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
}
/** Trigger deployment to the last known application package for the given application. */
- private HttpResponse deployApplication(String tenantName, String applicationName, HttpRequest request) {
+ private HttpResponse deployApplication(String tenantName, String applicationName, String instanceName, HttpRequest request) {
controller.auditLogger().log(request);
- ApplicationId id = ApplicationId.from(tenantName, applicationName, "default");
+ ApplicationId id = ApplicationId.from(tenantName, applicationName, instanceName);
StringBuilder response = new StringBuilder();
controller.applications().lockOrThrow(id, application -> {
Change change = Change.of(application.get().deploymentJobs().statusOf(JobType.component).get().lastSuccess().get().application());
@@ -863,8 +954,8 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
}
/** Cancel ongoing change for given application, e.g., everything with {"cancel":"all"} */
- private HttpResponse cancelDeploy(String tenantName, String applicationName, String choice) {
- ApplicationId id = ApplicationId.from(tenantName, applicationName, "default");
+ private HttpResponse cancelDeploy(String tenantName, String applicationName, String instanceName, String choice) {
+ ApplicationId id = ApplicationId.from(tenantName, applicationName, instanceName);
StringBuilder response = new StringBuilder();
controller.applications().lockOrThrow(id, application -> {
Change change = application.get().change();
@@ -891,12 +982,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
Optional<Hostname> hostname = Optional.ofNullable(request.getProperty("hostname")).map(Hostname::new);
controller.applications().restart(deploymentId, hostname);
- // TODO: Change to return JSON
- return new StringResponse("Requested restart of " + path(TenantResource.API_PATH, tenantName,
- ApplicationResource.API_PATH, applicationName,
- EnvironmentResource.API_PATH, environment,
- "region", region,
- "instance", instanceName));
+ return new MessageResponse("Requested restart of " + deploymentId);
}
private HttpResponse jobDeploy(ApplicationId id, JobType type, HttpRequest request) {
@@ -919,7 +1005,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
Slime slime = new Slime();
Cursor rootObject = slime.setObject();
rootObject.setString("message", "Deployment started in " + runId);
- rootObject.setString("location", controller.zoneRegistry().dashboardUrl(runId).toString());
+ rootObject.setLong("run", runId.number());
return new SlimeJsonResponse(slime);
}
@@ -934,11 +1020,11 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
Inspector deployOptions = SlimeUtils.jsonToSlime(dataParts.get("deployOptions")).get();
/*
- * Special handling of the zone application (the only system application with an application package)
+ * Special handling of the proxy application (the only system application with an application package)
* Setting any other deployOptions here is not supported for now (e.g. specifying version), but
* this might be handy later to handle emergency downgrades.
*/
- boolean isZoneApplication = SystemApplication.zone.id().equals(applicationId);
+ boolean isZoneApplication = SystemApplication.proxy.id().equals(applicationId);
if (isZoneApplication) { // TODO jvenstad: Separate out.
// Make it explicit that version is not yet supported here
String versionStr = deployOptions.field("vespaVersion").asString();
@@ -956,7 +1042,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
throw new IllegalArgumentException("Deployment of system applications is not permitted until system version is determined");
}
ActivateResult result = controller.applications()
- .deploySystemApplicationPackage(SystemApplication.zone, zone, systemVersion.get().versionNumber());
+ .deploySystemApplicationPackage(SystemApplication.proxy, zone, systemVersion.get().versionNumber());
return new SlimeJsonResponse(toSlime(result));
}
@@ -1037,59 +1123,23 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
return tenant(tenant.get(), request);
}
- private HttpResponse deleteApplication(String tenantName, String applicationName, HttpRequest request) {
- ApplicationId id = ApplicationId.from(tenantName, applicationName, "default");
+ private HttpResponse deleteApplication(String tenantName, String applicationName, String instanceName, HttpRequest request) {
+ ApplicationId id = ApplicationId.from(tenantName, applicationName, instanceName);
Optional<Credentials> credentials = controller.tenants().require(id.tenant()).type() == Tenant.Type.user
? Optional.empty()
: Optional.of(accessControlRequests.credentials(id.tenant(), toSlime(request.getData()).get(), request.getJDiscRequest()));
controller.applications().deleteApplication(id, credentials);
- return new EmptyJsonResponse(); // TODO: Replicates current behavior but should return a message response instead
+ return new MessageResponse("Deleted application " + id);
}
private HttpResponse deactivate(String tenantName, String applicationName, String instanceName, String environment, String region, HttpRequest request) {
Application application = controller.applications().require(ApplicationId.from(tenantName, applicationName, instanceName));
// Attempt to deactivate application even if the deployment is not known by the controller
- controller.applications().deactivate(application.id(), ZoneId.from(environment, region));
+ DeploymentId deploymentId = new DeploymentId(application.id(), ZoneId.from(environment, region));
+ controller.applications().deactivate(deploymentId.applicationId(), deploymentId.zoneId());
- // TODO: Change to return JSON
- return new StringResponse("Deactivated " + path(TenantResource.API_PATH, tenantName,
- ApplicationResource.API_PATH, applicationName,
- EnvironmentResource.API_PATH, environment,
- "region", region,
- "instance", instanceName));
- }
-
- /**
- * Promote application Chef environments. To be used by component jobs only
- */
- private HttpResponse promoteApplication(String tenantName, String applicationName, HttpRequest request) {
- try{
- ApplicationChefEnvironment chefEnvironment = new ApplicationChefEnvironment(controller.system());
- String sourceEnvironment = chefEnvironment.systemChefEnvironment();
- String targetEnvironment = chefEnvironment.applicationSourceEnvironment(TenantName.from(tenantName), ApplicationName.from(applicationName));
- controller.chefClient().copyChefEnvironment(sourceEnvironment, targetEnvironment);
- return new MessageResponse(String.format("Successfully copied environment %s to %s", sourceEnvironment, targetEnvironment));
- } catch (Exception e) {
- log.log(LogLevel.ERROR, String.format("Error during Chef copy environment. (%s.%s)", tenantName, applicationName), e);
- return ErrorResponse.internalServerError("Unable to promote Chef environments for application");
- }
- }
-
- /**
- * Promote application Chef environments for jobs that deploy applications
- */
- private HttpResponse promoteApplicationDeployment(String tenantName, String applicationName, String environmentName, String regionName, String instanceName, HttpRequest request) {
- try {
- ApplicationChefEnvironment chefEnvironment = new ApplicationChefEnvironment(controller.system());
- String sourceEnvironment = chefEnvironment.applicationSourceEnvironment(TenantName.from(tenantName), ApplicationName.from(applicationName));
- String targetEnvironment = chefEnvironment.applicationTargetEnvironment(TenantName.from(tenantName), ApplicationName.from(applicationName), Environment.from(environmentName), RegionName.from(regionName));
- controller.chefClient().copyChefEnvironment(sourceEnvironment, targetEnvironment);
- return new MessageResponse(String.format("Successfully copied environment %s to %s", sourceEnvironment, targetEnvironment));
- } catch (Exception e) {
- log.log(LogLevel.ERROR, String.format("Error during Chef copy environment. (%s.%s %s.%s)", tenantName, applicationName, environmentName, regionName), e);
- return ErrorResponse.internalServerError("Unable to promote Chef environments for application");
- }
+ return new MessageResponse("Deactivated " + deploymentId);
}
private HttpResponse notifyJobCompletion(String tenant, String application, HttpRequest request) {
@@ -1108,6 +1158,14 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
}
}
+ private HttpResponse testConfig(ApplicationId id, JobType type) {
+ var endpoints = controller.applications().clusterEndpoints(id, controller.jobController().testedZoneAndProductionZones(id, type));
+ return new SlimeJsonResponse(new TestConfigSerializer(controller.system()).configSlime(id,
+ type,
+ endpoints,
+ Collections.emptyMap()));
+ }
+
private static DeploymentJobs.JobReport toJobReport(String tenantName, String applicationName, Inspector report) {
Optional<DeploymentJobs.JobError> jobError = Optional.empty();
if (report.field("jobError").valid()) {
@@ -1144,7 +1202,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
private void toSlime(Cursor object, Tenant tenant, HttpRequest request) {
object.setString("tenant", tenant.name().value());
- object.setString("type", tentantType(tenant));
+ object.setString("type", tenantType(tenant));
switch (tenant.type()) {
case athenz:
AthenzTenant athenzTenant = (AthenzTenant) tenant;
@@ -1168,7 +1226,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
}
Cursor applicationArray = object.setArray("applications");
for (Application application : controller.applications().asList(tenant.name())) {
- if (application.id().instance().isDefault()) {// TODO: Skip non-default applications until supported properly
+ if ( ! application.id().instance().isTester()) {
if (recurseOverApplications(request))
toSlime(applicationArray.addObject(), application, request);
else
@@ -1181,7 +1239,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
private void tenantInTenantsListToSlime(Tenant tenant, URI requestURI, Cursor object) {
object.setString("tenant", tenant.name().value());
Cursor metaData = object.setObject("metaData");
- metaData.setString("type", tentantType(tenant));
+ metaData.setString("type", tenantType(tenant));
switch (tenant.type()) {
case athenz:
AthenzTenant athenzTenant = (AthenzTenant) tenant;
@@ -1257,8 +1315,11 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
object.setString("tenant", application.id().tenant().value());
object.setString("application", application.id().application().value());
object.setString("instance", application.id().instance().value());
- object.setString("url", withPath("/application/v4/tenant/" + application.id().tenant().value() +
- "/application/" + application.id().application().value(), request.getUri()).toString());
+ object.setString("url", withPath("/application/v4" +
+ "/tenant/" + application.id().tenant().value() +
+ "/application/" + application.id().application().value() +
+ "/instance/" + application.id().instance().value(),
+ request.getUri()).toString());
}
private Slime toSlime(ActivateResult result) {
@@ -1389,7 +1450,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
return ImmutableSet.of("all", "true", "deployment").contains(request.getProperty("recursive"));
}
- private static String tentantType(Tenant tenant) {
+ private static String tenantType(Tenant tenant) {
switch (tenant.type()) {
case user: return "USER";
case athenz: return "ATHENS";
@@ -1411,7 +1472,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
return new RunId(appIdFromPath(path), jobTypeFromPath(path), number);
}
- private HttpResponse submit(String tenant, String application, HttpRequest request) {
+ private HttpResponse submit(String tenant, String application, String instance, HttpRequest request) {
Map<String, byte[]> dataParts = parseDataParts(request);
Inspector submitOptions = SlimeUtils.jsonToSlime(dataParts.get(EnvironmentResource.SUBMIT_OPTIONS)).get();
SourceRevision sourceRevision = toSourceRevision(submitOptions);
@@ -1426,6 +1487,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
return JobControllerApiHandlerHelper.submitResponse(controller.jobController(),
tenant,
application,
+ instance,
sourceRevision,
authorEmail,
projectId,
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationChefEnvironment.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationChefEnvironment.java
deleted file mode 100644
index 7c32e48e218..00000000000
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationChefEnvironment.java
+++ /dev/null
@@ -1,43 +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.hosted.controller.restapi.application;
-
-import com.yahoo.config.provision.ApplicationName;
-import com.yahoo.config.provision.Environment;
-import com.yahoo.config.provision.RegionName;
-import com.yahoo.config.provision.SystemName;
-import com.yahoo.config.provision.TenantName;
-
-/**
- * Represents Chef environments for applications/deployments. Used for promotion of Chef environments
- *
- * @author mortent
- */
-public class ApplicationChefEnvironment {
-
- private final String systemChefEnvironment;
- private final String systemSuffix;
-
- public ApplicationChefEnvironment(SystemName system) {
- if (system == SystemName.main) {
- systemChefEnvironment = "hosted-verified-prod";
- systemSuffix = "";
- } else {
- systemChefEnvironment = "hosted-infra-cd";
- systemSuffix = "-cd";
- }
- }
-
- public String systemChefEnvironment() {
- return systemChefEnvironment;
- }
-
- public String applicationSourceEnvironment(TenantName tenantName, ApplicationName applicationName) {
- // placeholder and component already used in legacy chef promotion
- return String.format("hosted-instance%s_%s_%s_placeholder_component_default", systemSuffix, tenantName, applicationName);
- }
-
- public String applicationTargetEnvironment(TenantName tenantName, ApplicationName applicationName, Environment environment, RegionName regionName) {
- return String.format("hosted-instance%s_%s_%s_%s_%s_default", systemSuffix, tenantName, applicationName, regionName, environment);
- }
-
-}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/EmptyJsonResponse.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/EmptyResponse.java
index be3222cc1a8..e343615f066 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/EmptyJsonResponse.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/EmptyResponse.java
@@ -8,16 +8,13 @@ import java.io.OutputStream;
/**
* @author bratseth
*/
-public class EmptyJsonResponse extends HttpResponse {
+public class EmptyResponse extends HttpResponse {
- public EmptyJsonResponse() {
+ public EmptyResponse() {
super(200);
}
@Override
public void render(OutputStream stream) {}
- @Override
- public String getContentType() { return "application/json"; }
-
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java
index 48cf2a7824d..b34ea79c670 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java
@@ -6,26 +6,26 @@ import com.yahoo.component.Version;
import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.slime.Cursor;
import com.yahoo.slime.Slime;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.NotExistsException;
+import com.yahoo.vespa.hosted.controller.api.integration.LogEntry;
+import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.RunId;
-import com.yahoo.config.provision.zone.ZoneId;
-import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
+import com.yahoo.vespa.hosted.controller.api.integration.deployment.SourceRevision;
import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
import com.yahoo.vespa.hosted.controller.application.Change;
import com.yahoo.vespa.hosted.controller.application.Deployment;
import com.yahoo.vespa.hosted.controller.application.JobStatus;
-import com.yahoo.vespa.hosted.controller.api.integration.deployment.SourceRevision;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentSteps;
import com.yahoo.vespa.hosted.controller.deployment.JobController;
-import com.yahoo.vespa.hosted.controller.api.integration.LogEntry;
-import com.yahoo.vespa.hosted.controller.deployment.RunLog;
import com.yahoo.vespa.hosted.controller.deployment.Run;
+import com.yahoo.vespa.hosted.controller.deployment.RunLog;
import com.yahoo.vespa.hosted.controller.deployment.Step;
import com.yahoo.vespa.hosted.controller.deployment.Versions;
import com.yahoo.vespa.hosted.controller.restapi.MessageResponse;
@@ -95,11 +95,13 @@ class JobControllerApiHandlerHelper {
Cursor responseObject = slime.setObject();
Cursor lastVersionsObject = responseObject.setObject("lastVersions");
- lastPlatformToSlime(lastVersionsObject.setObject("platform"), controller, application, change, steps);
- lastApplicationToSlime(lastVersionsObject.setObject("application"), application, change, steps, controller);
+ if (application.deploymentJobs().statusOf(component).flatMap(JobStatus::lastSuccess).isPresent()) {
+ lastPlatformToSlime(lastVersionsObject.setObject("platform"), controller, application, change, steps);
+ lastApplicationToSlime(lastVersionsObject.setObject("application"), application, change, steps, controller);
+ }
+ Cursor deployingObject = responseObject.setObject("deploying");
if ( ! change.isEmpty()) {
- Cursor deployingObject = responseObject.setObject("deploying");
change.platform().ifPresent(version -> deployingObject.setString("platform", version.toString()));
change.application().ifPresent(version -> applicationVersionToSlime(deployingObject.setObject("application"), version));
}
@@ -132,6 +134,17 @@ class JobControllerApiHandlerHelper {
running,
baseUriForJobs.resolve(baseUriForJobs.getPath() + "/" + type.jobName()).normalize());
});
+
+ Cursor devJobsObject = responseObject.setObject("devJobs");
+ for (JobType type : JobType.allIn(controller.system()))
+ if ( type.environment() != null
+ && type.environment().isManuallyDeployed()
+ && application.deployments().containsKey(type.zone(controller.system())))
+ controller.jobController().last(application.id(), type)
+ .ifPresent(last -> runToSlime(devJobsObject.setObject(type.jobName()).setArray("runs").addObject(),
+ last,
+ baseUriForJobs.resolve(baseUriForJobs.getPath() + "/" + type.jobName()).normalize()));
+
return new SlimeJsonResponse(slime);
}
@@ -327,9 +340,10 @@ class JobControllerApiHandlerHelper {
/** Returns the status of the task represented by the given step, if it has started. */
private static Optional<String> taskStatus(Step step, Run run) {
- return run.readySteps().contains(step) ? Optional.of("running")
- : run.steps().get(step) != unfinished ? Optional.of(run.steps().get(step).name())
- : Optional.empty();
+ return run.readySteps().contains(step) ? Optional.of("running")
+ : Optional.ofNullable(run.steps().get(step))
+ .filter(status -> status != unfinished)
+ .map(Step.Status::name);
}
/** Returns a response with the runs for the given job type. */
@@ -351,6 +365,9 @@ class JobControllerApiHandlerHelper {
private static void applicationVersionToSlime(Cursor versionObject, ApplicationVersion version) {
versionObject.setString("hash", version.id());
+ if (version.isUnknown())
+ return;
+
versionObject.setLong("build", version.buildNumber().getAsLong());
Cursor sourceObject = versionObject.setObject("source");
sourceObject.setString("gitRepository", version.source().get().repository());
@@ -399,10 +416,10 @@ class JobControllerApiHandlerHelper {
*
* @return Response with the new application version
*/
- static HttpResponse submitResponse(JobController jobController, String tenant, String application,
+ static HttpResponse submitResponse(JobController jobController, String tenant, String application, String instance,
SourceRevision sourceRevision, String authorEmail, long projectId,
ApplicationPackage applicationPackage, byte[] testPackage) {
- ApplicationVersion version = jobController.submit(ApplicationId.from(tenant, application, "default"),
+ ApplicationVersion version = jobController.submit(ApplicationId.from(tenant, application, instance),
sourceRevision,
authorEmail,
projectId,
@@ -427,8 +444,8 @@ class JobControllerApiHandlerHelper {
}
/** Unregisters the application from the internal deployment pipeline. */
- static HttpResponse unregisterResponse(JobController jobs, String tenantName, String applicationName) {
- ApplicationId id = ApplicationId.from(tenantName, applicationName, "default");
+ static HttpResponse unregisterResponse(JobController jobs, String tenantName, String applicationName, String instanceName) {
+ ApplicationId id = ApplicationId.from(tenantName, applicationName, instanceName);
jobs.unregister(id);
Slime slime = new Slime();
slime.setObject().setString("message", "Unregistered '" + id + "' from internal deployment pipeline.");
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiHandler.java
index a59e0e9130f..dde79e78850 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiHandler.java
@@ -100,7 +100,8 @@ public class ControllerApiHandler extends AuditLoggingRequestHandler {
}
private HttpResponse setActive(String jobName, boolean active) {
- if ( ! maintenance.jobControl().jobs().contains(jobName))
+ boolean activatingInactiveJob = active && !maintenance.jobControl().isActive(jobName);
+ if (!activatingInactiveJob && !maintenance.jobControl().jobs().contains(jobName))
return ErrorResponse.notFoundError("No job named '" + jobName + "'");
maintenance.jobControl().setActive(jobName, active);
return new MessageResponse((active ? "Re-activated" : "Deactivated" ) + " job '" + jobName + "'");
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/cost/CostApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/cost/CostApiHandler.java
index bae790a49ad..796f786d823 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/cost/CostApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/cost/CostApiHandler.java
@@ -6,7 +6,7 @@ import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.container.jdisc.LoggingRequestHandler;
import com.yahoo.restapi.Path;
import com.yahoo.vespa.hosted.controller.Controller;
-import com.yahoo.vespa.hosted.controller.api.integration.noderepository.NodeRepositoryClientInterface;
+import com.yahoo.vespa.hosted.controller.api.integration.configserver.NodeRepository;
import com.yahoo.vespa.hosted.controller.restapi.ErrorResponse;
import com.yahoo.vespa.hosted.controller.restapi.StringResponse;
import com.yahoo.vespa.hosted.controller.restapi.cost.config.SelfHostedCostConfig;
@@ -19,10 +19,10 @@ import static com.yahoo.jdisc.http.HttpRequest.Method.GET;
public class CostApiHandler extends LoggingRequestHandler {
private final Controller controller;
- private final NodeRepositoryClientInterface nodeRepository;
+ private final NodeRepository nodeRepository;
private final SelfHostedCostConfig selfHostedCostConfig;
- public CostApiHandler(Context ctx, Controller controller, NodeRepositoryClientInterface nodeRepository, SelfHostedCostConfig selfHostedCostConfig) {
+ public CostApiHandler(Context ctx, Controller controller, NodeRepository nodeRepository, SelfHostedCostConfig selfHostedCostConfig) {
super(ctx);
this.controller = controller;
this.nodeRepository = nodeRepository;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/cost/CostCalculator.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/cost/CostCalculator.java
index 18c00d69b62..919cade1b05 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/cost/CostCalculator.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/cost/CostCalculator.java
@@ -4,8 +4,8 @@ import com.yahoo.config.provision.CloudName;
import com.yahoo.config.provision.Environment;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.api.identifiers.Property;
+import com.yahoo.vespa.hosted.controller.api.integration.configserver.NodeRepository;
import com.yahoo.vespa.hosted.controller.api.integration.noderepository.NodeOwner;
-import com.yahoo.vespa.hosted.controller.api.integration.noderepository.NodeRepositoryClientInterface;
import com.yahoo.vespa.hosted.controller.api.integration.noderepository.NodeRepositoryNode;
import com.yahoo.vespa.hosted.controller.api.integration.resource.ResourceAllocation;
import com.yahoo.vespa.hosted.controller.restapi.cost.config.SelfHostedCostConfig;
@@ -25,7 +25,7 @@ public class CostCalculator {
private static final double SELF_HOSTED_DISCOUNT = .5;
- public static String resourceShareByPropertyToCsv(NodeRepositoryClientInterface nodeRepository,
+ public static String resourceShareByPropertyToCsv(NodeRepository nodeRepository,
Controller controller,
Clock clock,
SelfHostedCostConfig selfHostedCostConfig,
@@ -34,8 +34,8 @@ public class CostCalculator {
String date = LocalDate.now(clock).toString();
List<NodeRepositoryNode> nodes = controller.zoneRegistry().zones()
- .reachable().in(Environment.prod).ofCloud(cloudName).ids().stream()
- .flatMap(zoneId -> uncheck(() -> nodeRepository.listNodes(zoneId, true).nodes().stream()))
+ .reachable().in(Environment.prod).ofCloud(cloudName).zones().stream()
+ .flatMap(zone -> uncheck(() -> nodeRepository.listNodes(zone.getId()).nodes().stream()))
.filter(node -> node.getOwner() != null && !node.getOwner().getTenant().equals("hosted-vespa"))
.collect(Collectors.toList());
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java
index 978b7e4397d..44b67a186b8 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java
@@ -8,18 +8,18 @@ import com.yahoo.config.provision.HostName;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.container.jdisc.LoggingRequestHandler;
+import com.yahoo.restapi.Path;
import com.yahoo.slime.Cursor;
import com.yahoo.slime.Slime;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.application.JobList;
import com.yahoo.vespa.hosted.controller.application.JobStatus;
-import com.yahoo.vespa.hosted.controller.versions.VespaVersion;
import com.yahoo.vespa.hosted.controller.restapi.ErrorResponse;
import com.yahoo.vespa.hosted.controller.restapi.SlimeJsonResponse;
import com.yahoo.vespa.hosted.controller.restapi.Uri;
-import com.yahoo.vespa.hosted.controller.restapi.application.EmptyJsonResponse;
-import com.yahoo.restapi.Path;
+import com.yahoo.vespa.hosted.controller.restapi.application.EmptyResponse;
+import com.yahoo.vespa.hosted.controller.versions.VespaVersion;
import com.yahoo.yolean.Exceptions;
import java.util.Optional;
@@ -71,7 +71,7 @@ public class DeploymentApiHandler extends LoggingRequestHandler {
private HttpResponse handleOPTIONS() {
// We implement this to avoid redirect loops on OPTIONS requests from browsers, but do not really bother
// spelling out the methods supported at each path, which we should
- EmptyJsonResponse response = new EmptyJsonResponse();
+ EmptyResponse response = new EmptyResponse();
response.headers().put("Allow", "GET,OPTIONS");
return response;
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiHandler.java
index 5454d71185a..bc360fe3c6f 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiHandler.java
@@ -5,6 +5,7 @@ import com.yahoo.component.Version;
import com.yahoo.config.provision.CloudName;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.io.IOUtils;
@@ -30,6 +31,7 @@ import java.util.List;
import java.util.Set;
import java.util.StringJoiner;
import java.util.logging.Level;
+import java.util.stream.Collectors;
/**
* This implements the /os/v1 API which provides operators with information about, and scheduling of OS upgrades for
@@ -123,7 +125,7 @@ public class OsApiHandler extends AuditLoggingRequestHandler {
ZoneList zones = controller.zoneRegistry().zones().controllerUpgraded();
if (path.get("region") != null) zones = zones.in(RegionName.from(path.get("region")));
if (path.get("environment") != null) zones = zones.in(Environment.from(path.get("environment")));
- return zones.ids();
+ return zones.zones().stream().map(ZoneApi::getId).collect(Collectors.toList());
}
private Slime setOsVersion(HttpRequest request) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java
index 5ef997b6d55..7a76f13392d 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java
@@ -13,20 +13,19 @@ import com.yahoo.slime.Cursor;
import com.yahoo.slime.Inspector;
import com.yahoo.slime.Slime;
import com.yahoo.vespa.config.SlimeUtils;
+import com.yahoo.vespa.hosted.controller.api.integration.user.Roles;
import com.yahoo.vespa.hosted.controller.api.integration.user.UserId;
import com.yahoo.vespa.hosted.controller.api.integration.user.UserManagement;
-import com.yahoo.vespa.hosted.controller.api.integration.user.Roles;
import com.yahoo.vespa.hosted.controller.api.role.Role;
import com.yahoo.vespa.hosted.controller.api.role.RoleDefinition;
import com.yahoo.vespa.hosted.controller.restapi.ErrorResponse;
import com.yahoo.vespa.hosted.controller.restapi.MessageResponse;
import com.yahoo.vespa.hosted.controller.restapi.SlimeJsonResponse;
-import com.yahoo.vespa.hosted.controller.restapi.application.EmptyJsonResponse;
+import com.yahoo.vespa.hosted.controller.restapi.application.EmptyResponse;
import com.yahoo.yolean.Exceptions;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -99,7 +98,7 @@ public class UserApiHandler extends LoggingRequestHandler {
}
private HttpResponse handleOPTIONS() {
- EmptyJsonResponse response = new EmptyJsonResponse();
+ EmptyResponse response = new EmptyResponse();
response.headers().put("Allow", "GET,PUT,POST,PATCH,DELETE,OPTIONS");
return response;
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiHandler.java
index fcf01f461a1..6cfaed93fa9 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiHandler.java
@@ -3,6 +3,8 @@ package com.yahoo.vespa.hosted.controller.restapi.zone.v1;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.Zone;
+import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
@@ -28,6 +30,8 @@ import java.util.stream.Collectors;
@SuppressWarnings("unused")
public class ZoneApiHandler extends LoggingRequestHandler {
+ private static final String OPTIONAL_PREFIX = "/api";
+
private final ZoneRegistry zoneRegistry;
public ZoneApiHandler(LoggingRequestHandler.Context parentCtx, ZoneRegistry zoneRegistry) {
@@ -54,7 +58,7 @@ public class ZoneApiHandler extends LoggingRequestHandler {
}
private HttpResponse get(HttpRequest request) {
- Path path = new Path(request.getUri());
+ Path path = new Path(request.getUri(), OPTIONAL_PREFIX);
if (path.matches("/zone/v1")) {
return root(request);
}
@@ -68,8 +72,8 @@ public class ZoneApiHandler extends LoggingRequestHandler {
}
private HttpResponse root(HttpRequest request) {
- List<Environment> environments = zoneRegistry.zones().all().ids().stream()
- .map(ZoneId::environment)
+ List<Environment> environments = zoneRegistry.zones().all().zones().stream()
+ .map(ZoneApi::getEnvironment)
.distinct()
.sorted(Comparator.comparing(Environment::value))
.collect(Collectors.toList());
@@ -88,17 +92,16 @@ public class ZoneApiHandler extends LoggingRequestHandler {
}
private HttpResponse environment(HttpRequest request, Environment environment) {
- List<ZoneId> zones = zoneRegistry.zones().all().in(environment).ids();
Slime slime = new Slime();
Cursor root = slime.setArray();
- zones.forEach(zone -> {
+ zoneRegistry.zones().all().in(environment).zones().forEach(zone -> {
Cursor object = root.addObject();
- object.setString("name", zone.region().value());
+ object.setString("name", zone.getRegionName().value());
object.setString("url", request.getUri()
.resolve("/zone/v2/environment/")
.resolve(environment.value() + "/")
.resolve("region/")
- .resolve(zone.region().value())
+ .resolve(zone.getRegionName().value())
.toString());
});
return new SlimeJsonResponse(slime);
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java
index 9d95383fbfb..f0259fc4d51 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java
@@ -94,16 +94,16 @@ public class ZoneApiHandler extends AuditLoggingRequestHandler {
Cursor root = slime.setObject();
Cursor uris = root.setArray("uris");
ZoneList zoneList = zoneRegistry.zones().reachable();
- zoneList.ids().forEach(zoneId -> uris.addString(request.getUri()
+ zoneList.zones().forEach(zone -> uris.addString(request.getUri()
.resolve("/zone/v2/")
- .resolve(zoneId.environment().value() + "/")
- .resolve(zoneId.region().value())
+ .resolve(zone.getEnvironment().value() + "/")
+ .resolve(zone.getRegionName().value())
.toString()));
Cursor zones = root.setArray("zones");
- zoneList.ids().forEach(zoneId -> {
+ zoneList.zones().forEach(zone -> {
Cursor object = zones.addObject();
- object.setString("environment", zoneId.environment().value());
- object.setString("region", zoneId.region().value());
+ object.setString("environment", zone.getEnvironment().value());
+ object.setString("region", zone.getRegionName().value());
});
return new SlimeJsonResponse(slime);
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepository.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepository.java
index b3953c47c01..f2bc50ec445 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepository.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepository.java
@@ -1,18 +1,30 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.rotation;
+import com.yahoo.collections.Pair;
+import com.yahoo.config.application.api.Endpoint;
+import com.yahoo.config.model.api.ContainerEndpoint;
+import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.Environment;
+import com.yahoo.config.provision.RegionName;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.ApplicationController;
+import com.yahoo.vespa.hosted.controller.application.AssignedRotation;
+import com.yahoo.vespa.hosted.controller.application.EndpointId;
import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
import com.yahoo.vespa.hosted.rotation.config.RotationsConfig;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Optional;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.function.Predicate;
import java.util.logging.Logger;
import java.util.stream.Collectors;
@@ -47,7 +59,12 @@ public class RotationRepository {
/** Get rotation for given application */
public Optional<Rotation> getRotation(Application application) {
- return application.rotation().map(allRotations::get);
+ return application.rotations().stream().map(allRotations::get).findFirst();
+ }
+
+ /** Get rotation for the given rotationId */
+ public Optional<Rotation> getRotation(RotationId rotationId) {
+ return Optional.of(allRotations.get(rotationId));
}
/**
@@ -60,8 +77,8 @@ public class RotationRepository {
* @param lock Lock which must be acquired by the caller
*/
public Rotation getOrAssignRotation(Application application, RotationLock lock) {
- if (application.rotation().isPresent()) {
- return allRotations.get(application.rotation().get());
+ if (! application.rotations().isEmpty()) {
+ return allRotations.get(application.rotations().get(0));
}
if (application.deploymentSpec().globalServiceId().isEmpty()) {
throw new IllegalArgumentException("global-service-id is not set in deployment spec");
@@ -76,13 +93,123 @@ public class RotationRepository {
}
/**
+ * Returns rotation assignments for all endpoints in application.
+ *
+ * If rotations are already assigned, these will be returned.
+ * If rotations are not assigned, a new assignment will be created taking new rotations from the repository.
+ * This method supports both global-service-id as well as the new endpoints tag.
+ *
+ * @param application The application requesting rotations.
+ * @param lock Lock which by acquired by the caller
+ * @return List of rotation assignments - either new or existing.
+ */
+ public List<AssignedRotation> getOrAssignRotations(Application application, RotationLock lock) {
+ if (application.deploymentSpec().globalServiceId().isPresent() && ! application.deploymentSpec().endpoints().isEmpty()) {
+ throw new IllegalArgumentException("Cannot provision rotations with both global-service-id and 'endpoints'");
+ }
+
+ // Support the older case of setting global-service-id
+ if (application.deploymentSpec().globalServiceId().isPresent()) {
+ final var regions = application.deploymentSpec().zones().stream()
+ .filter(zone -> zone.environment().isProduction())
+ .flatMap(zone -> zone.region().stream())
+ .collect(Collectors.toSet());
+
+ final var rotation = getOrAssignRotation(application, lock);
+
+ return List.of(
+ new AssignedRotation(
+ new ClusterSpec.Id(application.deploymentSpec().globalServiceId().get()),
+ EndpointId.default_(),
+ rotation.id(),
+ regions
+ )
+ );
+ }
+
+ final Map<EndpointId, AssignedRotation> existingAssignments = existingEndpointAssignments(application);
+ final Map<EndpointId, AssignedRotation> updatedAssignments = assignRotationsToEndpoints(application, existingAssignments, lock);
+
+ existingAssignments.putAll(updatedAssignments);
+
+ return List.copyOf(existingAssignments.values());
+ }
+
+ private Map<EndpointId, AssignedRotation> assignRotationsToEndpoints(Application application, Map<EndpointId, AssignedRotation> existingAssignments, RotationLock lock) {
+ final var availableRotations = new ArrayList<>(availableRotations(lock).values());
+
+ final var neededRotations = application.deploymentSpec().endpoints().stream()
+ .filter(Predicate.not(endpoint -> existingAssignments.containsKey(EndpointId.of(endpoint.endpointId()))))
+ .collect(Collectors.toSet());
+
+ if (neededRotations.size() > availableRotations.size()) {
+ throw new IllegalStateException("Hosted Vespa ran out of rotations, unable to assign rotation: need " + neededRotations.size() + ", have " + availableRotations.size());
+ }
+
+ return neededRotations.stream()
+ .map(endpoint -> {
+ return new AssignedRotation(
+ new ClusterSpec.Id(endpoint.containerId()),
+ EndpointId.of(endpoint.endpointId()),
+ availableRotations.remove(0).id(),
+ endpoint.regions()
+ );
+ })
+ .collect(
+ Collectors.toMap(
+ AssignedRotation::endpointId,
+ Function.identity(),
+ (a, b) -> { throw new IllegalStateException("Duplicate entries:" + a + ", " + b); },
+ LinkedHashMap::new
+ )
+ );
+ }
+
+ private Map<EndpointId, AssignedRotation> existingEndpointAssignments(Application application) {
+ //
+ // Get the regions that has been configured for an endpoint. Empty set if the endpoint
+ // is no longer mentioned in the configuration file.
+ //
+ final Function<EndpointId, Set<RegionName>> configuredRegionsForEndpoint = endpointId -> {
+ return application.deploymentSpec().endpoints().stream()
+ .filter(endpoint -> endpointId.id().equals(endpoint.endpointId()))
+ .map(Endpoint::regions)
+ .findFirst()
+ .orElse(Set.of());
+ };
+
+ //
+ // Build a new AssignedRotation instance where we update set of regions from the configuration instead
+ // of using the one already mentioned in the assignment. This allows us to overwrite the set of regions
+ // when
+ final Function<AssignedRotation, AssignedRotation> assignedRotationWithConfiguredRegions = assignedRotation -> {
+ return new AssignedRotation(
+ assignedRotation.clusterId(),
+ assignedRotation.endpointId(),
+ assignedRotation.rotationId(),
+ configuredRegionsForEndpoint.apply(assignedRotation.endpointId())
+ );
+ };
+
+ return application.assignedRotations().stream()
+ .collect(
+ Collectors.toMap(
+ AssignedRotation::endpointId,
+ assignedRotationWithConfiguredRegions,
+ (a, b) -> { throw new IllegalStateException("Duplicate entries: " + a + ", " + b); },
+ LinkedHashMap::new
+ )
+ );
+ }
+
+ /**
* Returns all unassigned rotations
* @param lock Lock which must be acquired by the caller
*/
public Map<RotationId, Rotation> availableRotations(@SuppressWarnings("unused") RotationLock lock) {
List<RotationId> assignedRotations = applications.asList().stream()
- .filter(application -> application.rotation().isPresent())
- .map(application -> application.rotation().get())
+ .filter(application -> ! application.rotations().isEmpty())
+ .flatMap(application -> application.rotations().stream())
.collect(Collectors.toList());
Map<RotationId, Rotation> unassignedRotations = new LinkedHashMap<>(this.allRotations);
assignedRotations.forEach(unassignedRotations::remove);
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tls/ControllerSslContextFactoryProvider.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tls/ControllerSslContextFactoryProvider.java
index dcc61b13bab..1ac82317695 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tls/ControllerSslContextFactoryProvider.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tls/ControllerSslContextFactoryProvider.java
@@ -2,15 +2,17 @@
package com.yahoo.vespa.hosted.controller.tls;
import com.google.inject.Inject;
-import com.yahoo.component.AbstractComponent;
import com.yahoo.container.jdisc.secretstore.SecretStore;
-import com.yahoo.jdisc.http.ssl.SslContextFactoryProvider;
+import com.yahoo.jdisc.http.ssl.impl.TlsContextBasedProvider;
import com.yahoo.security.KeyStoreBuilder;
import com.yahoo.security.KeyStoreType;
import com.yahoo.security.KeyUtils;
+import com.yahoo.security.SslContextBuilder;
import com.yahoo.security.X509CertificateUtils;
+import com.yahoo.security.tls.DefaultTlsContext;
+import com.yahoo.security.tls.PeerAuthentication;
+import com.yahoo.security.tls.TlsContext;
import com.yahoo.vespa.hosted.controller.tls.config.TlsConfig;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
import java.nio.file.Files;
import java.nio.file.Paths;
@@ -28,11 +30,11 @@ import java.util.concurrent.ConcurrentHashMap;
* @author bjorncs
*/
@SuppressWarnings("unused") // Injected
-public class ControllerSslContextFactoryProvider extends AbstractComponent implements SslContextFactoryProvider {
+public class ControllerSslContextFactoryProvider extends TlsContextBasedProvider {
private final KeyStore truststore;
private final KeyStore keystore;
- private final Map<Integer, SslContextFactory> sslContextFactories = new ConcurrentHashMap<>();
+ private final Map<Integer, TlsContext> tlsContextMap = new ConcurrentHashMap<>();
@Inject
public ControllerSslContextFactoryProvider(SecretStore secretStore, TlsConfig config) {
@@ -50,21 +52,17 @@ public class ControllerSslContextFactoryProvider extends AbstractComponent imple
}
@Override
- public SslContextFactory getInstance(String containerId, int port) {
- return sslContextFactories.computeIfAbsent(port, this::createSslContextFactory);
+ protected TlsContext getTlsContext(String containerId, int port) {
+ return tlsContextMap.computeIfAbsent(port, this::createTlsContext);
}
- /** Create a SslContextFactory backed by an in-memory key and trust store */
- private SslContextFactory createSslContextFactory(int port) {
- SslContextFactory factory = new SslContextFactory();
- if (port != 443) {
- factory.setWantClientAuth(true);
- }
- factory.setTrustStore(truststore);
- factory.setKeyStore(keystore);
- factory.setKeyStorePassword("");
- factory.setEndpointIdentificationAlgorithm(null); // disable https hostname verification of clients (must be disabled when using Athenz x509 certificates)
- return factory;
+ private TlsContext createTlsContext(int port) {
+ return new DefaultTlsContext(
+ new SslContextBuilder()
+ .withKeyStore(keystore, new char[0])
+ .withTrustStore(truststore)
+ .build(),
+ port != 443 ? PeerAuthentication.WANT : PeerAuthentication.DISABLED);
}
/** Get private key from secret store **/
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/MavenRepositoryClient.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/MavenRepositoryClient.java
new file mode 100644
index 00000000000..9f3addd4992
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/MavenRepositoryClient.java
@@ -0,0 +1,64 @@
+package com.yahoo.vespa.hosted.controller.versions;
+
+import com.yahoo.vespa.hosted.controller.api.integration.maven.ArtifactId;
+import com.yahoo.vespa.hosted.controller.api.integration.maven.MavenRepository;
+import com.yahoo.vespa.hosted.controller.api.integration.maven.Metadata;
+import com.yahoo.vespa.hosted.controller.maven.repository.config.MavenRepositoryConfig;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+/**
+ * Http client implementation of a {@link MavenRepository}, which uses a configured repository and artifact ID.
+ *
+ * @author jonmv
+ */
+public class MavenRepositoryClient implements MavenRepository {
+
+ private final HttpClient client;
+ private final URI apiUrl;
+ private final ArtifactId id;
+
+ public MavenRepositoryClient(MavenRepositoryConfig config) {
+ this.client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5)).build();
+ this.apiUrl = URI.create(config.apiUrl() + "/").normalize();
+ this.id = new ArtifactId(config.groupId(), config.artifactId());
+ }
+
+ @Override
+ public Metadata metadata() {
+ try {
+ HttpRequest request = HttpRequest.newBuilder(withArtifactPath(apiUrl, id)).build();
+ HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString(UTF_8));
+ if (response.statusCode() != 200)
+ throw new RuntimeException("Status code '" + response.statusCode() + "' and body\n'''\n" +
+ response.body() + "\n'''\nfor request " + request);
+
+ return Metadata.fromXml(response.body());
+ }
+ catch (IOException | InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public ArtifactId artifactId() {
+ return id;
+ }
+
+ static URI withArtifactPath(URI baseUrl, ArtifactId id) {
+ List<String> parts = new ArrayList<>(List.of(id.groupId().split("\\.")));
+ parts.add(id.artifactId());
+ parts.add("maven-metadata.xml");
+ return baseUrl.resolve(String.join("/", parts));
+ }
+
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/OsVersionStatus.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/OsVersionStatus.java
index a18c1f47036..f5b9d8263e5 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/OsVersionStatus.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/OsVersionStatus.java
@@ -7,6 +7,7 @@ import com.yahoo.config.provision.CloudName;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.application.SystemApplication;
@@ -67,27 +68,25 @@ public class OsVersionStatus {
controller.osVersions().forEach(osVersion -> versions.put(osVersion, new ArrayList<>()));
for (SystemApplication application : SystemApplication.all()) {
- if (application.nodeTypesWithUpgradableOs().isEmpty()) {
- continue; // Avoid querying applications that do not contain nodes with upgradable OS
+ if (!application.isEligibleForOsUpgrades()) {
+ continue; // Avoid querying applications that are not eligible for OS upgrades
}
- for (ZoneId zone : zonesToUpgrade(controller)) {
- controller.configServer().nodeRepository().list(zone, application.id()).stream()
+ for (ZoneApi zone : zonesToUpgrade(controller)) {
+ controller.configServer().nodeRepository().list(zone.getId(), application.id()).stream()
.filter(node -> OsUpgrader.eligibleForUpgrade(node, application))
- .map(node -> new Node(node.hostname(), node.currentOsVersion(), zone.environment(), zone.region()))
- .forEach(node -> versions.compute(new OsVersion(node.version(), zone.cloud()), (ignored, nodes) -> {
- if (nodes == null) {
- nodes = new ArrayList<>();
- }
- nodes.add(node);
- return nodes;
- }));
+ .map(node -> new Node(node.hostname(), node.currentOsVersion(), zone.getEnvironment(), zone.getRegionName()))
+ .forEach(node -> {
+ var version = new OsVersion(node.version(), zone.getCloudName());
+ versions.putIfAbsent(version, new ArrayList<>());
+ versions.get(version).add(node);
+ });
}
}
return new OsVersionStatus(versions);
}
- private static List<ZoneId> zonesToUpgrade(Controller controller) {
+ private static List<ZoneApi> zonesToUpgrade(Controller controller) {
return controller.zoneRegistry().osUpgradePolicies().stream()
.flatMap(upgradePolicy -> upgradePolicy.asList().stream())
.flatMap(Collection::stream)
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java
index e2d4c90f443..ab5fd2714e5 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java
@@ -6,12 +6,13 @@ import com.yahoo.collections.ListMap;
import com.yahoo.component.Version;
import com.yahoo.component.Vtag;
import com.yahoo.config.provision.HostName;
+import com.yahoo.config.provision.zone.ZoneApi;
+import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.log.LogLevel;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.Node;
import com.yahoo.vespa.hosted.controller.api.integration.github.GitSha;
-import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.application.ApplicationList;
import com.yahoo.vespa.hosted.controller.application.Deployment;
import com.yahoo.vespa.hosted.controller.application.JobList;
@@ -129,14 +130,17 @@ public class VersionStatus {
Collection<DeploymentStatistics> deploymentStatistics = computeDeploymentStatistics(infrastructureVersions,
controller.applications().asList());
List<VespaVersion> versions = new ArrayList<>();
+ List<Version> releasedVersions = controller.mavenRepository().metadata().versions();
for (DeploymentStatistics statistics : deploymentStatistics) {
if (statistics.version().isEmpty()) continue;
try {
+ boolean isReleased = Collections.binarySearch(releasedVersions, statistics.version()) >= 0;
VespaVersion vespaVersion = createVersion(statistics,
statistics.version().equals(controllerVersion),
statistics.version().equals(systemVersion),
+ isReleased,
systemApplicationVersions.getList(statistics.version()),
controller);
versions.add(vespaVersion);
@@ -145,25 +149,28 @@ public class VersionStatus {
statistics.version().toFullString(), e);
}
}
+
Collections.sort(versions);
return new VersionStatus(versions);
}
private static ListMap<Version, HostName> findSystemApplicationVersions(Controller controller) {
- List<ZoneId> zones = controller.zoneRegistry().zones()
- .controllerUpgraded()
- .ids();
ListMap<Version, HostName> versions = new ListMap<>();
- for (ZoneId zone : zones) {
+ for (ZoneApi zone : controller.zoneRegistry().zones().controllerUpgraded().zones()) {
for (SystemApplication application : SystemApplication.all()) {
- boolean configConverged = application.configConvergedIn(zone, controller);
+ List<Node> eligibleForUpgradeApplicationNodes = controller.configServer().nodeRepository()
+ .list(zone.getId(), application.id()).stream()
+ .filter(SystemUpgrader::eligibleForUpgrade)
+ .collect(Collectors.toList());
+ if (eligibleForUpgradeApplicationNodes.isEmpty())
+ continue;
+
+ boolean configConverged = application.configConvergedIn(zone.getId(), controller, Optional.empty());
if (!configConverged) {
log.log(LogLevel.WARNING, "Config for " + application.id() + " in " + zone + " has not converged");
}
- for (Node node : controller.configServer().nodeRepository().list(zone, application.id()).stream()
- .filter(SystemUpgrader::eligibleForUpgrade)
- .collect(Collectors.toList())) {
+ for (Node node : eligibleForUpgradeApplicationNodes) {
// Only use current node version if config has converged
Version nodeVersion = configConverged ? node.currentVersion() : controller.systemVersion();
versions.put(nodeVersion, node.hostname());
@@ -233,10 +240,11 @@ public class VersionStatus {
}
return versionMap.values();
}
-
+
private static VespaVersion createVersion(DeploymentStatistics statistics,
boolean isControllerVersion,
- boolean isSystemVersion,
+ boolean isSystemVersion,
+ boolean isReleased,
Collection<HostName> configServerHostnames,
Controller controller) {
GitSha gitSha = controller.gitHub().getCommit(VESPA_REPO_OWNER, VESPA_REPO, statistics.version().toFullString());
@@ -255,6 +263,7 @@ public class VersionStatus {
gitSha.sha, committedAt,
isControllerVersion,
isSystemVersion,
+ isReleased,
configServerHostnames,
confidence
);
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 ffbf24be12a..117ce80adaa 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
@@ -27,12 +27,13 @@ public class VespaVersion implements Comparable<VespaVersion> {
private final Instant committedAt;
private final boolean isControllerVersion;
private final boolean isSystemVersion;
+ private final boolean isReleased;
private final DeploymentStatistics statistics;
private final ImmutableSet<HostName> systemApplicationHostnames;
private final Confidence confidence;
public VespaVersion(DeploymentStatistics statistics, String releaseCommit, Instant committedAt,
- boolean isControllerVersion, boolean isSystemVersion,
+ boolean isControllerVersion, boolean isSystemVersion, boolean isReleased,
Collection<HostName> systemApplicationHostnames,
Confidence confidence) {
this.statistics = statistics;
@@ -40,6 +41,7 @@ public class VespaVersion implements Comparable<VespaVersion> {
this.committedAt = committedAt;
this.isControllerVersion = isControllerVersion;
this.isSystemVersion = isSystemVersion;
+ this.isReleased = isReleased;
this.systemApplicationHostnames = ImmutableSet.copyOf(systemApplicationHostnames);
this.confidence = confidence;
}
@@ -102,6 +104,9 @@ public class VespaVersion implements Comparable<VespaVersion> {
*/
public boolean isSystemVersion() { return isSystemVersion; }
+ /** Returns whether the artifacts of this release are available in the configured maven repository. */
+ public boolean isReleased() { return isReleased; }
+
/** Returns the hosts allocated to system applications (across all zones) which are currently of this version */
public Set<HostName> systemApplicationHostnames() { return systemApplicationHostnames; }
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/package-info.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/package-info.java
new file mode 100644
index 00000000000..c6f2c1e427d
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/package-info.java
@@ -0,0 +1,5 @@
+// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package com.yahoo.vespa.hosted.controller.versions;
+
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/controller-server/src/main/resources/configdefinitions/maven-repository.def b/controller-server/src/main/resources/configdefinitions/maven-repository.def
new file mode 100644
index 00000000000..0fd2d410e9b
--- /dev/null
+++ b/controller-server/src/main/resources/configdefinitions/maven-repository.def
@@ -0,0 +1,15 @@
+# Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+namespace=vespa.hosted.controller.maven.repository.config
+
+
+# URL to the Maven repository API that holds artifacts for tenants in the controller's system
+#
+apiUrl string default=https://repo.maven.apache.org/maven2/
+
+# Group ID of the artifact to list versions for
+#
+groupId string default=com.yahoo.vespa
+
+# Artifact ID of the artifact to list versions for
+#
+artifactId string default=tenant-base
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java
index 59d3f6dbb84..17ba19d8f7d 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java
@@ -23,6 +23,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.deployment.SourceRevisi
import com.yahoo.vespa.hosted.controller.api.integration.dns.Record;
import com.yahoo.vespa.hosted.controller.api.integration.routing.RoutingEndpoint;
import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
+import com.yahoo.vespa.hosted.controller.application.AssignedRotation;
import com.yahoo.vespa.hosted.controller.application.Deployment;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobError;
import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics;
@@ -30,6 +31,7 @@ import com.yahoo.vespa.hosted.controller.application.JobStatus;
import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder;
import com.yahoo.vespa.hosted.controller.deployment.BuildJob;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester;
+import com.yahoo.vespa.hosted.controller.integration.ZoneApiMock;
import com.yahoo.vespa.hosted.controller.rotation.RotationId;
import com.yahoo.vespa.hosted.controller.rotation.RotationLock;
import org.junit.Test;
@@ -274,6 +276,8 @@ public class ControllerTest {
@Test
public void testDnsAliasRegistration() {
+ ((InMemoryFlagSource) tester.controller().flagSource()).withBooleanFlag(Flags.MULTIPLE_GLOBAL_ENDPOINTS.id(), true);
+
Application application = tester.createApplication("app1", "tenant1", 1, 1L);
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
@@ -289,12 +293,42 @@ public class ControllerTest {
for (Deployment deployment : deployments) {
assertEquals("Rotation names are passed to config server in " + deployment.zone(),
Set.of("rotation-id-01",
- "app1--tenant1.global.vespa.oath.cloud",
- "app1.tenant1.global.vespa.yahooapis.com",
- "app1--tenant1.global.vespa.yahooapis.com"),
- tester.configServer().rotationCnames().get(new DeploymentId(application.id(), deployment.zone())));
+ "app1--tenant1.global.vespa.oath.cloud"),
+ tester.configServer().rotationNames().get(new DeploymentId(application.id(), deployment.zone())));
+ }
+ tester.flushDnsRequests();
+
+ assertEquals(1, tester.controllerTester().nameService().records().size());
+
+ var record = tester.controllerTester().findCname("app1--tenant1.global.vespa.oath.cloud");
+ assertTrue(record.isPresent());
+ assertEquals("app1--tenant1.global.vespa.oath.cloud", record.get().name().asString());
+ assertEquals("rotation-fqdn-01.", record.get().data().asString());
+ }
+
+ @Test
+ public void testDnsAliasRegistrationLegacy() {
+ Application application = tester.createApplication("app1", "tenant1", 1, 1L);
+
+ ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
+ .environment(Environment.prod)
+ .globalServiceId("foo")
+ .region("us-west-1")
+ .region("us-central-1") // Two deployments should result in each DNS alias being registered once
+ .build();
+
+ tester.deployCompletely(application, applicationPackage);
+ Collection<Deployment> deployments = tester.application(application.id()).deployments().values();
+ assertFalse(deployments.isEmpty());
+ for (Deployment deployment : deployments) {
+ assertEquals("Rotation names are passed to config server in " + deployment.zone(),
+ Set.of("rotation-id-01",
+ "app1--tenant1.global.vespa.oath.cloud",
+ "app1.tenant1.global.vespa.yahooapis.com",
+ "app1--tenant1.global.vespa.yahooapis.com"),
+ tester.configServer().rotationNames().get(new DeploymentId(application.id(), deployment.zone())));
}
- tester.updateDns();
+ tester.flushDnsRequests();
assertEquals(3, tester.controllerTester().nameService().records().size());
Optional<Record> record = tester.controllerTester().findCname("app1--tenant1.global.vespa.yahooapis.com");
@@ -314,38 +348,142 @@ public class ControllerTest {
}
@Test
- public void testRedirectLegacyDnsNames() { // TODO: Remove together with Flags.REDIRECT_LEGACY_DNS_NAMES
+ public void testDnsAliasRegistrationWithEndpoints() {
+ ((InMemoryFlagSource) tester.controller().flagSource()).withBooleanFlag(Flags.MULTIPLE_GLOBAL_ENDPOINTS.id(), true);
+
Application application = tester.createApplication("app1", "tenant1", 1, 1L);
+
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
.environment(Environment.prod)
- .globalServiceId("foo")
+ .endpoint("foobar", "qrs", "us-west-1", "us-central-1")
+ .endpoint("default", "qrs", "us-west-1", "us-central-1")
+ .endpoint("all", "qrs")
.region("us-west-1")
.region("us-central-1")
.build();
- ((InMemoryFlagSource) tester.controller().flagSource()).withBooleanFlag(Flags.REDIRECT_LEGACY_DNS_NAMES.id(), true);
-
tester.deployCompletely(application, applicationPackage);
+ Collection<Deployment> deployments = tester.application(application.id()).deployments().values();
+ assertFalse(deployments.isEmpty());
+ for (Deployment deployment : deployments) {
+ assertEquals("Rotation names are passed to config server in " + deployment.zone(),
+ Set.of(
+ "rotation-id-01",
+ "rotation-id-02",
+ "rotation-id-03",
+ "app1--tenant1.global.vespa.oath.cloud",
+ "foobar--app1--tenant1.global.vespa.oath.cloud",
+ "all--app1--tenant1.global.vespa.oath.cloud"
+ ),
+ tester.configServer().rotationNames().get(new DeploymentId(application.id(), deployment.zone())));
+ }
+ tester.flushDnsRequests();
+
assertEquals(3, tester.controllerTester().nameService().records().size());
- Optional<Record> record = tester.controllerTester().findCname("app1--tenant1.global.vespa.yahooapis.com");
- assertTrue(record.isPresent());
- assertEquals("app1--tenant1.global.vespa.yahooapis.com", record.get().name().asString());
- assertEquals("app1--tenant1.global.vespa.oath.cloud.", record.get().data().asString());
+ var record1 = tester.controllerTester().findCname("app1--tenant1.global.vespa.oath.cloud");
+ assertTrue(record1.isPresent());
+ assertEquals("app1--tenant1.global.vespa.oath.cloud", record1.get().name().asString());
+ assertEquals("rotation-fqdn-03.", record1.get().data().asString());
- record = tester.controllerTester().findCname("app1--tenant1.global.vespa.oath.cloud");
- assertTrue(record.isPresent());
- assertEquals("app1--tenant1.global.vespa.oath.cloud", record.get().name().asString());
- assertEquals("rotation-fqdn-01.", record.get().data().asString());
+ var record2 = tester.controllerTester().findCname("foobar--app1--tenant1.global.vespa.oath.cloud");
+ assertTrue(record2.isPresent());
+ assertEquals("foobar--app1--tenant1.global.vespa.oath.cloud", record2.get().name().asString());
+ assertEquals("rotation-fqdn-01.", record2.get().data().asString());
- record = tester.controllerTester().findCname("app1.tenant1.global.vespa.yahooapis.com");
- assertTrue(record.isPresent());
- assertEquals("app1.tenant1.global.vespa.yahooapis.com", record.get().name().asString());
- assertEquals("app1--tenant1.global.vespa.oath.cloud.", record.get().data().asString());
+ var record3 = tester.controllerTester().findCname("all--app1--tenant1.global.vespa.oath.cloud");
+ assertTrue(record3.isPresent());
+ assertEquals("all--app1--tenant1.global.vespa.oath.cloud", record3.get().name().asString());
+ assertEquals("rotation-fqdn-02.", record3.get().data().asString());
+ }
+
+ @Test
+ public void testDnsAliasRegistrationWithChangingZones() {
+ ((InMemoryFlagSource) tester.controller().flagSource()).withBooleanFlag(Flags.MULTIPLE_GLOBAL_ENDPOINTS.id(), true);
+
+ Application application = tester.createApplication("app1", "tenant1", 1, 1L);
+
+ ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
+ .environment(Environment.prod)
+ .endpoint("default", "qrs", "us-west-1", "us-central-1")
+ .region("us-west-1")
+ .region("us-central-1")
+ .build();
+
+ tester.deployCompletely(application, applicationPackage);
+
+ assertEquals(
+ Set.of("rotation-id-01", "app1--tenant1.global.vespa.oath.cloud"),
+ tester.configServer().rotationNames().get(new DeploymentId(application.id(), ZoneId.from("prod", "us-west-1")))
+ );
+
+ assertEquals(
+ Set.of("rotation-id-01", "app1--tenant1.global.vespa.oath.cloud"),
+ tester.configServer().rotationNames().get(new DeploymentId(application.id(), ZoneId.from("prod", "us-central-1")))
+ );
+
+
+ ApplicationPackage applicationPackage2 = new ApplicationPackageBuilder()
+ .environment(Environment.prod)
+ .endpoint("default", "qrs", "us-west-1")
+ .region("us-west-1")
+ .region("us-central-1")
+ .build();
+
+ tester.deployCompletely(application, applicationPackage2, BuildJob.defaultBuildNumber + 1);
+
+ assertEquals(
+ Set.of("rotation-id-01", "app1--tenant1.global.vespa.oath.cloud"),
+ tester.configServer().rotationNames().get(new DeploymentId(application.id(), ZoneId.from("prod", "us-west-1")))
+ );
+
+ assertEquals(
+ Set.of(),
+ tester.configServer().rotationNames().get(new DeploymentId(application.id(), ZoneId.from("prod", "us-central-1")))
+ );
+
+ assertEquals(Set.of(RegionName.from("us-west-1")), tester.application(application.id()).assignedRotations().get(0).regions());
}
@Test
+ public void testUnassignRotations() {
+ ((InMemoryFlagSource) tester.controller().flagSource()).withBooleanFlag(Flags.MULTIPLE_GLOBAL_ENDPOINTS.id(), true);
+
+ Application application = tester.createApplication("app1", "tenant1", 1, 1L);
+
+ ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
+ .environment(Environment.prod)
+ .endpoint("default", "qrs", "us-west-1", "us-central-1")
+ .region("us-west-1")
+ .region("us-central-1") // Two deployments should result in each DNS alias being registered once
+ .build();
+
+ tester.deployCompletely(application, applicationPackage);
+
+ ApplicationPackage applicationPackage2 = new ApplicationPackageBuilder()
+ .environment(Environment.prod)
+ .region("us-west-1")
+ .region("us-central-1") // Two deployments should result in each DNS alias being registered once
+ .build();
+
+ tester.deployCompletely(application, applicationPackage2, BuildJob.defaultBuildNumber + 1);
+
+
+ assertEquals(
+ List.of(AssignedRotation.fromStrings("qrs", "default", "rotation-id-01", Set.of())),
+ tester.application(application.id()).assignedRotations()
+ );
+
+ assertEquals(
+ Set.of(),
+ tester.configServer().rotationNames().get(new DeploymentId(application.id(), ZoneId.from("prod", "us-west-1")))
+ );
+ }
+
+ @Test
public void testUpdatesExistingDnsAlias() {
+ ((InMemoryFlagSource) tester.controller().flagSource()).withBooleanFlag(Flags.MULTIPLE_GLOBAL_ENDPOINTS.id(), true);
+
// Application 1 is deployed and deleted
{
Application app1 = tester.createApplication("app1", "tenant1", 1, 1L);
@@ -357,16 +495,11 @@ public class ControllerTest {
.build();
tester.deployCompletely(app1, applicationPackage);
- assertEquals(3, tester.controllerTester().nameService().records().size());
+ assertEquals(1, tester.controllerTester().nameService().records().size());
- Optional<Record> record = tester.controllerTester().findCname("app1--tenant1.global.vespa.yahooapis.com");
+ Optional<Record> record = tester.controllerTester().findCname("app1--tenant1.global.vespa.oath.cloud");
assertTrue(record.isPresent());
- assertEquals("app1--tenant1.global.vespa.yahooapis.com", record.get().name().asString());
- assertEquals("rotation-fqdn-01.", record.get().data().asString());
-
- record = tester.controllerTester().findCname("app1.tenant1.global.vespa.yahooapis.com");
- assertTrue(record.isPresent());
- assertEquals("app1.tenant1.global.vespa.yahooapis.com", record.get().name().asString());
+ assertEquals("app1--tenant1.global.vespa.oath.cloud", record.get().name().asString());
assertEquals("rotation-fqdn-01.", record.get().data().asString());
// Application is deleted and rotation is unassigned
@@ -384,16 +517,17 @@ public class ControllerTest {
tester.applications().rotationRepository().availableRotations(lock)
.containsKey(new RotationId("rotation-id-01")));
}
+ tester.flushDnsRequests();
- // Records remain
+ // Records are removed
record = tester.controllerTester().findCname("app1--tenant1.global.vespa.yahooapis.com");
- assertTrue(record.isPresent());
+ assertTrue(record.isEmpty());
record = tester.controllerTester().findCname("app1--tenant1.global.vespa.oath.cloud");
- assertTrue(record.isPresent());
+ assertTrue(record.isEmpty());
record = tester.controllerTester().findCname("app1.tenant1.global.vespa.yahooapis.com");
- assertTrue(record.isPresent());
+ assertTrue(record.isEmpty());
}
// Application 2 is deployed and assigned same rotation as application 1 had before deletion
@@ -406,22 +540,12 @@ public class ControllerTest {
.region("us-central-1")
.build();
tester.deployCompletely(app2, applicationPackage);
- assertEquals(6, tester.controllerTester().nameService().records().size());
-
- Optional<Record> record = tester.controllerTester().findCname("app2--tenant2.global.vespa.yahooapis.com");
- assertTrue(record.isPresent());
- assertEquals("app2--tenant2.global.vespa.yahooapis.com", record.get().name().asString());
- assertEquals("rotation-fqdn-01.", record.get().data().asString());
+ assertEquals(1, tester.controllerTester().nameService().records().size());
- record = tester.controllerTester().findCname("app2--tenant2.global.vespa.oath.cloud");
+ var record = tester.controllerTester().findCname("app2--tenant2.global.vespa.oath.cloud");
assertTrue(record.isPresent());
assertEquals("app2--tenant2.global.vespa.oath.cloud", record.get().name().asString());
assertEquals("rotation-fqdn-01.", record.get().data().asString());
-
- record = tester.controllerTester().findCname("app2.tenant2.global.vespa.yahooapis.com");
- assertTrue(record.isPresent());
- assertEquals("app2.tenant2.global.vespa.yahooapis.com", record.get().name().asString());
- assertEquals("rotation-fqdn-01.", record.get().data().asString());
}
// Application 1 is recreated, deployed and assigned a new rotation
@@ -436,22 +560,18 @@ public class ControllerTest {
.build();
tester.deployCompletely(app1, applicationPackage);
app1 = tester.applications().require(app1.id());
- assertEquals("rotation-id-02", app1.rotation().get().asString());
+ assertEquals("rotation-id-02", app1.rotations().get(0).asString());
- // Existing DNS records are updated to point to the newly assigned rotation
- assertEquals(6, tester.controllerTester().nameService().records().size());
+ // DNS records are created for the newly assigned rotation
+ assertEquals(2, tester.controllerTester().nameService().records().size());
- Optional<Record> record = tester.controllerTester().findCname("app1--tenant1.global.vespa.yahooapis.com");
- assertTrue(record.isPresent());
- assertEquals("rotation-fqdn-02.", record.get().data().asString());
-
- record = tester.controllerTester().findCname("app1--tenant1.global.vespa.oath.cloud");
- assertTrue(record.isPresent());
- assertEquals("rotation-fqdn-02.", record.get().data().asString());
+ var record1 = tester.controllerTester().findCname("app1--tenant1.global.vespa.oath.cloud");
+ assertTrue(record1.isPresent());
+ assertEquals("rotation-fqdn-02.", record1.get().data().asString());
- record = tester.controllerTester().findCname("app1.tenant1.global.vespa.yahooapis.com");
- assertTrue(record.isPresent());
- assertEquals("rotation-fqdn-02.", record.get().data().asString());
+ var record2 = tester.controllerTester().findCname("app2--tenant2.global.vespa.oath.cloud");
+ assertTrue(record2.isPresent());
+ assertEquals("rotation-fqdn-01.", record2.get().data().asString());
}
}
@@ -461,7 +581,7 @@ public class ControllerTest {
Version six = Version.fromString("6.1");
tester.upgradeSystem(six);
tester.controllerTester().zoneRegistry().setSystemName(SystemName.cd);
- tester.controllerTester().zoneRegistry().setZones(ZoneId.from("prod", "cd-us-central-1"));
+ tester.controllerTester().zoneRegistry().setZones(ZoneApiMock.fromId("prod.cd-us-central-1"));
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
.environment(Environment.prod)
.majorVersion(6)
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 2b0ee741e7e..d5935c752d9 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
@@ -5,6 +5,7 @@ import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.TenantName;
+import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.slime.Slime;
import com.yahoo.test.ManualClock;
import com.yahoo.vespa.athenz.api.AthenzDomain;
@@ -18,7 +19,6 @@ import com.yahoo.vespa.hosted.controller.api.application.v4.model.DeployOptions;
import com.yahoo.vespa.hosted.controller.api.identifiers.Property;
import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId;
import com.yahoo.vespa.hosted.controller.api.integration.BuildService;
-import com.yahoo.vespa.hosted.controller.api.integration.chef.ChefMock;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationStore;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.ArtifactRepository;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
@@ -28,17 +28,17 @@ import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordName;
import com.yahoo.vespa.hosted.controller.api.integration.github.GitHubMock;
import com.yahoo.vespa.hosted.controller.api.integration.organization.Contact;
import com.yahoo.vespa.hosted.controller.api.integration.organization.MockContactRetriever;
-import com.yahoo.vespa.hosted.controller.api.integration.organization.MockIssueHandler;
import com.yahoo.vespa.hosted.controller.api.integration.routing.RoutingGenerator;
import com.yahoo.vespa.hosted.controller.api.integration.stubs.MockBuildService;
import com.yahoo.vespa.hosted.controller.api.integration.stubs.MockMailer;
+import com.yahoo.vespa.hosted.controller.api.integration.stubs.MockMavenRepository;
import com.yahoo.vespa.hosted.controller.api.integration.stubs.MockRunDataStore;
import com.yahoo.vespa.hosted.controller.api.integration.stubs.MockTesterCloud;
-import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
import com.yahoo.vespa.hosted.controller.athenz.impl.AthenzFacade;
import com.yahoo.vespa.hosted.controller.athenz.mock.AthenzClientFactoryMock;
import com.yahoo.vespa.hosted.controller.athenz.mock.AthenzDbMock;
+import com.yahoo.vespa.hosted.controller.integration.ApplicationCertificateMock;
import com.yahoo.vespa.hosted.controller.integration.ApplicationStoreMock;
import com.yahoo.vespa.hosted.controller.integration.ArtifactRepositoryMock;
import com.yahoo.vespa.hosted.controller.integration.ConfigServerMock;
@@ -98,7 +98,7 @@ public final class ControllerTester {
this(new AthenzDbMock(), clock, new ConfigServerMock(new ZoneRegistryMock()),
new ZoneRegistryMock(), new GitHubMock(), curatorDb, rotationsConfig,
new MemoryNameService(), new ArtifactRepositoryMock(), new ApplicationStoreMock(), new MockBuildService(),
- metricsService, new RoutingGeneratorMock(), new MockContactRetriever(), new MockIssueHandler(clock));
+ metricsService, new RoutingGeneratorMock(), new MockContactRetriever());
}
public ControllerTester(ManualClock clock) {
@@ -123,7 +123,7 @@ public final class ControllerTester {
MemoryNameService nameService, ArtifactRepositoryMock artifactRepository,
ApplicationStoreMock appStoreMock, MockBuildService buildService,
MetricsServiceMock metricsService, RoutingGeneratorMock routingGenerator,
- MockContactRetriever contactRetriever, MockIssueHandler issueHandler) {
+ MockContactRetriever contactRetriever) {
this.athenzDb = athenzDb;
this.clock = clock;
this.configServer = configServer;
@@ -139,7 +139,7 @@ public final class ControllerTester {
this.routingGenerator = routingGenerator;
this.contactRetriever = contactRetriever;
this.controller = createController(curator, rotationsConfig, configServer, clock, gitHub, zoneRegistry,
- athenzDb, nameService, artifactRepository, appStoreMock, buildService,
+ athenzDb, artifactRepository, appStoreMock, buildService,
metricsService, routingGenerator);
// Make root logger use time from manual clock
@@ -197,7 +197,7 @@ public final class ControllerTester {
/** Create a new controller instance. Useful to verify that controller state is rebuilt from persistence */
public final void createNewController() {
controller = createController(curator, rotationsConfig, configServer, clock, gitHub, zoneRegistry, athenzDb,
- nameService, artifactRepository, applicationStore, buildService, metricsService,
+ artifactRepository, applicationStore, buildService, metricsService,
routingGenerator);
}
@@ -330,7 +330,7 @@ public final class ControllerTester {
private static Controller createController(CuratorDb curator, RotationsConfig rotationsConfig,
ConfigServerMock configServer, ManualClock clock,
GitHubMock gitHub, ZoneRegistryMock zoneRegistryMock,
- AthenzDbMock athensDb, MemoryNameService nameService,
+ AthenzDbMock athensDb,
ArtifactRepository artifactRepository, ApplicationStore applicationStore,
BuildService buildService, MetricsServiceMock metricsService,
RoutingGenerator routingGenerator) {
@@ -341,7 +341,6 @@ public final class ControllerTester {
configServer,
metricsService,
routingGenerator,
- new ChefMock(),
clock,
new AthenzFacade(new AthenzClientFactoryMock(athensDb)),
artifactRepository,
@@ -351,7 +350,9 @@ public final class ControllerTester {
new MockRunDataStore(),
() -> "test-controller",
new MockMailer(),
- new InMemoryFlagSource());
+ new InMemoryFlagSource(),
+ new MockMavenRepository(),
+ new ApplicationCertificateMock());
// Calculate initial versions
controller.updateVersionStatus(VersionStatus.compute(controller));
return controller;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/EndpointTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/EndpointTest.java
index 16b875c1892..f5047a82e2f 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/EndpointTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/EndpointTest.java
@@ -66,6 +66,50 @@ public class EndpointTest {
}
@Test
+ public void test_global_endpoints_with_endpoint_id() {
+ final var endpointId = EndpointId.default_();
+
+ Map<String, Endpoint> tests = Map.of(
+ // Legacy endpoint
+ "http://a1.t1.global.vespa.yahooapis.com:4080/",
+ Endpoint.of(app1).named(endpointId).on(Port.plain(4080)).legacy().in(SystemName.main),
+
+ // Legacy endpoint with TLS
+ "https://a1--t1.global.vespa.yahooapis.com:4443/",
+ Endpoint.of(app1).named(endpointId).on(Port.tls(4443)).legacy().in(SystemName.main),
+
+ // Main endpoint
+ "https://a1--t1.global.vespa.oath.cloud:4443/",
+ Endpoint.of(app1).named(endpointId).on(Port.tls(4443)).in(SystemName.main),
+
+ // Main endpoint in CD
+ "https://cd--a1--t1.global.vespa.oath.cloud:4443/",
+ Endpoint.of(app1).named(endpointId).on(Port.tls(4443)).in(SystemName.cd),
+
+ // Main endpoint with direct routing and default TLS port
+ "https://a1.t1.global.vespa.oath.cloud/",
+ Endpoint.of(app1).named(endpointId).on(Port.tls()).directRouting().in(SystemName.main),
+
+ // Main endpoint with custom rotation name
+ "https://r1.a1.t1.global.vespa.oath.cloud/",
+ Endpoint.of(app1).named(EndpointId.of("r1")).on(Port.tls()).directRouting().in(SystemName.main),
+
+ // Main endpoint for custom instance in default rotation
+ "https://a2.t2.global.vespa.oath.cloud/",
+ Endpoint.of(app2).named(endpointId).on(Port.tls()).directRouting().in(SystemName.main),
+
+ // Main endpoint for custom instance with custom rotation name
+ "https://r2.a2.t2.global.vespa.oath.cloud/",
+ Endpoint.of(app2).named(EndpointId.of("r2")).on(Port.tls()).directRouting().in(SystemName.main),
+
+ // Main endpoint in public system
+ "https://a1.t1.global.public.vespa.oath.cloud/",
+ Endpoint.of(app1).named(endpointId).on(Port.tls()).directRouting().in(SystemName.Public)
+ );
+ tests.forEach((expected, endpoint) -> assertEquals(expected, endpoint.url().toString()));
+ }
+
+ @Test
public void test_zone_endpoints() {
ClusterSpec.Id cluster = ClusterSpec.Id.from("default"); // Always default for non-direct routing
ZoneId prodZone = ZoneId.from("prod", "us-north-1");
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/auditlog/AuditLoggerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/auditlog/AuditLoggerTest.java
index 6470ce3663f..67979571f73 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/auditlog/AuditLoggerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/auditlog/AuditLoggerTest.java
@@ -24,11 +24,10 @@ import static org.junit.Assert.assertTrue;
public class AuditLoggerTest {
private final ControllerTester tester = new ControllerTester();
+ private final Supplier<AuditLog> log = () -> tester.controller().auditLogger().readLog();
@Test
public void test_logging() {
- Supplier<AuditLog> log = () -> tester.controller().auditLogger().readLog();
-
{ // GET request is ignored
HttpRequest request = testRequest(Method.GET, URI.create("http://localhost:8080/os/v1/"), "");
tester.controller().auditLogger().log(request);
@@ -40,11 +39,8 @@ public class AuditLoggerTest {
String data = "{\"cloud\":\"cloud9\",\"version\":\"42.0\"}";
HttpRequest request = testRequest(Method.PATCH, url, data);
tester.controller().auditLogger().log(request);
-
- assertEquals(instant(), log.get().entries().get(0).at());
+ assertEntry(Entry.Method.PATCH, 1, "/os/v1/?foo=bar");
assertEquals("user", log.get().entries().get(0).principal());
- assertEquals(Entry.Method.PATCH, log.get().entries().get(0).method());
- assertEquals("/os/v1/?foo=bar", log.get().entries().get(0).resource());
assertEquals(data, log.get().entries().get(0).data().get());
}
@@ -53,9 +49,31 @@ public class AuditLoggerTest {
HttpRequest request = testRequest(Method.PATCH, URI.create("http://localhost:8080/os/v1/"),
"{\"cloud\":\"cloud9\",\"version\":\"43.0\"}");
tester.controller().auditLogger().log(request);
- assertEquals(2, log.get().entries().size());
- assertEquals(instant(), log.get().entries().get(0).at());
- assertEquals("/os/v1/", log.get().entries().get(0).resource());
+ assertEntry(Entry.Method.PATCH, 2, "/os/v1/");
+ }
+
+ { // PUT is logged
+ tester.clock().advance(Duration.ofDays(1));
+ HttpRequest request = testRequest(Method.PUT, URI.create("http://localhost:8080/zone/v2/prod/us-north-1/nodes/v2/state/dirty/node1/"),
+ "");
+ tester.controller().auditLogger().log(request);
+ assertEntry(Entry.Method.PUT, 3, "/zone/v2/prod/us-north-1/nodes/v2/state/dirty/node1/");
+ }
+
+ { // DELETE is logged
+ tester.clock().advance(Duration.ofDays(1));
+ HttpRequest request = testRequest(Method.DELETE, URI.create("http://localhost:8080/zone/v2/prod/us-north-1/nodes/v2/node/node1"),
+ "");
+ tester.controller().auditLogger().log(request);
+ assertEntry(Entry.Method.DELETE, 4, "/zone/v2/prod/us-north-1/nodes/v2/node/node1");
+ }
+
+ { // POST is logged
+ tester.clock().advance(Duration.ofDays(1));
+ HttpRequest request = testRequest(Method.POST, URI.create("http://localhost:8080/controller/v1/jobs/upgrader/confidence/6.42"),
+ "6.42");
+ tester.controller().auditLogger().log(request);
+ assertEntry(Entry.Method.POST, 5, "/controller/v1/jobs/upgrader/confidence/6.42");
}
{ // 14 days pass and another PATCH request is logged. Older entries are removed due to expiry
@@ -63,8 +81,7 @@ public class AuditLoggerTest {
HttpRequest request = testRequest(Method.PATCH, URI.create("http://localhost:8080/os/v1/"),
"{\"cloud\":\"cloud9\",\"version\":\"44.0\"}");
tester.controller().auditLogger().log(request);
- assertEquals(1, log.get().entries().size());
- assertEquals(instant(), log.get().entries().get(0).at());
+ assertEntry(Entry.Method.PATCH, 1, "/os/v1/");
}
}
@@ -72,6 +89,13 @@ public class AuditLoggerTest {
return tester.clock().instant().truncatedTo(MILLIS);
}
+ private void assertEntry(Entry.Method method, int logSize, String resource) {
+ assertEquals(logSize, log.get().entries().size());
+ assertEquals(instant(), log.get().entries().get(0).at());
+ assertEquals(method, log.get().entries().get(0).method());
+ assertEquals(resource, log.get().entries().get(0).resource());
+ }
+
private static HttpRequest testRequest(Method method, URI url, String data) {
HttpRequest request = HttpRequest.createTestRequest(
url.toString(),
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 29bec7246ee..6635547e9be 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
@@ -5,6 +5,7 @@ import com.yahoo.config.application.api.ValidationId;
import com.yahoo.config.provision.AthenzDomain;
import com.yahoo.config.provision.AthenzService;
import com.yahoo.config.provision.Environment;
+import com.yahoo.config.provision.RegionName;
import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
import java.io.ByteArrayOutputStream;
@@ -16,6 +17,9 @@ import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
import java.util.OptionalInt;
import java.util.StringJoiner;
import java.util.zip.ZipEntry;
@@ -34,6 +38,7 @@ public class ApplicationPackageBuilder {
private final StringJoiner notifications = new StringJoiner("/>\n <email ",
"<notifications>\n <email ",
"/>\n</notifications>\n").setEmptyValue("");
+ private final StringBuilder endpointsBody = new StringBuilder();
private OptionalInt majorVersion = OptionalInt.empty();
private String upgradePolicy = null;
@@ -62,6 +67,22 @@ public class ApplicationPackageBuilder {
return this;
}
+ public ApplicationPackageBuilder endpoint(String endpointId, String containerId, String... regions) {
+ endpointsBody.append(" <endpoint");
+ endpointsBody.append(" id='").append(endpointId).append("'");
+ endpointsBody.append(" container-id='").append(containerId).append("'");
+ endpointsBody.append(">\n");
+ for (var region : regions) {
+ endpointsBody.append(" <region>").append(region).append("</region>\n");
+ }
+ endpointsBody.append(" </endpoint>\n");
+ return this;
+ }
+
+ public ApplicationPackageBuilder region(RegionName regionName) {
+ return region(regionName.value());
+ }
+
public ApplicationPackageBuilder region(String regionName) {
environmentBody.append(" <region active='true'>");
environmentBody.append(regionName);
@@ -152,7 +173,11 @@ public class ApplicationPackageBuilder {
xml.append(environmentBody);
xml.append(" </");
xml.append(environment.value());
- xml.append(">\n</deployment>");
+ xml.append(">\n");
+ xml.append(" <endpoints>\n");
+ xml.append(endpointsBody);
+ xml.append(" </endpoints>\n");
+ xml.append("</deployment>");
return xml.toString().getBytes(StandardCharsets.UTF_8);
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTester.java
index b6c7e369f07..887406ecba8 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTester.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTester.java
@@ -5,6 +5,7 @@ import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.TenantName;
+import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.test.ManualClock;
import com.yahoo.vespa.hosted.controller.Application;
@@ -123,10 +124,10 @@ public class DeploymentTester {
/** Upgrade system applications in all zones to given version */
public void upgradeSystemApplications(Version version) {
- for (ZoneId zone : tester.zoneRegistry().zones().all().ids()) {
+ for (ZoneApi zone : tester.zoneRegistry().zones().all().zones()) {
for (SystemApplication application : SystemApplication.all()) {
- tester.configServer().setVersion(application.id(), zone, version);
- tester.configServer().convergeServices(application.id(), zone);
+ tester.configServer().setVersion(application.id(), zone.getId(), version);
+ tester.configServer().convergeServices(application.id(), zone.getId());
}
}
computeVersionStatus();
@@ -140,8 +141,8 @@ public class DeploymentTester {
readyJobTrigger().maintain();
}
- /** Dispatch all pending name services requests */
- public void updateDns() {
+ /** Flush all pending name services requests */
+ public void flushDnsRequests() {
nameServiceDispatcher.run();
assertTrue("All name service requests dispatched",
controller().curator().readNameServiceQueue().requests().isEmpty());
@@ -156,8 +157,12 @@ public class DeploymentTester {
}
public Application createApplication(String applicationName, String tenantName, long projectId, long propertyId) {
+ return createApplication("default", applicationName, tenantName, projectId, propertyId);
+ }
+
+ public Application createApplication(String instanceName, String applicationName, String tenantName, long projectId, long propertyId) {
TenantName tenant = tester.createTenant(tenantName, UUID.randomUUID().toString(), propertyId);
- return tester.createApplication(tenant, applicationName, "default", projectId);
+ return tester.createApplication(tenant, applicationName, instanceName, projectId);
}
public void restartController() { tester.createNewController(); }
@@ -225,7 +230,7 @@ public class DeploymentTester {
assertFalse(applications().require(application.id()).change().hasTargets());
}
if (updateDnsAutomatically) {
- updateDns();
+ flushDnsRequests();
}
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java
index bf7c8cadd3c..0d8d32299ae 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.controller.deployment;
import com.yahoo.component.Version;
import com.yahoo.config.provision.Environment;
+import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.TenantName;
import com.yahoo.slime.Slime;
import com.yahoo.test.ManualClock;
@@ -109,6 +110,46 @@ public class DeploymentTriggerTest {
}
@Test
+ public void testIndependentInstances() {
+ Application instance1 = tester.createApplication("instance1", "app", "tenant", 1, 1L);
+ Application instance2 = tester.createApplication("instance2", "app", "tenant", 2, 1L);
+ ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
+ .upgradePolicy("default")
+ .environment(Environment.prod)
+ .region("us-west-1")
+ .build();
+
+ Version version = Version.fromString("6.2");
+ tester.upgradeSystem(version);
+
+ // Deploy completely once
+ tester.jobCompletion(component).application(instance1).uploadArtifact(applicationPackage).submit();
+ tester.deployAndNotify(instance1, applicationPackage, true, JobType.systemTest);
+ tester.deployAndNotify(instance1, applicationPackage, true, JobType.stagingTest);
+ tester.deployAndNotify(instance1, applicationPackage, true, JobType.productionUsWest1);
+
+ tester.jobCompletion(component).application(instance2).uploadArtifact(applicationPackage).submit();
+ tester.deployAndNotify(instance2, applicationPackage, true, JobType.systemTest);
+ tester.deployAndNotify(instance2, applicationPackage, true, JobType.stagingTest);
+ tester.deployAndNotify(instance2, applicationPackage, true, JobType.productionUsWest1);
+
+ // New version is released
+ Version newVersion = Version.fromString("6.3");
+ tester.upgradeSystem(newVersion);
+
+ // instance1 upgrades, but not instance 2
+ tester.deployAndNotify(instance1, applicationPackage, true, JobType.systemTest);
+ tester.deployAndNotify(instance1, applicationPackage, true, JobType.stagingTest);
+ tester.deployAndNotify(instance1, applicationPackage, true, JobType.productionUsWest1);
+
+ Version instance1Version = tester.application(instance1.id()).deployments().get(JobType.productionUsWest1.zone(main)).version();
+ Version instance2Version = tester.application(instance2.id()).deployments().get(JobType.productionUsWest1.zone(main)).version();
+
+ assertEquals(newVersion, instance1Version);
+ assertEquals(version, instance2Version);
+ }
+
+ @Test
public void abortsInternalJobsOnNewApplicationChange() {
InternalDeploymentTester iTester = new InternalDeploymentTester();
DeploymentTester tester = iTester.tester();
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalDeploymentTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalDeploymentTester.java
index 096a41e5b3f..a992ce1e3de 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalDeploymentTester.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalDeploymentTester.java
@@ -182,6 +182,7 @@ public class InternalDeploymentTester {
if (type == JobType.stagingTest) { // Do the initial deployment and installation of the real application.
assertEquals(unfinished, jobs.active(run.id()).get().steps().get(Step.installInitialReal));
tester.configServer().convergeServices(appId, zone);
+ setEndpoints(appId, zone);
run.versions().sourcePlatform().ifPresent(version -> tester.configServer().nodeRepository().doUpgrade(deployment, Optional.empty(), version));
runner.run();
assertEquals(Step.Status.succeeded, jobs.active(run.id()).get().steps().get(Step.installInitialReal));
@@ -193,6 +194,17 @@ public class InternalDeploymentTester {
assertEquals(unfinished, jobs.active(run.id()).get().steps().get(Step.installReal));
tester.configServer().convergeServices(appId, zone);
runner.run();
+ if ( ! (run.versions().sourceApplication().isPresent() && type.isProduction())
+ && type != JobType.stagingTest) {
+ assertEquals(unfinished, jobs.active(run.id()).get().steps().get(Step.installReal));
+ setEndpoints(appId, zone);
+ }
+ runner.run();
+ if (type.environment().isManuallyDeployed()) {
+ assertEquals(Step.Status.succeeded, jobs.run(run.id()).get().steps().get(Step.installReal));
+ assertTrue(jobs.run(run.id()).get().hasEnded());
+ return;
+ }
assertEquals(Step.Status.succeeded, jobs.active(run.id()).get().steps().get(Step.installReal));
assertEquals(unfinished, jobs.active(run.id()).get().steps().get(Step.installTester));
@@ -201,17 +213,12 @@ public class InternalDeploymentTester {
assertEquals(unfinished, jobs.active(run.id()).get().steps().get(Step.installTester));
tester.configServer().convergeServices(testerId.id(), zone);
runner.run();
- assertEquals(Step.Status.succeeded, jobs.active(run.id()).get().steps().get(Step.installTester));
-
- // All installation is complete. We now need endpoints, and the tests will then run, and cleanup finish.
- assertEquals(unfinished, jobs.active(run.id()).get().steps().get(Step.startTests));
+ assertEquals(unfinished, jobs.active(run.id()).get().steps().get(Step.installTester));
setEndpoints(testerId.id(), zone);
runner.run();
- if (!run.versions().sourceApplication().isPresent() || !type.isProduction()) {
- assertEquals(unfinished, jobs.active(run.id()).get().steps().get(Step.startTests));
- setEndpoints(appId, zone);
- }
- runner.run();
+ assertEquals(Step.Status.succeeded, jobs.active(run.id()).get().steps().get(Step.installTester));
+
+ // All installation is complete and endpoints are ready, so tests may begin.
assertEquals(Step.Status.succeeded, jobs.active(run.id()).get().steps().get(Step.startTests));
assertEquals(unfinished, jobs.active(run.id()).get().steps().get(Step.endTests));
@@ -222,7 +229,7 @@ public class InternalDeploymentTester {
assertEquals(type.isProduction(), app().deployments().containsKey(zone));
assertTrue(tester.configServer().nodeRepository().list(zone, testerId.id()).isEmpty());
- if (!app().deployments().containsKey(zone))
+ if ( ! app().deployments().containsKey(zone))
routing.removeEndpoints(deployment);
routing.removeEndpoints(new DeploymentId(testerId.id(), zone));
}
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 b53a7b39d61..095651df033 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
@@ -3,11 +3,15 @@ package com.yahoo.vespa.hosted.controller.deployment;
import com.yahoo.component.Version;
import com.yahoo.config.application.api.DeploymentSpec;
+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.slime.ArrayTraverser;
import com.yahoo.slime.Inspector;
+import com.yahoo.slime.JsonFormat;
+import com.yahoo.slime.ObjectTraverser;
import com.yahoo.vespa.config.SlimeUtils;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.configserverbindings.ConfigChangeActions;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.configserverbindings.RefeedAction;
@@ -20,9 +24,11 @@ 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 org.junit.Before;
import org.junit.Test;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URI;
@@ -32,7 +38,9 @@ import java.nio.file.Paths;
import java.time.Duration;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
+import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
@@ -46,6 +54,7 @@ import static com.yahoo.vespa.hosted.controller.deployment.InternalDeploymentTes
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;
@@ -156,6 +165,10 @@ public class InternalStepRunnerTest {
public void waitsForEndpointsAndTimesOut() {
tester.newRun(JobType.systemTest);
+ // Tester fails to show up for staging tests, and the real deployment for system tests.
+ tester.setEndpoints(testerId.id(), JobType.systemTest.zone(tester.tester().controller().system()));
+ tester.setEndpoints(appId, JobType.stagingTest.zone(tester.tester().controller().system()));
+
tester.runner().run();
tester.configServer().convergeServices(appId, JobType.stagingTest.zone(tester.tester().controller().system()));
tester.runner().run();
@@ -165,14 +178,10 @@ public class InternalStepRunnerTest {
tester.configServer().convergeServices(testerId.id(), JobType.stagingTest.zone(tester.tester().controller().system()));
tester.runner().run();
- // Tester fails to show up for system tests, and the real deployment for staging tests.
- tester.setEndpoints(appId, JobType.systemTest.zone(tester.tester().controller().system()));
- tester.setEndpoints(testerId.id(), JobType.stagingTest.zone(tester.tester().controller().system()));
-
tester.clock().advance(InternalStepRunner.endpointTimeout.plus(Duration.ofSeconds(1)));
tester.runner().run();
- assertEquals(failed, tester.jobs().last(appId, JobType.systemTest).get().steps().get(Step.startTests));
- assertEquals(failed, tester.jobs().last(appId, JobType.stagingTest).get().steps().get(Step.startTests));
+ assertEquals(failed, tester.jobs().last(appId, JobType.systemTest).get().steps().get(Step.installReal));
+ assertEquals(failed, tester.jobs().last(appId, JobType.stagingTest).get().steps().get(Step.installTester));
}
@Test
@@ -180,6 +189,7 @@ public class InternalStepRunnerTest {
tester.newRun(JobType.systemTest);
tester.runner().run();
tester.configServer().convergeServices(appId, JobType.systemTest.zone(tester.tester().controller().system()));
+ tester.setEndpoints(appId, JobType.systemTest.zone(tester.tester().controller().system()));
tester.runner().run();
assertEquals(succeeded, tester.jobs().last(appId, JobType.systemTest).get().steps().get(Step.installReal));
@@ -204,6 +214,32 @@ public class InternalStepRunnerTest {
}
@Test
+ public void alternativeEndpointsAreDetected() {
+ tester.newRun(JobType.systemTest);
+ tester.runner().run();;
+ tester.configServer().convergeServices(appId, JobType.systemTest.zone(tester.tester().controller().system()));
+ tester.configServer().convergeServices(testerId.id(), JobType.systemTest.zone(tester.tester().controller().system()));
+ assertEquals(unfinished, tester.jobs().last(appId, JobType.systemTest).get().steps().get(Step.installReal));
+ assertEquals(unfinished, tester.jobs().last(appId, JobType.systemTest).get().steps().get(Step.installTester));
+
+ tester.tester().controller().curator().writeRoutingPolicies(appId, Set.of(new RoutingPolicy(appId,
+ ClusterSpec.Id.from("default"),
+ JobType.systemTest.zone(tester.tester().controller().system()),
+ HostName.from("host"),
+ Optional.empty(),
+ emptySet())));
+ tester.tester().controller().curator().writeRoutingPolicies(testerId.id(), Set.of(new RoutingPolicy(testerId.id(),
+ ClusterSpec.Id.from("default"),
+ JobType.systemTest.zone(tester.tester().controller().system()),
+ HostName.from("host"),
+ Optional.empty(),
+ emptySet())));
+ tester.runner().run();;
+ assertEquals(succeeded, tester.jobs().last(appId, JobType.systemTest).get().steps().get(Step.installReal));
+ assertEquals(succeeded, tester.jobs().last(appId, JobType.systemTest).get().steps().get(Step.installTester));
+ }
+
+ @Test
public void testsFailIfTesterRestarts() {
RunId id = tester.startSystemTestTests();
tester.cloud().set(TesterCloud.Status.NOT_STARTED);
@@ -249,7 +285,7 @@ public class InternalStepRunnerTest {
Inspector configObject = SlimeUtils.jsonToSlime(tester.cloud().config()).get();
assertEquals(appId.serializedForm(), configObject.field("application").asString());
assertEquals(JobType.systemTest.zone(tester.tester().controller().system()).value(), configObject.field("zone").asString());
- assertEquals(tester.tester().controller().system().name(), configObject.field("system").asString());
+ assertEquals(tester.tester().controller().system().value(), configObject.field("system").asString());
assertEquals(1, configObject.field("endpoints").children());
assertEquals(1, configObject.field("endpoints").field(JobType.systemTest.zone(tester.tester().controller().system()).value()).entries());
configObject.field("endpoints").field(JobType.systemTest.zone(tester.tester().controller().system()).value()).traverse((ArrayTraverser) (__, endpoint) ->
@@ -299,6 +335,7 @@ public class InternalStepRunnerTest {
tester.runner().run(); // Job run order determined by JobType enum order per application.
tester.configServer().convergeServices(appId, zone);
+ tester.setEndpoints(appId, zone);
assertEquals(unfinished, tester.jobs().run(id).get().steps().get(Step.installReal));
assertEquals(otherPackage.hash(), tester.configServer().application(appId).get().applicationPackage().hash());
@@ -337,7 +374,7 @@ public class InternalStepRunnerTest {
tester.runner().run();
assertEquals(failed, tester.jobs().run(id).get().steps().get(Step.endTests));
assertTestLogEntries(id, Step.copyVespaLogs,
- new LogEntry(lastId + 2, tester.clock().millis(), debug, "Copying Vespa log from nodes of tenant.application in zone test.us-east-1 in default ..."),
+ new LogEntry(lastId + 2, tester.clock().millis(), debug, "Copying Vespa log from nodes of tenant.application in test.us-east-1 ..."),
new LogEntry(lastId + 3, 1554970337084L, info,
"17480180-v6-3.ostk.bm2.prod.ne1.yahoo.com\tcontainer\tContainer.com.yahoo.container.jdisc.ConfiguredApplication\n" +
"Switching to the latest deployed set of configurations and components. Application switch number: 2"),
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/TestConfigSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/TestConfigSerializerTest.java
new file mode 100644
index 00000000000..d4550ecc338
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/TestConfigSerializerTest.java
@@ -0,0 +1,38 @@
+package com.yahoo.vespa.hosted.controller.deployment;
+
+import com.yahoo.config.provision.ClusterSpec;
+import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.vespa.config.SlimeUtils;
+import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.Map;
+
+import static com.yahoo.vespa.hosted.controller.deployment.InternalDeploymentTester.appId;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author jonmv
+ */
+public class TestConfigSerializerTest {
+
+ @Test
+ public void testConfig() throws IOException {
+ ZoneId zone = JobType.systemTest.zone(SystemName.PublicCd);
+ byte[] json = new TestConfigSerializer(SystemName.PublicCd).configJson(appId,
+ JobType.systemTest,
+ Map.of(zone, Map.of(ClusterSpec.Id.from("ai"),
+ URI.create("https://server/"))),
+ Map.of(zone, List.of("facts")));
+ byte[] expected = Files.readAllBytes(Paths.get("src/test/resources/testConfig.json"));
+ assertEquals(new String(SlimeUtils.toJsonBytes(SlimeUtils.jsonToSlime(expected))),
+ new String(json));
+ }
+
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ApplicationCertificateMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ApplicationCertificateMock.java
new file mode 100644
index 00000000000..3246a260217
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ApplicationCertificateMock.java
@@ -0,0 +1,14 @@
+// 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.integration;
+
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.vespa.hosted.controller.api.integration.certificates.ApplicationCertificate;
+import com.yahoo.vespa.hosted.controller.api.integration.certificates.ApplicationCertificateProvider;
+
+public class ApplicationCertificateMock implements ApplicationCertificateProvider {
+
+ @Override
+ public ApplicationCertificate requestCaSignedCertificate(ApplicationId applicationId) {
+ return new ApplicationCertificate(String.format("vespa.tls.%s.%s", applicationId.tenant(),applicationId.application()));
+ }
+}
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 d78f4e90ccc..c62c39149b2 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
@@ -7,6 +7,7 @@ import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.NodeType;
+import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.DeployOptions;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.EndpointStatus;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.configserverbindings.ConfigChangeActions;
@@ -15,14 +16,15 @@ 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.identifiers.Identifier;
import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId;
+import com.yahoo.vespa.hosted.controller.api.integration.certificates.ApplicationCertificate;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServer;
+import com.yahoo.vespa.hosted.controller.api.integration.configserver.ContainerEndpoint;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.LoadBalancer;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.Log;
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.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
import com.yahoo.vespa.hosted.controller.application.SystemApplication;
import com.yahoo.vespa.serviceview.bindings.ApplicationView;
@@ -44,6 +46,7 @@ import java.util.Set;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
+import java.util.stream.Stream;
/**
* @author mortent
@@ -55,11 +58,12 @@ public class ConfigServerMock extends AbstractComponent implements ConfigServer
private final Map<String, EndpointStatus> endpoints = new HashMap<>();
private final NodeRepositoryMock nodeRepository = new NodeRepositoryMock();
private final Map<DeploymentId, ServiceConvergence> serviceStatus = new HashMap<>();
+ private final Set<ApplicationId> disallowConvergenceCheckApplications = new HashSet<>();
private final Version initialVersion = new Version(6, 1, 0);
private final Set<DeploymentId> suspendedApplications = new HashSet<>();
private final Map<ZoneId, List<LoadBalancer>> loadBalancers = new HashMap<>();
private final Map<DeploymentId, List<Log>> warnings = new HashMap<>();
- private final Map<DeploymentId, Set<String>> rotationCnames = new HashMap<>();
+ private final Map<DeploymentId, Set<String>> rotationNames = new HashMap<>();
private Version lastPrepareVersion = null;
private RuntimeException prepareException = null;
@@ -68,7 +72,7 @@ public class ConfigServerMock extends AbstractComponent implements ConfigServer
@Inject
public ConfigServerMock(ZoneRegistryMock zoneRegistry) {
- bootstrap(zoneRegistry.zones().all().ids(), SystemApplication.all(), Optional.empty());
+ bootstrap(zoneRegistry.zones().all().ids(), SystemApplication.all());
}
/** Sets the ConfigChangeActions that will be returned on next deployment. */
@@ -91,28 +95,22 @@ public class ConfigServerMock extends AbstractComponent implements ConfigServer
}
public void bootstrap(List<ZoneId> zones, SystemApplication... applications) {
- bootstrap(zones, List.of(applications), Optional.empty());
+ bootstrap(zones, List.of(applications));
}
- public void bootstrap(List<ZoneId> zones, List<SystemApplication> applications, Optional<NodeType> type) {
+ public void bootstrap(List<ZoneId> zones, List<SystemApplication> applications) {
nodeRepository().clear();
- addNodes(zones, applications, type);
+ addNodes(zones, applications);
}
- public void addNodes(List<ZoneId> zones, List<SystemApplication> applications, Optional<NodeType> type) {
+ public void addNodes(List<ZoneId> zones, List<SystemApplication> applications) {
for (ZoneId zone : zones) {
for (SystemApplication application : applications) {
- NodeType nodeType = type.orElseGet(() -> {
- // Zone application has two node types. Use proxy
- if (application == SystemApplication.zone) return NodeType.proxy;
- if (application.nodeTypes().size() != 1) throw new IllegalArgumentException(application + " has several node types. Unable to detect type automatically");
- return application.nodeTypes().iterator().next();
- });
List<Node> nodes = IntStream.rangeClosed(1, 3)
.mapToObj(i -> new Node(
HostName.from("node-" + i + "-" + application.id().application()
.value()),
- Node.State.active, nodeType,
+ Node.State.active, application.nodeType(),
Optional.of(application.id()),
initialVersion,
initialVersion
@@ -186,8 +184,8 @@ public class ConfigServerMock extends AbstractComponent implements ConfigServer
warnings.put(deployment, List.copyOf(logs));
}
- public Map<DeploymentId, Set<String>> rotationCnames() {
- return Collections.unmodifiableMap(rotationCnames);
+ public Map<DeploymentId, Set<String>> rotationNames() {
+ return Collections.unmodifiableMap(rotationNames);
}
@Override
@@ -196,23 +194,32 @@ public class ConfigServerMock extends AbstractComponent implements ConfigServer
}
@Override
- public Optional<ServiceConvergence> serviceConvergence(DeploymentId deployment) {
+ public Optional<ServiceConvergence> serviceConvergence(DeploymentId deployment, Optional<Version> version) {
+ if (disallowConvergenceCheckApplications.contains(deployment.applicationId()))
+ throw new IllegalStateException(deployment.applicationId() + " should not ask for service convergence");
+
return Optional.ofNullable(serviceStatus.get(deployment));
}
+ public void disallowConvergenceCheck(ApplicationId applicationId) {
+ disallowConvergenceCheckApplications.add(applicationId);
+ }
+
@Override
public List<LoadBalancer> getLoadBalancers(ZoneId zone) {
return loadBalancers.getOrDefault(zone, Collections.emptyList());
}
+ @Override
+ public List<LoadBalancer> getLoadBalancers(ApplicationId application, ZoneId zone) {
+ return getLoadBalancers(zone).stream()
+ .filter(lb -> lb.application().equals(application))
+ .collect(Collectors.toUnmodifiableList());
+ }
+
public void addLoadBalancers(ZoneId zone, List<LoadBalancer> loadBalancers) {
- this.loadBalancers.compute(zone, (k, existing) -> {
- if (existing == null) {
- existing = new ArrayList<>();
- }
- existing.addAll(loadBalancers);
- return existing;
- });
+ this.loadBalancers.putIfAbsent(zone, new ArrayList<>());
+ this.loadBalancers.get(zone).addAll(loadBalancers);
}
public void removeLoadBalancers(ApplicationId application, ZoneId zone) {
@@ -220,8 +227,8 @@ public class ConfigServerMock extends AbstractComponent implements ConfigServer
}
@Override
- public PreparedApplication deploy(DeploymentId deployment, DeployOptions deployOptions, Set<String> rotationCnames,
- Set<String> rotationNames, byte[] content) {
+ public PreparedApplication deploy(DeploymentId deployment, DeployOptions deployOptions, Set<String> rotationNames,
+ Set<ContainerEndpoint> containerEndpoints, ApplicationCertificate applicationCertificate, byte[] content) {
lastPrepareVersion = deployOptions.vespaVersion.map(Version::fromString).orElse(null);
if (prepareException != null) {
RuntimeException prepareException = this.prepareException;
@@ -233,64 +240,47 @@ public class ConfigServerMock extends AbstractComponent implements ConfigServer
if (nodeRepository().list(deployment.zoneId(), deployment.applicationId()).isEmpty())
provision(deployment.zoneId(), deployment.applicationId());
- this.rotationCnames.put(deployment, Set.copyOf(rotationCnames));
-
- return new PreparedApplication() {
-
- // TODO: Remove when no longer part of interface
- public void activate() {}
-
- // TODO: Remove when no longer part of interface
- public List<Log> messages() {
- Log warning = new Log();
- warning.level = "WARNING";
- warning.time = 1;
- warning.message = "The warning";
-
- Log info = new Log();
- info.level = "INFO";
- info.time = 2;
- info.message = "The info";
-
- return List.of(warning, info);
- }
-
- @Override
- public PrepareResponse prepareResponse() {
- Application application = applications.get(deployment.applicationId());
- application.activate();
- List<Node> nodes = nodeRepository.list(deployment.zoneId(), deployment.applicationId());
- for (Node node : nodes) {
- nodeRepository.putByHostname(deployment.zoneId(), new Node(node.hostname(),
- Node.State.active,
- node.type(),
- node.owner(),
- node.currentVersion(),
- application.version().get()));
- }
- serviceStatus.put(deployment, new ServiceConvergence(deployment.applicationId(),
- deployment.zoneId(),
- false,
- 2,
- nodes.stream()
- .map(node -> new ServiceConvergence.Status(node.hostname(),
- 43,
- "container",
- 1))
- .collect(Collectors.toList())));
-
- PrepareResponse prepareResponse = new PrepareResponse();
- prepareResponse.message = "foo";
- prepareResponse.configChangeActions = configChangeActions != null
- ? configChangeActions
- : new ConfigChangeActions(Collections.emptyList(),
- Collections.emptyList());
- setConfigChangeActions(null);
- prepareResponse.tenant = new TenantId("tenant");
- prepareResponse.log = warnings.getOrDefault(deployment, Collections.emptyList());
- return prepareResponse;
+ this.rotationNames.put(
+ deployment,
+ Stream.concat(
+ containerEndpoints.stream().flatMap(e -> e.names().stream()),
+ rotationNames.stream()
+ ).collect(Collectors.toSet())
+ );
+
+ return () -> {
+ Application application = applications.get(deployment.applicationId());
+ application.activate();
+ List<Node> nodes = nodeRepository.list(deployment.zoneId(), deployment.applicationId());
+ for (Node node : nodes) {
+ nodeRepository.putByHostname(deployment.zoneId(), new Node(node.hostname(),
+ Node.State.active,
+ node.type(),
+ node.owner(),
+ node.currentVersion(),
+ application.version().get()));
}
-
+ serviceStatus.put(deployment, new ServiceConvergence(deployment.applicationId(),
+ deployment.zoneId(),
+ false,
+ 2,
+ nodes.stream()
+ .map(node -> new ServiceConvergence.Status(node.hostname(),
+ 43,
+ "container",
+ 1))
+ .collect(Collectors.toList())));
+
+ PrepareResponse prepareResponse = new PrepareResponse();
+ prepareResponse.message = "foo";
+ prepareResponse.configChangeActions = configChangeActions != null
+ ? configChangeActions
+ : new ConfigChangeActions(Collections.emptyList(),
+ Collections.emptyList());
+ setConfigChangeActions(null);
+ prepareResponse.tenant = new TenantId("tenant");
+ prepareResponse.log = warnings.getOrDefault(deployment, Collections.emptyList());
+ return prepareResponse;
};
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryClientMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryClientMock.java
index 07978e51263..4f2d85adfbe 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryClientMock.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryClientMock.java
@@ -1,14 +1,16 @@
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.integration;
-import com.yahoo.vespa.hosted.controller.api.integration.noderepository.MaintenanceJobList;
+import com.yahoo.component.Version;
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.NodeType;
+import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.vespa.hosted.controller.api.integration.configserver.NodeRepository;
import com.yahoo.vespa.hosted.controller.api.integration.noderepository.NodeList;
import com.yahoo.vespa.hosted.controller.api.integration.noderepository.NodeMembership;
import com.yahoo.vespa.hosted.controller.api.integration.noderepository.NodeOwner;
-import com.yahoo.vespa.hosted.controller.api.integration.noderepository.NodeRepositoryClientInterface;
import com.yahoo.vespa.hosted.controller.api.integration.noderepository.NodeRepositoryNode;
import com.yahoo.vespa.hosted.controller.api.integration.noderepository.NodeState;
-import com.yahoo.config.provision.zone.ZoneId;
import java.util.Collection;
import java.util.List;
@@ -16,7 +18,7 @@ import java.util.List;
/**
* @author bjorncs
*/
-public class NodeRepositoryClientMock implements NodeRepositoryClientInterface {
+public class NodeRepositoryClientMock implements NodeRepository {
@Override
public void addNodes(ZoneId zone, Collection<NodeRepositoryNode> nodes) {
@@ -34,14 +36,14 @@ public class NodeRepositoryClientMock implements NodeRepositoryClientInterface {
}
@Override
- public NodeList listNodes(ZoneId zone, boolean recursive) {
+ public NodeList listNodes(ZoneId zone) {
NodeRepositoryNode nodeA = createNodeA();
NodeRepositoryNode nodeB = createNodeB();
return new NodeList(List.of(nodeA, nodeB));
}
@Override
- public NodeList listNodes(ZoneId zone, String tenant, String applicationId, String instance) {
+ public NodeList listNodes(ZoneId zone, ApplicationId application) {
NodeRepositoryNode nodeA = createNodeA();
NodeRepositoryNode nodeB = createNodeB();
return new NodeList(List.of(nodeA, nodeB));
@@ -64,6 +66,7 @@ public class NodeRepositoryClientMock implements NodeRepositoryClientInterface {
membership.clusterid = "clusterA";
membership.clustertype = "container";
node.setMembership(membership);
+ node.setState(NodeState.active);
return node;
}
@@ -84,61 +87,32 @@ public class NodeRepositoryClientMock implements NodeRepositoryClientInterface {
membership.clusterid = "clusterB";
membership.clustertype = "content";
node.setMembership(membership);
+ node.setState(NodeState.active);
return node;
}
@Override
- public String resetFailureInformation(ZoneId zone, String nodename) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String restart(ZoneId zone, String nodename) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String reboot(ZoneId zone, String nodename) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String cancelReboot(ZoneId zone, String nodename) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String wantTo(ZoneId zone, String nodename, WantTo... actions) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String cancelRestart(ZoneId zone, String nodename) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String setHardwareFailureDescription(ZoneId zone, String nodename, String hardwareFailureDescription) {
+ public void setState(ZoneId zone, NodeState nodeState, String nodename) {
throw new UnsupportedOperationException();
}
@Override
- public void setState(ZoneId zone, NodeState nodeState, String nodename) {
+ public void upgrade(ZoneId zone, NodeType type, Version version) {
throw new UnsupportedOperationException();
}
@Override
- public String enableMaintenanceJob(ZoneId zone, String jobName) {
+ public void upgradeOs(ZoneId zone, NodeType type, Version version) {
throw new UnsupportedOperationException();
}
@Override
- public String disableMaintenanceJob(ZoneId zone, String jobName) {
+ public void requestFirmwareCheck(ZoneId zone) {
throw new UnsupportedOperationException();
}
@Override
- public MaintenanceJobList listMaintenanceJobs(ZoneId zone) {
+ public void cancelFirmwareCheck(ZoneId zone) {
throw new UnsupportedOperationException();
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java
index 49bc910ac33..45c2df5cb77 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java
@@ -5,11 +5,15 @@ import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.NodeType;
+import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.Node;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.NodeRepository;
-import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.vespa.hosted.controller.api.integration.noderepository.NodeList;
+import com.yahoo.vespa.hosted.controller.api.integration.noderepository.NodeRepositoryNode;
+import com.yahoo.vespa.hosted.controller.api.integration.noderepository.NodeState;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -56,6 +60,36 @@ public class NodeRepositoryMock implements NodeRepository {
}
@Override
+ public void addNodes(ZoneId zone, Collection<NodeRepositoryNode> nodes) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void deleteNode(ZoneId zone, String hostname) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setState(ZoneId zone, NodeState nodeState, String nodename) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public NodeRepositoryNode getNode(ZoneId zone, String hostname) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public NodeList listNodes(ZoneId zone) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public NodeList listNodes(ZoneId zone, ApplicationId application) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public List<Node> list(ZoneId zone, ApplicationId application) {
return nodeRepository.getOrDefault(zone, Collections.emptyMap()).values().stream()
.filter(node -> node.owner().map(application::equals).orElse(false))
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/RoutingGeneratorMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/RoutingGeneratorMock.java
index 410d7950e97..98b2dd2f7f3 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/RoutingGeneratorMock.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/RoutingGeneratorMock.java
@@ -1,14 +1,17 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.integration;
+import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
import com.yahoo.vespa.hosted.controller.api.integration.routing.RoutingEndpoint;
import com.yahoo.vespa.hosted.controller.api.integration.routing.RoutingGenerator;
+import java.net.URI;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
/**
* Returns a default set of endpoints on every query if it has no mappings, or those added by the user, otherwise.
@@ -34,6 +37,14 @@ public class RoutingGeneratorMock implements RoutingGenerator {
: routingTable.getOrDefault(deployment, Collections.emptyList());
}
+ @Override
+ public Map<ClusterSpec.Id, URI> clusterEndpoints(DeploymentId deployment) {
+ return endpoints(deployment).stream()
+ .limit(1)
+ .collect(Collectors.toMap(__ -> ClusterSpec.Id.from("default"),
+ endpoint -> URI.create(endpoint.endpoint())));
+ }
+
public void putEndpoints(DeploymentId deployment, List<RoutingEndpoint> endpoints) {
routingTable.put(deployment, endpoints);
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneApiMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneApiMock.java
new file mode 100644
index 00000000000..4705982e1f2
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneApiMock.java
@@ -0,0 +1,70 @@
+// 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.integration;
+
+import com.yahoo.cloud.config.SentinelConfig;
+import com.yahoo.config.model.graph.ModelGraphBuilder;
+import com.yahoo.config.provision.CloudName;
+import com.yahoo.config.provision.Environment;
+import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.zone.ZoneApi;
+import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.messagebus.MessagebusConfig;
+
+/**
+ * @author hakonhall
+ */
+public class ZoneApiMock implements ZoneApi {
+ private final SystemName systemName;
+ private final ZoneId id;
+ private final CloudName cloudName;
+
+ public static Builder newBuilder() { return new Builder(); }
+
+ private ZoneApiMock(SystemName systemName, ZoneId id, CloudName cloudName) {
+ this.systemName = systemName;
+ this.id = id;
+ this.cloudName = cloudName;
+ }
+
+ public static ZoneApiMock fromId(String id) {
+ return newBuilder().withId(id).build();
+ }
+
+ public static ZoneApiMock from(Environment environment, RegionName region) {
+ return newBuilder().with(ZoneId.from(environment, region)).build();
+ }
+
+ @Override
+ public SystemName getSystemName() { return systemName; }
+
+ @Override
+ public ZoneId getId() { return id; }
+
+ @Override
+ public CloudName getCloudName() { return cloudName; }
+
+ public static class Builder {
+ private SystemName systemName = SystemName.defaultSystem();
+ private ZoneId id = ZoneId.defaultId();
+ private CloudName cloudName = CloudName.defaultName();
+
+ public Builder with(ZoneId id) {
+ this.id = id;
+ return this;
+ }
+
+ public Builder withId(String id) { return with(ZoneId.from(id)); }
+
+ public Builder with(CloudName cloudName) {
+ this.cloudName = cloudName;
+ return this;
+ }
+
+ public Builder withCloud(String cloud) { return with(CloudName.from(cloud)); }
+
+ public ZoneApiMock build() {
+ return new ZoneApiMock(systemName, id, cloudName);
+ }
+ }
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneFilterMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneFilterMock.java
new file mode 100644
index 00000000000..00e6162d5e5
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneFilterMock.java
@@ -0,0 +1,99 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.integration;
+
+import com.yahoo.config.provision.CloudName;
+import com.yahoo.config.provision.Environment;
+import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.zone.ZoneApi;
+import com.yahoo.config.provision.zone.ZoneFilter;
+import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.config.provision.zone.ZoneList;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+/**
+ * A ZoneList implementation which assumes all zones are controllerManaged.
+ *
+ * @author jonmv
+ */
+public class ZoneFilterMock implements ZoneList {
+
+ private final List<ZoneApi> zones;
+ private final boolean negate;
+
+ private ZoneFilterMock(List<ZoneApi> zones, boolean negate) {
+ this.zones = zones;
+ this.negate = negate;
+ }
+
+ public static ZoneFilter from(Collection<ZoneApi> zones) {
+ return new ZoneFilterMock(new ArrayList<>(zones), false);
+ }
+
+ @Override
+ public ZoneList not() {
+ return new ZoneFilterMock(zones, ! negate);
+ }
+
+ @Override
+ public ZoneList all() {
+ return filter(zone -> true);
+ }
+
+ @Override
+ public ZoneList controllerUpgraded() {
+ return all();
+ }
+
+ @Override
+ public ZoneList directlyRouted() {
+ return all();
+ }
+
+ @Override
+ public ZoneList reachable() {
+ return all();
+ }
+
+ @Override
+ public ZoneList in(Environment... environments) {
+ return filter(zone -> new HashSet<>(Arrays.asList(environments)).contains(zone.getEnvironment()));
+ }
+
+ @Override
+ public ZoneList in(RegionName... regions) {
+ return filter(zone -> new HashSet<>(Arrays.asList(regions)).contains(zone.getRegionName()));
+ }
+
+ @Override
+ public ZoneList among(ZoneId... zones) {
+ return filter(zone -> new HashSet<>(Arrays.asList(zones)).contains(zone.getId()));
+ }
+
+ @Override
+ public List<? extends ZoneApi> zones() {
+ return List.copyOf(zones);
+ }
+
+ @Override
+ public ZoneList ofCloud(CloudName cloud) {
+ return filter(zone -> zone.getCloudName().equals(cloud));
+ }
+
+ private ZoneFilterMock filter(Predicate<ZoneApi> condition) {
+ return new ZoneFilterMock(
+ zones.stream()
+ .filter(zone -> negate ?
+ condition.negate().test(zone) :
+ condition.test(zone))
+ .collect(Collectors.toList()),
+ false);
+ }
+
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java
index 4248a513950..f802a6af549 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java
@@ -10,13 +10,13 @@ import com.yahoo.config.provision.CloudName;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.SystemName;
-import com.yahoo.vespa.athenz.api.AthenzService;
-import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
-import com.yahoo.vespa.hosted.controller.api.integration.deployment.RunId;
import com.yahoo.config.provision.zone.UpgradePolicy;
+import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.config.provision.zone.ZoneFilter;
-import com.yahoo.config.provision.zone.ZoneFilterMock;
import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.vespa.athenz.api.AthenzService;
+import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
+import com.yahoo.vespa.hosted.controller.api.integration.deployment.RunId;
import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry;
import java.net.URI;
@@ -27,6 +27,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.stream.Collectors;
/**
* @author mpolden
@@ -35,14 +36,14 @@ public class ZoneRegistryMock extends AbstractComponent implements ZoneRegistry
private final Map<ZoneId, Duration> deploymentTimeToLive = new HashMap<>();
private final Map<Environment, RegionName> defaultRegionForEnvironment = new HashMap<>();
- private List<ZoneId> zones = new ArrayList<>();
+ private List<ZoneApi> zones = new ArrayList<>();
private SystemName system;
private UpgradePolicy upgradePolicy = null;
private Map<CloudName, UpgradePolicy> osUpgradePolicies = new HashMap<>();
@Inject
public ZoneRegistryMock(ConfigserverConfig config) {
- this(SystemName.valueOf(config.system()));
+ this(SystemName.from(config.system()));
}
public ZoneRegistryMock() {
@@ -51,10 +52,11 @@ public class ZoneRegistryMock extends AbstractComponent implements ZoneRegistry
public ZoneRegistryMock(SystemName system) {
this.system = system;
- zones.add(ZoneId.from("prod", "us-east-3"));
- zones.add(ZoneId.from("prod", "us-west-1"));
- zones.add(ZoneId.from("prod", "us-central-1"));
- zones.add(ZoneId.from("prod", "eu-west-1"));
+ setZones(List.of(
+ ZoneApiMock.fromId("prod.us-east-3"),
+ ZoneApiMock.fromId("prod.us-west-1"),
+ ZoneApiMock.fromId("prod.us-central-1"),
+ ZoneApiMock.fromId("prod.eu-west-1")));
}
public ZoneRegistryMock setDeploymentTimeToLive(ZoneId zone, Duration duration) {
@@ -67,12 +69,12 @@ public class ZoneRegistryMock extends AbstractComponent implements ZoneRegistry
return this;
}
- public ZoneRegistryMock setZones(List<ZoneId> zones) {
+ public ZoneRegistryMock setZones(List<ZoneApi> zones) {
this.zones = zones;
return this;
}
- public ZoneRegistryMock setZones(ZoneId... zone) {
+ public ZoneRegistryMock setZones(ZoneApi... zone) {
return setZones(List.of(zone));
}
@@ -98,7 +100,7 @@ public class ZoneRegistryMock extends AbstractComponent implements ZoneRegistry
@Override
public ZoneFilter zones() {
- return ZoneFilterMock.from(Collections.unmodifiableList(zones));
+ return ZoneFilterMock.from(List.copyOf(zones));
}
public AthenzService getConfigServerAthenzIdentity(ZoneId zone) {
@@ -147,7 +149,7 @@ public class ZoneRegistryMock extends AbstractComponent implements ZoneRegistry
@Override
public boolean hasZone(ZoneId zoneId) {
- return zones.contains(zoneId);
+ return zones.stream().anyMatch(zone -> zone.getId().equals(zoneId));
}
@Override
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CostReportMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CostReportMaintainerTest.java
index 890af974a0e..1edf371924a 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CostReportMaintainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CostReportMaintainerTest.java
@@ -1,7 +1,9 @@
package com.yahoo.vespa.hosted.controller.maintenance;
+import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.ControllerTester;
import com.yahoo.vespa.hosted.controller.integration.NodeRepositoryClientMock;
+import com.yahoo.vespa.hosted.controller.integration.ZoneApiMock;
import com.yahoo.vespa.hosted.controller.restapi.cost.CostReportConsumer;
import com.yahoo.vespa.hosted.controller.restapi.cost.config.SelfHostedCostConfig;
import org.junit.Assert;
@@ -10,13 +12,19 @@ import org.junit.Test;
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
-import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.List;
public class CostReportMaintainerTest {
@Test
public void maintain() {
ControllerTester tester = new ControllerTester();
+ tester.zoneRegistry().setZones(
+ ZoneApiMock.newBuilder().withId("prod.us-east-3").withCloud("yahoo").build(),
+ ZoneApiMock.newBuilder().withId("prod.us-west-1").withCloud("yahoo").build(),
+ ZoneApiMock.newBuilder().withId("prod.us-central-1").withCloud("yahoo").build(),
+ ZoneApiMock.newBuilder().withId("prod.eu-west-1").withCloud("yahoo").build());
CostReportConsumer mockConsumer = csv -> Assert.assertEquals(csv,
"Date,Property,Reserved Cpu Cores,Reserved Memory GB,Reserved Disk Space GB,Usage Fraction\n" +
@@ -42,7 +50,7 @@ public class CostReportMaintainerTest {
mockConsumer,
new JobControl(tester.curator()),
new NodeRepositoryClientMock(),
- Clock.fixed(Instant.EPOCH, ZoneId.of("UTC")),
+ Clock.fixed(Instant.EPOCH, java.time.ZoneId.of("UTC")),
costConfig);
maintainer.maintain();
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DnsMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DnsMaintainerTest.java
deleted file mode 100644
index 13218cc2442..00000000000
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DnsMaintainerTest.java
+++ /dev/null
@@ -1,141 +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.hosted.controller.maintenance;
-
-import com.yahoo.config.application.api.ValidationId;
-import com.yahoo.config.provision.Environment;
-import com.yahoo.config.provision.RegionName;
-import com.yahoo.config.provision.zone.ZoneId;
-import com.yahoo.vespa.hosted.controller.Application;
-import com.yahoo.vespa.hosted.controller.ControllerTester;
-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.Endpoint;
-import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder;
-import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester;
-import com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb;
-import com.yahoo.vespa.hosted.controller.rotation.Rotation;
-import com.yahoo.vespa.hosted.controller.rotation.RotationId;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.time.Duration;
-import java.util.Optional;
-import java.util.Set;
-import java.util.function.Function;
-
-import static com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType.component;
-import static com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType.systemTest;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-/**
- * @author mpolden
- */
-public class DnsMaintainerTest {
-
- private DeploymentTester tester;
- private DnsMaintainer maintainer;
-
- @Before
- public void before() {
- tester = new DeploymentTester();
- maintainer = new DnsMaintainer(tester.controller(), Duration.ofHours(12),
- new JobControl(new MockCuratorDb()));
- }
-
- @Test
- public void removes_record_for_unassigned_rotation() {
- Application application = tester.createApplication("app1", "tenant1", 1, 1L);
-
- ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
- .environment(Environment.prod)
- .globalServiceId("foo")
- .region("us-west-1")
- .region("us-central-1")
- .build();
-
- Function<String, Optional<Record>> findCname = (name) -> tester.controllerTester().nameService()
- .findRecords(Record.Type.CNAME,
- RecordName.from(name))
- .stream()
- .findFirst();
-
- // Deploy application
- tester.deployCompletely(application, applicationPackage);
- assertEquals(3, records().size());
-
- Optional<Record> record = findCname.apply("app1--tenant1.global.vespa.yahooapis.com");
- assertTrue(record.isPresent());
- assertEquals("app1--tenant1.global.vespa.yahooapis.com", record.get().name().asString());
- assertEquals("rotation-fqdn-01.", record.get().data().asString());
-
- record = findCname.apply("app1--tenant1.global.vespa.oath.cloud");
- assertTrue(record.isPresent());
- assertEquals("app1--tenant1.global.vespa.oath.cloud", record.get().name().asString());
- assertEquals("rotation-fqdn-01.", record.get().data().asString());
-
- record = findCname.apply("app1.tenant1.global.vespa.yahooapis.com");
- assertTrue(record.isPresent());
- assertEquals("app1.tenant1.global.vespa.yahooapis.com", record.get().name().asString());
- assertEquals("rotation-fqdn-01.", record.get().data().asString());
-
- // DnsMaintainer does nothing
- maintainer.maintain();
- assertTrue("DNS record is not removed", findCname.apply("app1--tenant1.global.vespa.yahooapis.com").isPresent());
- assertTrue("DNS record is not removed", findCname.apply("app1--tenant1.global.vespa.oath.cloud").isPresent());
- assertTrue("DNS record is not removed", findCname.apply("app1.tenant1.global.vespa.yahooapis.com").isPresent());
-
- // Remove application
- applicationPackage = new ApplicationPackageBuilder()
- .environment(Environment.prod)
- .allow(ValidationId.deploymentRemoval)
- .build();
- tester.jobCompletion(component).application(application).nextBuildNumber().uploadArtifact(applicationPackage).submit();
-
- tester.deployAndNotify(application, applicationPackage, true, systemTest);
- tester.applications().deactivate(application.id(), ZoneId.from(Environment.test, RegionName.from("us-east-1")));
- tester.applications().deactivate(application.id(), ZoneId.from(Environment.staging, RegionName.from("us-east-3")));
- tester.controllerTester().deleteApplication(application.id());
-
- // DnsMaintainer removes records
- for (int i = 0; i < ControllerTester.availableRotations; i++) {
- maintainer.maintain();
- }
- tester.updateDns();
- assertFalse("DNS record removed", findCname.apply("app1--tenant1.global.vespa.yahooapis.com").isPresent());
- assertFalse("DNS record removed", findCname.apply("app1--tenant1.global.vespa.oath.cloud").isPresent());
- assertFalse("DNS record removed", findCname.apply("app1.tenant1.global.vespa.yahooapis.com").isPresent());
- }
-
- @Test
- public void rate_limit_record_removal() {
- // Create stale records
- int staleTotal = ControllerTester.availableRotations;
- for (int i = 1; i <= staleTotal; i++) {
- Rotation r = rotation(i);
- tester.controllerTester().nameService().createCname(RecordName.from("stale-record-" + i + "." +
- Endpoint.OATH_DNS_SUFFIX),
- RecordData.from(r.name() + "."));
- }
-
- // One record is removed per run
- for (int i = 1; i <= staleTotal*2; i++) {
- maintainer.run();
- tester.updateDns();
- assertEquals(Math.max(staleTotal - i, 0), records().size());
- }
- }
-
- private Set<Record> records() {
- return tester.controllerTester().nameService().records();
- }
-
- private static Rotation rotation(int n) {
- String id = String.format("%02d", n);
- return new Rotation(new RotationId("rotation-id-" + id), "rotation-fqdn-" + id);
- }
-
-}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporterTest.java
index b18c39f4042..148be3f258e 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporterTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporterTest.java
@@ -1,38 +1,23 @@
// Copyright 2017 Yahoo Holdings. 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.fasterxml.jackson.databind.DeserializationFeature;
-import com.fasterxml.jackson.databind.ObjectMapper;
import com.yahoo.component.Version;
import com.yahoo.config.provision.Environment;
-import com.yahoo.config.provision.SystemName;
-import com.yahoo.test.ManualClock;
+import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.ControllerTester;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
-import com.yahoo.vespa.hosted.controller.api.integration.chef.ChefMock;
-import com.yahoo.vespa.hosted.controller.api.integration.chef.rest.PartialNodeResult;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
-import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester;
import com.yahoo.vespa.hosted.controller.deployment.InternalDeploymentTester;
import com.yahoo.vespa.hosted.controller.integration.MetricsMock;
-import com.yahoo.vespa.hosted.controller.integration.MetricsMock.MapContext;
import com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb;
-import org.junit.Before;
import org.junit.Test;
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.time.Clock;
import java.time.Duration;
-import java.time.Instant;
-import java.util.Map;
import static com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType.component;
import static com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType.productionUsWest1;
@@ -40,39 +25,13 @@ import static com.yahoo.vespa.hosted.controller.api.integration.deployment.JobTy
import static com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType.systemTest;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
/**
* @author mortent
*/
public class MetricsReporterTest {
- private static final Path testData = Paths.get("src/test/resources/");
-
- private MetricsMock metrics;
-
- @Before
- public void before() {
- metrics = new MetricsMock();
- }
-
- @Test
- public void test_chef_metrics() {
- Clock clock = new ManualClock(Instant.ofEpochSecond(1475497913));
- ControllerTester tester = new ControllerTester();
- MetricsReporter metricsReporter = createReporter(clock, tester.controller(), metrics, SystemName.cd);
- metricsReporter.maintain();
- assertEquals(2, metrics.getMetrics().size());
-
- Map<MapContext, Map<String, Number>> hostMetrics = getMetricsByHost("fake-node.test");
- assertEquals(1, hostMetrics.size());
- Map.Entry<MapContext, Map<String, Number>> metricEntry = hostMetrics.entrySet().iterator().next();
- MapContext metricContext = metricEntry.getKey();
- assertDimension(metricContext, "tenantName", "ciintegrationtests");
- assertDimension(metricContext, "app", "restart.default");
- assertDimension(metricContext, "zone", "prod.cd-us-east-1");
- assertEquals(727, metricEntry.getValue().get(MetricsReporter.CONVERGENCE_METRIC).longValue());
- }
+ private final MetricsMock metrics = new MetricsMock();
@Test
public void test_deployment_fail_ratio() {
@@ -81,7 +40,7 @@ public class MetricsReporterTest {
.environment(Environment.prod)
.region("us-west-1")
.build();
- MetricsReporter metricsReporter = createReporter(tester.controller(), metrics, SystemName.main);
+ MetricsReporter metricsReporter = createReporter(tester.controller());
metricsReporter.maintain();
assertEquals(0.0, metrics.getMetric(MetricsReporter.DEPLOYMENT_FAIL_METRIC));
@@ -108,14 +67,6 @@ public class MetricsReporterTest {
}
@Test
- public void test_chef_metrics_omit_zone_when_unknown() {
- ControllerTester tester = new ControllerTester();
- String hostname = "fake-node2.test";
- MapContext metricContext = getMetricContextByHost(tester.controller(), hostname);
- assertNull(metricContext.getDimensions().get("zone"));
- }
-
- @Test
public void test_deployment_average_duration() {
DeploymentTester tester = new DeploymentTester();
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
@@ -123,7 +74,7 @@ public class MetricsReporterTest {
.region("us-west-1")
.build();
- MetricsReporter reporter = createReporter(tester.controller(), metrics, SystemName.main);
+ MetricsReporter reporter = createReporter(tester.controller());
Application app = tester.createApplication("app1", "tenant1", 1, 11L);
tester.deployCompletely(app, applicationPackage);
@@ -165,7 +116,7 @@ public class MetricsReporterTest {
.region("us-west-1")
.build();
- MetricsReporter reporter = createReporter(tester.controller(), metrics, SystemName.main);
+ MetricsReporter reporter = createReporter(tester.controller());
Application app = tester.createApplication("app1", "tenant1", 1, 11L);
// Initial deployment without failures
@@ -216,7 +167,7 @@ public class MetricsReporterTest {
.region("us-west-1")
.region("us-east-3")
.build();
- MetricsReporter reporter = createReporter(tester.controller(), metrics, SystemName.main);
+ MetricsReporter reporter = createReporter(tester.controller());
Application application = tester.createApplication("app1", "tenant1", 1, 11L);
tester.configServer().generateWarnings(new DeploymentId(application.id(), ZoneId.from("prod", "us-west-1")), 3);
tester.configServer().generateWarnings(new DeploymentId(application.id(), ZoneId.from("prod", "us-east-3")), 4);
@@ -231,7 +182,7 @@ public class MetricsReporterTest {
ApplicationVersion version = tester.deployNewSubmission();
assertEquals(1000, version.buildTime().get().toEpochMilli());
- MetricsReporter reporter = createReporter(tester.tester().controller(), metrics, SystemName.main);
+ MetricsReporter reporter = createReporter(tester.tester().controller());
reporter.maintain();
assertEquals(tester.clock().instant().getEpochSecond() - 1,
getMetric(MetricsReporter.DEPLOYMENT_BUILD_AGE_SECONDS, tester.app()));
@@ -246,7 +197,7 @@ public class MetricsReporterTest {
.region("us-west-1")
.region("us-east-3")
.build();
- MetricsReporter reporter = createReporter(tester.controller(), metrics, SystemName.main);
+ MetricsReporter reporter = createReporter(tester.controller());
Application application = tester.createApplication("app1", "tenant1", 1, 11L);
reporter.maintain();
assertEquals("Queue is empty initially", 0, metrics.getMetric(MetricsReporter.NAME_SERVICE_REQUESTS_QUEUED).intValue());
@@ -255,7 +206,7 @@ public class MetricsReporterTest {
reporter.maintain();
assertEquals("Deployment queues name services requests", 6, metrics.getMetric(MetricsReporter.NAME_SERVICE_REQUESTS_QUEUED).intValue());
- tester.updateDns();
+ tester.flushDnsRequests();
reporter.maintain();
assertEquals("Queue consumed", 0, metrics.getMetric(MetricsReporter.NAME_SERVICE_REQUESTS_QUEUED).intValue());
}
@@ -279,43 +230,8 @@ public class MetricsReporterTest {
.orElseThrow(() -> new RuntimeException("Expected metric to exist for " + application.id()));
}
- private MetricsReporter createReporter(Controller controller, MetricsMock metricsMock, SystemName system) {
- return createReporter(controller.clock(), controller, metricsMock, system);
- }
-
- private MetricsReporter createReporter(Clock clock, Controller controller, MetricsMock metricsMock,
- SystemName system) {
- ChefMock chef = new ChefMock();
- PartialNodeResult result;
- try {
- result = new ObjectMapper()
- .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
- .readValue(testData.resolve("chef_output.json").toFile(), PartialNodeResult.class);
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- chef.addPartialResult(result.rows);
- return new MetricsReporter(controller, metricsMock, chef, clock, new JobControl(new MockCuratorDb()), system);
- }
-
- private Map<MapContext, Map<String, Number>> getMetricsByHost(String hostname) {
- return metrics.getMetrics((dimensions) -> hostname.equals(dimensions.get("host")));
- }
-
- private MapContext getMetricContextByHost(Controller controller, String hostname) {
- MetricsReporter metricsReporter = createReporter(controller, metrics, SystemName.main);
- metricsReporter.maintain();
-
- assertFalse(metrics.getMetrics().isEmpty());
-
- Map<MapContext, Map<String, Number>> metrics = getMetricsByHost(hostname);
- assertEquals(1, metrics.size());
- Map.Entry<MapContext, Map<String, Number>> metricEntry = metrics.entrySet().iterator().next();
- return metricEntry.getKey();
- }
-
- private static void assertDimension(MapContext metricContext, String dimensionName, String expectedValue) {
- assertEquals(expectedValue, metricContext.getDimensions().get(dimensionName));
+ private MetricsReporter createReporter(Controller controller) {
+ return new MetricsReporter(controller, metrics, new JobControl(new MockCuratorDb()));
}
private static String appDimension(Application application) {
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgraderTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgraderTest.java
index 7a008d1f478..38aa4af4756 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgraderTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgraderTest.java
@@ -3,26 +3,24 @@ package com.yahoo.vespa.hosted.controller.maintenance;
import com.yahoo.component.Version;
import com.yahoo.config.provision.CloudName;
-import com.yahoo.config.provision.NodeType;
import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.Node;
import com.yahoo.config.provision.zone.UpgradePolicy;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.application.SystemApplication;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester;
import com.yahoo.vespa.hosted.controller.integration.NodeRepositoryMock;
+import com.yahoo.vespa.hosted.controller.integration.ZoneApiMock;
import com.yahoo.vespa.hosted.controller.versions.OsVersionStatus;
import org.junit.Before;
import org.junit.Test;
import java.time.Duration;
-import java.util.Collections;
import java.util.List;
-import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
-import static java.util.Collections.singletonList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -31,11 +29,11 @@ import static org.junit.Assert.assertTrue;
*/
public class OsUpgraderTest {
- private static final ZoneId zone1 = ZoneId.from("prod", "eu-west-1");
- private static final ZoneId zone2 = ZoneId.from("prod", "us-west-1");
- private static final ZoneId zone3 = ZoneId.from("prod", "us-central-1");
- private static final ZoneId zone4 = ZoneId.from("prod", "us-east-3");
- private static final ZoneId zone5 = ZoneId.from("prod", "us-north-1", "other");
+ private static final ZoneApi zone1 = ZoneApiMock.newBuilder().withId("prod.eu-west-1").build();
+ private static final ZoneApi zone2 = ZoneApiMock.newBuilder().withId("prod.us-west-1").build();
+ private static final ZoneApi zone3 = ZoneApiMock.newBuilder().withId("prod.us-central-1").build();
+ private static final ZoneApi zone4 = ZoneApiMock.newBuilder().withId("prod.us-east-3").build();
+ private static final ZoneApi zone5 = ZoneApiMock.newBuilder().withId("prod.us-north-1").withCloud("other").build();
private DeploymentTester tester;
private OsVersionStatusUpdater statusUpdater;
@@ -59,18 +57,16 @@ public class OsUpgraderTest {
);
// Bootstrap system
- tester.configServer().bootstrap(List.of(zone1, zone2, zone3, zone4, zone5),
- singletonList(SystemApplication.zone),
- Optional.of(NodeType.host));
+ tester.configServer().bootstrap(List.of(zone1.getId(), zone2.getId(), zone3.getId(), zone4.getId(), zone5.getId()),
+ List.of(SystemApplication.tenantHost));
// Add system applications that exist in a real system, but are currently not upgraded
- tester.configServer().addNodes(List.of(zone1, zone2, zone3, zone4, zone5),
- Collections.singletonList(SystemApplication.configServer),
- Optional.empty());
+ tester.configServer().addNodes(List.of(zone1.getId(), zone2.getId(), zone3.getId(), zone4.getId(), zone5.getId()),
+ List.of(SystemApplication.configServer));
// Fail a few nodes. Failed nodes should not affect versions
- failNodeIn(zone1, SystemApplication.zone);
- failNodeIn(zone3, SystemApplication.zone);
+ failNodeIn(zone1.getId(), SystemApplication.tenantHost);
+ failNodeIn(zone3.getId(), SystemApplication.tenantHost);
// New OS version released
Version version1 = Version.fromString("7.1");
@@ -82,37 +78,37 @@ public class OsUpgraderTest {
// zone 1: begins upgrading
osUpgrader.maintain();
- assertWanted(version1, SystemApplication.zone, zone1);
+ assertWanted(version1, SystemApplication.tenantHost, zone1.getId());
// Other zones remain on previous version (none)
- assertWanted(Version.emptyVersion, SystemApplication.zone, zone2, zone3, zone4);
+ assertWanted(Version.emptyVersion, SystemApplication.proxy, zone2.getId(), zone3.getId(), zone4.getId());
// zone 1: completes upgrade
- completeUpgrade(version1, SystemApplication.zone, zone1);
+ completeUpgrade(version1, SystemApplication.tenantHost, zone1.getId());
statusUpdater.maintain();
assertEquals(2, nodesOn(version1).size());
assertEquals(11, nodesOn(Version.emptyVersion).size());
// zone 2 and 3: begins upgrading
osUpgrader.maintain();
- assertWanted(version1, SystemApplication.zone, zone2, zone3);
+ assertWanted(version1, SystemApplication.proxy, zone2.getId(), zone3.getId());
// zone 4: still on previous version
- assertWanted(Version.emptyVersion, SystemApplication.zone, zone4);
+ assertWanted(Version.emptyVersion, SystemApplication.tenantHost, zone4.getId());
// zone 2 and 3: completes upgrade
- completeUpgrade(version1, SystemApplication.zone, zone2, zone3);
+ completeUpgrade(version1, SystemApplication.tenantHost, zone2.getId(), zone3.getId());
// zone 4: begins upgrading
osUpgrader.maintain();
- assertWanted(version1, SystemApplication.zone, zone4);
+ assertWanted(version1, SystemApplication.tenantHost, zone4.getId());
// zone 4: completes upgrade
- completeUpgrade(version1, SystemApplication.zone, zone4);
+ completeUpgrade(version1, SystemApplication.tenantHost, zone4.getId());
// Next run does nothing as all zones are upgraded
osUpgrader.maintain();
- assertWanted(version1, SystemApplication.zone, zone1, zone2, zone3, zone4);
+ assertWanted(version1, SystemApplication.tenantHost, zone1.getId(), zone2.getId(), zone3.getId(), zone4.getId());
statusUpdater.maintain();
assertTrue("All nodes on target version", tester.controller().osVersionStatus().nodesIn(cloud).stream()
.allMatch(node -> node.version().equals(version1)));
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OsVersionStatusUpdaterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OsVersionStatusUpdaterTest.java
index d8e6f573592..fe7f39fd66d 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OsVersionStatusUpdaterTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OsVersionStatusUpdaterTest.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.controller.maintenance;
import com.yahoo.component.Version;
import com.yahoo.config.provision.CloudName;
+import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.vespa.hosted.controller.ControllerTester;
import com.yahoo.config.provision.zone.UpgradePolicy;
import com.yahoo.config.provision.zone.ZoneId;
@@ -32,7 +33,7 @@ public class OsVersionStatusUpdaterTest {
new JobControl(new MockCuratorDb()));
// Add all zones to upgrade policy
UpgradePolicy upgradePolicy = UpgradePolicy.create();
- for (ZoneId zone : tester.zoneRegistry().zones().controllerUpgraded().ids()) {
+ for (ZoneApi zone : tester.zoneRegistry().zones().controllerUpgraded().zones()) {
upgradePolicy = upgradePolicy.upgrade(zone);
}
tester.zoneRegistry().setOsUpgradePolicy(CloudName.defaultName(), upgradePolicy);
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainerTest.java
index df2a4b5ca7f..540efcba3b8 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainerTest.java
@@ -1,15 +1,21 @@
// 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.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.ControllerTester;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.vespa.hosted.controller.api.integration.noderepository.NodeRepositoryNode;
import com.yahoo.vespa.hosted.controller.api.integration.resource.ResourceSnapshot;
import com.yahoo.vespa.hosted.controller.api.integration.stubs.MockResourceSnapshotConsumer;
import com.yahoo.vespa.hosted.controller.integration.NodeRepositoryClientMock;
import com.yahoo.vespa.hosted.controller.integration.MetricsMock;
+import com.yahoo.vespa.hosted.controller.integration.ZoneApiMock;
import org.junit.Test;
import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
import java.util.Map;
import static org.junit.Assert.*;
@@ -27,6 +33,12 @@ public class ResourceMeterMaintainerTest {
@Test
public void testMaintainer() {
ControllerTester tester = new ControllerTester();
+ tester.zoneRegistry().setZones(
+ ZoneApiMock.newBuilder().withId("prod.us-east-3").build(),
+ ZoneApiMock.newBuilder().withId("prod.us-west-1").build(),
+ ZoneApiMock.newBuilder().withId("prod.us-central-1").build(),
+ ZoneApiMock.newBuilder().withId("prod.aws-us-east-1").withCloud("aws").build());
+
ResourceMeterMaintainer resourceMeterMaintainer = new ResourceMeterMaintainer(tester.controller(), Duration.ofMinutes(5), new JobControl(tester.curator()), nodeRepository, tester.clock(), metrics, snapshotConsumer);
resourceMeterMaintainer.maintain();
Map<ApplicationId, ResourceSnapshot> consumedResources = snapshotConsumer.consumedResources();
@@ -36,15 +48,15 @@ public class ResourceMeterMaintainerTest {
ResourceSnapshot app1 = consumedResources.get(ApplicationId.from("tenant1", "app1", "default"));
ResourceSnapshot app2 = consumedResources.get(ApplicationId.from("tenant2", "app2", "default"));
- assertEquals(96, app1.getResourceAllocation().getCpuCores(), DELTA);
- assertEquals(96, app1.getResourceAllocation().getMemoryGb(), DELTA);
- assertEquals(2000, app1.getResourceAllocation().getDiskGb(), DELTA);
+ assertEquals(24, app1.getResourceAllocation().getCpuCores(), DELTA);
+ assertEquals(24, app1.getResourceAllocation().getMemoryGb(), DELTA);
+ assertEquals(500, app1.getResourceAllocation().getDiskGb(), DELTA);
- assertEquals(160, app2.getResourceAllocation().getCpuCores(), DELTA);
- assertEquals(96, app2.getResourceAllocation().getMemoryGb(), DELTA);
- assertEquals(2000, app2.getResourceAllocation().getDiskGb(), DELTA);
+ assertEquals(40, app2.getResourceAllocation().getCpuCores(), DELTA);
+ assertEquals(24, app2.getResourceAllocation().getMemoryGb(), DELTA);
+ assertEquals(500, app2.getResourceAllocation().getDiskGb(), DELTA);
assertEquals(tester.clock().millis()/1000, metrics.getMetric("metering_last_reported"));
- assertEquals(4448.0d, (Double) metrics.getMetric("metering_total_reported"), DELTA);
+ assertEquals(1112.0d, (Double) metrics.getMetric("metering_total_reported"), DELTA);
}
} \ No newline at end of file
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPolicyMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPoliciesTest.java
index 14d5dc4e7c3..f0344cb8d12 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPolicyMaintainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPoliciesTest.java
@@ -9,18 +9,17 @@ import com.yahoo.config.provision.RotationName;
import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.Application;
+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.Record;
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.deployment.ApplicationPackageBuilder;
-import com.yahoo.vespa.hosted.controller.deployment.BuildJob;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester;
-import com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb;
import org.junit.Test;
-import java.time.Duration;
+import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -37,31 +36,32 @@ import static org.junit.Assert.assertTrue;
* @author mortent
* @author mpolden
*/
-public class RoutingPolicyMaintainerTest {
+public class RoutingPoliciesTest {
private final DeploymentTester tester = new DeploymentTester();
+
private final Application app1 = tester.createApplication("app1", "tenant1", 1, 1L);
private final Application app2 = tester.createApplication("app2", "tenant1", 1, 1L);
- private final RoutingPolicyMaintainer maintainer = new RoutingPolicyMaintainer(tester.controller(), Duration.ofHours(12),
- new JobControl(new MockCuratorDb()),
- tester.controllerTester().curator());
+ private final ZoneId zone1 = ZoneId.from("prod", "us-west-1");
+ private final ZoneId zone2 = ZoneId.from("prod", "us-central-1");
+ private final ZoneId zone3 = ZoneId.from("prod", "us-east-3");
+
private final ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
- .environment(Environment.prod)
- .region("us-west-1")
- .region("us-central-1")
+ .region(zone1.region())
+ .region(zone2.region())
.build();
@Test
public void maintains_global_routing_policies() {
+ int buildNumber = 42;
int clustersPerZone = 2;
- tester.deployCompletely(app1, applicationPackage);
- // Cluster is member of 2 global rotations
+ // Cluster 0 is member of 2 global rotations
Map<Integer, Set<RotationName>> rotations = Map.of(0, Set.of(RotationName.from("r0"), RotationName.from("r1")));
- provisionLoadBalancers(app1, clustersPerZone, rotations);
+ provisionLoadBalancers(clustersPerZone, rotations, app1.id(), zone1, zone2);
// Creates alias records for cluster0
- maintain();
+ tester.deployCompletely(app1, applicationPackage, ++buildNumber);
Supplier<List<Record>> records1 = () -> tester.controllerTester().nameService().findRecords(Record.Type.ALIAS, RecordName.from("r0.app1.tenant1.global.vespa.oath.cloud"));
Supplier<List<Record>> records2 = () -> tester.controllerTester().nameService().findRecords(Record.Type.ALIAS, RecordName.from("r1.app1.tenant1.global.vespa.oath.cloud"));
assertEquals(2, records1.get().size());
@@ -70,22 +70,21 @@ public class RoutingPolicyMaintainerTest {
assertEquals("lb-0--tenant1:app1:default--prod.us-west-1/dns-zone-1/prod.us-west-1", records1.get().get(1).data().asString());
assertEquals("lb-0--tenant1:app1:default--prod.us-central-1/dns-zone-1/prod.us-central-1", records2.get().get(0).data().asString());
assertEquals("lb-0--tenant1:app1:default--prod.us-west-1/dns-zone-1/prod.us-west-1", records2.get().get(1).data().asString());
- assertEquals(2, tester.controller().applications().routingPolicies(app1.id()).iterator().next()
+ assertEquals(2, tester.controller().applications().routingPolicies().get(app1.id()).iterator().next()
.rotationEndpointsIn(SystemName.main).asList().size());
// Applications gains a new deployment
ApplicationPackage updatedApplicationPackage = new ApplicationPackageBuilder()
.environment(Environment.prod)
- .region("us-west-1")
- .region("us-central-1")
- .region("us-east-3")
+ .region(zone1.region())
+ .region(zone2.region())
+ .region(zone3.region())
.build();
int numberOfDeployments = 3;
- tester.deployCompletely(app1, updatedApplicationPackage, BuildJob.defaultBuildNumber + 1);
+ provisionLoadBalancers(clustersPerZone, rotations, app1.id(), zone3);
+ tester.deployCompletely(app1, updatedApplicationPackage, ++buildNumber);
// Cluster in new deployment is added to the rotation
- provisionLoadBalancers(app1, 2, rotations);
- maintain();
assertEquals(numberOfDeployments, records1.get().size());
assertEquals("lb-0--tenant1:app1:default--prod.us-central-1/dns-zone-1/prod.us-central-1", records1.get().get(0).data().asString());
assertEquals("lb-0--tenant1:app1:default--prod.us-east-3/dns-zone-1/prod.us-east-3", records1.get().get(1).data().asString());
@@ -93,16 +92,15 @@ public class RoutingPolicyMaintainerTest {
// Another application is deployed
Supplier<List<Record>> records3 = () -> tester.controllerTester().nameService().findRecords(Record.Type.ALIAS, RecordName.from("r0.app2.tenant1.global.vespa.oath.cloud"));
+ provisionLoadBalancers(1, Map.of(0, Set.of(RotationName.from("r0"))), app2.id(), zone1, zone2);
tester.deployCompletely(app2, applicationPackage);
- provisionLoadBalancers(app2, 1, Map.of(0, Set.of(RotationName.from("r0"))));
- maintain();
assertEquals(2, records3.get().size());
assertEquals("lb-0--tenant1:app2:default--prod.us-central-1/dns-zone-1/prod.us-central-1", records3.get().get(0).data().asString());
assertEquals("lb-0--tenant1:app2:default--prod.us-west-1/dns-zone-1/prod.us-west-1", records3.get().get(1).data().asString());
// All rotations for app1 are removed
- provisionLoadBalancers(app1, clustersPerZone, Collections.emptyMap());
- maintain();
+ provisionLoadBalancers(clustersPerZone, Map.of(), app1.id(), zone1, zone2, zone3);
+ tester.deployCompletely(app1, updatedApplicationPackage, ++buildNumber);
assertEquals(List.of(), records1.get());
Set<RoutingPolicy> policies = tester.controller().curator().readRoutingPolicies(app1.id());
assertEquals(clustersPerZone * numberOfDeployments, policies.size());
@@ -115,11 +113,11 @@ public class RoutingPolicyMaintainerTest {
public void maintains_routing_policies_per_zone() {
// Deploy application
int clustersPerZone = 2;
- tester.deployCompletely(app1, applicationPackage);
- provisionLoadBalancers(app1, clustersPerZone);
+ int buildNumber = 42;
+ provisionLoadBalancers(clustersPerZone, app1.id(), zone1, zone2);
+ tester.deployCompletely(app1, applicationPackage, ++buildNumber);
- // Creates records and policies for all clusters in all zones
- maintain();
+ // Deployment creates records and policies for all clusters in all zones
Set<String> expectedRecords = Set.of(
"c0.app1.tenant1.us-west-1.vespa.oath.cloud",
"c1.app1.tenant1.us-west-1.vespa.oath.cloud",
@@ -129,14 +127,14 @@ public class RoutingPolicyMaintainerTest {
assertEquals(expectedRecords, recordNames());
assertEquals(4, policies(app1).size());
- // Next run does nothing
- maintain();
+ // Next deploy does nothing
+ tester.deployCompletely(app1, applicationPackage, ++buildNumber);
assertEquals(expectedRecords, recordNames());
assertEquals(4, policies(app1).size());
- // Add 1 cluster in each zone
- provisionLoadBalancers(app1, clustersPerZone + 1);
- maintain();
+ // Add 1 cluster in each zone and deploy
+ provisionLoadBalancers(clustersPerZone + 1, app1.id(), zone1, zone2);
+ tester.deployCompletely(app1, applicationPackage, ++buildNumber);
expectedRecords = Set.of(
"c0.app1.tenant1.us-west-1.vespa.oath.cloud",
"c1.app1.tenant1.us-west-1.vespa.oath.cloud",
@@ -148,10 +146,9 @@ public class RoutingPolicyMaintainerTest {
assertEquals(expectedRecords, recordNames());
assertEquals(6, policies(app1).size());
- // Add another application
- tester.deployCompletely(app2, applicationPackage);
- provisionLoadBalancers(app2, clustersPerZone);
- maintain();
+ // Deploy another application
+ provisionLoadBalancers(clustersPerZone, app2.id(), zone1, zone2);
+ tester.deployCompletely(app2, applicationPackage, ++buildNumber);
expectedRecords = Set.of(
"c0.app1.tenant1.us-west-1.vespa.oath.cloud",
"c1.app1.tenant1.us-west-1.vespa.oath.cloud",
@@ -167,9 +164,9 @@ public class RoutingPolicyMaintainerTest {
assertEquals(expectedRecords, recordNames());
assertEquals(4, policies(app2).size());
- // Remove cluster from app1
- provisionLoadBalancers(app1, clustersPerZone);
- maintain();
+ // Deploy removes cluster from app1
+ provisionLoadBalancers(clustersPerZone, app1.id(), zone1, zone2);
+ tester.deployCompletely(app1, applicationPackage, ++buildNumber);
expectedRecords = Set.of(
"c0.app1.tenant1.us-west-1.vespa.oath.cloud",
"c1.app1.tenant1.us-west-1.vespa.oath.cloud",
@@ -185,10 +182,10 @@ public class RoutingPolicyMaintainerTest {
// Remove app2 completely
tester.controller().applications().require(app2.id()).deployments().keySet()
.forEach(zone -> {
- tester.controller().applications().deactivate(app2.id(), zone);
tester.configServer().removeLoadBalancers(app2.id(), zone);
+ tester.controller().applications().deactivate(app2.id(), zone);
});
- maintain();
+ tester.flushDnsRequests();
expectedRecords = Set.of(
"c0.app1.tenant1.us-west-1.vespa.oath.cloud",
"c1.app1.tenant1.us-west-1.vespa.oath.cloud",
@@ -196,13 +193,22 @@ public class RoutingPolicyMaintainerTest {
"c1.app1.tenant1.us-central-1.vespa.oath.cloud"
);
assertEquals(expectedRecords, recordNames());
- assertTrue("Removes stale routing policies " + app2, tester.controller().applications().routingPolicies(app2.id()).isEmpty());
- assertEquals("Keeps routing policies for " + app1, 4, tester.controller().applications().routingPolicies(app1.id()).size());
+ assertTrue("Removes stale routing policies " + app2, tester.controller().applications().routingPolicies().get(app2.id()).isEmpty());
+ assertEquals("Keeps routing policies for " + app1, 4, tester.controller().applications().routingPolicies().get(app1.id()).size());
}
- private void maintain() {
- maintainer.run();
- tester.updateDns();
+ @Test
+ public void cluster_endpoints_resolve_from_policies() {
+ provisionLoadBalancers(3, app1.id(), zone1);
+ tester.deployCompletely(app1, applicationPackage);
+ tester.controllerTester().routingGenerator().putEndpoints(new DeploymentId(app1.id(), zone1), Collections.emptyList());
+ assertEquals(Map.of(ClusterSpec.Id.from("c0"),
+ URI.create("https://c0.app1.tenant1.us-west-1.vespa.oath.cloud/"),
+ ClusterSpec.Id.from("c1"),
+ URI.create("https://c1.app1.tenant1.us-west-1.vespa.oath.cloud/"),
+ ClusterSpec.Id.from("c2"),
+ URI.create("https://c2.app1.tenant1.us-west-1.vespa.oath.cloud/")),
+ tester.controller().applications().clusterEndpoints(new DeploymentId(app1.id(), zone1)));
}
private Set<RoutingPolicy> policies(Application application) {
@@ -216,22 +222,19 @@ public class RoutingPolicyMaintainerTest {
.collect(Collectors.toSet());
}
- private void provisionLoadBalancers(Application application, int clustersPerZone, Map<Integer, Set<RotationName>> clusterRotations) {
- tester.controller().applications().require(application.id())
- .deployments().keySet()
- .forEach(zone -> tester.configServer().removeLoadBalancers(application.id(), zone));
- tester.controller().applications().require(application.id())
- .deployments().keySet()
- .forEach(zone -> tester.configServer()
- .addLoadBalancers(zone, createLoadBalancers(zone, application.id(), clustersPerZone, clusterRotations)));
+ private void provisionLoadBalancers(int clustersPerZone, Map<Integer, Set<RotationName>> clusterRotations, ApplicationId application, ZoneId... zones) {
+ for (ZoneId zone : zones) {
+ tester.configServer().removeLoadBalancers(application, zone);
+ tester.configServer().addLoadBalancers(zone, createLoadBalancers(zone, application, clustersPerZone, clusterRotations));
+ }
}
- private void provisionLoadBalancers(Application application, int clustersPerZone) {
- provisionLoadBalancers(application, clustersPerZone, Collections.emptyMap());
+ private void provisionLoadBalancers(int clustersPerZone, ApplicationId application, ZoneId... zones) {
+ provisionLoadBalancers(clustersPerZone, Map.of(), application, zones);
}
private static List<LoadBalancer> createLoadBalancers(ZoneId zone, ApplicationId application, int count,
- Map<Integer, Set<RotationName>> clusterRotations) {
+ Map<Integer, Set<RotationName>> clusterRotations) {
List<LoadBalancer> loadBalancers = new ArrayList<>();
for (int i = 0; i < count; i++) {
Set<RotationName> rotations = clusterRotations.getOrDefault(i, Collections.emptySet());
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/SystemUpgraderTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/SystemUpgraderTest.java
index 7f558131dd0..7b817c175b8 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/SystemUpgraderTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/SystemUpgraderTest.java
@@ -3,19 +3,19 @@ package com.yahoo.vespa.hosted.controller.maintenance;
import com.yahoo.component.Version;
import com.yahoo.config.provision.zone.UpgradePolicy;
+import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.Node;
import com.yahoo.vespa.hosted.controller.application.SystemApplication;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester;
import com.yahoo.vespa.hosted.controller.integration.NodeRepositoryMock;
+import com.yahoo.vespa.hosted.controller.integration.ZoneApiMock;
import com.yahoo.vespa.hosted.controller.versions.VespaVersion;
import org.junit.Before;
import org.junit.Test;
import java.time.Duration;
-import java.util.Collections;
import java.util.List;
-import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -27,10 +27,10 @@ import static org.junit.Assert.assertTrue;
*/
public class SystemUpgraderTest {
- private static final ZoneId zone1 = ZoneId.from("prod", "eu-west-1");
- private static final ZoneId zone2 = ZoneId.from("prod", "us-west-1");
- private static final ZoneId zone3 = ZoneId.from("prod", "us-central-1");
- private static final ZoneId zone4 = ZoneId.from("prod", "us-east-3");
+ private static final ZoneApi zone1 = ZoneApiMock.fromId("prod.eu-west-1");
+ private static final ZoneApi zone2 = ZoneApiMock.fromId("prod.us-west-1");
+ private static final ZoneApi zone3 = ZoneApiMock.fromId("prod.us-central-1");
+ private static final ZoneApi zone4 = ZoneApiMock.fromId("prod.us-east-3");
private DeploymentTester tester;
@@ -50,15 +50,15 @@ public class SystemUpgraderTest {
Version version1 = Version.fromString("6.5");
// Bootstrap a system without host applications
- tester.configServer().bootstrap(List.of(zone1, zone2, zone3, zone4), SystemApplication.configServer,
- SystemApplication.zone);
+ tester.configServer().bootstrap(List.of(zone1.getId(), zone2.getId(), zone3.getId(), zone4.getId()),
+ SystemApplication.configServer, SystemApplication.proxy);
// Fail a few nodes. Failed nodes should not affect versions
failNodeIn(zone1, SystemApplication.configServer);
- failNodeIn(zone3, SystemApplication.zone);
+ failNodeIn(zone3, SystemApplication.proxy);
tester.upgradeSystem(version1);
systemUpgrader.maintain();
assertCurrentVersion(SystemApplication.configServer, version1, zone1, zone2, zone3, zone4);
- assertCurrentVersion(SystemApplication.zone, version1, zone1, zone2, zone3, zone4);
+ assertCurrentVersion(SystemApplication.proxy, version1, zone1, zone2, zone3, zone4);
// Controller upgrades
Version version2 = Version.fromString("6.6");
@@ -71,72 +71,72 @@ public class SystemUpgraderTest {
// Other zones remain on previous version
assertWantedVersion(SystemApplication.configServer, version1, zone2, zone3, zone4);
// Zone application is not upgraded yet
- assertWantedVersion(SystemApplication.zone, version1, zone1, zone2, zone3, zone4);
+ assertWantedVersion(SystemApplication.proxy, version1, zone1, zone2, zone3, zone4);
// zone1: zone-config-server upgrades
completeUpgrade(SystemApplication.configServer, version2, zone1);
- // zone 1: zone-application upgrades
+ // zone 1: proxy-application upgrades
systemUpgrader.maintain();
- assertWantedVersion(SystemApplication.zone, version2, zone1);
- completeUpgrade(SystemApplication.zone, version2, zone1);
- assertTrue("Deployed zone application",
- tester.configServer().application(SystemApplication.zone.id()).isPresent());
+ assertWantedVersion(SystemApplication.proxy, version2, zone1);
+ completeUpgrade(SystemApplication.proxy, version2, zone1);
+ assertTrue("Deployed proxy application",
+ tester.configServer().application(SystemApplication.proxy.id()).isPresent());
// zone 2, 3 and 4: still targets old version
assertWantedVersion(SystemApplication.configServer, version1, zone2, zone3, zone4);
- assertWantedVersion(SystemApplication.zone, version1, zone2, zone3, zone4);
+ assertWantedVersion(SystemApplication.proxy, version1, zone2, zone3, zone4);
// zone 2 and 3: upgrade does not start until zone 1 zone-application config converges
systemUpgrader.maintain();
assertWantedVersion(SystemApplication.configServer, version1, zone2, zone3);
- convergeServices(SystemApplication.zone, zone1);
+ convergeServices(SystemApplication.proxy, zone1);
// zone 2 and 3: zone-config-server upgrades, first in zone 2, then in zone 3
systemUpgrader.maintain();
assertWantedVersion(SystemApplication.configServer, version2, zone2, zone3);
assertWantedVersion(SystemApplication.configServer, version1, zone4);
- assertWantedVersion(SystemApplication.zone, version1, zone2, zone3, zone4);
+ assertWantedVersion(SystemApplication.proxy, version1, zone2, zone3, zone4);
completeUpgrade(SystemApplication.configServer, version2, zone2);
// zone-application starts upgrading in zone 2, while zone-config-server completes upgrade in zone 3
systemUpgrader.maintain();
- assertWantedVersion(SystemApplication.zone, version2, zone2);
- assertWantedVersion(SystemApplication.zone, version1, zone3);
+ assertWantedVersion(SystemApplication.proxy, version2, zone2);
+ assertWantedVersion(SystemApplication.proxy, version1, zone3);
completeUpgrade(SystemApplication.configServer, version2, zone3);
- // zone 2 and 3: zone-application upgrades in parallel
+ // zone 2 and 3: proxy-application upgrades in parallel
systemUpgrader.maintain();
- assertWantedVersion(SystemApplication.zone, version2, zone2, zone3);
- completeUpgrade(SystemApplication.zone, version2, zone2, zone3);
- convergeServices(SystemApplication.zone, zone2, zone3);
+ assertWantedVersion(SystemApplication.proxy, version2, zone2, zone3);
+ completeUpgrade(SystemApplication.proxy, version2, zone2, zone3);
+ convergeServices(SystemApplication.proxy, zone2, zone3);
// zone 4: zone-config-server upgrades
systemUpgrader.maintain();
assertWantedVersion(SystemApplication.configServer, version2, zone4);
- assertWantedVersion(SystemApplication.zone, version1, zone4);
+ assertWantedVersion(SystemApplication.proxy, version1, zone4);
completeUpgrade(SystemApplication.configServer, version2, zone4);
// System version remains unchanged until final application upgrades
tester.computeVersionStatus();
assertSystemVersion(version1);
- // zone 4: zone-application upgrades
+ // zone 4: proxy-application upgrades
systemUpgrader.maintain();
- assertWantedVersion(SystemApplication.zone, version2, zone4);
- completeUpgrade(SystemApplication.zone, version2, zone4);
+ assertWantedVersion(SystemApplication.proxy, version2, zone4);
+ completeUpgrade(SystemApplication.proxy, version2, zone4);
// zone 4: System version remains unchanged until config converges
tester.computeVersionStatus();
assertSystemVersion(version1);
- convergeServices(SystemApplication.zone, zone4);
+ convergeServices(SystemApplication.proxy, zone4);
tester.computeVersionStatus();
assertSystemVersion(version2);
// Next run does nothing as system is now upgraded
systemUpgrader.maintain();
assertWantedVersion(SystemApplication.configServer, version2, zone1, zone2, zone3, zone4);
- assertWantedVersion(SystemApplication.zone, version2, zone1, zone2, zone3, zone4);
+ assertWantedVersion(SystemApplication.proxy, version2, zone1, zone2, zone3, zone4);
}
@Test
@@ -144,8 +144,8 @@ public class SystemUpgraderTest {
SystemUpgrader systemUpgrader = systemUpgrader(UpgradePolicy.create().upgrade(zone1));
// Bootstrap system
- tester.configServer().bootstrap(Collections.singletonList(zone1), SystemApplication.configServer,
- SystemApplication.zone);
+ tester.configServer().bootstrap(List.of(zone1.getId()), SystemApplication.configServer,
+ SystemApplication.proxy);
Version version1 = Version.fromString("6.5");
tester.upgradeSystem(version1);
@@ -157,9 +157,9 @@ public class SystemUpgraderTest {
systemUpgrader.maintain();
completeUpgrade(SystemApplication.configServer, version2, zone1);
systemUpgrader.maintain();
- completeUpgrade(SystemApplication.zone, version2, zone1);
+ completeUpgrade(SystemApplication.proxy, version2, zone1);
tester.computeVersionStatus();
- assertSystemVersion(version1); // Unchanged until zone-application converges
+ assertSystemVersion(version1); // Unchanged until proxy-application converges
// Controller upgrades again
Version version3 = Version.fromString("6.7");
@@ -167,8 +167,8 @@ public class SystemUpgraderTest {
assertSystemVersion(version1);
assertControllerVersion(version3);
- // zone 1: zone-application converges and system version changes
- convergeServices(SystemApplication.zone, zone1);
+ // zone 1: proxy-application converges and system version changes
+ convergeServices(SystemApplication.proxy, zone1);
tester.computeVersionStatus();
assertSystemVersion(version2);
assertControllerVersion(version3);
@@ -184,7 +184,7 @@ public class SystemUpgraderTest {
);
Version version1 = Version.fromString("6.5");
- tester.configServer().bootstrap(List.of(zone1, zone2, zone3, zone4), SystemApplication.all(), Optional.empty());
+ tester.configServer().bootstrap(List.of(zone1.getId(), zone2.getId(), zone3.getId(), zone4.getId()), SystemApplication.all());
tester.upgradeSystem(version1);
systemUpgrader.maintain();
assertCurrentVersion(SystemApplication.all(), version1, zone1, zone2, zone3, zone4);
@@ -196,33 +196,29 @@ public class SystemUpgraderTest {
// System upgrades in zone 1:
systemUpgrader.maintain();
- List<SystemApplication> allExceptZoneAndConfig = List.of(SystemApplication.configServerHost,
- SystemApplication.proxyHost);
- completeUpgrade(allExceptZoneAndConfig, version2, zone1);
+ List<SystemApplication> allExceptZone = List.of(SystemApplication.configServerHost,
+ SystemApplication.configServer,
+ SystemApplication.proxyHost,
+ SystemApplication.tenantHost);
+ completeUpgrade(allExceptZone, version2, zone1);
systemUpgrader.maintain();
- completeUpgrade(SystemApplication.configServer, version2, zone1);
- systemUpgrader.maintain();
- completeUpgrade(SystemApplication.zone, version2, zone1);
- convergeServices(SystemApplication.zone, zone1);
+ completeUpgrade(SystemApplication.proxy, version2, zone1);
+ convergeServices(SystemApplication.proxy, zone1);
assertWantedVersion(SystemApplication.all(), version1, zone2, zone3, zone4);
// zone 2 and 3:
systemUpgrader.maintain();
- completeUpgrade(allExceptZoneAndConfig, version2, zone2, zone3);
+ completeUpgrade(allExceptZone, version2, zone2, zone3);
systemUpgrader.maintain();
- completeUpgrade(SystemApplication.configServer, version2, zone2, zone3);
- systemUpgrader.maintain();
- completeUpgrade(SystemApplication.zone, version2, zone2, zone3);
- convergeServices(SystemApplication.zone, zone2, zone3);
+ completeUpgrade(SystemApplication.proxy, version2, zone2, zone3);
+ convergeServices(SystemApplication.proxy, zone2, zone3);
assertWantedVersion(SystemApplication.all(), version1, zone4);
// zone 4:
systemUpgrader.maintain();
- completeUpgrade(allExceptZoneAndConfig, version2, zone4);
+ completeUpgrade(allExceptZone, version2, zone4);
systemUpgrader.maintain();
- completeUpgrade(SystemApplication.configServer, version2, zone4);
- systemUpgrader.maintain();
- completeUpgrade(SystemApplication.zone, version2, zone4);
+ completeUpgrade(SystemApplication.proxy, version2, zone4);
// All done
systemUpgrader.maintain();
@@ -237,7 +233,7 @@ public class SystemUpgraderTest {
tester.upgradeSystem(version);
systemUpgrader.maintain();
assertWantedVersion(SystemApplication.configServer, version, zone1);
- assertWantedVersion(SystemApplication.zone, version, zone1);
+ assertWantedVersion(SystemApplication.proxy, version, zone1);
// Controller is downgraded
tester.upgradeController(Version.fromString("6.4"));
@@ -245,7 +241,7 @@ public class SystemUpgraderTest {
// Wanted version for zone remains unchanged
systemUpgrader.maintain();
assertWantedVersion(SystemApplication.configServer, version, zone1);
- assertWantedVersion(SystemApplication.zone, version, zone1);
+ assertWantedVersion(SystemApplication.proxy, version, zone1);
}
@Test
@@ -257,10 +253,10 @@ public class SystemUpgraderTest {
tester.upgradeSystem(version1);
systemUpgrader.maintain();
assertCurrentVersion(List.of(SystemApplication.configServerHost, SystemApplication.proxyHost,
- SystemApplication.configServer, SystemApplication.zone),
+ SystemApplication.configServer, SystemApplication.proxy),
version1, zone1);
assertCurrentVersion(List.of(SystemApplication.configServerHost, SystemApplication.proxyHost,
- SystemApplication.configServer, SystemApplication.zone),
+ SystemApplication.configServer, SystemApplication.proxy),
version1, zone2);
// System starts upgrading to next version
@@ -271,48 +267,65 @@ public class SystemUpgraderTest {
systemUpgrader.maintain();
completeUpgrade(SystemApplication.configServer, version2, zone1);
systemUpgrader.maintain();
- completeUpgrade(SystemApplication.zone, version2, zone1);
- convergeServices(SystemApplication.zone, zone1);
+ completeUpgrade(SystemApplication.proxy, version2, zone1);
+ convergeServices(SystemApplication.proxy, zone1);
// Confidence is reduced to broken and next zone is not scheduled for upgrade
tester.upgrader().overrideConfidence(version2, VespaVersion.Confidence.broken);
tester.computeVersionStatus();
systemUpgrader.maintain();
assertWantedVersion(List.of(SystemApplication.configServerHost, SystemApplication.proxyHost,
- SystemApplication.configServer, SystemApplication.zone), version1, zone2);
+ SystemApplication.configServer, SystemApplication.proxy), version1, zone2);
+ }
+
+ @Test
+ public void does_not_deploy_proxy_app_in_zones_without_proxy() {
+ List<SystemApplication> applications = List.of(
+ SystemApplication.configServerHost, SystemApplication.configServer, SystemApplication.tenantHost);
+ tester.configServer().bootstrap(List.of(zone1.getId()), applications);
+ tester.configServer().disallowConvergenceCheck(SystemApplication.proxy.id());
+
+ SystemUpgrader systemUpgrader = systemUpgrader(UpgradePolicy.create().upgrade(zone1));
+
+ Version version1 = Version.fromString("6.5");
+ tester.upgradeSystem(version1);
+ systemUpgrader.maintain();
+ assertCurrentVersion(applications, version1, zone1);
}
/** Simulate upgrade of nodes allocated to given application. In a real system this is done by the node itself */
- private void completeUpgrade(SystemApplication application, Version version, ZoneId... zones) {
+ private void completeUpgrade(SystemApplication application, Version version, ZoneApi... zones) {
assertWantedVersion(application, version, zones);
- for (ZoneId zone : zones) {
+ for (ZoneApi zone : zones) {
for (Node node : listNodes(zone, application)) {
- nodeRepository().putByHostname(zone, new Node(node.hostname(), node.state(), node.type(), node.owner(),
- node.wantedVersion(), node.wantedVersion()));
+ nodeRepository().putByHostname(
+ zone.getId(),
+ new Node(node.hostname(), node.state(), node.type(), node.owner(), node.wantedVersion(), node.wantedVersion()));
}
assertCurrentVersion(application, version, zone);
}
}
- private void convergeServices(SystemApplication application, ZoneId... zones) {
- for (ZoneId zone : zones) {
- tester.controllerTester().configServer().convergeServices(application.id(), zone);
+ private void convergeServices(SystemApplication application, ZoneApi... zones) {
+ for (ZoneApi zone : zones) {
+ tester.controllerTester().configServer().convergeServices(application.id(), zone.getId());
}
}
- private void completeUpgrade(List<SystemApplication> applications, Version version, ZoneId... zones) {
+ private void completeUpgrade(List<SystemApplication> applications, Version version, ZoneApi... zones) {
applications.forEach(application -> completeUpgrade(application, version, zones));
}
- private void failNodeIn(ZoneId zone, SystemApplication application) {
- List<Node> nodes = nodeRepository().list(zone, application.id());
+ private void failNodeIn(ZoneApi zone, SystemApplication application) {
+ List<Node> nodes = nodeRepository().list(zone.getId(), application.id());
if (nodes.isEmpty()) {
throw new IllegalArgumentException("No nodes allocated to " + application.id());
}
Node node = nodes.get(0);
- nodeRepository().putByHostname(zone, new Node(node.hostname(), Node.State.failed, node.type(), node.owner(),
- node.currentVersion(), node.wantedVersion()));
+ nodeRepository().putByHostname(
+ zone.getId(),
+ new Node(node.hostname(), Node.State.failed, node.type(), node.owner(), node.currentVersion(), node.wantedVersion()));
}
private void assertSystemVersion(Version version) {
@@ -323,33 +336,33 @@ public class SystemUpgraderTest {
assertEquals(version, tester.controller().versionStatus().controllerVersion().get().versionNumber());
}
- private void assertWantedVersion(SystemApplication application, Version version, ZoneId... zones) {
+ private void assertWantedVersion(SystemApplication application, Version version, ZoneApi... zones) {
assertVersion(application, version, Node::wantedVersion, zones);
}
- private void assertCurrentVersion(SystemApplication application, Version version, ZoneId... zones) {
+ private void assertCurrentVersion(SystemApplication application, Version version, ZoneApi... zones) {
assertVersion(application, version, Node::currentVersion, zones);
}
- private void assertWantedVersion(List<SystemApplication> applications, Version version, ZoneId... zones) {
+ private void assertWantedVersion(List<SystemApplication> applications, Version version, ZoneApi... zones) {
applications.forEach(application -> assertVersion(application, version, Node::wantedVersion, zones));
}
- private void assertCurrentVersion(List<SystemApplication> applications, Version version, ZoneId... zones) {
+ private void assertCurrentVersion(List<SystemApplication> applications, Version version, ZoneApi... zones) {
applications.forEach(application -> assertVersion(application, version, Node::currentVersion, zones));
}
private void assertVersion(SystemApplication application, Version version, Function<Node, Version> versionField,
- ZoneId... zones) {
- for (ZoneId zone : requireNonEmpty(zones)) {
+ ZoneApi... zones) {
+ for (ZoneApi zone : requireNonEmpty(zones)) {
for (Node node : listNodes(zone, application)) {
assertEquals(application + " version", version, versionField.apply(node));
}
}
}
- private List<Node> listNodes(ZoneId zone, SystemApplication application) {
- return nodeRepository().list(zone, application.id()).stream()
+ private List<Node> listNodes(ZoneApi zone, SystemApplication application) {
+ return nodeRepository().list(zone.getId(), application.id()).stream()
.filter(SystemUpgrader::eligibleForUpgrade)
.collect(Collectors.toList());
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java
index 674227a500e..8befff9a875 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java
@@ -7,15 +7,18 @@ import com.yahoo.config.application.api.ValidationOverrides;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.HostName;
+import com.yahoo.config.provision.RegionName;
import com.yahoo.vespa.config.SlimeUtils;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.api.integration.MetricsService;
+import com.yahoo.vespa.hosted.controller.api.integration.certificates.ApplicationCertificate;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.SourceRevision;
import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId;
import com.yahoo.vespa.hosted.controller.api.integration.organization.User;
import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.vespa.hosted.controller.application.AssignedRotation;
import com.yahoo.vespa.hosted.controller.application.Change;
import com.yahoo.vespa.hosted.controller.application.ClusterInfo;
import com.yahoo.vespa.hosted.controller.application.ClusterUtilization;
@@ -24,11 +27,13 @@ import com.yahoo.vespa.hosted.controller.application.DeploymentActivity;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobError;
import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics;
+import com.yahoo.vespa.hosted.controller.application.EndpointId;
import com.yahoo.vespa.hosted.controller.application.JobStatus;
import com.yahoo.vespa.hosted.controller.application.RotationStatus;
import com.yahoo.vespa.hosted.controller.rotation.RotationId;
import org.junit.Test;
+import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -43,6 +48,7 @@ import java.util.Optional;
import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.OptionalLong;
+import java.util.Set;
import java.util.TreeMap;
import static com.yahoo.config.provision.SystemName.main;
@@ -116,8 +122,9 @@ public class ApplicationSerializerTest {
OptionalInt.of(7),
new MetricsService.ApplicationMetrics(0.5, 0.9),
Optional.of("-----BEGIN PUBLIC KEY-----\n∠( ᐛ 」∠)_\n-----END PUBLIC KEY-----"),
- Optional.of(new RotationId("my-rotation")),
- rotationStatus);
+ List.of(AssignedRotation.fromStrings("foo", "default", "my-rotation", Set.of())),
+ rotationStatus,
+ Optional.of(new ApplicationCertificate("vespa.certificate")));
Application serialized = applicationSerializer.fromSlime(applicationSerializer.toSlime(original));
@@ -152,9 +159,11 @@ public class ApplicationSerializerTest {
assertEquals(original.change(), serialized.change());
assertEquals(original.pemDeployKey(), serialized.pemDeployKey());
- assertEquals(original.rotation().get(), serialized.rotation().get());
+ assertEquals(original.rotations(), serialized.rotations());
assertEquals(original.rotationStatus(), serialized.rotationStatus());
+ assertEquals(original.applicationCertificate(), serialized.applicationCertificate());
+
// Test cluster utilization
assertEquals(0, serialized.deployments().get(zone1).clusterUtils().size());
assertEquals(3, serialized.deployments().get(zone2).clusterUtils().size());
@@ -245,4 +254,80 @@ public class ApplicationSerializerTest {
// ok if no error
}
+ /** TODO: Test can be removed after June 2019 - once legacy field for single rotation is retired */
+ @Test
+ public void testParsingLegacyRotationElement() throws IOException {
+ // Use the 'complete-application.json' as a baseline
+ final var applicationJson = Files.readAllBytes(testData.resolve("complete-application.json"));
+ final var slime = SlimeUtils.jsonToSlime(applicationJson);
+
+ final var regions = Set.of(
+ RegionName.from("us-east-3"),
+ RegionName.from("us-west-1")
+ );
+
+ // Add the necessary fields to the Slime representation of the application
+ final var cursor = slime.get();
+ cursor.setString("rotation", "single-rotation");
+
+ final var rotations = cursor.setArray("endpoints");
+ rotations.addString("multiple-rotation-1");
+ rotations.addString("multiple-rotation-2");
+
+ final var assignedRotations = cursor.setArray("assignedRotations");
+ final var assignedRotation = assignedRotations.addObject();
+ assignedRotation.setString("clusterId", "foobar");
+ assignedRotation.setString("endpointId", "nice-endpoint");
+ assignedRotation.setString("rotationId", "assigned-rotation");
+
+ // Parse and test the output from parsing contains both legacy rotation and multiple rotations
+ final var application = applicationSerializer.fromSlime(slime);
+
+ // Since only one AssignedEndpoint can be "default", we make sure that we are ignoring the
+ // multiple-rotation entries as the globalServiceId will override them
+ assertEquals(
+ List.of(
+ new RotationId("single-rotation"),
+ new RotationId("assigned-rotation")
+ ),
+ application.rotations()
+ );
+
+ assertEquals(
+ Optional.of(new RotationId("single-rotation")), application.legacyRotation()
+ );
+
+ // The same goes here for AssignedRotations with "default" EndpointId as in the .rotations() test above.
+ // Note that we are only using Set.of() on "assigned-rotation" because in this test we do not have access
+ // to a deployment.xml that describes the zones a rotation should map to.
+ assertEquals(
+ List.of(
+ new AssignedRotation(new ClusterSpec.Id("foo"), EndpointId.of("default"), new RotationId("single-rotation"), regions),
+ new AssignedRotation(new ClusterSpec.Id("foobar"), EndpointId.of("nice-endpoint"), new RotationId("assigned-rotation"), Set.of())
+ ),
+ application.assignedRotations()
+ );
+ }
+
+ @Test
+ public void testParsingOnlyLegacyRotationElement() throws IOException {
+ // Use the 'complete-application.json' as a baseline
+ final var applicationJson = Files.readAllBytes(testData.resolve("complete-application.json"));
+ final var slime = SlimeUtils.jsonToSlime(applicationJson);
+
+ // Add the necessary fields to the Slime representation of the application
+ final var cursor = slime.get();
+
+ cursor.setString("rotation", "single-rotation");
+
+ // Parse and test the output from parsing contains both legacy rotation and multiple rotations
+ final var application = applicationSerializer.fromSlime(slime);
+
+ assertEquals(
+ List.of(
+ new RotationId("single-rotation")
+ ),
+ application.rotations()
+ );
+ }
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/VersionStatusSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/VersionStatusSerializerTest.java
index 5e6f9811376..a1e22b4fc64 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/VersionStatusSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/VersionStatusSerializerTest.java
@@ -36,9 +36,9 @@ public class VersionStatusSerializerTest {
ApplicationId.from("tenant2", "success2", "default"))
);
vespaVersions.add(new VespaVersion(statistics, "dead", Instant.now(), false, false,
- asHostnames("cfg1", "cfg2", "cfg3"), VespaVersion.Confidence.normal));
+ true, asHostnames("cfg1", "cfg2", "cfg3"), VespaVersion.Confidence.normal));
vespaVersions.add(new VespaVersion(statistics, "cafe", Instant.now(), true, true,
- asHostnames("cfg1", "cfg2", "cfg3"), VespaVersion.Confidence.normal));
+ false, asHostnames("cfg1", "cfg2", "cfg3"), VespaVersion.Confidence.normal));
VersionStatus status = new VersionStatus(vespaVersions);
VersionStatusSerializer serializer = new VersionStatusSerializer();
VersionStatus deserialized = serializer.fromSlime(serializer.toSlime(status));
@@ -51,6 +51,7 @@ public class VersionStatusSerializerTest {
assertEquals(a.committedAt().truncatedTo(MILLIS), b.committedAt());
assertEquals(a.isControllerVersion(), b.isControllerVersion());
assertEquals(a.isSystemVersion(), b.isSystemVersion());
+ assertEquals(a.isReleased(), b.isReleased());
assertEquals(a.statistics(), b.statistics());
assertEquals(a.systemApplicationHostnames(), b.systemApplicationHostnames());
assertEquals(a.confidence(), b.confidence());
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerControllerTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerControllerTester.java
index c851cb18e8c..427428a3a94 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerControllerTester.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerControllerTester.java
@@ -54,7 +54,7 @@ public class ContainerControllerTester {
public ContainerControllerTester(JDisc container, String responseFilePath) {
containerTester = new ContainerTester(container, responseFilePath);
- CuratorDb curatorDb = new MockCuratorDb();
+ CuratorDb curatorDb = controller().curator();
curatorDb.writeUpgradesPerMinute(100);
upgrader = new Upgrader(controller(), Duration.ofDays(1), new JobControl(curatorDb), curatorDb);
}
@@ -72,11 +72,10 @@ public class ContainerControllerTester {
public ContainerTester containerTester() { return containerTester; }
public Application createApplication() {
- return createApplication("domain1","tenant1",
- "application1");
+ return createApplication("domain1","tenant1", "application1", "default");
}
- public Application createApplication(String athensDomain, String tenant, String application) {
+ public Application createApplication(String athensDomain, String tenant, String application, String instance) {
AthenzDomain domain1 = addTenantAthenzDomain(athensDomain, "user");
AthenzPrincipal user = new AthenzPrincipal(new AthenzUser("user"));
AthenzCredentials credentials = new AthenzCredentials(user, domain1, new OktaAccessToken("okta-token"));
@@ -86,7 +85,7 @@ public class ContainerControllerTester {
Optional.of(new PropertyId("1234")));
controller().tenants().create(tenantSpec, credentials);
- ApplicationId app = ApplicationId.from(tenant, application, "default");
+ ApplicationId app = ApplicationId.from(tenant, application, instance);
return controller().applications().createApplication(app, Optional.of(credentials));
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java
index ef86ffa125f..c7be543dd00 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java
@@ -6,6 +6,7 @@ import com.yahoo.application.container.handler.Request;
import com.yahoo.application.container.handler.Response;
import com.yahoo.component.ComponentSpecification;
import com.yahoo.component.Version;
+import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.container.http.filter.FilterChainRepository;
import com.yahoo.jdisc.http.filter.SecurityRequestFilter;
@@ -59,10 +60,10 @@ public class ContainerTester {
public void upgradeSystem(Version version) {
controller().curator().writeControllerVersion(controller().hostname(), version);
- for (ZoneId zone : controller().zoneRegistry().zones().all().ids()) {
+ for (ZoneApi zone : controller().zoneRegistry().zones().all().zones()) {
for (SystemApplication application : SystemApplication.all()) {
- configServer().setVersion(application.id(), zone, version);
- configServer().convergeServices(application.id(), zone);
+ configServer().setVersion(application.id(), zone.getId(), version);
+ configServer().convergeServices(application.id(), zone.getId());
}
}
computeVersionStatus();
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerCloudTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerCloudTest.java
index 4f068451d24..102326d0a5e 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerCloudTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerCloudTest.java
@@ -34,6 +34,13 @@ public class ControllerContainerCloudTest extends ControllerContainerTest {
" <binding>http://*/api/application/v4/*</binding>\n" +
" </handler>\n" +
+ " <handler id='com.yahoo.vespa.hosted.controller.restapi.zone.v1.ZoneApiHandler'>\n" +
+ " <binding>http://*/zone/v1</binding>\n" +
+ " <binding>http://*/zone/v1/*</binding>\n" +
+ " <binding>http://*/api/zone/v1</binding>\n" +
+ " <binding>http://*/api/zone/v1/*</binding>\n" +
+ " </handler>\n" +
+
" <handler id='com.yahoo.vespa.hosted.controller.restapi.user.UserApiHandler'>\n" +
" <binding>http://*/user/v1/*</binding>\n" +
" <binding>http://*/api/user/v1/*</binding>\n" +
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java
index b81edaccd09..11aa132b478 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java
@@ -43,12 +43,12 @@ public class ControllerContainerTest {
public void stopContainer() { container.close(); }
private String controllerServicesXml() {
- return "<jdisc version='1.0'>\n" +
+ return "<container version='1.0'>\n" +
" <config name=\"container.handler.threadpool\">\n" +
" <maxthreads>10</maxthreads>\n" +
" </config> \n" +
" <config name=\"cloud.config.configserver\">\n" +
- " <system>" + system().name() + "</system>\n" +
+ " <system>" + system().value() + "</system>\n" +
" </config> \n" +
" <config name=\"vespa.hosted.rotation.config.rotations\">\n" +
" <rotations>\n" +
@@ -62,7 +62,6 @@ public class ControllerContainerTest {
" <component id='com.yahoo.vespa.flags.InMemoryFlagSource'/>\n" +
" <component id='com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb'/>\n" +
" <component id='com.yahoo.vespa.hosted.controller.athenz.mock.AthenzClientFactoryMock'/>\n" +
- " <component id='com.yahoo.vespa.hosted.controller.api.integration.chef.ChefMock'/>\n" +
" <component id='com.yahoo.vespa.hosted.controller.api.integration.dns.MemoryNameService'/>\n" +
" <component id='com.yahoo.vespa.hosted.controller.api.integration.entity.MemoryEntityService'/>\n" +
" <component id='com.yahoo.vespa.hosted.controller.api.integration.github.GitHubMock'/>\n" +
@@ -72,6 +71,7 @@ public class ControllerContainerTest {
" <component id='com.yahoo.vespa.hosted.controller.api.integration.stubs.MockRunDataStore'/>\n" +
" <component id='com.yahoo.vespa.hosted.controller.api.integration.organization.MockContactRetriever'/>\n" +
" <component id='com.yahoo.vespa.hosted.controller.api.integration.organization.MockIssueHandler'/>\n" +
+ " <component id='com.yahoo.vespa.hosted.controller.api.integration.organization.MockBilling'/>\n" +
" <component id='com.yahoo.vespa.hosted.controller.api.integration.stubs.MockResourceSnapshotConsumer'/>\n" +
" <component id='com.yahoo.vespa.hosted.controller.integration.ConfigServerMock'/>\n" +
" <component id='com.yahoo.vespa.hosted.controller.integration.NodeRepositoryClientMock'/>\n" +
@@ -91,6 +91,8 @@ public class ControllerContainerTest {
" <component id='com.yahoo.vespa.hosted.controller.integration.ApplicationStoreMock'/>\n" +
" <component id='com.yahoo.vespa.hosted.controller.api.integration.stubs.MockTesterCloud'/>\n" +
" <component id='com.yahoo.vespa.hosted.controller.api.integration.stubs.MockMailer'/>\n" +
+ " <component id='com.yahoo.vespa.hosted.controller.integration.ApplicationCertificateMock'/>\n" +
+ " <component id='com.yahoo.vespa.hosted.controller.api.integration.stubs.MockMavenRepository'/>\n" +
" <handler id='com.yahoo.vespa.hosted.controller.restapi.deployment.DeploymentApiHandler'>\n" +
" <binding>http://*/deployment/v1/*</binding>\n" +
" </handler>\n" +
@@ -106,16 +108,12 @@ public class ControllerContainerTest {
" <handler id='com.yahoo.vespa.hosted.controller.restapi.cost.CostApiHandler'>\n" +
" <binding>http://*/cost/v1/*</binding>\n" +
" </handler>\n" +
- " <handler id='com.yahoo.vespa.hosted.controller.restapi.zone.v1.ZoneApiHandler'>\n" +
- " <binding>http://*/zone/v1</binding>\n" +
- " <binding>http://*/zone/v1/*</binding>\n" +
- " </handler>\n" +
" <handler id='com.yahoo.vespa.hosted.controller.restapi.zone.v2.ZoneApiHandler'>\n" +
" <binding>http://*/zone/v2</binding>\n" +
" <binding>http://*/zone/v2/*</binding>\n" +
" </handler>\n" +
variablePartXml() +
- "</jdisc>";
+ "</container>";
}
protected SystemName system() {
@@ -132,6 +130,10 @@ public class ControllerContainerTest {
" <handler id='com.yahoo.vespa.hosted.controller.restapi.athenz.AthenzApiHandler'>\n" +
" <binding>http://*/athenz/v1/*</binding>\n" +
" </handler>\n" +
+ " <handler id='com.yahoo.vespa.hosted.controller.restapi.zone.v1.ZoneApiHandler'>\n" +
+ " <binding>http://*/zone/v1</binding>\n" +
+ " <binding>http://*/zone/v1/*</binding>\n" +
+ " </handler>\n" +
" <http>\n" +
" <server id='default' port='8080' />\n" +
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 aeb9d02336a..577b8491bd2 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
@@ -60,6 +60,7 @@ import com.yahoo.vespa.hosted.controller.restapi.ContainerControllerTester;
import com.yahoo.vespa.hosted.controller.restapi.ContainerTester;
import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerTest;
import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant;
+import com.yahoo.vespa.hosted.controller.versions.VespaVersion;
import com.yahoo.yolean.Exceptions;
import org.junit.Before;
import org.junit.Test;
@@ -133,6 +134,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
@Test
public void testApplicationApi() {
tester.computeVersionStatus();
+ tester.controller().jobController().setRunner(__ -> { }); // Avoid uncontrollable, multi-threaded job execution
createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID); // (Necessary but not provided in this API)
@@ -191,7 +193,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
new File("tenant-with-contact-info.json"));
// POST (create) an application
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1", POST)
.userIdentity(USER_ID)
.oktaAccessToken(OKTA_AT),
new File("application-reference.json"));
@@ -202,12 +204,15 @@ public class ApplicationApiTest extends ControllerContainerTest {
// GET tenant applications
tester.assertResponse(request("/application/v4/tenant/tenant1/application/", GET).userIdentity(USER_ID),
new File("application-list.json"));
+ // GET tenant applications (instances of "application1" only)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/", GET).userIdentity(USER_ID),
+ new File("application-list.json"));
addUserToHostedOperatorRole(HostedAthenzIdentities.from(HOSTED_VESPA_OPERATOR));
// POST (deploy) an application to a zone - manual user deployment (includes a content hash for verification)
MultiPartStreamer entity = createApplicationDeployData(applicationPackage, true);
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/dev/region/us-west-1/instance/default/deploy", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/dev/region/us-west-1/instance/instance1/deploy", POST)
.data(entity)
.header("X-Content-Hash", Base64.getEncoder().encodeToString(Signatures.sha256Digest(entity::data)))
.userIdentity(USER_ID),
@@ -215,7 +220,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
// POST (deploy) an application to a zone. This simulates calls done by our tenant pipeline.
- ApplicationId id = ApplicationId.from("tenant1", "application1", "default");
+ ApplicationId id = ApplicationId.from("tenant1", "application1", "instance1");
long screwdriverProjectId = 123;
addScrewdriverUserToDeployRole(SCREWDRIVER_ID,
@@ -230,13 +235,13 @@ public class ApplicationApiTest extends ControllerContainerTest {
.submit();
// ... systemtest
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/test/region/us-east-1/instance/default/", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/test/region/us-east-1/instance/instance1/", POST)
.data(createApplicationDeployData(Optional.empty(), false))
.screwdriverIdentity(SCREWDRIVER_ID),
new File("deploy-result.json"));
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/test/region/us-east-1/instance/default", DELETE)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/test/region/us-east-1/instance/instance1", DELETE)
.screwdriverIdentity(SCREWDRIVER_ID),
- "Deactivated tenant/tenant1/application/application1/environment/test/region/us-east-1/instance/default");
+ "{\"message\":\"Deactivated tenant1.application1.instance1 in test.us-east-1\"}");
controllerTester.jobCompletion(JobType.systemTest)
.application(id)
@@ -244,20 +249,20 @@ public class ApplicationApiTest extends ControllerContainerTest {
.submit();
// ... staging
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/staging/region/us-east-3/instance/default/", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/staging/region/us-east-3/instance/instance1/", POST)
.data(createApplicationDeployData(Optional.empty(), false))
.screwdriverIdentity(SCREWDRIVER_ID),
new File("deploy-result.json"));
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/staging/region/us-east-3/instance/default", DELETE)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/staging/region/us-east-3/instance/instance1", DELETE)
.screwdriverIdentity(SCREWDRIVER_ID),
- "Deactivated tenant/tenant1/application/application1/environment/staging/region/us-east-3/instance/default");
+ "{\"message\":\"Deactivated tenant1.application1.instance1 in staging.us-east-3\"}");
controllerTester.jobCompletion(JobType.stagingTest)
.application(id)
.projectId(screwdriverProjectId)
.submit();
// ... prod zone
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/default/", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/instance1/", POST)
.data(createApplicationDeployData(Optional.empty(), false))
.screwdriverIdentity(SCREWDRIVER_ID),
new File("deploy-result.json"));
@@ -272,10 +277,10 @@ public class ApplicationApiTest extends ControllerContainerTest {
Optional.of(ApplicationVersion.from(BuildJob.defaultSourceRevision,
BuildJob.defaultBuildNumber - 1)),
true);
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/default/", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/instance1/", POST)
.data(entity)
.userIdentity(HOSTED_VESPA_OPERATOR),
- "{\"error-code\":\"BAD_REQUEST\",\"message\":\"No application package found for tenant1.application1 with version 1.0.41-commit1\"}",
+ "{\"error-code\":\"BAD_REQUEST\",\"message\":\"No application package found for tenant1.application1.instance1 with version 1.0.41-commit1\"}",
400);
// POST an application deployment to a production zone - operator emergency deployment - works with known package
@@ -283,7 +288,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
Optional.of(ApplicationVersion.from(BuildJob.defaultSourceRevision,
BuildJob.defaultBuildNumber)),
true);
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/default/", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/instance1/", POST)
.data(entity)
.userIdentity(HOSTED_VESPA_OPERATOR),
new File("deploy-result.json"));
@@ -362,19 +367,22 @@ public class ApplicationApiTest extends ControllerContainerTest {
tester.assertResponse(request("/application/v4/tenant/tenant2/application/application2", DELETE)
.userIdentity(USER_ID)
.oktaAccessToken(OKTA_AT),
- "");
+ "{\"message\":\"Deleted application tenant2.application2\"}");
+ // Set version 6.1 to broken to change compile version for.
+ controllerTester.upgrader().overrideConfidence(Version.fromString("6.1"), VespaVersion.Confidence.broken);
+ tester.computeVersionStatus();
setDeploymentMaintainedInfo(controllerTester);
// GET tenant application deployments
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", GET)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1", GET)
.userIdentity(USER_ID),
new File("application.json"));
// GET an application deployment
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/default", GET)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/instance1", GET)
.userIdentity(USER_ID),
new File("deployment.json"));
- addIssues(controllerTester, ApplicationId.from("tenant1", "application1", "default"));
+ addIssues(controllerTester, ApplicationId.from("tenant1", "application1", "instance1"));
// GET at root, with "&recursive=deployment", returns info about all tenants, their applications and their deployments
tester.assertResponse(request("/application/v4/", GET)
.userIdentity(USER_ID)
@@ -391,13 +399,13 @@ public class ApplicationApiTest extends ControllerContainerTest {
.recursive("true"),
new File("tenant1-recursive.json"));
// GET at an application, with "&recursive=true", returns full info about its deployments
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/", GET)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1", GET)
.userIdentity(USER_ID)
.recursive("true"),
new File("application1-recursive.json"));
// GET nodes
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/default/nodes", GET)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/instance1/nodes", GET)
.userIdentity(USER_ID),
new File("application-nodes.json"));
@@ -407,143 +415,143 @@ public class ApplicationApiTest extends ControllerContainerTest {
"INFO - All good");
// DELETE (cancel) ongoing change
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/deploying", DELETE)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying", DELETE)
.userIdentity(HOSTED_VESPA_OPERATOR),
- "{\"message\":\"Changed deployment from 'application change to 1.0.42-commit1' to 'no change' for application 'tenant1.application1'\"}");
+ "{\"message\":\"Changed deployment from 'application change to 1.0.42-commit1' to 'no change' for application 'tenant1.application1.instance1'\"}");
// DELETE (cancel) again is a no-op
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/deploying", DELETE)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying", DELETE)
.userIdentity(USER_ID)
.data("{\"cancel\":\"all\"}"),
- "{\"message\":\"No deployment in progress for application 'tenant1.application1' at this time\"}");
+ "{\"message\":\"No deployment in progress for application 'tenant1.application1.instance1' at this time\"}");
// POST pinning to a given version to an application
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/deploying/pin", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/pin", POST)
.userIdentity(USER_ID)
.data("6.1.0"),
- "{\"message\":\"Triggered pin to 6.1 for tenant1.application1\"}");
+ "{\"message\":\"Triggered pin to 6.1 for tenant1.application1.instance1\"}");
assertTrue("Action is logged to audit log",
tester.controller().auditLogger().readLog().entries().stream()
- .anyMatch(entry -> entry.resource().equals("/application/v4/tenant/tenant1/application/application1/deploying/pin")));
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/deploying", GET)
+ .anyMatch(entry -> entry.resource().equals("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/pin")));
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying", GET)
.userIdentity(USER_ID), "{\"platform\":\"6.1\",\"pinned\":true}");
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/deploying/pin", GET)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/pin", GET)
.userIdentity(USER_ID), "{\"platform\":\"6.1\",\"pinned\":true}");
// DELETE only the pin to a given version
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/deploying/pin", DELETE)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/pin", DELETE)
.userIdentity(USER_ID),
- "{\"message\":\"Changed deployment from 'pin to 6.1' to 'upgrade to 6.1' for application 'tenant1.application1'\"}");
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/deploying", GET)
+ "{\"message\":\"Changed deployment from 'pin to 6.1' to 'upgrade to 6.1' for application 'tenant1.application1.instance1'\"}");
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying", GET)
.userIdentity(USER_ID), "{\"platform\":\"6.1\",\"pinned\":false}");
// POST pinning again
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/deploying/pin", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/pin", POST)
.userIdentity(USER_ID)
.data("6.1"),
- "{\"message\":\"Triggered pin to 6.1 for tenant1.application1\"}");
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/deploying", GET)
+ "{\"message\":\"Triggered pin to 6.1 for tenant1.application1.instance1\"}");
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying", GET)
.userIdentity(USER_ID), "{\"platform\":\"6.1\",\"pinned\":true}");
// DELETE only the version, but leave the pin
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/deploying/platform", DELETE)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/platform", DELETE)
.userIdentity(USER_ID),
- "{\"message\":\"Changed deployment from 'pin to 6.1' to 'pin to current platform' for application 'tenant1.application1'\"}");
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/deploying", GET)
+ "{\"message\":\"Changed deployment from 'pin to 6.1' to 'pin to current platform' for application 'tenant1.application1.instance1'\"}");
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying", GET)
.userIdentity(USER_ID), "{\"pinned\":true}");
// DELETE also the pin to a given version
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/deploying/pin", DELETE)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/pin", DELETE)
.userIdentity(USER_ID),
- "{\"message\":\"Changed deployment from 'pin to current platform' to 'no change' for application 'tenant1.application1'\"}");
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/deploying", GET)
+ "{\"message\":\"Changed deployment from 'pin to current platform' to 'no change' for application 'tenant1.application1.instance1'\"}");
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying", GET)
.userIdentity(USER_ID), "{}");
// POST a pause to a production job
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/default/job/production-us-west-1/pause", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/job/production-us-west-1/pause", POST)
.userIdentity(USER_ID),
- "{\"message\":\"production-us-west-1 for tenant1.application1 paused for " + DeploymentTrigger.maxPause + "\"}");
+ "{\"message\":\"production-us-west-1 for tenant1.application1.instance1 paused for " + DeploymentTrigger.maxPause + "\"}");
// POST a triggering to the same production job
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/default/job/production-us-west-1", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/job/production-us-west-1", POST)
.userIdentity(USER_ID),
- "{\"message\":\"Triggered production-us-west-1 for tenant1.application1\"}");
+ "{\"message\":\"Triggered production-us-west-1 for tenant1.application1.instance1\"}");
// POST a 'restart application' command
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/default/restart", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/instance1/restart", POST)
.userIdentity(USER_ID),
- "Requested restart of tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/default");
+ "{\"message\":\"Requested restart of tenant1.application1.instance1 in prod.us-central-1\"}");
// POST a 'restart application' command
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/default/restart", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/instance1/restart", POST)
.screwdriverIdentity(SCREWDRIVER_ID),
- "Requested restart of tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/default");
+ "{\"message\":\"Requested restart of tenant1.application1.instance1 in prod.us-central-1\"}");
// POST a 'restart application' in staging environment command
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/staging/region/us-central-1/instance/default/restart", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/staging/region/us-central-1/instance/instance1/restart", POST)
.screwdriverIdentity(SCREWDRIVER_ID),
- "Requested restart of tenant/tenant1/application/application1/environment/staging/region/us-central-1/instance/default");
+ "{\"message\":\"Requested restart of tenant1.application1.instance1 in staging.us-central-1\"}");
// POST a 'restart application' in staging test command
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/test/region/us-central-1/instance/default/restart", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/test/region/us-central-1/instance/instance1/restart", POST)
.screwdriverIdentity(SCREWDRIVER_ID),
- "Requested restart of tenant/tenant1/application/application1/environment/test/region/us-central-1/instance/default");
+ "{\"message\":\"Requested restart of tenant1.application1.instance1 in test.us-central-1\"}");
// POST a 'restart application' in staging dev command
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/dev/region/us-central-1/instance/default/restart", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/dev/region/us-central-1/instance/instance1/restart", POST)
.userIdentity(USER_ID),
- "Requested restart of tenant/tenant1/application/application1/environment/dev/region/us-central-1/instance/default");
+ "{\"message\":\"Requested restart of tenant1.application1.instance1 in dev.us-central-1\"}");
// POST a 'restart application' command with a host filter (other filters not supported yet)
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/default/restart?hostname=host1", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/instance1/restart?hostname=host1", POST)
.screwdriverIdentity(SCREWDRIVER_ID),
"{\"error-code\":\"INTERNAL_SERVER_ERROR\",\"message\":\"No node with the hostname host1 is known.\"}", 500);
// GET suspended
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/default/suspended", GET)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/instance1/suspended", GET)
.userIdentity(USER_ID),
new File("suspended.json"));
// GET services
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/default/service", GET)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/instance1/service", GET)
.userIdentity(USER_ID),
new File("services.json"));
// GET service
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/default/service/storagenode-awe3slno6mmq2fye191y324jl/state/v1/", GET)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/instance1/service/storagenode-awe3slno6mmq2fye191y324jl/state/v1/", GET)
.userIdentity(USER_ID),
new File("service.json"));
// DELETE application with active deployments fails
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", DELETE)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1", DELETE)
.userIdentity(USER_ID)
.oktaAccessToken(OKTA_AT),
new File("delete-with-active-deployments.json"), 400);
// DELETE (deactivate) a deployment - dev
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/dev/region/us-west-1/instance/default", DELETE)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/dev/region/us-west-1/instance/instance1", DELETE)
.userIdentity(USER_ID),
- "Deactivated tenant/tenant1/application/application1/environment/dev/region/us-west-1/instance/default");
+ "{\"message\":\"Deactivated tenant1.application1.instance1 in dev.us-west-1\"}");
// DELETE (deactivate) a deployment - prod
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/default", DELETE)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/instance1", DELETE)
.screwdriverIdentity(SCREWDRIVER_ID),
- "Deactivated tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/default");
+ "{\"message\":\"Deactivated tenant1.application1.instance1 in prod.us-central-1\"}");
// DELETE (deactivate) a deployment is idempotent
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/default", DELETE)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/instance1", DELETE)
.screwdriverIdentity(SCREWDRIVER_ID),
- "Deactivated tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/default");
+ "{\"message\":\"Deactivated tenant1.application1.instance1 in prod.us-central-1\"}");
// POST an application package to start a deployment to dev
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/default/deploy/dev-us-east-1", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploy/dev-us-east-1", POST)
.userIdentity(USER_ID)
.data(createApplicationDeployData(applicationPackage, false)),
new File("deployment-job-accepted.json"));
// POST an application package and a test jar, submitting a new application for internal pipeline deployment.
// First attempt does not have an Athenz service definition in deployment spec, and is accepted.
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/submit", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/submit", POST)
.screwdriverIdentity(SCREWDRIVER_ID)
.data(createApplicationSubmissionData(applicationPackage)),
"{\"message\":\"Application package version: 1.0.43-d00d, source revision of repository 'repo', branch 'master' with commit 'd00d', by a@b, built against 6.1 at 1970-01-01T00:00:01Z\"}");
@@ -554,7 +562,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
.athenzIdentity(com.yahoo.config.provision.AthenzDomain.from(ATHENZ_TENANT_DOMAIN_2.getName()), AthenzService.from("service"))
.region("us-west-1")
.build();
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/submit", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/submit", POST)
.screwdriverIdentity(SCREWDRIVER_ID)
.data(createApplicationSubmissionData(packageWithServiceForWrongDomain)),
"{\"error-code\":\"BAD_REQUEST\",\"message\":\"Athenz domain in deployment.xml: [domain2] must match tenant domain: [domain1]\"}", 400);
@@ -565,13 +573,13 @@ public class ApplicationApiTest extends ControllerContainerTest {
.athenzIdentity(com.yahoo.config.provision.AthenzDomain.from(ATHENZ_TENANT_DOMAIN.getName()), AthenzService.from("service"))
.region("us-west-1")
.build();
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/submit", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/submit", POST)
.screwdriverIdentity(SCREWDRIVER_ID)
.data(createApplicationSubmissionData(packageWithService)),
"{\"message\":\"Application package version: 1.0.44-d00d, source revision of repository 'repo', branch 'master' with commit 'd00d', by a@b, built against 6.1 at 1970-01-01T00:00:01Z\"}");
// Fourth attempt has a wrong content hash in a header, and fails.
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/submit", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/submit", POST)
.screwdriverIdentity(SCREWDRIVER_ID)
.header("X-Content-Hash", "not/the/right/hash")
.data(createApplicationSubmissionData(packageWithService)),
@@ -579,14 +587,14 @@ public class ApplicationApiTest extends ControllerContainerTest {
// Fifth attempt has the right content hash in a header, and succeeds.
MultiPartStreamer streamer = createApplicationSubmissionData(packageWithService);
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/submit", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/submit", POST)
.screwdriverIdentity(SCREWDRIVER_ID)
.header("X-Content-Hash", Base64.getEncoder().encodeToString(Signatures.sha256Digest(streamer::data)))
.data(streamer),
"{\"message\":\"Application package version: 1.0.45-d00d, source revision of repository 'repo', branch 'master' with commit 'd00d', by a@b, built against 6.1 at 1970-01-01T00:00:01Z\"}");
- ApplicationId app1 = ApplicationId.from("tenant1", "application1", "default");
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/jobreport", POST)
+ ApplicationId app1 = ApplicationId.from("tenant1", "application1", "instance1");
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/jobreport", POST)
.screwdriverIdentity(SCREWDRIVER_ID)
.data(asJson(DeploymentJobs.JobReport.ofComponent(app1,
1234,
@@ -600,31 +608,31 @@ public class ApplicationApiTest extends ControllerContainerTest {
// GET deployment job overview, after triggering system and staging test jobs.
assertEquals(2, tester.controller().applications().deploymentTrigger().triggerReadyJobs());
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/default/job", GET)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/job", GET)
.userIdentity(USER_ID),
new File("jobs.json"));
// GET system test job overview.
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/default/job/system-test", GET)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/job/system-test", GET)
.userIdentity(USER_ID),
new File("system-test-job.json"));
// GET system test run 1 details.
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/default/job/system-test/run/1", GET)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/job/system-test/run/1", GET)
.userIdentity(USER_ID),
new File("system-test-details.json"));
// DELETE a running job to have it aborted.
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/default/job/staging-test", DELETE)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/job/staging-test", DELETE)
.userIdentity(USER_ID),
- "{\"message\":\"Aborting run 1 of staging-test for tenant1.application1\"}");
+ "{\"message\":\"Aborting run 1 of staging-test for tenant1.application1.instance1\"}");
// DELETE submission to unsubscribe from continuous deployment.
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/submit", DELETE)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/submit", DELETE)
.userIdentity(HOSTED_VESPA_OPERATOR),
- "{\"message\":\"Unregistered 'tenant1.application1' from internal deployment pipeline.\"}");
+ "{\"message\":\"Unregistered 'tenant1.application1.instance1' from internal deployment pipeline.\"}");
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/jobreport", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/jobreport", POST)
.screwdriverIdentity(SCREWDRIVER_ID)
.data(asJson(DeploymentJobs.JobReport.ofComponent(app1,
1234,
@@ -650,18 +658,10 @@ public class ApplicationApiTest extends ControllerContainerTest {
.userIdentity(USER_ID),
"");
- // Promote from pipeline
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/promote", POST)
- .screwdriverIdentity(SCREWDRIVER_ID),
- "{\"message\":\"Successfully copied environment hosted-verified-prod to hosted-instance_tenant1_application1_placeholder_component_default\"}");
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-west-1/instance/default/promote", POST)
- .screwdriverIdentity(SCREWDRIVER_ID),
- "{\"message\":\"Successfully copied environment hosted-instance_tenant1_application1_placeholder_component_default to hosted-instance_tenant1_application1_us-west-1_prod_default\"}");
-
// DELETE an application
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", DELETE).userIdentity(USER_ID)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1", DELETE).userIdentity(USER_ID)
.oktaAccessToken(OKTA_AT),
- "");
+ "{\"message\":\"Deleted application tenant1.application1.instance1\"}");
// DELETE a tenant
tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE).userIdentity(USER_ID)
.oktaAccessToken(OKTA_AT),
@@ -693,7 +693,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
startAndTestChange(controllerTester, id, projectId, applicationPackage, deployData, 100);
// us-west-1
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-west-1/instance/default/deploy", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/environment/prod/region/us-west-1/deploy", POST)
.data(deployData)
.screwdriverIdentity(SCREWDRIVER_ID),
new File("deploy-result.json"));
@@ -710,37 +710,37 @@ public class ApplicationApiTest extends ControllerContainerTest {
400);
// Invalid deployment fails
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-east-3/instance/default/global-rotation", GET)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/environment/prod/region/us-east-3/global-rotation", GET)
.userIdentity(USER_ID),
- "{\"error-code\":\"NOT_FOUND\",\"message\":\"application 'tenant1.application1' has no deployment in zone prod.us-east-3 in default\"}",
+ "{\"error-code\":\"NOT_FOUND\",\"message\":\"application 'tenant1.application1.instance1' has no deployment in prod.us-east-3\"}",
404);
// Change status of non-existing deployment fails
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-east-3/instance/default/global-rotation/override", PUT)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/environment/prod/region/us-east-3/global-rotation/override", PUT)
.userIdentity(USER_ID)
.data("{\"reason\":\"unit-test\"}"),
- "{\"error-code\":\"NOT_FOUND\",\"message\":\"application 'tenant1.application1' has no deployment in zone prod.us-east-3 in default\"}",
+ "{\"error-code\":\"NOT_FOUND\",\"message\":\"application 'tenant1.application1.instance1' has no deployment in prod.us-east-3\"}",
404);
// GET global rotation status
setZoneInRotation("rotation-fqdn-1", ZoneId.from("prod", "us-west-1"));
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-west-1/instance/default/global-rotation", GET)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/environment/prod/region/us-west-1/global-rotation", GET)
.userIdentity(USER_ID),
new File("global-rotation.json"));
// GET global rotation override status
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-west-1/instance/default/global-rotation/override", GET)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/environment/prod/region/us-west-1/global-rotation/override", GET)
.userIdentity(USER_ID),
new File("global-rotation-get.json"));
// SET global rotation override status
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-west-1/instance/default/global-rotation/override", PUT)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/environment/prod/region/us-west-1/global-rotation/override", PUT)
.userIdentity(USER_ID)
.data("{\"reason\":\"unit-test\"}"),
new File("global-rotation-put.json"));
// DELETE global rotation override status
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-west-1/instance/default/global-rotation/override", DELETE)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/environment/prod/region/us-west-1/global-rotation/override", DELETE)
.userIdentity(USER_ID)
.data("{\"reason\":\"unit-test\"}"),
new File("global-rotation-delete.json"));
@@ -760,7 +760,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
new File("tenant-without-applications.json"));
// Create application
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1", POST)
.userIdentity(USER_ID)
.oktaAccessToken(OKTA_AT),
new File("application-reference.json"));
@@ -772,7 +772,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
// POST (deploy) an application to a prod zone - allowed when project ID is not specified
MultiPartStreamer entity = createApplicationDeployData(applicationPackage, true);
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/default/deploy", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/instance1/deploy", POST)
.data(entity)
.screwdriverIdentity(SCREWDRIVER_ID),
new File("deploy-result.json"));
@@ -791,7 +791,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
new File("deploy-result.json"));
// POST (deploy) a system application without an application package
- tester.assertResponse(request("/application/v4/tenant/hosted-vespa/application/proxy-host/environment/prod/region/us-central-1/instance/default/deploy", POST)
+ tester.assertResponse(request("/application/v4/tenant/hosted-vespa/application/proxy-host/environment/prod/region/us-central-1/instance/instance1/deploy", POST)
.data(noAppEntity)
.userIdentity(HOSTED_VESPA_OPERATOR),
new File("deploy-no-deployment.json"), 400);
@@ -811,7 +811,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
startAndTestChange(controllerTester, id, projectId, applicationPackage, deployData, 100);
// us-east-3
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-east-3/instance/default/deploy", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/environment/prod/region/us-east-3/deploy", POST)
.data(deployData)
.screwdriverIdentity(SCREWDRIVER_ID),
new File("deploy-result.json"));
@@ -830,7 +830,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
startAndTestChange(controllerTester, id, projectId, applicationPackage, deployData, 101);
// us-west-1
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-west-1/instance/default/deploy", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/environment/prod/region/us-west-1/deploy", POST)
.data(deployData)
.screwdriverIdentity(SCREWDRIVER_ID),
new File("deploy-result.json"));
@@ -842,7 +842,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
setZoneInRotation("rotation-fqdn-1", ZoneId.from("prod", "us-west-1"));
// us-east-3
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-east-3/instance/default/deploy", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/environment/prod/region/us-east-3/deploy", POST)
.data(deployData)
.screwdriverIdentity(SCREWDRIVER_ID),
new File("deploy-result.json"));
@@ -852,11 +852,19 @@ public class ApplicationApiTest extends ControllerContainerTest {
.submit();
setDeploymentMaintainedInfo(controllerTester);
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", GET)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1", GET)
.userIdentity(USER_ID),
new File("application-without-change-multiple-deployments.json"));
}
-
+
+ @Test
+ public void testMeteringResponses() {
+ tester.assertResponse(request("/application/v4/tenant/doesnotexist/application/doesnotexist/metering", GET)
+ .userIdentity(USER_ID)
+ .oktaAccessToken(OKTA_AT),
+ new File("application1-metering.json"));
+ }
+
@Test
public void testErrorResponses() throws Exception {
tester.computeVersionStatus();
@@ -936,16 +944,16 @@ public class ApplicationApiTest extends ControllerContainerTest {
400);
// POST (create) an (empty) application
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1", POST)
.userIdentity(USER_ID)
.oktaAccessToken(OKTA_AT),
new File("application-reference.json"));
// Create the same application again
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1", POST)
.oktaAccessToken(OKTA_AT)
.userIdentity(USER_ID),
- "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Could not create 'tenant1.application1': Application already exists\"}",
+ "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Could not create 'tenant1.application1.instance1': Application already exists\"}",
400);
ConfigServerMock configServer = (ConfigServerMock) container.components().getComponent(ConfigServerMock.class.getName());
@@ -953,28 +961,28 @@ public class ApplicationApiTest extends ControllerContainerTest {
// POST (deploy) an application with an invalid application package
MultiPartStreamer entity = createApplicationDeployData(applicationPackage, true);
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/dev/region/us-west-1/instance/default/deploy", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/dev/region/us-west-1/instance/instance1/deploy", POST)
.data(entity)
.userIdentity(USER_ID),
new File("deploy-failure.json"), 400);
// POST (deploy) an application without available capacity
configServer.throwOnNextPrepare(new ConfigServerException(new URI("server-url"), "Failed to prepare application", ConfigServerException.ErrorCode.OUT_OF_CAPACITY, null));
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/dev/region/us-west-1/instance/default/deploy", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/dev/region/us-west-1/instance/instance1/deploy", POST)
.data(entity)
.userIdentity(USER_ID),
new File("deploy-out-of-capacity.json"), 400);
// POST (deploy) an application where activation fails
configServer.throwOnNextPrepare(new ConfigServerException(new URI("server-url"), "Failed to activate application", ConfigServerException.ErrorCode.ACTIVATION_CONFLICT, null));
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/dev/region/us-west-1/instance/default/deploy", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/dev/region/us-west-1/instance/instance1/deploy", POST)
.data(entity)
.userIdentity(USER_ID),
new File("deploy-activation-conflict.json"), 409);
// POST (deploy) an application where we get an internal server error
configServer.throwOnNextPrepare(new ConfigServerException(new URI("server-url"), "Internal server error", ConfigServerException.ErrorCode.INTERNAL_SERVER_ERROR, null));
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/dev/region/us-west-1/instance/default/deploy", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/dev/region/us-west-1/instance/instance1/deploy", POST)
.data(entity)
.userIdentity(USER_ID),
new File("deploy-internal-server-error.json"), 500);
@@ -987,15 +995,15 @@ public class ApplicationApiTest extends ControllerContainerTest {
400);
// DELETE application
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", DELETE)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1", DELETE)
.userIdentity(USER_ID)
.oktaAccessToken(OKTA_AT),
- "");
+ "{\"message\":\"Deleted application tenant1.application1.instance1\"}");
// DELETE application again - should produce 404
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", DELETE)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1", DELETE)
.oktaAccessToken(OKTA_AT)
.userIdentity(USER_ID),
- "{\"error-code\":\"NOT_FOUND\",\"message\":\"Could not delete application 'tenant1.application1': Application not found\"}",
+ "{\"error-code\":\"NOT_FOUND\",\"message\":\"Could not delete application 'tenant1.application1.instance1': Application not found\"}",
404);
// DELETE tenant
@@ -1009,12 +1017,6 @@ public class ApplicationApiTest extends ControllerContainerTest {
"{\n \"code\" : 403,\n \"message\" : \"Access denied\"\n}",
403);
- // Promote application chef env for nonexistent tenant/application
- tester.assertResponse(request("/application/v4/tenant/dontexist/application/dontexist/environment/prod/region/us-west-1/instance/default/promote", POST)
- .screwdriverIdentity(SCREWDRIVER_ID),
- "{\n \"code\" : 403,\n \"message\" : \"Access denied\"\n}",
- 403);
-
// Create legancy tenant name containing underscores
tester.controller().curator().writeTenant(new AthenzTenant(TenantName.from("my_tenant"), ATHENZ_TENANT_DOMAIN,
new Property("property1"), Optional.empty(), Optional.empty()));
@@ -1064,14 +1066,14 @@ public class ApplicationApiTest extends ControllerContainerTest {
200);
// Creating an application for an Athens domain the user is not admin for is disallowed
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1", POST)
.userIdentity(unauthorizedUser)
.oktaAccessToken(OKTA_AT),
"{\n \"code\" : 403,\n \"message\" : \"Access denied\"\n}",
403);
// (Create it with the right tenant id)
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1", POST)
.userIdentity(authorizedUser)
.oktaAccessToken(OKTA_AT),
new File("application-reference.json"),
@@ -1095,7 +1097,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", DELETE)
.userIdentity(authorizedUser)
.oktaAccessToken(OKTA_AT),
- "",
+ "{\"message\":\"Deleted application tenant1.application1\"}",
200);
// Updating a tenant for an Athens domain the user is not admin for is disallowed
@@ -1132,7 +1134,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
long screwdriverProjectId = 123;
createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID);
- Application application = controllerTester.createApplication(ATHENZ_TENANT_DOMAIN.getName(), "tenant1", "application1");
+ Application application = controllerTester.createApplication(ATHENZ_TENANT_DOMAIN.getName(), "tenant1", "application1", "default");
ScrewdriverId screwdriverId = new ScrewdriverId(Long.toString(screwdriverProjectId));
controllerTester.authorize(ATHENZ_TENANT_DOMAIN, screwdriverId, ApplicationAction.deploy, application);
@@ -1163,7 +1165,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID);
- Application application = controllerTester.createApplication(ATHENZ_TENANT_DOMAIN.getName(), "tenant1", "application1");
+ Application application = controllerTester.createApplication(ATHENZ_TENANT_DOMAIN.getName(), "tenant1", "application1", "default");
controllerTester.authorize(ATHENZ_TENANT_DOMAIN, screwdriverId, ApplicationAction.deploy, application);
// Allow systemtest to succeed by notifying completion of system test
@@ -1261,7 +1263,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID);
- Application application = controllerTester.createApplication(ATHENZ_TENANT_DOMAIN.getName(), "tenant1", "application1");
+ Application application = controllerTester.createApplication(ATHENZ_TENANT_DOMAIN.getName(), "tenant1", "application1", "default");
controllerTester.authorize(ATHENZ_TENANT_DOMAIN, screwdriverId, ApplicationAction.deploy, application);
// Allow systemtest to succeed by notifying completion of system test
@@ -1307,14 +1309,18 @@ public class ApplicationApiTest extends ControllerContainerTest {
.data(asJson(job.type(JobType.systemTest).report()))
.userIdentity(HOSTED_VESPA_OPERATOR)
.get();
- tester.assertResponse(request, new File("jobreport-unexpected-system-test-completion.json"), 400);
+ tester.assertResponse(request, "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Notified of completion " +
+ "of system-test for tenant1.application1, but that has not been triggered; last was " +
+ controllerTester.controller().applications().require(app.id()).deploymentJobs().jobStatus().get(JobType.systemTest).lastTriggered().get().at() + "\"}", 400);
// Notifying about unknown job fails
request = request("/application/v4/tenant/tenant1/application/application1/jobreport", POST)
.data(asJson(job.type(JobType.productionUsEast3).report()))
.userIdentity(HOSTED_VESPA_OPERATOR)
.get();
- tester.assertResponse(request, new File("jobreport-unexpected-completion.json"), 400);
+ tester.assertResponse(request, "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Notified of completion " +
+ "of production-us-east-3 for tenant1.application1, but that has not been triggered; last was never\"}",
+ 400);
// ... and assert it was recorded
JobStatus recordedStatus =
@@ -1497,14 +1503,14 @@ public class ApplicationApiTest extends ControllerContainerTest {
.data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}")
.oktaAccessToken(OKTA_AT),
new File("tenant-without-applications.json"));
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1", POST)
.userIdentity(USER_ID)
.oktaAccessToken(OKTA_AT),
new File("application-reference.json"));
addScrewdriverUserToDeployRole(SCREWDRIVER_ID, ATHENZ_TENANT_DOMAIN,
new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId("application1"));
- return ApplicationId.from("tenant1", "application1", "default");
+ return ApplicationId.from("tenant1", "application1", "instance1");
}
private void startAndTestChange(ContainerControllerTester controllerTester, ApplicationId application,
@@ -1522,30 +1528,30 @@ public class ApplicationApiTest extends ControllerContainerTest {
.submit();
// system-test
- String testPath = String.format("/application/v4/tenant/%s/application/%s/environment/test/region/us-east-1/instance/default",
- application.tenant().value(), application.application().value());
+ String testPath = String.format("/application/v4/tenant/%s/application/%s/instance/%s/environment/test/region/us-east-1",
+ application.tenant().value(), application.application().value(), application.instance().value());
tester.assertResponse(request(testPath, POST)
.data(deployData)
.screwdriverIdentity(SCREWDRIVER_ID),
new File("deploy-result.json"));
tester.assertResponse(request(testPath, DELETE)
.screwdriverIdentity(SCREWDRIVER_ID),
- "Deactivated " + testPath.replaceFirst("/application/v4/", ""));
+ "{\"message\":\"Deactivated " + application + " in test.us-east-1\"}");
controllerTester.jobCompletion(JobType.systemTest)
.application(application)
.projectId(projectId)
.submit();
// staging
- String stagingPath = String.format("/application/v4/tenant/%s/application/%s/environment/staging/region/us-east-3/instance/default",
- application.tenant().value(), application.application().value());
+ String stagingPath = String.format("/application/v4/tenant/%s/application/%s/instance/%s/environment/staging/region/us-east-3",
+ application.tenant().value(), application.application().value(), application.instance().value());
tester.assertResponse(request(stagingPath, POST)
.data(deployData)
.screwdriverIdentity(SCREWDRIVER_ID),
new File("deploy-result.json"));
tester.assertResponse(request(stagingPath, DELETE)
.screwdriverIdentity(SCREWDRIVER_ID),
- "Deactivated " + stagingPath.replaceFirst("/application/v4/", ""));
+ "{\"message\":\"Deactivated " + application + " in staging.us-east-3\"}");
controllerTester.jobCompletion(JobType.stagingTest)
.application(application)
.projectId(projectId)
@@ -1567,7 +1573,9 @@ public class ApplicationApiTest extends ControllerContainerTest {
List<String> hostnames = new ArrayList<>();
hostnames.add("host1");
hostnames.add("host2");
- clusterInfo.put(ClusterSpec.Id.from("cluster1"), new ClusterInfo("flavor1", 37, 2, 4, 50, ClusterSpec.Type.content, hostnames));
+ clusterInfo.put(ClusterSpec.Id.from("cluster1"),
+ new ClusterInfo("flavor1", 37, 2, 4, 50,
+ ClusterSpec.Type.content, hostnames));
Map<ClusterSpec.Id, ClusterUtilization> clusterUtils = new HashMap<>();
clusterUtils.put(ClusterSpec.Id.from("cluster1"), new ClusterUtilization(0.3, 0.6, 0.4, 0.3));
DeploymentMetrics metrics = new DeploymentMetrics(1, 2, 3, 4, 5,
@@ -1615,7 +1623,10 @@ public class ApplicationApiTest extends ControllerContainerTest {
}
private void updateContactInformation() {
- Contact contact = new Contact(URI.create("www.contacts.tld/1234"), URI.create("www.properties.tld/1234"), URI.create("www.issues.tld/1234"), List.of(List.of("alice"), List.of("bob")), "queue", Optional.empty());
+ Contact contact = new Contact(URI.create("www.contacts.tld/1234"),
+ URI.create("www.properties.tld/1234"),
+ URI.create("www.issues.tld/1234"),
+ List.of(List.of("alice"), List.of("bob")), "queue", Optional.empty());
tester.controller().tenants().lockIfPresent(TenantName.from("tenant2"),
LockedTenant.Athenz.class,
lockedTenant -> tester.controller().tenants().store(lockedTenant.with(contact)));
@@ -1623,7 +1634,10 @@ public class ApplicationApiTest extends ControllerContainerTest {
private void registerContact(long propertyId) {
PropertyId p = new PropertyId(String.valueOf(propertyId));
- contactRetriever().addContact(p, new Contact(URI.create("www.issues.tld/" + p.id()), URI.create("www.contacts.tld/" + p.id()), URI.create("www.properties.tld/" + p.id()), List.of(Collections.singletonList("alice"),
+ contactRetriever().addContact(p, new Contact(URI.create("www.issues.tld/" + p.id()),
+ URI.create("www.contacts.tld/" + p.id()),
+ URI.create("www.properties.tld/" + p.id()),
+ List.of(Collections.singletonList("alice"),
Collections.singletonList("bob")), "queue", Optional.empty()));
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelperTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelperTest.java
index 212ee272a63..616db640132 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelperTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelperTest.java
@@ -6,6 +6,7 @@ import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerException;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
+import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.deployment.InternalDeploymentTester;
import org.json.JSONException;
import org.json.JSONObject;
@@ -19,8 +20,10 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.time.Instant;
+import java.util.Optional;
import static com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerException.ErrorCode.INVALID_APPLICATION_PACKAGE;
+import static com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType.devAwsUsEast2a;
import static com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType.productionUsCentral1;
import static com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType.productionUsEast3;
import static com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType.productionUsWest1;
@@ -28,6 +31,7 @@ import static com.yahoo.vespa.hosted.controller.api.integration.deployment.JobTy
import static com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType.systemTest;
import static com.yahoo.vespa.hosted.controller.api.integration.deployment.TesterCloud.Status.FAILURE;
import static com.yahoo.vespa.hosted.controller.deployment.InternalDeploymentTester.appId;
+import static com.yahoo.vespa.hosted.controller.deployment.InternalDeploymentTester.applicationPackage;
import static com.yahoo.vespa.hosted.controller.deployment.InternalDeploymentTester.testerId;
import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.deploymentFailed;
import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.installationFailed;
@@ -111,6 +115,23 @@ public class JobControllerApiHandlerHelperTest {
assertResponse(JobControllerApiHandlerHelper.runDetailsResponse(tester.jobs(), tester.jobs().last(appId, productionUsEast3).get().id(), "0"), "us-east-3-log-without-first.json");
assertResponse(JobControllerApiHandlerHelper.jobTypeResponse(tester.tester().controller(), appId, URI.create("https://some.url:43/root/")), "overview.json");
+ tester.jobs().deploy(appId, JobType.devAwsUsEast2a, Optional.empty(), applicationPackage);
+ tester.runJob(JobType.devAwsUsEast2a);
+ assertResponse(JobControllerApiHandlerHelper.runResponse(tester.jobs().runs(appId, devAwsUsEast2a), URI.create("https://some.url:43/root")), "dev-aws-us-east-2a-runs.json");
+ }
+
+ @Test
+ public void testDevResponses() {
+ InternalDeploymentTester tester = new InternalDeploymentTester();
+ tester.clock().setInstant(Instant.EPOCH);
+
+ ZoneId zone = JobType.devUsEast1.zone(tester.tester().controller().system());
+ tester.jobs().deploy(appId, JobType.devUsEast1, Optional.empty(), applicationPackage);
+ tester.configServer().convergeServices(appId, zone);
+ tester.setEndpoints(appId, zone);
+ tester.runner().run();
+
+ assertResponse(JobControllerApiHandlerHelper.jobTypeResponse(tester.tester().controller(), appId, URI.create("https://some.url:43/root")), "dev-overview.json");
}
private void compare(HttpResponse response, String expected) throws JSONException, IOException {
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-nodes.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-nodes.json
index eb53ff7161e..e673a610f87 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-nodes.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-nodes.json
@@ -1,7 +1,7 @@
{
"nodes": [
{
- "hostname": "host-tenant1:application1:default-prod.us-central-1",
+ "hostname": "host-tenant1:application1:instance1-prod.us-central-1",
"state": "active",
"orchestration": "unorchestrated",
"version": "6.1",
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-reference-2.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-reference-2.json
index ff22b95739d..0c9dc2ca1e7 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-reference-2.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-reference-2.json
@@ -2,5 +2,5 @@
"tenant": "tenant2",
"application": "application2",
"instance": "default",
- "url": "http://localhost:8080/application/v4/tenant/tenant2/application/application2"
+ "url": "http://localhost:8080/application/v4/tenant/tenant2/application/application2/instance/default"
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-reference.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-reference.json
index 1d56944f6bc..60243633614 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-reference.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-reference.json
@@ -1,6 +1,6 @@
{
"tenant": "tenant1",
"application":"application1",
- "instance":"default",
- "url":"http://localhost:8080/application/v4/tenant/tenant1/application/application1"
+ "instance":"instance1",
+ "url":"http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1"
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-without-change-multiple-deployments.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-without-change-multiple-deployments.json
index b52fec761d8..383a1b667f7 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-without-change-multiple-deployments.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-without-change-multiple-deployments.json
@@ -1,8 +1,8 @@
{
"tenant": "tenant1",
"application": "application1",
- "instance": "default",
- "deployments": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/default/job/",
+ "instance": "instance1",
+ "deployments": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/job/",
"source": {
"gitRepository": "repository1",
"gitBranch": "master",
@@ -241,8 +241,8 @@
},
"environment": "prod",
"region": "us-west-1",
- "instance": "default",
- "url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-west-1/instance/default"
+ "instance": "instance1",
+ "url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/environment/prod/region/us-west-1"
},
{
"bcpStatus": {
@@ -250,8 +250,8 @@
},
"environment": "prod",
"region": "us-east-3",
- "instance": "default",
- "url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-east-3/instance/default"
+ "instance": "instance1",
+ "url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/environment/prod/region/us-east-3"
}
],
"metrics": {
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application.json
index 4b2cb397b5b..1d719133ac3 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application.json
@@ -1,8 +1,8 @@
{
"tenant": "tenant1",
"application": "application1",
- "instance": "default",
- "deployments": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/default/job/",
+ "instance": "instance1",
+ "deployments": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/job/",
"source": {
"gitRepository": "repository1",
"gitBranch": "master",
@@ -217,7 +217,7 @@
]
}
],
- "compileVersion": "(ignore)",
+ "compileVersion": "6.0.0",
"globalRotations": [
"https://application1--tenant1.global.vespa.oath.cloud:4443/"
],
@@ -226,8 +226,8 @@
{
"environment": "dev",
"region": "us-west-1",
- "instance": "default",
- "url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/environment/dev/region/us-west-1/instance/default"
+ "instance": "instance1",
+ "url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/environment/dev/region/us-west-1"
},
{
"bcpStatus": {
@@ -235,8 +235,8 @@
},
"environment": "prod",
"region": "us-central-1",
- "instance": "default",
- "url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/default"
+ "instance": "instance1",
+ "url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/environment/prod/region/us-central-1"
}
],
"metrics": {
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application1-metering.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application1-metering.json
new file mode 100644
index 00000000000..63e1c1ebbd1
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application1-metering.json
@@ -0,0 +1,34 @@
+{
+ "currentrate": {
+ "cpu": 0.0,
+ "mem": 0.0,
+ "disk": 0.0
+ },
+ "thismonth": {
+ "cpu": 0.0,
+ "mem": 0.0,
+ "disk": 0.0
+ },
+ "lastmonth": {
+ "cpu": 0.0,
+ "mem": 0.0,
+ "disk": 0.0
+ },
+ "details": {
+ "cpu": {
+ "dummy": {
+ "data": []
+ }
+ },
+ "mem": {
+ "dummy": {
+ "data": []
+ }
+ },
+ "disk": {
+ "dummy": {
+ "data": []
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application1-recursive.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application1-recursive.json
index fa903b61825..c0e9d10a40c 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application1-recursive.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application1-recursive.json
@@ -1,8 +1,8 @@
{
"tenant": "tenant1",
"application": "application1",
- "instance": "default",
- "deployments": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/default/job/",
+ "instance": "instance1",
+ "deployments": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/job/",
"source": {
"gitRepository": "repository1",
"gitBranch": "master",
@@ -223,7 +223,7 @@
],
"rotationId": "rotation-id-1",
"instances": [
- @include(dev-us-west-1.json),
+ @include(dev-us-east-1.json),
@include(prod-us-central-1.json)
],
"metrics": {
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/delete-with-active-deployments.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/delete-with-active-deployments.json
index f85ac6dbf8b..ad7e4f00027 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/delete-with-active-deployments.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/delete-with-active-deployments.json
@@ -1,4 +1,4 @@
{
"error-code": "BAD_REQUEST",
- "message": "Could not delete 'application 'tenant1.application1'': It has active deployments"
+ "message": "Could not delete 'application 'tenant1.application1.instance1'': It has active deployments in: dev.us-west-1, prod.us-central-1"
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment-job-accepted.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment-job-accepted.json
index 66fce327701..65ecb3c6979 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment-job-accepted.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment-job-accepted.json
@@ -1,4 +1,4 @@
{
- "message": "Deployment started in run 1 of dev-us-east-1 for tenant1.application1",
- "location": "https://dashboard.tld/tenant1.application1/dev-us-east-1/1"
+ "message": "Deployment started in run 1 of dev-us-east-1 for tenant1.application1.instance1",
+ "run": 1
} \ No newline at end of file
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment.json
index 6caac3bd532..25948e998f1 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment.json
@@ -1,7 +1,7 @@
{
"tenant": "tenant1",
"application": "application1",
- "instance": "default",
+ "instance": "instance1",
"environment": "prod",
"region": "us-central-1",
"endpoints": [],
@@ -12,8 +12,8 @@
"http://global-endpoint.vespa.yahooapis.com:4080",
"http://alias-endpoint.vespa.yahooapis.com:4080"
],
- "nodes": "http://localhost:8080/zone/v2/prod/us-central-1/nodes/v2/node/%3F&recursive=true&application=tenant1.application1.default",
- "yamasUrl": "http://monitoring-system.test/?environment=prod&region=us-central-1&application=tenant1.application1",
+ "nodes": "http://localhost:8080/zone/v2/prod/us-central-1/nodes/v2/node/%3F&recursive=true&application=tenant1.application1.instance1",
+ "yamasUrl": "http://monitoring-system.test/?environment=prod&region=us-central-1&application=tenant1.application1.instance1",
"version": "(ignore)",
"revision": "(ignore)",
"deployTimeEpochMs": "(ignore)",
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/dev-aws-us-east-2a-runs.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/dev-aws-us-east-2a-runs.json
new file mode 100644
index 00000000000..82bdc4ca195
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/dev-aws-us-east-2a-runs.json
@@ -0,0 +1,21 @@
+{
+ "1": {
+ "id": 1,
+ "status": "success",
+ "start": 102000,
+ "end": 102000,
+ "wantedPlatform": "7.1",
+ "wantedApplication": {
+ "hash": "unknown"
+ },
+ "steps": {
+ "deployReal": "succeeded",
+ "installReal": "succeeded"
+ },
+ "tasks": {
+ "deploy": "succeeded",
+ "install": "succeeded"
+ },
+ "log": "https://some.url:43/root/run/1"
+ }
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/dev-overview.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/dev-overview.json
new file mode 100644
index 00000000000..93b6138d987
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/dev-overview.json
@@ -0,0 +1,31 @@
+{
+ "lastVersions": {},
+ "deploying": {},
+ "deployments": [],
+ "jobs": {},
+ "devJobs": {
+ "dev-us-east-1": {
+ "runs": [
+ {
+ "id": 1,
+ "status": "success",
+ "start": 0,
+ "end": 0,
+ "wantedPlatform": "6.1",
+ "wantedApplication": {
+ "hash": "unknown"
+ },
+ "steps": {
+ "deployReal": "succeeded",
+ "installReal": "succeeded"
+ },
+ "tasks": {
+ "deploy": "succeeded",
+ "install": "succeeded"
+ },
+ "log": "https://some.url:43/root/dev-us-east-1/run/1"
+ }
+ ]
+ }
+ }
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/dev-us-west-1.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/dev-us-east-1.json
index 67c71ee3880..1a2025e4de2 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/dev-us-west-1.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/dev-us-east-1.json
@@ -1,7 +1,7 @@
{
"tenant": "tenant1",
"application": "application1",
- "instance": "default",
+ "instance": "instance1",
"environment": "dev",
"region": "us-west-1",
"endpoints": [],
@@ -12,8 +12,8 @@
"http://global-endpoint.vespa.yahooapis.com:4080",
"http://alias-endpoint.vespa.yahooapis.com:4080"
],
- "nodes": "http://localhost:8080/zone/v2/dev/us-west-1/nodes/v2/node/%3F&recursive=true&application=tenant1.application1.default",
- "yamasUrl": "http://monitoring-system.test/?environment=dev&region=us-west-1&application=tenant1.application1",
+ "nodes": "http://localhost:8080/zone/v2/dev/us-west-1/nodes/v2/node/%3F&recursive=true&application=tenant1.application1.instance1",
+ "yamasUrl": "http://monitoring-system.test/?environment=dev&region=us-west-1&application=tenant1.application1.instance1",
"version": "(ignore)",
"revision": "(ignore)",
"deployTimeEpochMs": "(ignore)",
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/global-rotation-delete.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/global-rotation-delete.json
index 048ddcbc5c5..2df97a6c765 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/global-rotation-delete.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/global-rotation-delete.json
@@ -1 +1 @@
-{"message":"Successfully set tenant1.application1 in prod.us-west-1 in service"}
+{"message":"Successfully set tenant1.application1.instance1 in prod.us-west-1 in service"}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/global-rotation-put.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/global-rotation-put.json
index f67b8dd56d9..6a41b0000e4 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/global-rotation-put.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/global-rotation-put.json
@@ -1 +1 @@
-{"message":"Successfully set tenant1.application1 in prod.us-west-1 out of service"}
+{"message":"Successfully set tenant1.application1.instance1 in prod.us-west-1 out of service"}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/jobreport-unexpected-completion.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/jobreport-unexpected-completion.json
deleted file mode 100644
index 72123e7ae41..00000000000
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/jobreport-unexpected-completion.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "error-code": "BAD_REQUEST",
- "message": "Notified of completion of production-us-east-3 for tenant1.application1, but that has neither been triggered nor deployed"
-}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/jobreport-unexpected-system-test-completion.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/jobreport-unexpected-system-test-completion.json
deleted file mode 100644
index 513cfb754ae..00000000000
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/jobreport-unexpected-system-test-completion.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "error-code": "BAD_REQUEST",
- "message": "Notified of completion of system-test for tenant1.application1, but that has neither been triggered nor deployed"
-}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/jobs.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/jobs.json
index 72dd13474dc..c5cfd5981c0 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/jobs.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/jobs.json
@@ -65,10 +65,10 @@
"tasks": {
"deploy": "running"
},
- "log": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/default/job/system-test/run/1"
+ "log": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/job/system-test/run/1"
}
],
- "url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/default/job/system-test"
+ "url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/job/system-test"
},
"staging-test": {
"runs": [
@@ -101,10 +101,10 @@
"report": "unfinished"
},
"tasks": {},
- "log": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/default/job/staging-test/run/1"
+ "log": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/job/staging-test/run/1"
}
],
- "url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/default/job/staging-test"
+ "url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/job/staging-test"
},
"us-west-1": {
"runs": [
@@ -126,7 +126,9 @@
}
}
],
- "url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/default/job/production-us-west-1"
+ "url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/job/production-us-west-1"
}
- }
+ },
+ "devJobs": {}
}
+
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/overview.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/overview.json
index 84866650afc..a8ec9295868 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/overview.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/overview.json
@@ -739,5 +739,6 @@
],
"url": "https://some.url:43/root/production-us-east-3"
}
- }
+ },
+ "devJobs": {}
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/prod-us-central-1.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/prod-us-central-1.json
index 9b08fccf883..4810c8f92b2 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/prod-us-central-1.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/prod-us-central-1.json
@@ -4,7 +4,7 @@
},
"tenant": "tenant1",
"application": "application1",
- "instance": "default",
+ "instance": "instance1",
"environment": "prod",
"region": "us-central-1",
"endpoints": [],
@@ -15,8 +15,8 @@
"http://global-endpoint.vespa.yahooapis.com:4080",
"http://alias-endpoint.vespa.yahooapis.com:4080"
],
- "nodes": "http://localhost:8080/zone/v2/prod/us-central-1/nodes/v2/node/%3F&recursive=true&application=tenant1.application1.default",
- "yamasUrl": "http://monitoring-system.test/?environment=prod&region=us-central-1&application=tenant1.application1",
+ "nodes": "http://localhost:8080/zone/v2/prod/us-central-1/nodes/v2/node/%3F&recursive=true&application=tenant1.application1.instance1",
+ "yamasUrl": "http://monitoring-system.test/?environment=prod&region=us-central-1&application=tenant1.application1.instance1",
"version": "(ignore)",
"revision": "(ignore)",
"deployTimeEpochMs": "(ignore)",
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/services.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/services.json
index 782d8e42aa3..1a434afafbb 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/services.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/services.json
@@ -3,10 +3,10 @@
{
"name": "cluster1",
"type": "content",
- "url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/default/service/container-clustercontroller-6s8slgtps7ry8uh6lx21ejjiv/cluster/v2/cluster1",
+ "url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/instance1/service/container-clustercontroller-6s8slgtps7ry8uh6lx21ejjiv/cluster/v2/cluster1",
"services": [
{
- "url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/default/service/storagenode-awe3slno6mmq2fye191y324jl/state/v1/",
+ "url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/instance1/service/storagenode-awe3slno6mmq2fye191y324jl/state/v1/",
"serviceType": "storagenode",
"serviceName": "storagenode",
"configId": "cluster1/storage/0",
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/system-test-job.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/system-test-job.json
index 107d969e8ad..800c3976188 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/system-test-job.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/system-test-job.json
@@ -28,6 +28,6 @@
"tasks": {
"deploy": "running"
},
- "log": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/default/job/system-test/run/1"
+ "log": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/job/system-test/run/1"
}
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/tenant-with-application.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/tenant-with-application.json
index b222c33291c..edd3d7cc34f 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/tenant-with-application.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/tenant-with-application.json
@@ -7,8 +7,8 @@
{
"tenant": "tenant1",
"application":"application1",
- "instance":"default",
- "url":"http://localhost:8080/application/v4/tenant/tenant1/application/application1"
+ "instance":"instance1",
+ "url":"http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1"
}
]
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/us-east-3-log-without-first.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/us-east-3-log-without-first.json
index 77d5b3479be..390024fe33d 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/us-east-3-log-without-first.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/us-east-3-log-without-first.json
@@ -12,7 +12,7 @@
{
"at": 1000,
"type": "debug",
- "message": "Deactivating tester of tenant.application in zone prod.us-east-3 in default ..."
+ "message": "Deactivating tester of tenant.application in prod.us-east-3 ..."
}
]
},
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiTest.java
index 8856cc5a97f..74d637499bd 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiTest.java
@@ -17,6 +17,7 @@ import java.io.File;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
+import java.util.Set;
import static org.junit.Assert.assertFalse;
@@ -40,7 +41,7 @@ public class ControllerApiTest extends ControllerContainerTest {
public void testControllerApi() {
tester.assertResponse(authenticatedRequest("http://localhost:8080/controller/v1/", new byte[0], Request.Method.GET), new File("root.json"));
- // POST deactivation of a maintenance job
+ // POST deactivates a maintenance job
tester.assertResponse(hostedOperatorRequest("http://localhost:8080/controller/v1/maintenance/inactive/DeploymentExpirer",
"", Request.Method.POST),
"{\"message\":\"Deactivated job 'DeploymentExpirer'\"}", 200);
@@ -48,12 +49,30 @@ public class ControllerApiTest extends ControllerContainerTest {
// GET a list of all maintenance jobs
tester.assertResponse(authenticatedRequest("http://localhost:8080/controller/v1/maintenance/", new byte[0], Request.Method.GET),
new File("maintenance.json"));
- // DELETE deactivation of a maintenance job
+
+ // DELETE activates maintenance job
tester.assertResponse(hostedOperatorRequest("http://localhost:8080/controller/v1/maintenance/inactive/DeploymentExpirer",
"", Request.Method.DELETE),
"{\"message\":\"Re-activated job 'DeploymentExpirer'\"}",
200);
+ // DELETE fails to activate unknown maintenance job
+ tester.assertResponse(hostedOperatorRequest("http://localhost:8080/controller/v1/maintenance/inactive/foo",
+ "", Request.Method.DELETE),
+ "{\"error-code\":\"NOT_FOUND\",\"message\":\"No job named 'foo'\"}",
+ 404);
+
+ // DELETE clears inactive flag for maintenance job that has been removed from the code base
+ tester.controller().curator().writeInactiveJobs(Set.of("bar"));
+ tester.assertResponse(hostedOperatorRequest("http://localhost:8080/controller/v1/maintenance/inactive/bar",
+ "", Request.Method.DELETE),
+ "{\"message\":\"Re-activated job 'bar'\"}",
+ 200);
+ tester.assertResponse(hostedOperatorRequest("http://localhost:8080/controller/v1/maintenance/inactive/bar",
+ "", Request.Method.DELETE),
+ "{\"error-code\":\"NOT_FOUND\",\"message\":\"No job named 'bar'\"}",
+ 404);
+
assertFalse("Actions are logged to audit log", tester.controller().auditLogger().readLog().entries().isEmpty());
}
@@ -61,7 +80,7 @@ public class ControllerApiTest extends ControllerContainerTest {
public void testUpgraderApi() {
// Get current configuration
tester.assertResponse(authenticatedRequest("http://localhost:8080/controller/v1/jobs/upgrader", new byte[0], Request.Method.GET),
- "{\"upgradesPerMinute\":0.125,\"confidenceOverrides\":[]}",
+ "{\"upgradesPerMinute\":100.0,\"confidenceOverrides\":[]}",
200);
// Set invalid configuration
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json
index 6b20adf835e..d4f3e20ac14 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json
@@ -4,6 +4,9 @@
"name": "ApplicationOwnershipConfirmer"
},
{
+ "name": "BillingMaintainer"
+ },
+ {
"name": "ClusterInfoMaintainer"
},
{
@@ -28,9 +31,6 @@
"name": "DeploymentMetricsMaintainer"
},
{
- "name": "DnsMaintainer"
- },
- {
"name": "JobRunner"
},
{
@@ -52,9 +52,6 @@
"name": "ResourceMeterMaintainer"
},
{
- "name": "RoutingPolicyMaintainer"
- },
- {
"name": "SystemUpgrader"
},
{
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/cost/CostApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/cost/CostApiTest.java
index 8cb439fee37..b085a87672e 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/cost/CostApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/cost/CostApiTest.java
@@ -4,9 +4,10 @@ package com.yahoo.vespa.hosted.controller.restapi.cost;
import com.yahoo.application.container.handler.Request;
import com.yahoo.config.provision.CloudName;
import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.vespa.athenz.api.AthenzIdentity;
import com.yahoo.vespa.athenz.api.AthenzUser;
-import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.vespa.hosted.controller.integration.ZoneApiMock;
import com.yahoo.vespa.hosted.controller.integration.ZoneRegistryMock;
import com.yahoo.vespa.hosted.controller.restapi.ContainerControllerTester;
import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerTest;
@@ -22,9 +23,9 @@ public class CostApiTest extends ControllerContainerTest {
private static final AthenzIdentity operator = AthenzUser.fromUserId("operatorUser");
private static final CloudName cloud1 = CloudName.from("yahoo");
private static final CloudName cloud2 = CloudName.from("cloud2");
- private static final ZoneId zone1 = ZoneId.from("prod", "us-east-3", cloud1.value());
- private static final ZoneId zone2 = ZoneId.from("prod", "us-west-1", cloud1.value());
- private static final ZoneId zone3 = ZoneId.from("prod", "eu-west-1", cloud2.value());
+ private static final ZoneApi zone1 = ZoneApiMock.newBuilder().withId("prod.us-east-3").with(cloud1).build();
+ private static final ZoneApi zone2 = ZoneApiMock.newBuilder().withId("prod.us-west-1").with(cloud1).build();
+ private static final ZoneApi zone3 = ZoneApiMock.newBuilder().withId("prod.eu-west-1").with(cloud2).build();
private ContainerControllerTester tester;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiTest.java
index 6eb7663e4ab..35ec5b0e37e 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiTest.java
@@ -20,7 +20,7 @@ public class BadgeApiTest extends ControllerContainerTest {
@Test
public void testBadgeApi() {
ContainerControllerTester tester = new ContainerControllerTester(container, responseFiles);
- Application application = tester.createApplication("domain", "tenant", "application");
+ Application application = tester.createApplication("domain", "tenant", "application", "default");
ApplicationPackage packageWithService = new ApplicationPackageBuilder()
.environment(Environment.prod)
.athenzIdentity(com.yahoo.config.provision.AthenzDomain.from("domain"), AthenzService.from("service"))
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiTest.java
index 73977d7c2fa..d6620733efe 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiTest.java
@@ -39,12 +39,9 @@ public class DeploymentApiTest extends ControllerContainerTest {
.build();
// 3 applications deploy on current system version
- Application failingApplication = tester.createApplication("domain1", "tenant1",
- "application1");
- Application productionApplication = tester.createApplication("domain2", "tenant2",
- "application2");
- Application applicationWithoutDeployment = tester.createApplication("domain3", "tenant3",
- "application3");
+ Application failingApplication = tester.createApplication("domain1", "tenant1", "application1", "default");
+ Application productionApplication = tester.createApplication("domain2", "tenant2", "application2", "default");
+ Application applicationWithoutDeployment = tester.createApplication("domain3", "tenant3", "application3", "default");
tester.deployCompletely(failingApplication, applicationPackage, 1L, false);
tester.deployCompletely(productionApplication, applicationPackage, 2L, false);
@@ -77,6 +74,7 @@ public class DeploymentApiTest extends ControllerContainerTest {
version.committedAt(),
version.isControllerVersion(),
version.isSystemVersion(),
+ version.isReleased(),
ImmutableSet.of("config1.test", "config2.test").stream()
.map(HostName::from)
.collect(Collectors.toSet()),
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiTest.java
index e4b0f526519..745a7af203b 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiTest.java
@@ -5,6 +5,7 @@ import com.yahoo.application.container.handler.Request;
import com.yahoo.config.provision.CloudName;
import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.zone.UpgradePolicy;
+import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.athenz.api.AthenzIdentity;
import com.yahoo.vespa.athenz.api.AthenzUser;
@@ -12,6 +13,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.configserver.Node;
import com.yahoo.vespa.hosted.controller.application.SystemApplication;
import com.yahoo.vespa.hosted.controller.integration.ConfigServerMock;
import com.yahoo.vespa.hosted.controller.integration.NodeRepositoryMock;
+import com.yahoo.vespa.hosted.controller.integration.ZoneApiMock;
import com.yahoo.vespa.hosted.controller.integration.ZoneRegistryMock;
import com.yahoo.vespa.hosted.controller.maintenance.JobControl;
import com.yahoo.vespa.hosted.controller.maintenance.Maintainer;
@@ -38,9 +40,9 @@ public class OsApiTest extends ControllerContainerTest {
private static final AthenzIdentity operator = AthenzUser.fromUserId("operatorUser");
private static final CloudName cloud1 = CloudName.from("cloud1");
private static final CloudName cloud2 = CloudName.from("cloud2");
- private static final ZoneId zone1 = ZoneId.from("prod", "us-east-3", cloud1.value());
- private static final ZoneId zone2 = ZoneId.from("prod", "us-west-1", cloud1.value());
- private static final ZoneId zone3 = ZoneId.from("prod", "eu-west-1", cloud2.value());
+ private static final ZoneApi zone1 = ZoneApiMock.newBuilder().withId("prod.us-east-3").with(cloud1).build();
+ private static final ZoneApi zone2 = ZoneApiMock.newBuilder().withId("prod.us-west-1").with(cloud1).build();
+ private static final ZoneApi zone3 = ZoneApiMock.newBuilder().withId("prod.eu-west-1").with(cloud2).build();
private ContainerControllerTester tester;
private List<OsUpgrader> osUpgraders;
@@ -79,12 +81,12 @@ public class OsApiTest extends ControllerContainerTest {
// Status is updated after some zones are upgraded
upgradeAndUpdateStatus();
- completeUpgrade(zone1);
+ completeUpgrade(zone1.getId());
assertFile(new Request("http://localhost:8080/os/v1/"), "versions-partially-upgraded.json");
// All zones are upgraded
upgradeAndUpdateStatus();
- completeUpgrade(zone2, zone3);
+ completeUpgrade(zone2.getId(), zone3.getId());
assertFile(new Request("http://localhost:8080/os/v1/"), "versions-all-upgraded.json");
// Downgrade with force is permitted
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/responses/versions-all-upgraded.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/responses/versions-all-upgraded.json
index e1d81a874dc..17f90259fa8 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/responses/versions-all-upgraded.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/responses/versions-all-upgraded.json
@@ -6,6 +6,11 @@
"cloud": "cloud1",
"nodes": [
{
+ "hostname": "node-1-configserver-host",
+ "environment": "prod",
+ "region": "us-east-3"
+ },
+ {
"hostname": "node-3-configserver-host",
"environment": "prod",
"region": "us-east-3"
@@ -18,7 +23,7 @@
{
"hostname": "node-1-configserver-host",
"environment": "prod",
- "region": "us-east-3"
+ "region": "us-west-1"
},
{
"hostname": "node-3-configserver-host",
@@ -31,12 +36,12 @@
"region": "us-west-1"
},
{
- "hostname": "node-1-configserver-host",
+ "hostname": "node-2-proxy-host",
"environment": "prod",
- "region": "us-west-1"
+ "region": "us-east-3"
},
{
- "hostname": "node-2-proxy-host",
+ "hostname": "node-3-proxy-host",
"environment": "prod",
"region": "us-east-3"
},
@@ -46,22 +51,47 @@
"region": "us-east-3"
},
{
+ "hostname": "node-2-proxy-host",
+ "environment": "prod",
+ "region": "us-west-1"
+ },
+ {
"hostname": "node-3-proxy-host",
"environment": "prod",
+ "region": "us-west-1"
+ },
+ {
+ "hostname": "node-1-proxy-host",
+ "environment": "prod",
+ "region": "us-west-1"
+ },
+ {
+ "hostname": "node-2-tenant-host",
+ "environment": "prod",
"region": "us-east-3"
},
{
- "hostname": "node-2-proxy-host",
+ "hostname": "node-1-tenant-host",
+ "environment": "prod",
+ "region": "us-east-3"
+ },
+ {
+ "hostname": "node-3-tenant-host",
+ "environment": "prod",
+ "region": "us-east-3"
+ },
+ {
+ "hostname": "node-2-tenant-host",
"environment": "prod",
"region": "us-west-1"
},
{
- "hostname": "node-1-proxy-host",
+ "hostname": "node-1-tenant-host",
"environment": "prod",
"region": "us-west-1"
},
{
- "hostname": "node-3-proxy-host",
+ "hostname": "node-3-tenant-host",
"environment": "prod",
"region": "us-west-1"
}
@@ -73,6 +103,11 @@
"cloud": "cloud2",
"nodes": [
{
+ "hostname": "node-1-configserver-host",
+ "environment": "prod",
+ "region": "eu-west-1"
+ },
+ {
"hostname": "node-3-configserver-host",
"environment": "prod",
"region": "eu-west-1"
@@ -83,12 +118,12 @@
"region": "eu-west-1"
},
{
- "hostname": "node-1-configserver-host",
+ "hostname": "node-2-proxy-host",
"environment": "prod",
"region": "eu-west-1"
},
{
- "hostname": "node-2-proxy-host",
+ "hostname": "node-3-proxy-host",
"environment": "prod",
"region": "eu-west-1"
},
@@ -98,7 +133,17 @@
"region": "eu-west-1"
},
{
- "hostname": "node-3-proxy-host",
+ "hostname": "node-2-tenant-host",
+ "environment": "prod",
+ "region": "eu-west-1"
+ },
+ {
+ "hostname": "node-1-tenant-host",
+ "environment": "prod",
+ "region": "eu-west-1"
+ },
+ {
+ "hostname": "node-3-tenant-host",
"environment": "prod",
"region": "eu-west-1"
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/responses/versions-initial.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/responses/versions-initial.json
index 9c1625fdcd5..86bc272fcd1 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/responses/versions-initial.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/responses/versions-initial.json
@@ -6,6 +6,11 @@
"cloud": "cloud1",
"nodes": [
{
+ "hostname": "node-1-configserver-host",
+ "environment": "prod",
+ "region": "us-east-3"
+ },
+ {
"hostname": "node-3-configserver-host",
"environment": "prod",
"region": "us-east-3"
@@ -18,7 +23,7 @@
{
"hostname": "node-1-configserver-host",
"environment": "prod",
- "region": "us-east-3"
+ "region": "us-west-1"
},
{
"hostname": "node-3-configserver-host",
@@ -31,12 +36,12 @@
"region": "us-west-1"
},
{
- "hostname": "node-1-configserver-host",
+ "hostname": "node-2-proxy-host",
"environment": "prod",
- "region": "us-west-1"
+ "region": "us-east-3"
},
{
- "hostname": "node-2-proxy-host",
+ "hostname": "node-3-proxy-host",
"environment": "prod",
"region": "us-east-3"
},
@@ -46,22 +51,47 @@
"region": "us-east-3"
},
{
+ "hostname": "node-2-proxy-host",
+ "environment": "prod",
+ "region": "us-west-1"
+ },
+ {
"hostname": "node-3-proxy-host",
"environment": "prod",
+ "region": "us-west-1"
+ },
+ {
+ "hostname": "node-1-proxy-host",
+ "environment": "prod",
+ "region": "us-west-1"
+ },
+ {
+ "hostname": "node-2-tenant-host",
+ "environment": "prod",
"region": "us-east-3"
},
{
- "hostname": "node-2-proxy-host",
+ "hostname": "node-1-tenant-host",
+ "environment": "prod",
+ "region": "us-east-3"
+ },
+ {
+ "hostname": "node-3-tenant-host",
+ "environment": "prod",
+ "region": "us-east-3"
+ },
+ {
+ "hostname": "node-2-tenant-host",
"environment": "prod",
"region": "us-west-1"
},
{
- "hostname": "node-1-proxy-host",
+ "hostname": "node-1-tenant-host",
"environment": "prod",
"region": "us-west-1"
},
{
- "hostname": "node-3-proxy-host",
+ "hostname": "node-3-tenant-host",
"environment": "prod",
"region": "us-west-1"
}
@@ -73,6 +103,11 @@
"cloud": "cloud2",
"nodes": [
{
+ "hostname": "node-1-configserver-host",
+ "environment": "prod",
+ "region": "eu-west-1"
+ },
+ {
"hostname": "node-3-configserver-host",
"environment": "prod",
"region": "eu-west-1"
@@ -83,12 +118,12 @@
"region": "eu-west-1"
},
{
- "hostname": "node-1-configserver-host",
+ "hostname": "node-2-proxy-host",
"environment": "prod",
"region": "eu-west-1"
},
{
- "hostname": "node-2-proxy-host",
+ "hostname": "node-3-proxy-host",
"environment": "prod",
"region": "eu-west-1"
},
@@ -98,7 +133,17 @@
"region": "eu-west-1"
},
{
- "hostname": "node-3-proxy-host",
+ "hostname": "node-2-tenant-host",
+ "environment": "prod",
+ "region": "eu-west-1"
+ },
+ {
+ "hostname": "node-1-tenant-host",
+ "environment": "prod",
+ "region": "eu-west-1"
+ },
+ {
+ "hostname": "node-3-tenant-host",
"environment": "prod",
"region": "eu-west-1"
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/responses/versions-partially-upgraded.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/responses/versions-partially-upgraded.json
index 48a96b70df7..e8007fbf6c5 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/responses/versions-partially-upgraded.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/responses/versions-partially-upgraded.json
@@ -6,6 +6,11 @@
"cloud": "cloud1",
"nodes": [
{
+ "hostname": "node-1-configserver-host",
+ "environment": "prod",
+ "region": "us-west-1"
+ },
+ {
"hostname": "node-3-configserver-host",
"environment": "prod",
"region": "us-west-1"
@@ -16,12 +21,12 @@
"region": "us-west-1"
},
{
- "hostname": "node-1-configserver-host",
+ "hostname": "node-2-proxy-host",
"environment": "prod",
"region": "us-west-1"
},
{
- "hostname": "node-2-proxy-host",
+ "hostname": "node-3-proxy-host",
"environment": "prod",
"region": "us-west-1"
},
@@ -31,7 +36,17 @@
"region": "us-west-1"
},
{
- "hostname": "node-3-proxy-host",
+ "hostname": "node-2-tenant-host",
+ "environment": "prod",
+ "region": "us-west-1"
+ },
+ {
+ "hostname": "node-1-tenant-host",
+ "environment": "prod",
+ "region": "us-west-1"
+ },
+ {
+ "hostname": "node-3-tenant-host",
"environment": "prod",
"region": "us-west-1"
}
@@ -43,6 +58,11 @@
"cloud": "cloud1",
"nodes": [
{
+ "hostname": "node-1-configserver-host",
+ "environment": "prod",
+ "region": "us-east-3"
+ },
+ {
"hostname": "node-3-configserver-host",
"environment": "prod",
"region": "us-east-3"
@@ -53,12 +73,12 @@
"region": "us-east-3"
},
{
- "hostname": "node-1-configserver-host",
+ "hostname": "node-2-proxy-host",
"environment": "prod",
"region": "us-east-3"
},
{
- "hostname": "node-2-proxy-host",
+ "hostname": "node-3-proxy-host",
"environment": "prod",
"region": "us-east-3"
},
@@ -68,7 +88,17 @@
"region": "us-east-3"
},
{
- "hostname": "node-3-proxy-host",
+ "hostname": "node-2-tenant-host",
+ "environment": "prod",
+ "region": "us-east-3"
+ },
+ {
+ "hostname": "node-1-tenant-host",
+ "environment": "prod",
+ "region": "us-east-3"
+ },
+ {
+ "hostname": "node-3-tenant-host",
"environment": "prod",
"region": "us-east-3"
}
@@ -80,6 +110,11 @@
"cloud": "cloud2",
"nodes": [
{
+ "hostname": "node-1-configserver-host",
+ "environment": "prod",
+ "region": "eu-west-1"
+ },
+ {
"hostname": "node-3-configserver-host",
"environment": "prod",
"region": "eu-west-1"
@@ -90,12 +125,12 @@
"region": "eu-west-1"
},
{
- "hostname": "node-1-configserver-host",
+ "hostname": "node-2-proxy-host",
"environment": "prod",
"region": "eu-west-1"
},
{
- "hostname": "node-2-proxy-host",
+ "hostname": "node-3-proxy-host",
"environment": "prod",
"region": "eu-west-1"
},
@@ -105,7 +140,17 @@
"region": "eu-west-1"
},
{
- "hostname": "node-3-proxy-host",
+ "hostname": "node-2-tenant-host",
+ "environment": "prod",
+ "region": "eu-west-1"
+ },
+ {
+ "hostname": "node-1-tenant-host",
+ "environment": "prod",
+ "region": "eu-west-1"
+ },
+ {
+ "hostname": "node-3-tenant-host",
"environment": "prod",
"region": "eu-west-1"
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/statuspage/StatusPageProxyApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/statuspage/StatusPageProxyApiTest.java
index 2d4c43f1d44..f39914fd8fb 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/statuspage/StatusPageProxyApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/statuspage/StatusPageProxyApiTest.java
@@ -76,7 +76,7 @@ public class StatusPageProxyApiTest {
private String servicesXml() {
String statusPageApiUrl = "http://127.0.0.1:" + wireMock.port();
- return "<jdisc version='1.0'>\n" +
+ return "<container version='1.0'>\n" +
" <config name='vespa.hosted.controller.statuspage.config.statuspage'>\n" +
" <apiUrl>" + statusPageApiUrl + "</apiUrl>\n" +
" </config>\n" +
@@ -87,7 +87,7 @@ public class StatusPageProxyApiTest {
" <http>\n" +
" <server id='default' port='8080'/>\n" +
" </http>\n" +
- "</jdisc>";
+ "</container>";
}
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java
index 59f63f0472a..d0e9ae77965 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java
@@ -137,7 +137,7 @@ public class UserApiTest extends ControllerContainerCloudTest {
// DELETE an application is available to application admins.
tester.assertResponse(request("/application/v4/tenant/my-tenant/application/my-app", DELETE)
.roles(Set.of(Role.applicationAdmin(id.tenant(), id.application()))),
- "");
+ "{\"message\":\"Deleted application my-tenant.my-app\"}");
// DELETE a tenant role is available to tenant admins.
tester.assertResponse(request("/user/v1/tenant/my-tenant", DELETE)
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/application-created.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/application-created.json
index 2a779f0ee55..6cf4dc76173 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/application-created.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/application-created.json
@@ -2,5 +2,5 @@
"tenant": "my-tenant",
"application": "my-app",
"instance": "default",
- "url": "http://localhost:8080/application/v4/tenant/my-tenant/application/my-app"
+ "url": "http://localhost:8080/application/v4/tenant/my-tenant/application/my-app/instance/default"
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiTest.java
index 9c853f211ed..89d90fe6221 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiTest.java
@@ -3,28 +3,32 @@ package com.yahoo.vespa.hosted.controller.restapi.zone.v1;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.RegionName;
-import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.config.provision.zone.ZoneApi;
+import com.yahoo.vespa.hosted.controller.api.role.Role;
+import com.yahoo.vespa.hosted.controller.integration.ZoneApiMock;
import com.yahoo.vespa.hosted.controller.integration.ZoneRegistryMock;
import com.yahoo.vespa.hosted.controller.restapi.ContainerControllerTester;
-import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerTest;
+import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerCloudTest;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.util.List;
+import java.util.Set;
/**
* @author mpolden
*/
-public class ZoneApiTest extends ControllerContainerTest {
+public class ZoneApiTest extends ControllerContainerCloudTest {
private static final String responseFiles = "src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/responses/";
- private static final List<ZoneId> zones = List.of(
- ZoneId.from(Environment.prod, RegionName.from("us-north-1")),
- ZoneId.from(Environment.dev, RegionName.from("us-north-2")),
- ZoneId.from(Environment.test, RegionName.from("us-north-3")),
- ZoneId.from(Environment.staging, RegionName.from("us-north-4"))
- );
+ private static final List<ZoneApi> zones = List.of(
+ ZoneApiMock.fromId("prod.us-north-1"),
+ ZoneApiMock.fromId("dev.us-north-2"),
+ ZoneApiMock.fromId("test.us-north-3"),
+ ZoneApiMock.fromId("staging.us-north-4"));
+
+ private static final Set<Role> everyone = Set.of(Role.everyone());
private ContainerControllerTester tester;
@@ -40,22 +44,26 @@ public class ZoneApiTest extends ControllerContainerTest {
@Test
public void test_requests() {
// GET /zone/v1
- tester.containerTester().assertResponse(authenticatedRequest("http://localhost:8080/zone/v1"),
+ tester.containerTester().assertResponse(request("/zone/v1")
+ .roles(everyone),
new File("root.json"));
// GET /zone/v1/environment/prod
- tester.containerTester().assertResponse(authenticatedRequest("http://localhost:8080/zone/v1/environment/prod"),
+ tester.containerTester().assertResponse(request("/zone/v1/environment/prod")
+ .roles(everyone),
new File("prod.json"));
// GET /zone/v1/environment/dev/default
- tester.containerTester().assertResponse(authenticatedRequest("http://localhost:8080/zone/v1/environment/dev/default"),
+ tester.containerTester().assertResponse(request("/api/zone/v1/environment/dev/default")
+ .roles(everyone),
new File("default-for-region.json"));
}
@Test
public void test_invalid_requests() {
// GET /zone/v1/environment/prod/default: No default region
- tester.containerTester().assertResponse(authenticatedRequest("http://localhost:8080/zone/v1/environment/prod/default"),
+ tester.containerTester().assertResponse(request("/zone/v1/environment/prod/default")
+ .roles(everyone),
new File("no-default-region.json"),
400);
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiTest.java
index eff212b6c16..19061b61431 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiTest.java
@@ -5,11 +5,12 @@ import com.yahoo.application.container.handler.Request;
import com.yahoo.application.container.handler.Request.Method;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.text.Utf8;
import com.yahoo.vespa.athenz.api.AthenzIdentity;
import com.yahoo.vespa.athenz.api.AthenzUser;
-import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.integration.ConfigServerProxyMock;
+import com.yahoo.vespa.hosted.controller.integration.ZoneApiMock;
import com.yahoo.vespa.hosted.controller.integration.ZoneRegistryMock;
import com.yahoo.vespa.hosted.controller.restapi.ContainerControllerTester;
import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerTest;
@@ -29,12 +30,11 @@ public class ZoneApiTest extends ControllerContainerTest {
private static final AthenzIdentity HOSTED_VESPA_OPERATOR = AthenzUser.fromUserId("johnoperator");
private static final String responseFiles = "src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/responses/";
- private static final List<ZoneId> zones = List.of(
- ZoneId.from(Environment.prod, RegionName.from("us-north-1")),
- ZoneId.from(Environment.dev, RegionName.from("us-north-2")),
- ZoneId.from(Environment.test, RegionName.from("us-north-3")),
- ZoneId.from(Environment.staging, RegionName.from("us-north-4"))
- );
+ private static final List<ZoneApi> zones = List.of(
+ ZoneApiMock.fromId("prod.us-north-1"),
+ ZoneApiMock.fromId("dev.us-north-2"),
+ ZoneApiMock.fromId("test.us-north-3"),
+ ZoneApiMock.fromId("staging.us-north-4"));
private ContainerControllerTester tester;
private ConfigServerProxyMock proxy;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepositoryTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepositoryTest.java
index 8d1f40260e3..8f02fa74c6e 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepositoryTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepositoryTest.java
@@ -14,10 +14,10 @@ import org.junit.Test;
import org.junit.rules.ExpectedException;
import java.net.URI;
-import java.util.Optional;
+import java.util.List;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
/**
* @author Oyvind Gronnesby
@@ -64,7 +64,7 @@ public class RotationRepositoryTest {
Rotation expected = new Rotation(new RotationId("foo-1"), "foo-1.com");
application = tester.applications().require(application.id());
- assertEquals(expected.id(), application.rotation().get());
+ assertEquals(List.of(expected.id()), application.rotations());
assertEquals(URI.create("https://app1--tenant1.global.vespa.oath.cloud:4443/"),
application.endpointsIn(SystemName.main).main().get().url());
try (RotationLock lock = repository.lock()) {
@@ -80,7 +80,7 @@ public class RotationRepositoryTest {
.searchDefinition("search foo { }") // Update application package so there is something to deploy
.build();
tester.deployCompletely(application, applicationPackage, 43);
- assertEquals(expected.id(), tester.applications().require(application.id()).rotation().get());
+ assertEquals(List.of(expected.id()), tester.applications().require(application.id()).rotations());
}
@Test
@@ -139,8 +139,7 @@ public class RotationRepositoryTest {
.build();
tester.deployCompletely(application, applicationPackage);
Application app = tester.applications().require(application.id());
- Optional<RotationId> rotation = app.rotation();
- assertFalse(rotation.isPresent());
+ assertTrue(app.rotations().isEmpty());
}
@Test
@@ -153,7 +152,7 @@ public class RotationRepositoryTest {
Application application = tester.createApplication("app2", "tenant2", 22L,
2L);
tester.deployCompletely(application, applicationPackage);
- assertEquals(new RotationId("foo-1"), tester.applications().require(application.id()).rotation().get());
+ assertEquals(List.of(new RotationId("foo-1")), tester.applications().require(application.id()).rotations());
assertEquals("https://cd--app2--tenant2.global.vespa.oath.cloud:4443/", tester.applications().require(application.id())
.endpointsIn(SystemName.cd).main().get().url().toString());
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/tls/SecureContainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/tls/SecureContainerTest.java
index c2c8f592738..c1188778292 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/tls/SecureContainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/tls/SecureContainerTest.java
@@ -59,7 +59,7 @@ public class SecureContainerTest {
}
private String servicesXml(Path trustStore) {
- return "<jdisc version='1.0'>\n" +
+ return "<container version='1.0'>\n" +
" <config name=\"container.handler.threadpool\">\n" +
" <maxthreads>10</maxthreads>\n" +
" </config> \n" +
@@ -74,7 +74,7 @@ public class SecureContainerTest {
" <ssl-provider class='com.yahoo.vespa.hosted.controller.tls.ControllerSslContextFactoryProvider' bundle='controller-server'/>\n" +
" </server>\n" +
" </http>\n" +
- "</jdisc>";
+ "</container>";
}
private Path writeKeyStore() {
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/MavenRepositoryClientTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/MavenRepositoryClientTest.java
new file mode 100644
index 00000000000..026d174cb73
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/MavenRepositoryClientTest.java
@@ -0,0 +1,22 @@
+package com.yahoo.vespa.hosted.controller.versions;
+
+import com.yahoo.vespa.hosted.controller.api.integration.maven.ArtifactId;
+import org.junit.Test;
+
+import java.net.URI;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author jonmv
+ */
+public class MavenRepositoryClientTest {
+
+ @Test
+ public void testUri() {
+ assertEquals(URI.create("https://domain:123/base/group/id/artifact-id/maven-metadata.xml"),
+ MavenRepositoryClient.withArtifactPath(URI.create("https://domain:123/base/"),
+ new ArtifactId("group.id", "artifact-id")));
+ }
+
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/VersionStatusTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/VersionStatusTest.java
index a365285b752..655c16ccceb 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/VersionStatusTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/VersionStatusTest.java
@@ -6,6 +6,7 @@ import com.yahoo.component.Version;
import com.yahoo.component.Vtag;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.HostName;
+import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.ControllerTester;
@@ -60,10 +61,10 @@ public class VersionStatusTest {
Version version0 = Version.fromString("6.1");
Version version1 = Version.fromString("6.5");
// Upgrade some config servers
- for (ZoneId zone : tester.zoneRegistry().zones().all().ids()) {
- for (Node node : tester.configServer().nodeRepository().list(zone, SystemApplication.configServer.id())) {
- tester.configServer().nodeRepository().putByHostname(zone, new Node(node.hostname(), node.state(), node.type(),
- node.owner(), version1, node.wantedVersion()));
+ for (ZoneApi zone : tester.zoneRegistry().zones().all().zones()) {
+ for (Node node : tester.configServer().nodeRepository().list(zone.getId(), SystemApplication.configServer.id())) {
+ Node upgradedNode = new Node(node.hostname(), node.state(), node.type(), node.owner(), version1, node.wantedVersion());
+ tester.configServer().nodeRepository().putByHostname(zone.getId(), upgradedNode);
break;
}
}
@@ -105,10 +106,10 @@ public class VersionStatusTest {
// Downgrade one config server in each zone
Version ancientVersion = Version.fromString("5.1");
- for (ZoneId zone : tester.controller().zoneRegistry().zones().all().ids()) {
- for (Node node : tester.configServer().nodeRepository().list(zone, SystemApplication.configServer.id())) {
- tester.configServer().nodeRepository().putByHostname(zone, new Node(node.hostname(), node.state(), node.type(),
- node.owner(), ancientVersion, node.wantedVersion()));
+ for (ZoneApi zone : tester.controller().zoneRegistry().zones().all().zones()) {
+ for (Node node : tester.configServer().nodeRepository().list(zone.getId(), SystemApplication.configServer.id())) {
+ Node downgradedNode = new Node(node.hostname(), node.state(), node.type(), node.owner(), ancientVersion, node.wantedVersion());
+ tester.configServer().nodeRepository().putByHostname(zone.getId(), downgradedNode);
break;
}
}
@@ -254,7 +255,7 @@ public class VersionStatusTest {
assertTrue("Status for version without applications is removed",
tester.controller().versionStatus().versions().stream()
.noneMatch(vespaVersion -> vespaVersion.versionNumber().equals(version1)));
-
+
// Another default application upgrades, raising confidence to high
tester.completeUpgrade(default8, version2, "default");
tester.completeUpgrade(default9, version2, "default");
@@ -294,6 +295,11 @@ public class VersionStatusTest {
assertEquals("6.2", versions.get(0).versionNumber().toString());
assertEquals("6.4", versions.get(1).versionNumber().toString());
assertEquals("6.5", versions.get(2).versionNumber().toString());
+
+ // Check release status is correct (static data in MockMavenRepository).
+ assertTrue(versions.get(0).isReleased());
+ assertFalse(versions.get(1).isReleased());
+ assertFalse(versions.get(2).isReleased());
}
@Test
diff --git a/controller-server/src/test/resources/chef_output.json b/controller-server/src/test/resources/chef_output.json
deleted file mode 100644
index 257065f7b5b..00000000000
--- a/controller-server/src/test/resources/chef_output.json
+++ /dev/null
@@ -1,34 +0,0 @@
-{
- "total": 1,
- "start": 0,
- "rows": [
- {
- "url": "https://chef-server.test/organizations/vespa/nodes/fake-node.test",
- "data": {
- "fqdn": "fake-node.test",
- "ohai_time": 1475497186.68962,
- "tenant": "ciintegrationtests",
- "application": "restart",
- "instance": "default",
- "zone": "cd_cd-us-east-1_prod",
- "system": "cd",
- "environment": "prod",
- "region": "cd-us-east-1"
- }
- },
- {
- "url": "https://chef-server.test/organizations/vespa/nodes/fake-node2.test",
- "data": {
- "fqdn": "fake-node2.test",
- "ohai_time": 1475497186.68962,
- "tenant": null,
- "application": null,
- "instance": null,
- "zone": null,
- "system": null,
- "environment": null,
- "region": null
- }
- }
- ]
-}
diff --git a/controller-server/src/test/resources/job-grandparent.json b/controller-server/src/test/resources/job-grandparent.json
deleted file mode 100644
index 63602bed146..00000000000
--- a/controller-server/src/test/resources/job-grandparent.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "duration": 720000,
- "causes": []
-}
diff --git a/controller-server/src/test/resources/job-parent.json b/controller-server/src/test/resources/job-parent.json
deleted file mode 100644
index 88d50de394f..00000000000
--- a/controller-server/src/test/resources/job-parent.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "duration": 1200000,
- "causes": [
- {
- "upstreamBuild": 231,
- "upstreamProject": "3-v3-job-grandparent"
- }
- ]
-}
diff --git a/controller-server/src/test/resources/testConfig.json b/controller-server/src/test/resources/testConfig.json
new file mode 100644
index 00000000000..4145d863995
--- /dev/null
+++ b/controller-server/src/test/resources/testConfig.json
@@ -0,0 +1,20 @@
+{
+ "application": "tenant:application:default",
+ "zone": "test.aws-us-east-1c",
+ "system": "publiccd",
+ "endpoints": {
+ "test.aws-us-east-1c": [
+ "https://server/"
+ ]
+ },
+ "zoneEndpoints": {
+ "test.aws-us-east-1c": {
+ "ai": "https://server/"
+ }
+ },
+ "clusters": {
+ "test.aws-us-east-1c": [
+ "facts"
+ ]
+ }
+}
diff --git a/controller-server/src/test/resources/test_runner_services.xml-cd b/controller-server/src/test/resources/test_runner_services.xml-cd
index 15d3c98b995..bd49f3be55a 100644
--- a/controller-server/src/test/resources/test_runner_services.xml-cd
+++ b/controller-server/src/test/resources/test_runner_services.xml-cd
@@ -1,11 +1,11 @@
<?xml version='1.0' encoding='UTF-8'?>
<services xmlns:deploy='vespa' version='1.0'>
- <container version='1.0' id='default'>
+ <container version='1.0' id='tester'>
<component id="com.yahoo.vespa.hosted.testrunner.TestRunner" bundle="vespa-testrunner-components">
<config name="com.yahoo.vespa.hosted.testrunner.test-runner">
<artifactsPath>artifacts</artifactsPath>
- <surefireMemoryMb>7680</surefireMemoryMb>
+ <surefireMemoryMb>5120</surefireMemoryMb>
</config>
</component>
@@ -38,6 +38,8 @@
</filtering>
</http>
- <nodes count="1" flavor="d-2-12-75" allocated-memory="17%" />
+ <nodes count="1" flavor="d-2-12-75">
+ <jvm allocated-memory="17%" />
+ </nodes>
</container>
</services>