summaryrefslogtreecommitdiffstats
path: root/controller-server
diff options
context:
space:
mode:
Diffstat (limited to 'controller-server')
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java10
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java119
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java6
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java46
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationList.java145
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationPackageValidator.java20
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentJobs.java14
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java1
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointList.java25
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/JobStatus.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzFacade.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentStatus.java49
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentSteps.java51
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java313
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java22
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java104
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobList.java141
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobStatus.java107
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Run.java1
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RunStatus.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Versions.java5
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunner.java16
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployer.java5
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPolicies.java27
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java28
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java35
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java12
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializer.java5
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImpl.java91
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequest.java37
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java175
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java77
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandler.java129
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/configserver/package-info.java5
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilter.java6
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilter.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java55
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiHandler.java5
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java19
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VespaVersion.java8
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java20
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java42
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/EndpointTest.java16
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/BuildJob.java128
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java25
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTester.java127
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java36
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunnerTest.java166
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ApplicationStoreMock.java6
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java86
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java28
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmerTest.java46
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirerTest.java23
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporterTest.java63
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunnerTest.java71
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporterTest.java10
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployerTest.java41
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPoliciesTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java18
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/metric/ConfigServerMetricsTest.java3
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java17
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequestTest.java6
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponseTest.java6
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerControllerTester.java166
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java36
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java23
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java772
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelperTest.java79
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application2-with-patches.json48
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application2.json48
-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-with-routing-policy.json18
-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-us-east-1.json12
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/global-rotation-get.json2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/instance-with-routing-policy.json55
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/instance-without-change-multiple-deployments.json33
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/instance.json54
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/instance1-recursive.json50
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/jobs-direct-deployment.json76
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/jobs.json237
-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/system-test-details.json405
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/system-test-job.json49
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/athenz/AthenzApiTest.java3
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandlerTest.java143
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/configserver/responses/root.json29
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiTest.java48
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/cost/CostApiTest.java17
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiTest.java26
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiTest.java45
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/responses/root.json43
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/flags/AuditedFlagsApiTest.java6
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiTest.java13
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiTest.java21
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiTest.java70
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/responses/root.json4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepositoryTest.java38
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/VersionStatusTest.java2
101 files changed, 2990 insertions, 2611 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 0a8b5ca8c3d..681c1b4283a 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
@@ -45,7 +45,6 @@ public class Application {
private final ValidationOverrides validationOverrides;
private final Optional<ApplicationVersion> latestVersion;
private final OptionalLong projectId;
- private final boolean internal;
private final Change change;
private final Change outstandingChange;
private final Optional<IssueId> deploymentIssueId;
@@ -60,14 +59,14 @@ public class Application {
public Application(TenantAndApplicationId id, Instant now) {
this(id, now, DeploymentSpec.empty, ValidationOverrides.empty, Change.empty(), Change.empty(),
Optional.empty(), Optional.empty(), Optional.empty(), OptionalInt.empty(),
- new ApplicationMetrics(0, 0), Set.of(), OptionalLong.empty(), false, Optional.empty(), List.of());
+ new ApplicationMetrics(0, 0), Set.of(), OptionalLong.empty(), Optional.empty(), List.of());
}
// DO NOT USE! For serialization purposes, only.
public Application(TenantAndApplicationId id, Instant createdAt, DeploymentSpec deploymentSpec, ValidationOverrides validationOverrides,
Change change, Change outstandingChange, Optional<IssueId> deploymentIssueId, Optional<IssueId> ownershipIssueId, Optional<User> owner,
OptionalInt majorVersion, ApplicationMetrics metrics, Set<PublicKey> deployKeys, OptionalLong projectId,
- boolean internal, Optional<ApplicationVersion> latestVersion, Collection<Instance> instances) {
+ Optional<ApplicationVersion> latestVersion, Collection<Instance> instances) {
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");
@@ -81,7 +80,6 @@ public class Application {
this.metrics = Objects.requireNonNull(metrics, "metrics cannot be null");
this.deployKeys = Objects.requireNonNull(deployKeys, "deployKeys cannot be null");
this.projectId = Objects.requireNonNull(projectId, "projectId cannot be null");
- this.internal = internal;
this.latestVersion = requireNotUnknown(latestVersion);
this.instances = ImmutableSortedMap.copyOf(instances.stream().collect(Collectors.toMap(Instance::name, Function.identity())));
}
@@ -102,10 +100,6 @@ public class Application {
/** Returns the last submitted version of this application. */
public Optional<ApplicationVersion> latestVersion() { return latestVersion; }
- /** Returns whether this application is run on the internal deployment pipeline. */
- // TODO jonmv: Remove, as will be always true.
- public boolean internal() { return internal; }
-
/**
* Returns the last deployed validation overrides of this application,
* or the empty validation overrides if it has never been deployed
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 7c718518129..d28df826957 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
@@ -147,7 +147,7 @@ public class ApplicationController {
routingPolicies = new RoutingPolicies(controller);
rotationRepository = new RotationRepository(rotationsConfig, this, curator);
- deploymentTrigger = new DeploymentTrigger(controller, controller.serviceRegistry().buildService(), clock);
+ deploymentTrigger = new DeploymentTrigger(controller, clock);
provisionApplicationCertificate = Flags.PROVISION_APPLICATION_CERTIFICATE.bindTo(controller.flagSource());
applicationPackageValidator = new ApplicationPackageValidator(controller);
@@ -207,6 +207,11 @@ public class ApplicationController {
return curator.readApplications();
}
+ /** Returns the ID of all known applications. */
+ public List<TenantAndApplicationId> idList() {
+ return curator.readApplicationIds();
+ }
+
/** Returns a snapshot of all applications of a tenant */
public List<Application> asList(TenantName tenant) {
return curator.readApplications(tenant);
@@ -385,7 +390,7 @@ public class ApplicationController {
applicationVersion = preferOldestVersion ? triggered.sourceApplication().orElse(triggered.application())
: triggered.application();
- applicationPackage = getApplicationPackage(instanceId, application.get().internal(), applicationVersion);
+ applicationPackage = getApplicationPackage(instanceId, applicationVersion);
applicationPackage = withTesterCertificate(applicationPackage, instanceId, jobType);
validateRun(application.get(), instance, zone, platformVersion, applicationVersion);
}
@@ -397,13 +402,6 @@ public class ApplicationController {
applicationCertificate = Optional.empty();
}
- // TODO jonmv: REMOVE! This is now irrelevant for non-CD-test deployments and non-unit tests.
- if ( ! preferOldestVersion
- && ! application.get().internal()
- && ! zone.environment().isManuallyDeployed()) {
- application = storeWithUpdatedConfig(application, applicationPackage);
- }
-
endpoints = registerEndpointsInDns(applicationPackage.deploymentSpec(), application.get().require(instanceId.instance()), zone);
} // Release application lock while doing the deployment, which is a lengthy task.
@@ -434,25 +432,8 @@ public class ApplicationController {
}
/** Fetches the requested application package from the artifact store(s). */
- public ApplicationPackage getApplicationPackage(ApplicationId id, boolean internal, ApplicationVersion version) {
- try {
- return internal
- ? new ApplicationPackage(applicationStore.get(id.tenant(), id.application(), version))
- : new ApplicationPackage(artifactRepository.getApplicationPackage(id, version.id()));
- }
- catch (RuntimeException e) { // If application has switched deployment pipeline, artifacts stored prior to the switch are in the other artifact store.
- try {
- log.info("Fetching application package for " + id + " from alternate repository; it is now deployed "
- + (internal ? "internally" : "externally") + "\nException was: " + Exceptions.toMessageString(e));
- return internal
- ? new ApplicationPackage(artifactRepository.getApplicationPackage(id, version.id()))
- : new ApplicationPackage(applicationStore.get(id.tenant(), id.application(), version));
- }
- catch (RuntimeException s) { // If this fails, too, the first failure is most likely the relevant one.
- e.addSuppressed(s);
- throw e;
- }
- }
+ public ApplicationPackage getApplicationPackage(ApplicationId id, ApplicationVersion version) {
+ return new ApplicationPackage(applicationStore.get(id.tenant(), id.application(), version));
}
/** Stores the deployment spec and validation overrides from the application package, and runs cleanup. */
@@ -800,6 +781,7 @@ public class ApplicationController {
});
});
curator.writeApplication(application.without(instanceId.instance()).get());
+ controller.jobController().collectGarbage();
log.info("Deleted " + instanceId);
});
@@ -945,50 +927,61 @@ public class ApplicationController {
public void verifyApplicationIdentityConfiguration(TenantName tenantName, ApplicationPackage applicationPackage, Optional<Principal> deployer) {
verifyAllowedLaunchAthenzService(applicationPackage.deploymentSpec());
- applicationPackage.deploymentSpec().athenzDomain().ifPresent(identityDomain -> {
- Tenant tenant = controller.tenants().require(tenantName);
- deployer.filter(AthenzPrincipal.class::isInstance)
- .map(AthenzPrincipal.class::cast)
- .map(AthenzPrincipal::getIdentity)
- .filter(AthenzUser.class::isInstance)
- .ifPresentOrElse(user -> {
- if ( ! ((AthenzFacade) accessControl).hasTenantAdminAccess(user, new AthenzDomain(identityDomain.value())))
- throw new IllegalArgumentException("User " + user.getFullName() + " is not allowed to launch " +
- "services in Athenz domain " + identityDomain.value() + ". " +
- "Please reach out to the domain admin.");
- },
- () -> {
- if (tenant.type() != Tenant.Type.athenz)
- throw new IllegalArgumentException("Athenz domain defined in deployment.xml, but no " +
- "Athenz domain for tenant " + tenantName.value());
-
- AthenzDomain tenantDomain = ((AthenzTenant) tenant).domain();
- if ( ! Objects.equals(tenantDomain.getName(), identityDomain.value()))
- throw new IllegalArgumentException("Athenz domain in deployment.xml: [" + identityDomain.value() + "] " +
- "must match tenant domain: [" + tenantDomain.getName() + "]");
- });
- });
+ Tenant tenant = controller.tenants().require(tenantName);
+ Stream.concat(applicationPackage.deploymentSpec().athenzDomain().stream(),
+ applicationPackage.deploymentSpec().instances().stream()
+ .flatMap(spec -> spec.athenzDomain().stream()))
+ .distinct()
+ .forEach(identityDomain -> {
+ deployer.filter(AthenzPrincipal.class::isInstance)
+ .map(AthenzPrincipal.class::cast)
+ .map(AthenzPrincipal::getIdentity)
+ .filter(AthenzUser.class::isInstance)
+ .ifPresentOrElse(user -> {
+ if ( ! ((AthenzFacade) accessControl).hasTenantAdminAccess(user, new AthenzDomain(identityDomain.value())))
+ throw new IllegalArgumentException("User " + user.getFullName() + " is not allowed to launch " +
+ "services in Athenz domain " + identityDomain.value() + ". " +
+ "Please reach out to the domain admin.");
+ },
+ () -> {
+ if (tenant.type() != Tenant.Type.athenz)
+ throw new IllegalArgumentException("Athenz domain defined in deployment.xml, but no " +
+ "Athenz domain for tenant " + tenantName.value());
+
+ AthenzDomain tenantDomain = ((AthenzTenant) tenant).domain();
+ if ( ! Objects.equals(tenantDomain.getName(), identityDomain.value()))
+ throw new IllegalArgumentException("Athenz domain in deployment.xml: [" + identityDomain.value() + "] " +
+ "must match tenant domain: [" + tenantDomain.getName() + "]");
+ });
+ });
}
/*
* Verifies that the configured athenz service (if any) can be launched.
*/
private void verifyAllowedLaunchAthenzService(DeploymentSpec deploymentSpec) {
- deploymentSpec.athenzDomain().ifPresent(athenzDomain -> {
- controller.zoneRegistry().zones().reachable().ids()
- .forEach(zone -> {
- AthenzIdentity configServerAthenzIdentity = controller.zoneRegistry().getConfigServerHttpsIdentity(zone);
- deploymentSpec.athenzService(zone.environment(), zone.region())
- .map(service -> new AthenzService(athenzDomain.value(), service.value()))
- .ifPresent(service -> {
- boolean allowedToLaunch = ((AthenzFacade) accessControl).canLaunch(configServerAthenzIdentity, service);
- if (!allowedToLaunch)
- throw new IllegalArgumentException("Not allowed to launch Athenz service " + service.getFullName());
- });
- });
+ controller.zoneRegistry().zones().reachable().ids().forEach(zone -> {
+ AthenzIdentity configServerAthenzIdentity = controller.zoneRegistry().getConfigServerHttpsIdentity(zone);
+ deploymentSpec.athenzDomain().ifPresent(domain -> {
+ deploymentSpec.athenzService().ifPresent(service -> {
+ verifyAthenzServiceCanBeLaunchedBy(configServerAthenzIdentity, new AthenzService(domain.value(), service.value()));
+ });
+ });
+ deploymentSpec.instances().forEach(spec -> {
+ spec.athenzDomain().ifPresent(domain -> {
+ spec.athenzService(zone.environment(), zone.region()).ifPresent(service -> {
+ verifyAthenzServiceCanBeLaunchedBy(configServerAthenzIdentity, new AthenzService(domain.value(), service.value()));
+ });
+ });
+ });
});
}
+ private void verifyAthenzServiceCanBeLaunchedBy(AthenzIdentity configServerAthenzIdentity, AthenzService athenzService) {
+ if ( ! ((AthenzFacade) accessControl).canLaunch(configServerAthenzIdentity, athenzService))
+ throw new IllegalArgumentException("Not allowed to launch Athenz service " + athenzService.getFullName());
+ }
+
/** Returns the latest known version within the given major. */
private Optional<Version> lastCompatibleVersion(int targetMajorVersion) {
return controller.versionStatus().versions().stream()
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 dcadd992b32..4f6fe2ac2db 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
@@ -78,19 +78,17 @@ public class Controller extends AbstractComponent implements ApplicationIdSource
*/
@Inject
public Controller(CuratorDb curator, RotationsConfig rotationsConfig,
- ZoneRegistry zoneRegistry,
AccessControl accessControl,
FlagSource flagSource,
MavenRepository mavenRepository,
ServiceRegistry serviceRegistry) {
- this(curator, rotationsConfig, zoneRegistry,
+ this(curator, rotationsConfig,
accessControl,
com.yahoo.net.HostName::getLocalhost, flagSource,
mavenRepository, serviceRegistry);
}
public Controller(CuratorDb curator, RotationsConfig rotationsConfig,
- ZoneRegistry zoneRegistry,
AccessControl accessControl,
Supplier<String> hostnameSupplier,
FlagSource flagSource, MavenRepository mavenRepository,
@@ -98,8 +96,8 @@ public class Controller extends AbstractComponent implements ApplicationIdSource
this.hostnameSupplier = Objects.requireNonNull(hostnameSupplier, "HostnameSupplier cannot be null");
this.curator = Objects.requireNonNull(curator, "Curator cannot be null");
- this.zoneRegistry = Objects.requireNonNull(zoneRegistry, "ZoneRegistry cannot be null");
this.serviceRegistry = Objects.requireNonNull(serviceRegistry, "ServiceRegistry cannot be null");
+ this.zoneRegistry = Objects.requireNonNull(serviceRegistry.zoneRegistry(), "ZoneRegistry cannot be null");
this.clock = Objects.requireNonNull(serviceRegistry.clock(), "Clock cannot be null");
this.flagSource = Objects.requireNonNull(flagSource, "FlagSource cannot be null");
this.mavenRepository = Objects.requireNonNull(mavenRepository, "MavenRepository cannot be null");
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 46d1d436521..fa81a990c70 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
@@ -45,7 +45,6 @@ public class LockedApplication {
private final ApplicationMetrics metrics;
private final Set<PublicKey> deployKeys;
private final OptionalLong projectId;
- private final boolean internal;
private final Optional<ApplicationVersion> latestVersion;
private final Map<InstanceName, Instance> instances;
@@ -60,14 +59,14 @@ public class LockedApplication {
application.deploymentSpec(), application.validationOverrides(), application.change(),
application.outstandingChange(), application.deploymentIssueId(), application.ownershipIssueId(),
application.owner(), application.majorVersion(), application.metrics(), application.deployKeys(),
- application.projectId(), application.internal(), application.latestVersion(), application.instances());
+ application.projectId(), application.latestVersion(), application.instances());
}
private LockedApplication(Lock lock, TenantAndApplicationId id, Instant createdAt, DeploymentSpec deploymentSpec,
ValidationOverrides validationOverrides, Change change, Change outstandingChange,
Optional<IssueId> deploymentIssueId, Optional<IssueId> ownershipIssueId, Optional<User> owner,
OptionalInt majorVersion, ApplicationMetrics metrics, Set<PublicKey> deployKeys,
- OptionalLong projectId, boolean internal, Optional<ApplicationVersion> latestVersion,
+ OptionalLong projectId, Optional<ApplicationVersion> latestVersion,
Map<InstanceName, Instance> instances) {
this.lock = lock;
this.id = id;
@@ -83,7 +82,6 @@ public class LockedApplication {
this.metrics = metrics;
this.deployKeys = deployKeys;
this.projectId = projectId;
- this.internal = internal;
this.latestVersion = latestVersion;
this.instances = Map.copyOf(instances);
}
@@ -92,7 +90,7 @@ public class LockedApplication {
public Application get() {
return new Application(id, createdAt, deploymentSpec, validationOverrides, change, outstandingChange,
deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, deployKeys,
- projectId, internal, latestVersion, instances.values());
+ projectId, latestVersion, instances.values());
}
public LockedApplication withNewInstance(InstanceName instance) {
@@ -100,7 +98,7 @@ public class LockedApplication {
instances.put(instance, new Instance(id.instance(instance)));
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change, outstandingChange,
deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, deployKeys,
- projectId, internal, latestVersion, instances);
+ projectId, latestVersion, instances);
}
public LockedApplication with(InstanceName instance, UnaryOperator<Instance> modification) {
@@ -108,7 +106,7 @@ public class LockedApplication {
instances.put(instance, modification.apply(instances.get(instance)));
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change, outstandingChange,
deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, deployKeys,
- projectId, internal, latestVersion, instances);
+ projectId, latestVersion, instances);
}
public LockedApplication without(InstanceName instance) {
@@ -116,67 +114,61 @@ public class LockedApplication {
instances.remove(instance);
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change, outstandingChange,
deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, deployKeys,
- projectId, internal, latestVersion, instances);
+ projectId, latestVersion, instances);
}
public LockedApplication withNewSubmission(ApplicationVersion latestVersion) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change, outstandingChange,
deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, deployKeys,
- projectId, internal, Optional.of(latestVersion), instances);
- }
-
- public LockedApplication withBuiltInternally(boolean builtInternally) {
- return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change, outstandingChange,
- deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, deployKeys,
- projectId, builtInternally, latestVersion, instances);
+ projectId, Optional.of(latestVersion), instances);
}
public LockedApplication withProjectId(OptionalLong projectId) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change, outstandingChange,
deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, deployKeys,
- projectId, internal, latestVersion, instances);
+ projectId, latestVersion, instances);
}
public LockedApplication withDeploymentIssueId(IssueId issueId) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change, outstandingChange,
Optional.ofNullable(issueId), ownershipIssueId, owner, majorVersion, metrics, deployKeys,
- projectId, internal, latestVersion, instances);
+ projectId, latestVersion, instances);
}
public LockedApplication with(DeploymentSpec deploymentSpec) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change, outstandingChange,
deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, deployKeys,
- projectId, internal, latestVersion, instances);
+ projectId, latestVersion, instances);
}
public LockedApplication with(ValidationOverrides validationOverrides) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change, outstandingChange,
deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, deployKeys,
- projectId, internal, latestVersion, instances);
+ projectId, latestVersion, instances);
}
public LockedApplication withChange(Change change) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change, outstandingChange,
deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, deployKeys,
- projectId, internal, latestVersion, instances);
+ projectId, latestVersion, instances);
}
public LockedApplication withOutstandingChange(Change outstandingChange) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change, outstandingChange,
deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, deployKeys,
- projectId, internal, latestVersion, instances);
+ projectId, latestVersion, instances);
}
public LockedApplication withOwnershipIssueId(IssueId issueId) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change, outstandingChange,
deploymentIssueId, Optional.of(issueId), owner, majorVersion, metrics, deployKeys,
- projectId, internal, latestVersion, instances);
+ projectId, latestVersion, instances);
}
public LockedApplication withOwner(User owner) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change, outstandingChange,
deploymentIssueId, ownershipIssueId, Optional.of(owner), majorVersion, metrics, deployKeys,
- projectId, internal, latestVersion, instances);
+ projectId, latestVersion, instances);
}
/** Set a major version for this, or set to null to remove any major version override */
@@ -184,13 +176,13 @@ public class LockedApplication {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change, outstandingChange,
deploymentIssueId, ownershipIssueId, owner,
majorVersion == null ? OptionalInt.empty() : OptionalInt.of(majorVersion),
- metrics, deployKeys, projectId, internal, latestVersion, instances);
+ metrics, deployKeys, projectId, latestVersion, instances);
}
public LockedApplication with(ApplicationMetrics metrics) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change, outstandingChange,
deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, deployKeys,
- projectId, internal, latestVersion, instances);
+ projectId, latestVersion, instances);
}
public LockedApplication withDeployKey(PublicKey pemDeployKey) {
@@ -198,7 +190,7 @@ public class LockedApplication {
keys.add(pemDeployKey);
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change, outstandingChange,
deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, keys,
- projectId, internal, latestVersion, instances);
+ projectId, latestVersion, instances);
}
public LockedApplication withoutDeployKey(PublicKey pemDeployKey) {
@@ -206,7 +198,7 @@ public class LockedApplication {
keys.remove(pemDeployKey);
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change, outstandingChange,
deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, keys,
- projectId, internal, latestVersion, instances);
+ projectId, latestVersion, instances);
}
@Override
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationList.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationList.java
index a2487e8a0d1..3fe8e9f52c3 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationList.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationList.java
@@ -1,8 +1,9 @@
// 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.application;
-import com.google.common.collect.ImmutableList;
+import com.yahoo.collections.AbstractFilteringList;
import com.yahoo.component.Version;
+import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.application.api.DeploymentSpec.UpgradePolicy;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.vespa.hosted.controller.Application;
@@ -11,11 +12,9 @@ import com.yahoo.vespa.hosted.controller.Instance;
import java.time.Instant;
import java.util.Collection;
-import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
-import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
@@ -23,166 +22,137 @@ import java.util.stream.Collectors;
*
* @author jonmv
*/
-public class ApplicationList {
+public class ApplicationList extends AbstractFilteringList<Application, ApplicationList> {
- private final List<Application> list;
-
- private ApplicationList(List<Application> applications) {
- this.list = applications;
+ private ApplicationList(Collection<? extends Application> applications, boolean negate) {
+ super(applications, negate, ApplicationList::new);
}
// ----------------------------------- Factories
- public static ApplicationList from(Collection<Application> applications) {
- return new ApplicationList(List.copyOf(applications));
+ public static ApplicationList from(Collection<? extends Application> applications) {
+ return new ApplicationList(applications, false);
}
public static ApplicationList from(Collection<ApplicationId> ids, ApplicationController applications) {
- return new ApplicationList(ids.stream()
- .map(TenantAndApplicationId::from)
- .distinct()
- .map(applications::requireApplication)
- .collect(Collectors.toUnmodifiableList()));
+ return from(ids.stream()
+ .map(TenantAndApplicationId::from)
+ .distinct()
+ .map(applications::requireApplication)
+ .collect(Collectors.toUnmodifiableList()));
}
// ----------------------------------- Accessors
- /** Returns the applications in this as an immutable list */
- public List<Application> asList() { return list; }
-
/** Returns the ids of the applications in this as an immutable list */
- public List<TenantAndApplicationId> idList() { return list.stream().map(Application::id).collect(Collectors.toUnmodifiableList()); }
-
- public boolean isEmpty() { return list.isEmpty(); }
-
- public int size() { return list.size(); }
+ public List<TenantAndApplicationId> idList() {
+ return mapToList(Application::id);
+ }
// ----------------------------------- Filters
/** Returns the subset of applications which are upgrading (to any version), not considering block windows. */
public ApplicationList upgrading() {
- return filteredOn(application -> application.change().platform().isPresent());
+ return matching(application -> application.change().platform().isPresent());
}
/** Returns the subset of applications which are currently upgrading to the given version */
public ApplicationList upgradingTo(Version version) {
- return filteredOn(application -> isUpgradingTo(version, application));
+ return upgradingTo(List.of(version));
}
- /** Returns the subset of applications which are not pinned to a certain Vespa version. */
- public ApplicationList unpinned() {
- return filteredOn(application -> ! application.change().isPinned());
+ /** Returns the subset of applications which are currently upgrading to the given version */
+ public ApplicationList upgradingTo(Collection<Version> versions) {
+ return matching(application -> versions.stream().anyMatch(version -> isUpgradingTo(version, application)));
}
- /** Returns the subset of applications which are currently not upgrading to the given version */
- public ApplicationList notUpgradingTo(Version version) {
- return notUpgradingTo(Collections.singletonList(version));
+ /** Returns the subset of applications which are not pinned to a certain Vespa version. */
+ public ApplicationList unpinned() {
+ return matching(application -> ! application.change().isPinned());
}
- public ApplicationList notFailingUpgrade() {
- return filteredOn(application -> application.instances().values().stream()
+ public ApplicationList failingUpgrade() {
+ return matching(application -> ! application.instances().values().stream()
.allMatch(instance -> JobList.from(instance)
.failing()
.not().failingApplicationChange()
.isEmpty()));
}
- /** Returns the subset of applications which are currently not upgrading to any of the given versions */
- public ApplicationList notUpgradingTo(Collection<Version> versions) {
- return filteredOn(application -> versions.stream().noneMatch(version -> isUpgradingTo(version, application)));
- }
-
- /**
- * Returns the subset of applications which are currently not upgrading to the given version,
- * or returns all if no version is specified
- */
- public ApplicationList notUpgradingTo(Optional<Version> version) {
- if (version.isEmpty()) return this;
- return notUpgradingTo(version.get());
- }
-
/** Returns the subset of applications which have changes left to deploy; blocked, or deploying */
public ApplicationList withChanges() {
- return filteredOn(application -> application.change().hasTargets() || application.outstandingChange().hasTargets());
+ return matching(application -> application.change().hasTargets() || application.outstandingChange().hasTargets());
}
- /** Returns the subset of applications which are currently not deploying a change */
- public ApplicationList notDeploying() {
- return filteredOn(application -> ! application.change().hasTargets());
- }
-
- /** Returns the subset of applications which currently does not have any failing jobs */
- public ApplicationList notFailing() {
- return filteredOn(application -> application.instances().values().stream()
- .noneMatch(instance -> instance.deploymentJobs().hasFailures()));
+ /** Returns the subset of applications which are currently deploying a change */
+ public ApplicationList deploying() {
+ return matching(application -> application.change().hasTargets());
}
/** Returns the subset of applications which currently have failing jobs */
public ApplicationList failing() {
- return filteredOn(application -> application.instances().values().stream()
+ return matching(application -> application.instances().values().stream()
.anyMatch(instance -> instance.deploymentJobs().hasFailures()));
}
/** Returns the subset of applications which have been failing an upgrade to the given version since the given instant */
public ApplicationList failingUpgradeToVersionSince(Version version, Instant threshold) {
- return filteredOn(application -> application.instances().values().stream()
- .anyMatch(instance -> failingUpgradeToVersionSince(instance, version, threshold)));
+ return matching(application -> application.instances().values().stream()
+ .anyMatch(instance -> failingUpgradeToVersionSince(instance, version, threshold)));
}
/** Returns the subset of applications which have been failing an application change since the given instant */
public ApplicationList failingApplicationChangeSince(Instant threshold) {
- return filteredOn(application -> application.instances().values().stream()
- .anyMatch(instance -> failingApplicationChangeSince(instance, threshold)));
+ return matching(application -> application.instances().values().stream()
+ .anyMatch(instance -> failingApplicationChangeSince(instance, threshold)));
}
- /** Returns the subset of applications which currently does not have any failing jobs on the given version */
- public ApplicationList notFailingOn(Version version) {
- return filteredOn(application -> application.instances().values().stream()
- .noneMatch(instance -> failingOn(version, instance)));
+ /** Returns the subset of applications which currently have failing jobs on the given version */
+ public ApplicationList failingOn(Version version) {
+ return matching(application -> application.instances().values().stream()
+ .anyMatch(instance -> failingOn(version, instance)));
}
/** Returns the subset of applications which have at least one production deployment */
public ApplicationList withProductionDeployment() {
- return filteredOn(application -> application.instances().values().stream()
+ return matching(application -> application.instances().values().stream()
.anyMatch(instance -> instance.productionDeployments().size() > 0));
}
/** Returns the subset of applications which started failing on the given version */
public ApplicationList startedFailingOn(Version version) {
- return filteredOn(application -> application.instances().values().stream()
+ return matching(application -> application.instances().values().stream()
.anyMatch(instance -> ! JobList.from(instance).firstFailing().on(version).isEmpty()));
}
/** Returns the subset of applications which has the given upgrade policy */
+ // TODO jonmv: Make this instance based when instances are orchestrated, and deployments reported per instance.
public ApplicationList with(UpgradePolicy policy) {
- return filteredOn(application -> application.deploymentSpec().upgradePolicy() == policy);
- }
-
- /** Returns the subset of applications which does not have the given upgrade policy */
- public ApplicationList without(UpgradePolicy policy) {
- return filteredOn(application -> application.deploymentSpec().upgradePolicy() != policy);
+ return matching(application -> application.deploymentSpec().instances().stream()
+ .anyMatch(instance -> instance.upgradePolicy() == policy));
}
/** Returns the subset of applications which have at least one deployment on a lower version than the given one */
public ApplicationList onLowerVersionThan(Version version) {
- return filteredOn(application -> application.instances().values().stream()
+ return matching(application -> application.instances().values().stream()
.flatMap(instance -> instance.productionDeployments().values().stream())
.anyMatch(deployment -> deployment.version().isBefore(version)));
}
/** Returns the subset of applications which have a project ID */
public ApplicationList withProjectId() {
- return filteredOn(application -> application.projectId().isPresent());
+ return matching(application -> application.projectId().isPresent());
}
/** Returns the subset of applications that are allowed to upgrade at the given time */
public ApplicationList canUpgradeAt(Instant instant) {
- return filteredOn(application -> application.deploymentSpec().canUpgradeAt(instant));
+ return matching(application -> application.deploymentSpec().instances().stream()
+ .allMatch(instance -> instance.canUpgradeAt(instant)));
}
/** Returns the subset of applications that have at least one assigned rotation */
public ApplicationList hasRotation() {
- return filteredOn(application -> application.instances().values().stream()
+ return matching(application -> application.instances().values().stream()
.anyMatch(instance -> ! instance.rotations().isEmpty()));
}
@@ -193,15 +163,14 @@ public class ApplicationList {
* @param defaultMajorVersion the default major version to assume for applications not specifying one
*/
public ApplicationList allowMajorVersion(int targetMajorVersion, int defaultMajorVersion) {
- return filteredOn(application -> targetMajorVersion <= application.deploymentSpec().majorVersion()
+ return matching(application -> targetMajorVersion <= application.deploymentSpec().majorVersion()
.orElse(application.majorVersion()
.orElse(defaultMajorVersion)));
}
- /** Returns the first n application in this (or all, if there are less than n). */
- public ApplicationList first(int n) {
- if (list.size() < n) return this;
- return new ApplicationList(list.subList(0, n));
+ /** Returns the subset of application which have submitted a non-empty deployment spec. */
+ public ApplicationList withDeploymentSpec() {
+ return matching(application -> ! DeploymentSpec.empty.equals(application.deploymentSpec()));
}
// ----------------------------------- Sorting
@@ -212,10 +181,8 @@ public class ApplicationList {
* Applications without any deployments are ordered first.
*/
public ApplicationList byIncreasingDeployedVersion() {
- return new ApplicationList(list.stream()
- .sorted(Comparator.comparing(application -> application.oldestDeployedPlatform()
- .orElse(Version.emptyVersion)))
- .collect(Collectors.toUnmodifiableList()));
+ return sortedBy(Comparator.comparing(application -> application.oldestDeployedPlatform()
+ .orElse(Version.emptyVersion)));
}
// ----------------------------------- Internal helpers
@@ -246,8 +213,4 @@ public class ApplicationList {
.isEmpty();
}
- private ApplicationList filteredOn(Predicate<Application> condition) {
- return new ApplicationList(list.stream().filter(condition).collect(Collectors.toUnmodifiableList()));
- }
-
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationPackageValidator.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationPackageValidator.java
index 5ee269f8448..d7347b46f52 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationPackageValidator.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationPackageValidator.java
@@ -50,15 +50,17 @@ public class ApplicationPackageValidator {
/** Verify that each of the production zones listed in the deployment spec exist in this system */
private void validateSteps(DeploymentSpec deploymentSpec) {
- new DeploymentSteps(deploymentSpec, controller::system).jobs();
- deploymentSpec.instances().stream().flatMap(instance -> instance.zones().stream())
- .filter(zone -> zone.environment() == Environment.prod)
- .forEach(zone -> {
- if ( ! controller.zoneRegistry().hasZone(ZoneId.from(zone.environment(),
- zone.region().orElse(null)))) {
- throw new IllegalArgumentException("Zone " + zone + " in deployment spec was not found in this system!");
- }
- });
+ for (var spec : deploymentSpec.instances()) {
+ new DeploymentSteps(spec, controller::system).jobs();
+ spec.zones().stream()
+ .filter(zone -> zone.environment() == Environment.prod)
+ .forEach(zone -> {
+ if ( ! controller.zoneRegistry().hasZone(ZoneId.from(zone.environment(),
+ zone.region().orElseThrow()))) {
+ throw new IllegalArgumentException("Zone " + zone + " in deployment spec was not found in this system!");
+ }
+ });
+ }
}
/** Verify that no single endpoint contains regions in different clouds */
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentJobs.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentJobs.java
index bc2b18005b6..e126128ce2e 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentJobs.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentJobs.java
@@ -3,10 +3,8 @@ package com.yahoo.vespa.hosted.controller.application;
import com.google.common.collect.ImmutableMap;
import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.vespa.hosted.controller.api.integration.BuildService;
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 java.util.Collection;
import java.util.LinkedHashMap;
@@ -105,17 +103,6 @@ public class DeploymentJobs {
this.version = version;
}
- public static JobReport ofComponent(ApplicationId applicationId, long projectId, long buildNumber,
- Optional<JobError> jobError, SourceRevision sourceRevision) {
- return new JobReport(applicationId, JobType.component, projectId, buildNumber,
- jobError, Optional.of(ApplicationVersion.from(sourceRevision, buildNumber)));
- }
-
- public static JobReport ofSubmission(ApplicationId applicationId, long projectId, ApplicationVersion version) {
- return new JobReport(applicationId, JobType.component, projectId, version.buildNumber().getAsLong(),
- Optional.empty(), Optional.of(version));
- }
-
public static JobReport ofJob(ApplicationId applicationId, JobType jobType, long buildNumber, Optional<JobError> jobError) {
return new JobReport(applicationId, jobType, -1, buildNumber, jobError, Optional.empty());
}
@@ -127,7 +114,6 @@ public class DeploymentJobs {
public boolean success() { return ! jobError.isPresent(); }
public Optional<ApplicationVersion> version() { return version; }
public Optional<JobError> jobError() { return jobError; }
- public BuildService.BuildJob buildJob() { return BuildService.BuildJob.of(applicationId, projectId, jobType.jobName()); }
}
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 83d1b5ef803..9a6daf026c3 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
@@ -145,7 +145,6 @@ public class Endpoint {
}
private static String instancePart(ApplicationId application, ZoneId zone, String separator) {
- if (zone == null) return ""; // Always omit instance for global endpoints
if (application.instance().isDefault()) return ""; // Skip "default"
return application.instance().value() + separator;
}
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 c4613db27d1..e12bb5cda7f 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
@@ -1,10 +1,12 @@
// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.application;
+import com.yahoo.collections.AbstractFilteringList;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.SystemName;
import com.yahoo.vespa.hosted.controller.application.Endpoint.Port;
+import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
@@ -17,41 +19,34 @@ import java.util.stream.Stream;
*
* @author mpolden
*/
-public class EndpointList {
+public class EndpointList extends AbstractFilteringList<Endpoint, EndpointList> {
public static final EndpointList EMPTY = new EndpointList(List.of());
- private final List<Endpoint> endpoints;
-
- private EndpointList(List<Endpoint> endpoints) {
+ private EndpointList(Collection<? extends Endpoint> endpoints, boolean negate) {
+ super(endpoints, negate, EndpointList::new);
if (endpoints.stream().distinct().count() != endpoints.size()) {
throw new IllegalArgumentException("Expected all endpoints to be distinct, got " + endpoints);
}
- this.endpoints = List.copyOf(endpoints);
}
- public List<Endpoint> asList() {
- return endpoints;
+ private EndpointList(Collection<? extends Endpoint> endpoints) {
+ this(endpoints, false);
}
/** Returns the main endpoint, if any */
public Optional<Endpoint> main() {
- return endpoints.stream().filter(Predicate.not(Endpoint::legacy)).findFirst();
+ return asList().stream().filter(Predicate.not(Endpoint::legacy)).findFirst();
}
/** Returns the subset of endpoints are either legacy or not */
public EndpointList legacy(boolean legacy) {
- return of(endpoints.stream().filter(endpoint -> endpoint.legacy() == legacy));
+ return matching(endpoint -> endpoint.legacy() == legacy);
}
/** Returns the subset of endpoints with given scope */
public EndpointList scope(Endpoint.Scope scope) {
- return of(endpoints.stream().filter(endpoint -> endpoint.scope() == scope));
- }
-
- /** Returns the union of this and given endpoints */
- public EndpointList and(EndpointList endpoints) {
- return of(Stream.concat(asList().stream(), endpoints.asList().stream()));
+ return matching(endpoint -> endpoint.scope() == scope);
}
public static EndpointList of(Stream<Endpoint> endpoints) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/JobStatus.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/JobStatus.java
index 5306cc4ae2b..cc6b770a809 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/JobStatus.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/JobStatus.java
@@ -43,7 +43,7 @@ public class JobStatus {
this.jobError = requireNonNull(jobError, "jobError cannot be null");
// Never say we triggered component because we don't:
- this.lastTriggered = type == JobType.component ? Optional.empty() : requireNonNull(lastTriggered, "lastTriggered cannot be null");
+ this.lastTriggered = requireNonNull(lastTriggered, "lastTriggered cannot be null");
this.lastCompleted = requireNonNull(lastCompleted, "lastCompleted cannot be null");
this.firstFailing = requireNonNull(firstFailing, "firstFailing cannot be null");
this.lastSuccess = requireNonNull(lastSuccess, "lastSuccess cannot be null");
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzFacade.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzFacade.java
index 70c504dd220..8f84845a94b 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzFacade.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzFacade.java
@@ -208,6 +208,10 @@ public class AthenzFacade implements AccessControl {
return hasAccess("launch", service.getDomain().getName() + ":service."+service.getName(), principal);
}
+ public boolean hasSystemFlagsDeployAccess(AthenzIdentity identity) {
+ return hasAccess("deploy", new AthenzResourceName(service.getDomain(), "system-flags").toResourceNameString(), identity);
+ }
+
/**
* Used when creating tenancies. As there are no tenancy policies at this point,
* we cannot use {@link #hasTenantAdminAccess(AthenzIdentity, AthenzDomain)}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentStatus.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentStatus.java
new file mode 100644
index 00000000000..1582bc144f4
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentStatus.java
@@ -0,0 +1,49 @@
+package com.yahoo.vespa.hosted.controller.deployment;
+
+import com.yahoo.config.provision.InstanceName;
+import com.yahoo.vespa.hosted.controller.Application;
+import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobId;
+import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * Status of the deployment jobs of an {@link Application}.
+ *
+ * @author jonmv
+ */
+public class DeploymentStatus {
+
+ private final Application application;
+ private final Map<JobId, JobStatus> jobs;
+
+ public DeploymentStatus(Application application, Map<JobId, JobStatus> jobs) {
+ this.application = Objects.requireNonNull(application);
+ this.jobs = Map.copyOf(jobs);
+ }
+
+ public Application application() {
+ return application;
+ }
+
+ public Map<JobId, JobStatus> jobs() {
+ return jobs;
+ }
+
+ public boolean hasFailures() {
+ return ! JobList.from(jobs.values())
+ .failing()
+ .not().withStatus(RunStatus.outOfCapacity)
+ .isEmpty();
+ }
+
+ public Map<JobType, JobStatus> instanceJobs(InstanceName instance) {
+ return jobs.entrySet().stream()
+ .filter(entry -> entry.getKey().application().equals(application.id().instance(instance)))
+ .collect(Collectors.toUnmodifiableMap(entry -> entry.getKey().type(),
+ entry -> entry.getValue()));
+ }
+
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentSteps.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentSteps.java
index 33db6b95db1..1b722c80ec1 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentSteps.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentSteps.java
@@ -1,12 +1,14 @@
// 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.deployment;
+import com.yahoo.config.application.api.DeploymentInstanceSpec;
import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.provision.Environment;
+import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.zone.ZoneId;
-import com.yahoo.vespa.hosted.controller.application.Deployment;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
+import com.yahoo.vespa.hosted.controller.application.Deployment;
import com.yahoo.vespa.hosted.controller.application.JobStatus;
import java.util.Collection;
@@ -16,8 +18,8 @@ import java.util.Objects;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
-import static java.util.Collections.singletonList;
import static java.util.Comparator.comparingInt;
import static java.util.stream.Collectors.collectingAndThen;
@@ -28,19 +30,20 @@ import static java.util.stream.Collectors.collectingAndThen;
*/
public class DeploymentSteps {
- private final DeploymentSpec spec;
+ private final DeploymentInstanceSpec spec;
private final Supplier<SystemName> system;
- public DeploymentSteps(DeploymentSpec spec, Supplier<SystemName> system) {
+ public DeploymentSteps(DeploymentInstanceSpec spec, Supplier<SystemName> system) {
this.spec = Objects.requireNonNull(spec, "spec cannot be null");
this.system = Objects.requireNonNull(system, "system cannot be null");
}
- /** Returns jobs for this, in the order they are declared */
+ /** Returns jobs for this, in the order they should run */
public List<JobType> jobs() {
- return spec.steps().stream()
- .flatMap(step -> toJobs(step).stream())
- .collect(collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
+ return Stream.concat(production().isEmpty() ? Stream.of() : Stream.of(JobType.systemTest, JobType.stagingTest),
+ spec.steps().stream().flatMap(step -> toJobs(step).stream()))
+ .distinct()
+ .collect(Collectors.toUnmodifiableList());
}
/** Returns job status sorted according to deployment spec */
@@ -48,7 +51,7 @@ public class DeploymentSteps {
List<JobType> sortedJobs = jobs();
return jobStatus.stream()
.sorted(comparingInt(job -> sortedJobs.indexOf(job.type())))
- .collect(collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
+ .collect(Collectors.toUnmodifiableList());
}
/** Returns deployments sorted according to declared zones */
@@ -56,7 +59,7 @@ public class DeploymentSteps {
List<ZoneId> productionZones = spec.zones().stream()
.filter(z -> z.region().isPresent())
.map(z -> ZoneId.from(z.environment(), z.region().get()))
- .collect(Collectors.toList());
+ .collect(Collectors.toUnmodifiableList());
return deployments.stream()
.sorted(comparingInt(deployment -> productionZones.indexOf(deployment.zone())))
.collect(collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
@@ -67,34 +70,28 @@ public class DeploymentSteps {
return step.zones().stream()
.map(this::toJob)
.flatMap(Optional::stream)
- .collect(collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
+ .collect(Collectors.toUnmodifiableList());
}
- /** Returns test jobs in this */
+ /** Returns test jobs to run for this spec */
public List<JobType> testJobs() {
- return toJobs(test());
+ return jobs().stream().filter(JobType::isTest).collect(Collectors.toUnmodifiableList());
}
- /** Returns production jobs in this */
+ /** Returns declared production jobs in this */
public List<JobType> productionJobs() {
return toJobs(production());
}
- /** Returns test steps in this */
- public List<DeploymentSpec.Step> test() {
- if (spec.steps().isEmpty()) {
- return singletonList(new DeploymentSpec.DeclaredZone(Environment.test));
- }
+ /** Returns declared production steps in this */
+ public List<DeploymentSpec.Step> production() {
return spec.steps().stream()
- .filter(step -> step.deploysTo(Environment.test) || step.deploysTo(Environment.staging))
- .collect(collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
+ .filter(step -> ! isTest(step))
+ .collect(Collectors.toUnmodifiableList());
}
- /** Returns production steps in this */
- public List<DeploymentSpec.Step> production() {
- return spec.steps().stream()
- .filter(step -> step.deploysTo(Environment.prod) || step.zones().isEmpty())
- .collect(collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
+ private boolean isTest(DeploymentSpec.Step step) {
+ return step.deploysTo(Environment.test) || step.deploysTo(Environment.staging);
}
/** Resolve job from deployment zone */
@@ -106,7 +103,7 @@ public class DeploymentSteps {
private List<JobType> toJobs(List<DeploymentSpec.Step> steps) {
return steps.stream()
.flatMap(step -> toJobs(step).stream())
- .collect(collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
+ .collect(Collectors.toUnmodifiableList());
}
}
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 9573f5d07f5..f1b93c7b3b2 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
@@ -5,7 +5,6 @@ import com.yahoo.config.application.api.DeploymentInstanceSpec;
import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.application.api.DeploymentSpec.Step;
import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.log.LogLevel;
import com.yahoo.vespa.hosted.controller.Application;
@@ -13,15 +12,13 @@ import com.yahoo.vespa.hosted.controller.ApplicationController;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.Instance;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
-import com.yahoo.vespa.hosted.controller.api.integration.BuildService;
-import com.yahoo.vespa.hosted.controller.api.integration.BuildService.JobState;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
+import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobId;
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.Change;
import com.yahoo.vespa.hosted.controller.application.Deployment;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobReport;
-import com.yahoo.vespa.hosted.controller.application.JobStatus;
import com.yahoo.vespa.hosted.controller.application.JobStatus.JobRun;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
@@ -31,7 +28,6 @@ import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
@@ -44,15 +40,9 @@ import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
-import static com.yahoo.vespa.hosted.controller.api.integration.BuildService.BuildJob;
-import static com.yahoo.vespa.hosted.controller.api.integration.BuildService.JobState.idle;
-import static com.yahoo.vespa.hosted.controller.api.integration.BuildService.JobState.queued;
-import static com.yahoo.vespa.hosted.controller.api.integration.BuildService.JobState.running;
-import static com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType.component;
import static com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType.stagingTest;
import static com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType.systemTest;
import static java.util.Collections.emptyList;
-import static java.util.Collections.singletonList;
import static java.util.Comparator.comparing;
import static java.util.Comparator.naturalOrder;
import static java.util.stream.Collectors.groupingBy;
@@ -72,31 +62,20 @@ import static java.util.stream.Collectors.toList;
*/
public class DeploymentTrigger {
- /*
- * Instance orchestration TODO jonmv.
- * Store new production application packages under non-instance path
- * Read production packages from non-instance path, with fallback
- * Deprecate and redirect some instance qualified paths in application/v4
- * Orchestrate deployment across instances.
- */
-
public static final Duration maxPause = Duration.ofDays(3);
-
private final static Logger log = Logger.getLogger(DeploymentTrigger.class.getName());
private final Controller controller;
private final Clock clock;
- private final BuildService buildService;
private final JobController jobs;
- public DeploymentTrigger(Controller controller, BuildService buildService, Clock clock) {
+ public DeploymentTrigger(Controller controller, Clock clock) {
this.controller = Objects.requireNonNull(controller, "controller cannot be null");
this.clock = Objects.requireNonNull(clock, "clock cannot be null");
- this.buildService = Objects.requireNonNull(buildService, "buildService cannot be null");
this.jobs = controller.jobController();
}
- public DeploymentSteps steps(DeploymentSpec spec) {
+ public DeploymentSteps steps(DeploymentInstanceSpec spec) {
return new DeploymentSteps(spec, controller::system);
}
@@ -111,10 +90,9 @@ public class DeploymentTrigger {
if (acceptNewApplicationVersion(application.get())) {
application = application.withChange(application.get().change().with(version))
.withOutstandingChange(Change.empty());
- if (application.get().internal())
- for (Run run : jobs.active(id))
- if ( ! run.id().type().environment().isManuallyDeployed())
- jobs.abort(run.id());
+ for (Run run : jobs.active(id))
+ if ( ! run.id().type().environment().isManuallyDeployed())
+ jobs.abort(run.id());
}
else
application = application.withOutstandingChange(Change.of(version));
@@ -143,24 +121,17 @@ public class DeploymentTrigger {
}
applications().lockApplicationOrThrow(TenantAndApplicationId.from(report.applicationId()), application -> {
- if (report.jobType() == component) {
- if (report.success())
- notifyOfSubmission(application.get().id(), report.version().get(), report.projectId());
-
- return;
- }
- JobRun triggering;
- Optional<JobStatus> status = application.get().require(report.applicationId().instance())
- .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();
+ var status = application.get().require(report.applicationId().instance())
+ .deploymentJobs().statusOf(report.jobType());
+ var 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.with(report.applicationId().instance(),
instance -> instance.withJobCompletion(report.jobType(),
@@ -170,11 +141,6 @@ public class DeploymentTrigger {
});
}
- /** Returns a map of jobs that are scheduled to be run, grouped by the job type */
- public Map<JobType, ? extends List<? extends BuildJob>> jobsToRun() {
- return computeReadyJobs().stream().collect(groupingBy(Job::jobType));
- }
-
/**
* Finds and triggers jobs that can and should run but are currently not, and returns the number of triggered jobs.
*
@@ -212,13 +178,10 @@ public class DeploymentTrigger {
log.log(LogLevel.DEBUG, String.format("Triggering %s: %s", job, job.triggering));
try {
applications().lockApplicationOrThrow(TenantAndApplicationId.from(job.applicationId()), application -> {
- if (application.get().internal())
- jobs.start(job.applicationId(), job.jobType, new Versions(job.triggering.platform(),
- job.triggering.application(),
- job.triggering.sourcePlatform(),
- job.triggering.sourceApplication()));
- else
- buildService.trigger(job);
+ jobs.start(job.applicationId(), job.jobType, new Versions(job.triggering.platform(),
+ job.triggering.application(),
+ job.triggering.sourcePlatform(),
+ job.triggering.sourceApplication()));
applications().store(application.with(job.applicationId().instance(),
instance -> instance.withJobTriggering(job.jobType, job.triggering)));
@@ -238,19 +201,13 @@ public class DeploymentTrigger {
public List<JobType> forceTrigger(ApplicationId applicationId, JobType jobType, String user) {
Application application = applications().requireApplication(TenantAndApplicationId.from(applicationId));
Instance instance = application.require(applicationId.instance());
- if (jobType == component) {
- if (application.internal())
- throw new IllegalArgumentException(applicationId + " has no component job we can trigger.");
-
- buildService.trigger(BuildJob.of(applicationId, application.projectId().getAsLong(), jobType.jobName()));
- return singletonList(component);
- }
Versions versions = Versions.from(application.change(), application, deploymentFor(instance, jobType),
controller.systemVersion());
String reason = "Job triggered manually by " + user;
- return (jobType.isProduction() && ! isTested(instance, versions)
- ? testJobs(application.deploymentSpec(), application.change(), instance, versions, reason, clock.instant(), __ -> true).stream()
- : Stream.of(deploymentJob(instance, versions, application.change(), jobType, reason, clock.instant())))
+ var jobStatus = jobs.deploymentStatus(application).instanceJobs(instance.name());
+ return (jobType.isProduction() && ! isTested(jobStatus, versions)
+ ? testJobs(application.deploymentSpec(), application.change(), instance, jobStatus, versions, reason, clock.instant(), __ -> true).stream()
+ : Stream.of(deploymentJob(instance, versions, application.change(), jobType, jobStatus.get(jobType), reason, clock.instant())))
.peek(this::trigger)
.map(Job::jobType).collect(toList());
}
@@ -306,9 +263,8 @@ public class DeploymentTrigger {
return controller.applications();
}
- private Optional<JobRun> successOn(Instance instance, JobType jobType, Versions versions) {
- return instance.deploymentJobs().statusOf(jobType).flatMap(JobStatus::lastSuccess)
- .filter(versions::targetsMatch);
+ private Optional<Run> successOn(JobStatus status, Versions versions) {
+ return status.lastSuccess().filter(run -> versions.targetsMatch(run.versions()));
}
private Optional<Deployment> deploymentFor(Instance instance, JobType jobType) {
@@ -324,12 +280,13 @@ public class DeploymentTrigger {
/** Returns the set of all jobs which have changes to propagate from the upstream steps. */
private List<Job> computeReadyJobs() {
return ApplicationList.from(applications().asList())
- .withProjectId()
- .withChanges()
- .idList().stream()
- .map(this::computeReadyJobs)
- .flatMap(Collection::stream)
- .collect(toList());
+ .withProjectId() // Need to keep this, as we have applications with deployment spec that shouldn't be orchestrated.
+ .withChanges()
+ .withDeploymentSpec()
+ .idList().stream()
+ .map(this::computeReadyJobs)
+ .flatMap(Collection::stream)
+ .collect(toList());
}
/**
@@ -338,40 +295,40 @@ public class DeploymentTrigger {
private List<Job> computeReadyJobs(TenantAndApplicationId id) {
List<Job> jobs = new ArrayList<>();
applications().getApplication(id).ifPresent(application -> {
- Collection<Instance> instances = application.deploymentSpec().equals(DeploymentSpec.empty)
- ? application.instances().values()
- : application.deploymentSpec().instances().stream()
- .flatMap(instance -> application.get(instance.name()).stream())
- .collect(Collectors.toUnmodifiableList());
+ Collection<Instance> instances = application.deploymentSpec().instances().stream()
+ .flatMap(instance -> application.get(instance.name()).stream())
+ .collect(Collectors.toUnmodifiableList());
+ DeploymentStatus deploymentStatus = this.jobs.deploymentStatus(application);
for (Instance instance : instances) {
+ var jobStatus = deploymentStatus.instanceJobs(instance.name());
Change change = application.change();
- Optional<Instant> completedAt = max(instance.deploymentJobs().statusOf(systemTest)
- .<Instant>flatMap(job -> job.lastSuccess().map(JobRun::at)),
- instance.deploymentJobs().statusOf(stagingTest)
- .<Instant>flatMap(job -> job.lastSuccess().map(JobRun::at)));
+ Optional<Instant> completedAt = max(Optional.ofNullable(jobStatus.get(systemTest))
+ .<Instant>flatMap(job -> job.lastSuccess().map(run -> run.end().get())),
+ Optional.ofNullable(jobStatus.get(stagingTest))
+ .<Instant>flatMap(job -> job.lastSuccess().map(run -> run.end().get())));
String reason = "New change available";
List<Job> testJobs = null; // null means "uninitialised", while empty means "don't run any jobs".
- DeploymentSteps steps = steps(application.deploymentSpec());
+ DeploymentSteps steps = steps(application.deploymentSpec().requireInstance(instance.name()));
if (change.hasTargets()) {
for (Step step : steps.production()) {
List<JobType> stepJobs = steps.toJobs(step);
- List<JobType> remainingJobs = stepJobs.stream().filter(job -> ! isComplete(change, change, instance, job)).collect(toList());
+ List<JobType> remainingJobs = stepJobs.stream().filter(job -> ! isComplete(change, change, instance, job, jobStatus.get(job))).collect(toList());
if ( ! remainingJobs.isEmpty()) { // Change is incomplete; trigger remaining jobs if ready, or their test jobs if untested.
for (JobType job : remainingJobs) {
Versions versions = Versions.from(change, application, deploymentFor(instance, job),
controller.systemVersion());
- if (isTested(instance, versions)) {
- if (completedAt.isPresent() && canTrigger(job, versions, instance, application.deploymentSpec(), stepJobs)) {
- jobs.add(deploymentJob(instance, versions, change, job, reason, completedAt.get()));
+ if (isTested(jobStatus, versions)) {
+ if (completedAt.isPresent() && canTrigger(job, jobStatus, versions, instance, application.deploymentSpec(), stepJobs)) {
+ jobs.add(deploymentJob(instance, versions, change, job, jobStatus.get(job), reason, completedAt.get()));
}
- if ( ! alreadyTriggered(instance, versions) && testJobs == null) {
+ if ( ! alreadyTriggered(jobStatus, versions) && testJobs == null) {
testJobs = emptyList();
}
}
else if (testJobs == null) {
testJobs = testJobs(application.deploymentSpec(),
- change, instance, versions,
+ change, instance, jobStatus, versions,
String.format("Testing deployment for %s (%s)",
job.jobName(), versions.toString()),
completedAt.orElseGet(clock::instant));
@@ -385,14 +342,14 @@ public class DeploymentTrigger {
reason += " after a delay of " + step.delay();
}
else {
- completedAt = stepJobs.stream().map(job -> instance.deploymentJobs().statusOf(job).get().lastCompleted().get().at()).max(naturalOrder());
+ completedAt = stepJobs.stream().map(job -> jobStatus.get(job).lastCompleted().get().end().get()).max(naturalOrder());
reason = "Available change in " + stepJobs.stream().map(JobType::jobName).collect(joining(", "));
}
}
}
}
if (testJobs == null) { // If nothing to test, but outstanding commits, test those.
- testJobs = testJobs(application.deploymentSpec(), change, instance,
+ testJobs = testJobs(application.deploymentSpec(), change, instance, jobStatus,
Versions.from(application.outstandingChange().onTopOf(change),
application,
steps.sortedDeployments(instance.productionDeployments().values()).stream().findFirst(),
@@ -406,21 +363,21 @@ public class DeploymentTrigger {
}
/** Returns whether given job should be triggered */
- private boolean canTrigger(JobType job, Versions versions, Instance instance, DeploymentSpec deploymentSpec, List<JobType> parallelJobs) {
- if (jobStateOf(instance, job) != idle) return false;
+ private boolean canTrigger(JobType job, Map<JobType, JobStatus> status, Versions versions, Instance instance, DeploymentSpec deploymentSpec, List<JobType> parallelJobs) {
+ if (status.get(job).isRunning()) return false;
// Are we already running jobs which are not in the set which can run in parallel with this?
- if (parallelJobs != null && ! parallelJobs.containsAll(runningProductionJobs(instance))) return false;
+ if (parallelJobs != null && ! parallelJobs.containsAll(runningProductionJobs(status))) return false;
// Are there another suspended deployment such that we shouldn't simultaneously change this?
if (job.isProduction() && isSuspendedInAnotherZone(instance, job.zone(controller.system()))) return false;
- return triggerAt(clock.instant(), job, versions, instance, deploymentSpec);
+ return triggerAt(clock.instant(), job, status.get(job), versions, instance, deploymentSpec);
}
/** Returns whether given job should be triggered */
- private boolean canTrigger(JobType job, Versions versions, Instance instance, DeploymentSpec deploymentSpec) {
- return canTrigger(job, versions, instance, deploymentSpec, null);
+ private boolean canTrigger(JobType job, Map<JobType, JobStatus> status, Versions versions, Instance instance, DeploymentSpec deploymentSpec) {
+ return canTrigger(job, status, versions, instance, deploymentSpec, null);
}
private boolean isSuspendedInAnotherZone(Instance instance, ZoneId zone) {
@@ -433,24 +390,23 @@ public class DeploymentTrigger {
}
/** Returns whether the given job can trigger at the given instant */
- public boolean triggerAt(Instant instant, JobType job, Versions versions, Instance instance, DeploymentSpec deploymentSpec) {
- Optional<JobStatus> jobStatus = instance.deploymentJobs().statusOf(job);
- if (jobStatus.isEmpty()) return true;
- if (jobStatus.get().pausedUntil().isPresent() && jobStatus.get().pausedUntil().getAsLong() > clock.instant().toEpochMilli()) return false;
- if (jobStatus.get().isSuccess()) return true; // Success
- if (jobStatus.get().lastCompleted().isEmpty()) return true; // Never completed
- if (jobStatus.get().firstFailing().isEmpty()) return true; // Should not happen as firstFailing should be set for an unsuccessful job
- if ( ! versions.targetsMatch(jobStatus.get().lastCompleted().get())) return true; // Always trigger as targets have changed
- if (deploymentSpec.upgradePolicy() == DeploymentSpec.UpgradePolicy.canary) return true; // Don't throttle canaries
-
- Instant firstFailing = jobStatus.get().firstFailing().get().at();
- Instant lastCompleted = jobStatus.get().lastCompleted().get().at();
+ public boolean triggerAt(Instant instant, JobType job, JobStatus jobStatus, Versions versions, Instance instance, DeploymentSpec deploymentSpec) {
+ if (instance.deploymentJobs().statusOf(job).map(status -> status.pausedUntil().orElse(0)).orElse(0L) > clock.millis()) return false;
+ if (jobStatus.lastTriggered().isEmpty()) return true;
+ if (jobStatus.isSuccess()) return true; // Success
+ if (jobStatus.lastCompleted().isEmpty()) return true; // Never completed
+ if (jobStatus.firstFailing().isEmpty()) return true; // Should not happen as firstFailing should be set for an unsuccessful job
+ if ( ! versions.targetsMatch(jobStatus.lastCompleted().get().versions())) return true; // Always trigger as targets have changed
+ if (deploymentSpec.requireInstance(instance.name()).upgradePolicy() == DeploymentSpec.UpgradePolicy.canary) return true; // Don't throttle canaries
+
+ Instant firstFailing = jobStatus.firstFailing().get().end().get();
+ Instant lastCompleted = jobStatus.lastCompleted().get().end().get();
// Retry all errors immediately for 1 minute
if (firstFailing.isAfter(instant.minus(Duration.ofMinutes(1)))) return true;
// Retry out of capacity errors in test environments every minute
- if (job.isTest() && jobStatus.get().isOutOfCapacity()) {
+ if (job.isTest() && jobStatus.isOutOfCapacity()) {
return lastCompleted.isBefore(instant.minus(Duration.ofMinutes(1)));
}
@@ -463,27 +419,12 @@ public class DeploymentTrigger {
// ---------- Job state helpers ----------
- private List<JobType> runningProductionJobs(Instance instance) {
- return instance.deploymentJobs().jobStatus().keySet().parallelStream()
- .filter(JobType::isProduction)
- .filter(job -> isRunning(instance, job))
- .collect(toList());
- }
-
- /** Returns whether the given job is currently running; false if completed since last triggered, asking the build service otherwise. */
- private boolean isRunning(Instance instance, JobType jobType) {
- return ! instance.deploymentJobs().statusOf(jobType)
- .flatMap(job -> job.lastCompleted().map(run -> run.at().isAfter(job.lastTriggered().get().at())))
- .orElse(false)
- && EnumSet.of(running, queued).contains(jobStateOf(instance, jobType));
- }
-
- private JobState jobStateOf(Instance instance, JobType jobType) {
- if (controller.applications().requireApplication(TenantAndApplicationId.from(instance.id())).internal()) {
- Optional<Run> run = controller.jobController().last(instance.id(), jobType);
- return run.isPresent() && ! run.get().hasEnded() ? JobState.running : JobState.idle;
- }
- return buildService.stateOf(BuildJob.of(instance.id(), 0, jobType.jobName()));
+ private List<JobType> runningProductionJobs(Map<JobType, JobStatus> status) {
+ return status.values().parallelStream()
+ .filter(job -> job.isRunning())
+ .map(job -> job.id().type())
+ .filter(JobType::isProduction)
+ .collect(toList());
}
// ---------- Completion logic ----------
@@ -500,17 +441,18 @@ public class DeploymentTrigger {
* Additionally, if the application is pinned to a Vespa version, and the given change has a (this) platform,
* the deployment for the job must be on the pinned version.
*/
- public boolean isComplete(Change change, Change fullChange, Instance instance, JobType jobType) {
+ public boolean isComplete(Change change, Change fullChange, Instance instance, JobType jobType,
+ JobStatus status) {
Optional<Deployment> existingDeployment = deploymentFor(instance, jobType);
if ( change.isPinned()
&& change.platform().isPresent()
&& ! existingDeployment.map(Deployment::version).equals(change.platform()))
return false;
- return instance.deploymentJobs().statusOf(jobType).flatMap(JobStatus::lastSuccess)
- .map(job -> change.platform().map(job.platform()::equals).orElse(true)
- && change.application().map(job.application()::equals).orElse(true))
- .orElse(false)
+ return status.lastSuccess()
+ .map(run -> change.platform().map(run.versions().targetPlatform()::equals).orElse(true)
+ && change.application().map(run.versions().targetApplication()::equals).orElse(true))
+ .orElse(false)
|| jobType.isProduction()
&& existingDeployment.map(deployment -> ! isUpgrade(change, deployment) && isDowngrade(fullChange, deployment))
.orElse(false);
@@ -524,27 +466,28 @@ public class DeploymentTrigger {
return change.downgrades(deployment.version()) || change.downgrades(deployment.applicationVersion());
}
- private boolean isTested(Instance instance, Versions versions) {
- return testedIn(instance, systemTest, versions)
- && testedIn(instance, stagingTest, versions)
- || alreadyTriggered(instance, versions);
+ private boolean isTested(Map<JobType, JobStatus> status, Versions versions) {
+ return testedIn(systemTest, status.get(systemTest), versions)
+ && testedIn(stagingTest, status.get(stagingTest), versions)
+ || alreadyTriggered(status, versions);
}
- public boolean testedIn(Instance instance, JobType testType, Versions versions) {
+ public boolean testedIn(JobType testType, JobStatus status, Versions versions) {
if (testType == systemTest)
- return successOn(instance, systemTest, versions).isPresent();
+ return successOn(status, versions).isPresent();
if (testType == stagingTest)
- return successOn(instance, stagingTest, versions).filter(versions::sourcesMatchIfPresent).isPresent();
+ return successOn(status, versions).map(Run::versions).filter(versions::sourcesMatchIfPresent).isPresent();
throw new IllegalArgumentException(testType + " is not a test job!");
}
- public boolean alreadyTriggered(Instance instance, Versions versions) {
- return instance.deploymentJobs().jobStatus().values().stream()
- .filter(job -> job.type().isProduction())
+ public boolean alreadyTriggered(Map<JobType, JobStatus> status, Versions versions) {
+ return status.values().stream()
+ .filter(job -> job.id().type().isProduction())
.anyMatch(job -> job.lastTriggered()
- .filter(versions::targetsMatch)
- .filter(versions::sourcesMatchIfPresent)
- .isPresent());
+ .map(Run::versions)
+ .filter(versions::targetsMatch)
+ .filter(versions::sourcesMatchIfPresent)
+ .isPresent());
}
// ---------- Change management o_O ----------
@@ -559,19 +502,22 @@ public class DeploymentTrigger {
}
private Change remainingChange(Application application) {
- DeploymentSteps steps = steps(application.deploymentSpec());
- List<JobType> jobs = steps.production().isEmpty()
- ? steps.testJobs()
- : steps.productionJobs();
-
Change change = application.change();
- for (Instance instance : application.instances().values()) {
- if (jobs.stream().allMatch(job -> isComplete(application.change().withoutApplication(), application.change(), instance, job)))
- change = change.withoutPlatform();
-
- if (jobs.stream().allMatch(job -> isComplete(application.change().withoutPlatform(), application.change(), instance, job)))
- change = change.withoutApplication();
- }
+ if (application.deploymentSpec().instances().stream()
+ .allMatch(spec -> {
+ DeploymentSteps steps = new DeploymentSteps(spec, controller::system);
+ return (steps.productionJobs().isEmpty() ? steps.testJobs() : steps.productionJobs())
+ .stream().allMatch(job -> isComplete(application.change().withoutApplication(), application.change(), application.require(spec.name()), job, jobs.jobStatus(new JobId(application.id().instance(spec.name()), job))));
+ }))
+ change = change.withoutPlatform();
+
+ if (application.deploymentSpec().instances().stream()
+ .allMatch(spec -> {
+ DeploymentSteps steps = new DeploymentSteps(spec, controller::system);
+ return (steps.productionJobs().isEmpty() ? steps.testJobs() : steps.productionJobs())
+ .stream().allMatch(job -> isComplete(application.change().withoutPlatform(), application.change(), application.require(spec.name()), job, jobs.jobStatus(new JobId(application.id().instance(spec.name()), job))));
+ }))
+ change = change.withoutApplication();
return change;
}
@@ -581,44 +527,42 @@ public class DeploymentTrigger {
/**
* Returns the list of test jobs that should run now, and that need to succeed on the given versions for it to be considered tested.
*/
- private List<Job> testJobs(DeploymentSpec deploymentSpec, Change change, Instance instance, Versions versions,
+ private List<Job> testJobs(DeploymentSpec deploymentSpec, Change change, Instance instance, Map<JobType, JobStatus> status, Versions versions,
String reason, Instant availableSince) {
- return testJobs(deploymentSpec, change, instance, versions, reason, availableSince,
- jobType -> canTrigger(jobType, versions, instance, deploymentSpec));
+ return testJobs(deploymentSpec, change, instance, status, versions, reason, availableSince,
+ jobType -> canTrigger(jobType, status, versions, instance, deploymentSpec));
}
/**
* Returns the list of test jobs that need to succeed on the given versions for it to be considered tested, filtered by the given condition.
*/
- private List<Job> testJobs(DeploymentSpec deploymentSpec, Change change, Instance instance, Versions versions,
+ private List<Job> testJobs(DeploymentSpec deploymentSpec, Change change, Instance instance, Map<JobType, JobStatus> status, Versions versions,
String reason, Instant availableSince, Predicate<JobType> condition) {
List<Job> jobs = new ArrayList<>();
- for (JobType jobType : steps(deploymentSpec).testJobs()) {
- Optional<JobRun> completion = successOn(instance, jobType, versions)
- .filter(run -> versions.sourcesMatchIfPresent(run) || jobType == systemTest);
+ for (JobType jobType : new DeploymentSteps(deploymentSpec.requireInstance(instance.name()), controller::system).testJobs()) { // TODO jonmv: Allow cross-instance validation
+ Optional<Run> completion = successOn(status.get(jobType), versions)
+ .filter(run -> versions.sourcesMatchIfPresent(run.versions()) || jobType == systemTest);
if (completion.isEmpty() && condition.test(jobType))
- jobs.add(deploymentJob(instance, versions, change, jobType, reason, availableSince));
+ jobs.add(deploymentJob(instance, versions, change, jobType, status.get(jobType), reason, availableSince));
}
return jobs;
}
- private Job deploymentJob(Instance instance, Versions versions, Change change, JobType jobType, String reason, Instant availableSince) {
- boolean isRetry = instance.deploymentJobs().statusOf(jobType)
- .map(JobStatus::isOutOfCapacity)
- .orElse(false);
- if (isRetry) reason += "; retrying on out of capacity";
+ private Job deploymentJob(Instance instance, Versions versions, Change change, JobType jobType, JobStatus jobStatus, String reason, Instant availableSince) {
+ if (jobStatus.isOutOfCapacity()) reason += "; retrying on out of capacity";
- JobRun triggering = JobRun.triggering(versions.targetPlatform(), versions.targetApplication(),
- versions.sourcePlatform(), versions.sourceApplication(),
- reason, clock.instant());
- return new Job(instance, triggering, jobType, availableSince, isRetry, change.application().isPresent());
+ var triggering = JobRun.triggering(versions.targetPlatform(), versions.targetApplication(),
+ versions.sourcePlatform(), versions.sourceApplication(),
+ reason, clock.instant());
+ return new Job(instance, triggering, jobType, availableSince, jobStatus.isOutOfCapacity(), change.application().isPresent());
}
// ---------- Data containers ----------
- private static class Job extends BuildJob {
+ private static class Job {
+ private final ApplicationId instanceId;
private final JobType jobType;
private final JobRun triggering;
private final Instant availableSince;
@@ -627,7 +571,7 @@ public class DeploymentTrigger {
private Job(Instance instance, JobRun triggering, JobType jobType, Instant availableSince,
boolean isRetry, boolean isApplicationUpgrade) {
- super(instance.id(), 0L, jobType.jobName());
+ this.instanceId = instance.id();
this.jobType = jobType;
this.triggering = triggering;
this.availableSince = availableSince;
@@ -635,6 +579,7 @@ public class DeploymentTrigger {
this.isApplicationUpgrade = isApplicationUpgrade;
}
+ ApplicationId applicationId() { return instanceId; }
JobType jobType() { return jobType; }
Instant availableSince() { return availableSince; } // TODO jvenstad: This is 95% broken now. Change.at() can restore it.
boolean isRetry() { return isRetry; }
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 9df0dff3966..8cb5b08bcdb 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
@@ -31,6 +31,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationV
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.TesterCloud;
+import com.yahoo.vespa.hosted.controller.api.integration.deployment.TesterId;
import com.yahoo.vespa.hosted.controller.api.integration.organization.DeploymentFailureMails;
import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
import com.yahoo.vespa.hosted.controller.application.Deployment;
@@ -558,14 +559,6 @@ public class InternalStepRunner implements StepRunner {
private Optional<RunStatus> report(RunId id, DualLogger logger) {
try {
controller.jobController().active(id).ifPresent(run -> {
- JobReport report = JobReport.ofJob(run.id().application(),
- run.id().type(),
- run.id().number(),
- ! run.hasFailed() ? Optional.empty()
- : Optional.of(run.status() == outOfCapacity ? DeploymentJobs.JobError.outOfCapacity
- : DeploymentJobs.JobError.unknown));
- controller.applications().deploymentTrigger().notifyOfCompletion(report);
-
if (run.hasFailed())
sendNotification(run, logger);
});
@@ -580,7 +573,7 @@ public class InternalStepRunner implements StepRunner {
/** Sends a mail with a notification of a failed run, if one should be sent. */
private void sendNotification(Run run, DualLogger logger) {
Application application = controller.applications().requireApplication(TenantAndApplicationId.from(run.id().application()));
- Notifications notifications = application.deploymentSpec().notifications();
+ Notifications notifications = application.deploymentSpec().requireInstance(run.id().application().instance()).notifications();
boolean newCommit = application.change().application()
.map(run.versions().targetApplication()::equals)
.orElse(false);
@@ -657,7 +650,9 @@ public class InternalStepRunner implements StepRunner {
.orElse(zone.region().value().contains("aws-") ?
DEFAULT_TESTER_RESOURCES_AWS : DEFAULT_TESTER_RESOURCES));
byte[] testPackage = controller.applications().applicationStore().getTester(id.application().tenant(), id.application().application(), version);
- byte[] deploymentXml = deploymentXml(spec.athenzDomain(), spec.athenzService(zone.environment(), zone.region()));
+ byte[] deploymentXml = deploymentXml(id.tester(),
+ spec.requireInstance(id.application().instance()).athenzDomain(),
+ spec.requireInstance(id.application().instance()).athenzService(zone.environment(), zone.region()));
try (ZipBuilder zipBuilder = new ZipBuilder(testPackage.length + servicesXml.length + 1000)) {
zipBuilder.add(testPackage);
@@ -779,13 +774,14 @@ public class InternalStepRunner implements StepRunner {
}
/** Returns a dummy deployment xml which sets up the service identity for the tester, if present. */
- private static byte[] deploymentXml(Optional<AthenzDomain> athenzDomain, Optional<AthenzService> athenzService) {
+ private static byte[] deploymentXml(TesterId id, Optional<AthenzDomain> athenzDomain, Optional<AthenzService> athenzService) {
String deploymentSpec =
"<?xml version='1.0' encoding='UTF-8'?>\n" +
"<deployment version=\"1.0\" " +
athenzDomain.map(domain -> "athenz-domain=\"" + domain.value() + "\" ").orElse("") +
- athenzService.map(service -> "athenz-service=\"" + service.value() + "\" ").orElse("")
- + "/>";
+ athenzService.map(service -> "athenz-service=\"" + service.value() + "\" ").orElse("") + ">" +
+ " <instance id=\"" + id.id().instance().value() + "\" />" +
+ "</deployment>";
return deploymentSpec.getBytes(UTF_8);
}
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 c4f394d237b..e6c59b464a6 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java
@@ -1,8 +1,8 @@
// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.deployment;
-import com.google.common.collect.ImmutableMap;
import com.yahoo.component.Version;
+import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.zone.ZoneId;
@@ -10,7 +10,6 @@ import com.yahoo.vespa.curator.Lock;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.Instance;
-import com.yahoo.vespa.hosted.controller.LockedApplication;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
import com.yahoo.vespa.hosted.controller.api.integration.LogEntry;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.NotFoundException;
@@ -38,6 +37,7 @@ import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.NavigableMap;
import java.util.Optional;
import java.util.Set;
import java.util.SortedMap;
@@ -54,6 +54,8 @@ import static com.yahoo.vespa.hosted.controller.deployment.Step.copyVespaLogs;
import static com.yahoo.vespa.hosted.controller.deployment.Step.deactivateTester;
import static com.yahoo.vespa.hosted.controller.deployment.Step.endTests;
import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toUnmodifiableList;
+import static java.util.stream.Collectors.toUnmodifiableMap;
/**
* A singleton owned by the controller, which contains the state and methods for controlling deployment jobs.
@@ -193,7 +195,6 @@ public class JobController {
/** Returns a list of all applications which have registered. */
public List<TenantAndApplicationId> applications() {
return copyOf(controller.applications().asList().stream()
- .filter(Application::internal)
.map(Application::id)
.iterator());
}
@@ -201,7 +202,6 @@ public class JobController {
/** Returns a list of all instances of applications which have registered. */
public List<ApplicationId> instances() {
return copyOf(controller.applications().asList().stream()
- .filter(Application::internal)
.flatMap(application -> application.instances().values().stream())
.map(Instance::id)
.iterator());
@@ -215,10 +215,15 @@ public class JobController {
}
/** Returns an immutable map of all known runs for the given application and job type. */
- public Map<RunId, Run> runs(ApplicationId id, JobType type) {
- SortedMap<RunId, Run> runs = curator.readHistoricRuns(id, type);
+ public NavigableMap<RunId, Run> runs(JobId id) {
+ return runs(id.application(), id.type());
+ }
+
+ /** Returns an immutable map of all known runs for the given application and job type. */
+ public NavigableMap<RunId, Run> runs(ApplicationId id, JobType type) {
+ NavigableMap<RunId, Run> runs = curator.readHistoricRuns(id, type);
last(id, type).ifPresent(run -> runs.put(run.id(), run));
- return ImmutableMap.copyOf(runs);
+ return Collections.unmodifiableNavigableMap(runs);
}
/** Returns the run with the given id, if it exists. */
@@ -238,6 +243,21 @@ public class JobController {
return curator.readLastRun(id, type);
}
+ /** Returns the last completed of the given job. */
+ public Optional<Run> lastCompleted(JobId id) {
+ return JobStatus.lastCompleted(runs(id));
+ }
+
+ /** Returns the first failing of the given job. */
+ public Optional<Run> firstFailing(JobId id) {
+ return JobStatus.firstFailing(runs(id));
+ }
+
+ /** Returns the last success of the given job. */
+ public Optional<Run> lastSuccess(JobId id) {
+ return JobStatus.lastSuccess(runs(id));
+ }
+
/** Returns the run with the given id, provided it is still active. */
public Optional<Run> active(RunId id) {
return last(id.application(), id.type())
@@ -247,9 +267,9 @@ public class JobController {
/** Returns a list of all active runs. */
public List<Run> active() {
- return copyOf(applications().stream()
- .flatMap(id -> active(id).stream())
- .iterator());
+ return controller.applications().idList().stream()
+ .flatMap(id -> active(id).stream())
+ .collect(toUnmodifiableList());
}
/** Returns a list of all active runs for the given instance. */
@@ -262,6 +282,22 @@ public class JobController {
.iterator());
}
+ /** Returns the job status of the given job, possibly empty. */
+ public JobStatus jobStatus(JobId id) {
+ return new JobStatus(id, runs(id));
+ }
+
+ /** Returns the job status of all declared jobs for the given instance id, indexed by job type. */
+ public DeploymentStatus deploymentStatus(Application application) {
+ return new DeploymentStatus(application,
+ application.deploymentSpec().instances().stream()
+ .flatMap(spec -> new DeploymentSteps(spec, controller::system)
+ .jobs().stream()
+ .map(type -> jobStatus(new JobId(application.id().instance(spec.name()), type))))
+ .collect(toUnmodifiableMap(status -> status.id(),
+ status -> status)));
+ }
+
/** Changes the status of the given step, for the given run, provided it is still active. */
public void update(RunId id, RunStatus status, LockedStep step) {
locked(id, run -> run.with(status, step));
@@ -274,11 +310,19 @@ public class JobController {
locked(id.application(), id.type(), runs -> {
runs.put(run.id(), finishedRun);
long last = id.number();
+ long successes = runs.values().stream().filter(old -> old.status() == RunStatus.success).count();
var oldEntries = runs.entrySet().iterator();
for (var old = oldEntries.next();
old.getKey().number() <= last - historyLength
|| old.getValue().start().isBefore(controller.clock().instant().minus(maxHistoryAge));
old = oldEntries.next()) {
+
+ // Make sure we keep the last success and the first failing
+ if (successes == 1 && old.getValue().status() == RunStatus.success) {
+ oldEntries.next();
+ continue;
+ }
+
logs.delete(old.getKey());
oldEntries.remove();
}
@@ -300,9 +344,6 @@ public class JobController {
ApplicationPackage applicationPackage, byte[] testPackageBytes) {
AtomicReference<ApplicationVersion> version = new AtomicReference<>();
controller.applications().lockApplicationOrThrow(id, application -> {
- if ( ! application.get().internal())
- application = registered(application);
-
long run = 1 + application.get().latestVersion()
.map(latestVersion -> latestVersion.buildNumber().getAsLong())
.orElse(0L);
@@ -330,33 +371,12 @@ public class JobController {
return version.get();
}
- /** Registers the given application, copying necessary application packages, and returns the modified version. */
- private LockedApplication registered(LockedApplication application) {
- for (Instance instance : application.get().instances().values()) {
- // TODO jvenstad: Remove when everyone has migrated off SDv3 pipelines. Real soon now!
- // Copy all current packages to the new application store
- instance.productionDeployments().values().stream()
- .map(Deployment::applicationVersion)
- .distinct()
- .forEach(appVersion -> {
- byte[] content = controller.applications().artifacts().getApplicationPackage(instance.id(), appVersion.id());
- controller.applications().applicationStore().put(instance.id().tenant(), instance.id().application(), appVersion, content);
- });
- }
- // Make sure any ongoing upgrade is cancelled, since future jobs will require the tester artifact.
- return application.withChange(application.get().change().withoutPlatform().withoutApplication())
- .withBuiltInternally(true);
- }
-
/** Orders a run of the given type, or throws an IllegalStateException if that job type is already running. */
public void start(ApplicationId id, JobType type, Versions versions) {
if ( ! type.environment().isManuallyDeployed() && versions.targetApplication().isUnknown())
throw new IllegalArgumentException("Target application must be a valid reference.");
controller.applications().lockApplicationIfPresent(TenantAndApplicationId.from(id), application -> {
- if ( ! application.get().internal())
- throw new IllegalArgumentException(id + " is not built here!");
-
locked(id, type, __ -> {
Optional<Run> last = last(id, type);
if (last.flatMap(run -> active(run.id())).isPresent())
@@ -378,9 +398,6 @@ public class JobController {
controller.applications().createApplication(TenantAndApplicationId.from(id), Optional.empty());
controller.applications().lockApplicationOrThrow(TenantAndApplicationId.from(id), application -> {
- if ( ! application.get().internal())
- application = registered(application);
-
if ( ! application.get().instances().containsKey(id.instance()))
application = application.withNewInstance(id.instance());
@@ -415,15 +432,6 @@ public class JobController {
}
}
- /** Unregisters the given application and makes all associated data eligible for garbage collection. */
- public void unregister(TenantAndApplicationId id) {
- controller.applications().lockApplicationIfPresent(id, application -> {
- controller.applications().store(application.withBuiltInternally(false));
- for (InstanceName instance : application.get().instances().keySet())
- jobs(id.instance(instance)).forEach(type -> last(id.instance(instance), type).ifPresent(last -> abort(last.id())));
- });
- }
-
/** Deletes run data and tester deployments for applications which are unknown, or no longer built internally. */
public void collectGarbage() {
Set<ApplicationId> applicationsToBuild = new HashSet<>(instances());
@@ -471,7 +479,9 @@ public class JobController {
/** Returns a URI which points at a badge showing current status for all jobs for the given application. */
public URI overviewBadge(ApplicationId id) {
- DeploymentSteps steps = new DeploymentSteps(controller.applications().requireApplication(TenantAndApplicationId.from(id)).deploymentSpec(), controller::system);
+ DeploymentSteps steps = new DeploymentSteps(controller.applications().requireApplication(TenantAndApplicationId.from(id))
+ .deploymentSpec().requireInstance(id.instance()),
+ controller::system);
return badges.overview(id,
steps.jobs().stream()
.map(type -> last(id, type))
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobList.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobList.java
new file mode 100644
index 00000000000..1ef83153bef
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobList.java
@@ -0,0 +1,141 @@
+// 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.deployment;
+
+import com.yahoo.collections.AbstractFilteringList;
+import com.yahoo.component.Version;
+import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
+import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
+
+import java.time.Instant;
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+/**
+ * A list of deployment jobs that can be filtered in various ways.
+ *
+ * @author jonmv
+ */
+public class JobList extends AbstractFilteringList<JobStatus, JobList> {
+
+ private JobList(Collection<? extends JobStatus> jobs, boolean negate) {
+ super(jobs, negate, JobList::new);
+ }
+
+ // ----------------------------------- Factories
+
+ public static JobList from(Collection<? extends JobStatus> jobs) {
+ return new JobList(jobs, false);
+ }
+
+ // ----------------------------------- Basic filters
+
+ /** Returns the subset of jobs which are currently upgrading */
+ public JobList upgrading() {
+ return matching(job -> job.isRunning()
+ && job.lastSuccess().isPresent()
+ && job.lastSuccess().get().versions().targetPlatform().isBefore(job.lastTriggered().get().versions().targetPlatform()));
+ }
+
+ /** Returns the subset of jobs which are currently failing */
+ public JobList failing() {
+ return matching(job -> ! job.isSuccess());
+ }
+
+ /** Returns the subset of jobs which must be failing due to an application change */
+ public JobList failingApplicationChange() {
+ return matching(JobList::failingApplicationChange);
+ }
+
+ /** Returns the subset of jobs which are failing with the given run status */
+ public JobList withStatus(RunStatus status) {
+ return matching(job -> job.lastStatus().map(status::equals).orElse(false));
+ }
+
+ /** Returns the subset of jobs of the given type -- most useful when negated */
+ public JobList type(JobType... types) {
+ return matching(job -> List.of(types).contains(job.id().type()));
+ }
+
+ /** Returns the subset of jobs of which are production jobs */
+ public JobList production() {
+ return matching(job -> job.id().type().isProduction());
+ }
+
+ // ----------------------------------- JobRun filtering
+
+ /** Returns the list in a state where the next filter is for the lastTriggered run type */
+ public RunFilter lastTriggered() {
+ return new RunFilter(JobStatus::lastTriggered);
+ }
+
+ /** Returns the list in a state where the next filter is for the lastCompleted run type */
+ public RunFilter lastCompleted() {
+ return new RunFilter(JobStatus::lastCompleted);
+ }
+
+ /** Returns the list in a state where the next filter is for the lastSuccess run type */
+ public RunFilter lastSuccess() {
+ return new RunFilter(JobStatus::lastSuccess);
+ }
+
+ /** Returns the list in a state where the next filter is for the firstFailing run type */
+ public RunFilter firstFailing() {
+ return new RunFilter(JobStatus::firstFailing);
+ }
+
+
+ /** Allows sub-filters for runs of the given kind */
+ public class RunFilter {
+
+ private final Function<JobStatus, Optional<Run>> which;
+
+ private RunFilter(Function<JobStatus, Optional<Run>> which) {
+ this.which = which;
+ }
+
+ /** Returns the subset of jobs where the run of the given type exists */
+ public JobList present() {
+ return matching(run -> true);
+ }
+
+ /** Returns the subset of jobs where the run of the given type occurred before the given instant */
+ public JobList startedBefore(Instant threshold) {
+ return matching(run -> run.start().isBefore(threshold));
+ }
+
+ /** Returns the subset of jobs where the run of the given type occurred after the given instant */
+ public JobList startedAfter(Instant threshold) {
+ return matching(run -> run.start().isAfter(threshold));
+ }
+
+ /** Returns the subset of jobs where the run of the given type was on the given version */
+ public JobList on(ApplicationVersion version) {
+ return matching(run -> run.versions().targetApplication().equals(version));
+ }
+
+ /** Returns the subset of jobs where the run of the given type was on the given version */
+ public JobList on(Version version) {
+ return matching(run -> run.versions().targetPlatform().equals(version));
+ }
+
+ /** Transforms the JobRun condition to a JobStatus condition, by considering only the JobRun mapped by which, and executes */
+ private JobList matching(Predicate<Run> condition) {
+ return JobList.this.matching(job -> which.apply(job).filter(condition).isPresent());
+ }
+
+ }
+
+ // ----------------------------------- Internal helpers
+
+ private static boolean failingApplicationChange(JobStatus job) {
+ if (job.isSuccess()) return false;
+ if (job.lastSuccess().isEmpty()) return true; // An application which never succeeded is surely bad.
+ if ( ! job.firstFailing().get().versions().targetPlatform().equals(job.lastSuccess().get().versions().targetPlatform())) return false; // Version change may be to blame.
+ return ! job.firstFailing().get().versions().targetApplication().equals(job.lastSuccess().get().versions().targetApplication()); // Return whether there is an application change.
+ }
+
+}
+
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobStatus.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobStatus.java
new file mode 100644
index 00000000000..52d60aca388
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobStatus.java
@@ -0,0 +1,107 @@
+package com.yahoo.vespa.hosted.controller.deployment;
+
+import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobId;
+import com.yahoo.vespa.hosted.controller.api.integration.deployment.RunId;
+
+import java.util.NavigableMap;
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * Aggregates information about all known runs of a given job to provide the high level status.
+ *
+ * @author jonmv
+ */
+public class JobStatus {
+
+ private final JobId id;
+ private final NavigableMap<RunId, Run> runs;
+ private final Optional<Run> lastTriggered;
+ private final Optional<Run> lastCompleted;
+ private final Optional<Run> lastSuccess;
+ private final Optional<Run> firstFailing;
+
+ public JobStatus(JobId id, NavigableMap<RunId, Run> runs) {
+ this.id = Objects.requireNonNull(id);
+ this.runs = Objects.requireNonNull(runs);
+ this.lastTriggered = runs.descendingMap().values().stream().findFirst();
+ this.lastCompleted = lastCompleted(runs);
+ this.lastSuccess = lastSuccess(runs);
+ this.firstFailing = firstFailing(runs);
+ }
+
+ public JobId id() {
+ return id;
+ }
+
+ public NavigableMap<RunId, Run> runs() {
+ return runs;
+ }
+
+ public Optional<Run> lastTriggered() {
+ return lastTriggered;
+ }
+
+ public Optional<Run> lastCompleted() {
+ return lastCompleted;
+ }
+
+ public Optional<Run> lastSuccess() {
+ return lastSuccess;
+ }
+
+ public Optional<Run> firstFailing() {
+ return firstFailing;
+ }
+
+ public Optional<RunStatus> lastStatus() {
+ return lastCompleted().map(Run::status);
+ }
+
+ public boolean isSuccess() {
+ return lastStatus().isPresent() && lastStatus().get() == RunStatus.success;
+ }
+
+ public boolean isRunning() {
+ return lastTriggered.isPresent() && ! lastTriggered.get().hasEnded();
+ }
+
+ public boolean isOutOfCapacity() {
+ return lastStatus().isPresent() && lastStatus().get() == RunStatus.outOfCapacity;
+ }
+
+ @Override
+ public String toString() {
+ return "JobStatus{" +
+ "id=" + id +
+ ", lastTriggered=" + lastTriggered +
+ ", lastCompleted=" + lastCompleted +
+ ", lastSuccess=" + lastSuccess +
+ ", firstFailing=" + firstFailing +
+ '}';
+ }
+
+ static Optional<Run> lastCompleted(NavigableMap<RunId, Run> runs) {
+ return runs.descendingMap().values().stream()
+ .filter(run -> run.hasEnded())
+ .findFirst();
+ }
+
+ static Optional<Run> lastSuccess(NavigableMap<RunId, Run> runs) {
+ return runs.descendingMap().values().stream()
+ .filter(run -> run.status() == RunStatus.success)
+ .findFirst();
+ }
+
+ static Optional<Run> firstFailing(NavigableMap<RunId, Run> runs) {
+ Run failed = null;
+ loop: for (Run run : runs.descendingMap().values())
+ switch (run.status()) {
+ case running: continue loop;
+ case success: break loop;
+ default: failed = run;
+ }
+ return Optional.ofNullable(failed);
+ }
+
+}
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 d7d6134ccb9..2f9c5ea9e08 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
@@ -179,7 +179,6 @@ public class Run {
", start=" + start +
", end=" + end +
", status=" + status +
- ", steps=" + steps +
'}';
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RunStatus.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RunStatus.java
index ea6cc983b71..4d0b7ef3b90 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RunStatus.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RunStatus.java
@@ -11,7 +11,7 @@ public enum RunStatus {
/** Run is still proceeding normally, i.e., without failures. */
running,
- /** Deployment was rejected due to missing capacity. */
+ /** Deployment was rejected due to lack of capacity. */
outOfCapacity,
/** Deployment of the real application was rejected. */
@@ -29,7 +29,7 @@ public enum RunStatus {
/** Everything completed with great success! */
success,
- /** Run has been abandoned, due to user intervention or timeout. */
+ /** Run was abandoned, due to user intervention or job timeout. */
aborted
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Versions.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Versions.java
index 5d4a380411d..b2b217d0814 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Versions.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Versions.java
@@ -80,11 +80,6 @@ public class Versions {
targetApplication.equals(versions.targetApplication());
}
- public boolean targetsMatch(JobStatus.JobRun jobRun) {
- return targetPlatform.equals(jobRun.platform()) &&
- targetApplication.equals(jobRun.application());
- }
-
@Override
public boolean equals(Object o) {
if (this == o) return true;
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 fc311e2a2af..ffe90b8c44d 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
@@ -4,6 +4,7 @@ package com.yahoo.vespa.hosted.controller.maintenance;
import com.google.inject.Inject;
import com.yahoo.log.LogLevel;
import com.yahoo.vespa.hosted.controller.Controller;
+import com.yahoo.vespa.hosted.controller.application.DeploymentJobs;
import com.yahoo.vespa.hosted.controller.deployment.InternalStepRunner;
import com.yahoo.vespa.hosted.controller.deployment.JobController;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.RunId;
@@ -13,6 +14,7 @@ import com.yahoo.vespa.hosted.controller.deployment.StepRunner;
import org.jetbrains.annotations.TestOnly;
import java.time.Duration;
+import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@@ -21,6 +23,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
+import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.outOfCapacity;
+
/**
* Advances the set of {@link Run}s for a {@link JobController}.
*
@@ -82,7 +86,17 @@ public class JobRunner extends Maintainer {
private void finish(RunId id) {
try {
jobs.finish(id);
- }
+ controller().jobController().run(id).ifPresent(run -> {
+ DeploymentJobs.JobReport report = DeploymentJobs.JobReport.ofJob(run.id().application(),
+ run.id().type(),
+ run.id().number(),
+ ! run.hasFailed() ? Optional.empty()
+ : Optional.of(run.status() == outOfCapacity ? DeploymentJobs.JobError.outOfCapacity
+ : DeploymentJobs.JobError.unknown));
+ controller().applications().deploymentTrigger().notifyOfCompletion(report);
+ });
+
+ }
catch (Exception e) {
log.log(LogLevel.WARNING, "Exception finishing " + id, e);
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployer.java
index 95e1c53f10c..b130f7107dd 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployer.java
@@ -21,8 +21,9 @@ public class OutstandingChangeDeployer extends Maintainer {
@Override
protected void maintain() {
for (Application application : controller().applications().asList()) {
- if (application.outstandingChange().hasTargets()
- && application.deploymentSpec().canChangeRevisionAt(controller().clock().instant())) {
+ if ( application.outstandingChange().hasTargets()
+ && application.deploymentSpec().instances().stream()
+ .allMatch(instance -> instance.canChangeRevisionAt(controller().clock().instant()))) {
controller().applications().deploymentTrigger().triggerChange(application.id(),
application.outstandingChange());
}
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
index d6080bcda6c..98483763a0d 100644
--- 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
@@ -1,6 +1,7 @@
// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.maintenance;
+import com.yahoo.config.application.api.DeploymentInstanceSpec;
import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.zone.ZoneId;
@@ -106,19 +107,21 @@ public class RoutingPolicies {
private void storePoliciesOf(AllocatedLoadBalancers loadBalancers, DeploymentSpec spec, @SuppressWarnings("unused") Lock lock) {
Set<RoutingPolicy> policies = new LinkedHashSet<>(get(loadBalancers.application));
for (LoadBalancer loadBalancer : loadBalancers.list) {
- RoutingPolicy policy = createPolicy(loadBalancers.application, spec, loadBalancers.zone, loadBalancer);
- if (!policies.add(policy)) {
- policies.remove(policy);
- policies.add(policy);
- }
+ spec.instance(loadBalancer.application().instance()).ifPresent(instanceSpec -> {
+ RoutingPolicy policy = createPolicy(loadBalancers.application, instanceSpec, 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, DeploymentSpec deploymentSpec, ZoneId zone,
+ private RoutingPolicy createPolicy(ApplicationId application, DeploymentInstanceSpec instanceSpec, ZoneId zone,
LoadBalancer loadBalancer) {
- var endpoints = endpointIdsOf(loadBalancer, zone, deploymentSpec);
+ var endpoints = endpointIdsOf(loadBalancer, zone, instanceSpec);
var routingPolicy = new RoutingPolicy(application, loadBalancer.cluster(), zone,
loadBalancer.hostname(), loadBalancer.dnsZone(),
endpoints);
@@ -162,9 +165,11 @@ public class RoutingPolicies {
private static Set<RoutingId> routingIdsFrom(AllocatedLoadBalancers loadBalancers, DeploymentSpec spec) {
Set<RoutingId> routingIds = new LinkedHashSet<>();
for (var loadBalancer : loadBalancers.list) {
- for (var endpointId : endpointIdsOf(loadBalancer, loadBalancers.zone, spec)) {
- routingIds.add(new RoutingId(loadBalancer.application(), endpointId));
- }
+ spec.instance(loadBalancer.application().instance()).ifPresent(instanceSpec -> {
+ for (var endpointId : endpointIdsOf(loadBalancer, loadBalancers.zone, instanceSpec)) {
+ routingIds.add(new RoutingId(loadBalancer.application(), endpointId));
+ }
+ });
}
return Collections.unmodifiableSet(routingIds);
}
@@ -183,7 +188,7 @@ public class RoutingPolicies {
}
/** Compute all endpoint IDs of given load balancer */
- private static Set<EndpointId> endpointIdsOf(LoadBalancer loadBalancer, ZoneId zone, DeploymentSpec spec) {
+ private static Set<EndpointId> endpointIdsOf(LoadBalancer loadBalancer, ZoneId zone, DeploymentInstanceSpec spec) {
return spec.endpoints().stream()
.filter(endpoint -> endpoint.containerId().equals(loadBalancer.cluster().value()))
.filter(endpoint -> endpoint.regions().contains(zone.region()))
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java
index c20904710ea..28e276c1497 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java
@@ -49,29 +49,29 @@ public class Upgrader extends Maintainer {
@Override
public void maintain() {
// Determine target versions for each upgrade policy
- Optional<Version> canaryTarget = controller().versionStatus().systemVersion().map(VespaVersion::versionNumber);
+ Version canaryTarget = controller().systemVersion();
Collection<Version> defaultTargets = targetVersions(Confidence.normal);
Collection<Version> conservativeTargets = targetVersions(Confidence.high);
// Cancel upgrades to broken targets (let other ongoing upgrades complete to avoid starvation)
for (VespaVersion version : controller().versionStatus().versions()) {
if (version.confidence() == Confidence.broken)
- cancelUpgradesOf(applications().without(UpgradePolicy.canary).upgradingTo(version.versionNumber()),
+ cancelUpgradesOf(applications().not().with(UpgradePolicy.canary).upgradingTo(version.versionNumber()),
version.versionNumber() + " is broken");
}
// Canaries should always try the canary target
- cancelUpgradesOf(applications().with(UpgradePolicy.canary).upgrading().notUpgradingTo(canaryTarget),
+ cancelUpgradesOf(applications().with(UpgradePolicy.canary).upgrading().not().upgradingTo(canaryTarget),
"Outdated target version for Canaries");
// Cancel *failed* upgrades to earlier versions, as the new version may fix it
String reason = "Failing on outdated version";
- cancelUpgradesOf(applications().with(UpgradePolicy.defaultPolicy).upgrading().failing().notUpgradingTo(defaultTargets), reason);
- cancelUpgradesOf(applications().with(UpgradePolicy.conservative).upgrading().failing().notUpgradingTo(conservativeTargets), reason);
+ cancelUpgradesOf(applications().with(UpgradePolicy.defaultPolicy).upgrading().failing().not().upgradingTo(defaultTargets), reason);
+ cancelUpgradesOf(applications().with(UpgradePolicy.conservative).upgrading().failing().not().upgradingTo(conservativeTargets), reason);
// Schedule the right upgrades
ApplicationList applications = applications();
- canaryTarget.ifPresent(target -> upgrade(applications.with(UpgradePolicy.canary), target));
+ upgrade(applications.with(UpgradePolicy.canary), canaryTarget);
defaultTargets.forEach(target -> upgrade(applications.with(UpgradePolicy.defaultPolicy), target));
conservativeTargets.forEach(target -> upgrade(applications.with(UpgradePolicy.conservative), target));
}
@@ -98,14 +98,13 @@ public class Upgrader extends Maintainer {
applications = applications.withProductionDeployment();
applications = applications.onLowerVersionThan(version);
applications = applications.allowMajorVersion(version.getMajor(), targetMajorVersion().orElse(version.getMajor()));
- applications = applications.notDeploying(); // wait with applications deploying an application change or already upgrading
- applications = applications.notFailingOn(version); // try to upgrade only if it hasn't failed on this version
+ applications = applications.not().deploying(); // wait with applications deploying an application change or already upgrading
+ applications = applications.not().failingOn(version); // try to upgrade only if it hasn't failed on this version
applications = applications.canUpgradeAt(controller().clock().instant()); // wait with applications that are currently blocking upgrades
applications = applications.byIncreasingDeployedVersion(); // start with lowest versions
- if (!containsOnlyCanaries(applications)) { // throttle upgrades of non-canaries
- applications = applications.first(numberOfApplicationsToUpgrade());
- }
- for (Application application : applications.asList())
+ for (Application application : applications.with(UpgradePolicy.canary).asList())
+ controller().applications().deploymentTrigger().triggerChange(application.id(), Change.of(version));
+ for (Application application : applications.not().with(UpgradePolicy.canary).first(numberOfApplicationsToUpgrade()).asList())
controller().applications().deploymentTrigger().triggerChange(application.id(), Change.of(version));
}
@@ -173,9 +172,4 @@ public class Upgrader extends Maintainer {
controller().removeConfidenceOverride(version::equals);
}
- /** Returns whether all given applications are canaries */
- private static boolean containsOnlyCanaries(ApplicationList applications) {
- return applications.asList().stream().allMatch(application -> application.deploymentSpec().upgradePolicy() == UpgradePolicy.canary);
- }
-
}
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 356a20f6eba..79296476aaa 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
@@ -2,10 +2,13 @@
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.component.Version;
+import com.yahoo.config.application.api.DeploymentInstanceSpec;
import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.application.api.ValidationOverrides;
import com.yahoo.config.provision.ClusterSpec;
+import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.InstanceName;
+import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.security.KeyUtils;
import com.yahoo.slime.ArrayTraverser;
@@ -42,6 +45,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
@@ -175,7 +179,7 @@ public class ApplicationSerializer {
application.projectId().ifPresent(projectId -> root.setLong(projectIdField, projectId));
application.deploymentIssueId().ifPresent(jiraIssueId -> root.setString(deploymentIssueField, jiraIssueId.value()));
application.ownershipIssueId().ifPresent(issueId -> root.setString(ownershipIssueIdField, issueId.value()));
- root.setBool(builtInternallyField, application.internal());
+ root.setBool(builtInternallyField, true); // TODO jonmv: remove when the change with this comment has deployed.
toSlime(application.change(), root, deployingField);
toSlime(application.outstandingChange(), root, outstandingChangeField);
application.owner().ifPresent(owner -> root.setString(ownerField, owner.username()));
@@ -282,8 +286,7 @@ public class ApplicationSerializer {
private void jobStatusToSlime(Collection<JobStatus> jobStatuses, Cursor jobStatusArray) {
for (JobStatus jobStatus : jobStatuses)
- if (jobStatus.type() != JobType.component)
- toSlime(jobStatus, jobStatusArray.addObject());
+ toSlime(jobStatus, jobStatusArray.addObject());
}
private void toSlime(JobStatus jobStatus, Cursor object) {
@@ -366,11 +369,10 @@ public class ApplicationSerializer {
List<Instance> instances = instancesFromSlime(id, deploymentSpec, root.field(instancesField));
OptionalLong projectId = Serializers.optionalLong(root.field(projectIdField));
Optional<ApplicationVersion> latestVersion = latestVersionFromSlime(root.field(latestVersionField));
- boolean builtInternally = root.field(builtInternallyField).asBool();
return new Application(id, createdAt, deploymentSpec, validationOverrides, deploying, outstandingChange,
deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics,
- deployKeys, projectId, builtInternally, latestVersion, instances);
+ deployKeys, projectId, latestVersion, instances);
}
private Optional<ApplicationVersion> latestVersionFromSlime(Inspector latestVersionObject) {
@@ -386,7 +388,7 @@ public class ApplicationSerializer {
InstanceName instanceName = InstanceName.from(object.field(instanceNameField).asString());
List<Deployment> deployments = deploymentsFromSlime(object.field(deploymentsField));
DeploymentJobs deploymentJobs = deploymentJobsFromSlime(object.field(deploymentJobsField));
- List<AssignedRotation> assignedRotations = assignedRotationsFromSlime(deploymentSpec, object);
+ List<AssignedRotation> assignedRotations = assignedRotationsFromSlime(deploymentSpec, instanceName, object);
RotationStatus rotationStatus = rotationStatusFromSlime(object);
instances.add(new Instance(id.instance(instanceName),
deployments,
@@ -570,21 +572,32 @@ public class ApplicationSerializer {
Instant.ofEpochMilli(object.field(atField).asLong())));
}
- private List<AssignedRotation> assignedRotationsFromSlime(DeploymentSpec deploymentSpec, Inspector root) {
+ private List<AssignedRotation> assignedRotationsFromSlime(DeploymentSpec deploymentSpec, InstanceName instance, Inspector root) {
var assignedRotations = new LinkedHashMap<EndpointId, AssignedRotation>();
root.field(assignedRotationsField).traverse((ArrayTraverser) (idx, inspector) -> {
var clusterId = new ClusterSpec.Id(inspector.field(assignedRotationClusterField).asString());
var endpointId = EndpointId.of(inspector.field(assignedRotationEndpointField).asString());
var rotationId = new RotationId(inspector.field(assignedRotationRotationField).asString());
- var regions = deploymentSpec.endpoints().stream()
- .filter(endpoint -> endpoint.endpointId().equals(endpointId.id()))
- .flatMap(endpoint -> endpoint.regions().stream())
- .collect(Collectors.toSet());
+ var regions = deploymentSpec.instance(instance)
+ .map(spec -> globalEndpointRegions(spec, endpointId))
+ .orElse(Set.of());
assignedRotations.putIfAbsent(endpointId, new AssignedRotation(clusterId, endpointId, rotationId, regions));
});
return List.copyOf(assignedRotations.values());
}
+ private Set<RegionName> globalEndpointRegions(DeploymentInstanceSpec spec, EndpointId endpointId) {
+ if (spec.globalServiceId().isPresent())
+ return spec.zones().stream()
+ .flatMap(zone -> zone.region().stream())
+ .collect(Collectors.toSet());
+
+ return spec.endpoints().stream()
+ .filter(endpoint -> endpoint.endpointId().equals(endpointId.id()))
+ .flatMap(endpoint -> endpoint.regions().stream())
+ .collect(Collectors.toSet());
+ }
+
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java
index dbd52fc6d02..637da842d2f 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
@@ -39,6 +39,7 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.NavigableMap;
import java.util.Optional;
import java.util.Set;
import java.util.SortedMap;
@@ -55,6 +56,7 @@ import java.util.stream.Stream;
import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.collectingAndThen;
+import static java.util.stream.Collectors.toUnmodifiableList;
/**
* Curator backed database for storing the persistence state of controllers. This maps controller specific operations
@@ -351,16 +353,18 @@ public class CuratorDb {
}
private List<Application> readApplications(Predicate<TenantAndApplicationId> applicationFilter) {
- return readApplicationIds().filter(applicationFilter)
+ return readApplicationIds().stream()
+ .filter(applicationFilter)
.sorted()
.map(this::readApplication)
.flatMap(Optional::stream)
.collect(Collectors.toUnmodifiableList());
}
- private Stream<TenantAndApplicationId> readApplicationIds() {
+ public List<TenantAndApplicationId> readApplicationIds() {
return curator.getChildren(applicationRoot).stream()
- .map(TenantAndApplicationId::fromSerialized);
+ .map(TenantAndApplicationId::fromSerialized)
+ .collect(toUnmodifiableList());
}
public void removeApplication(TenantAndApplicationId id) {
@@ -381,7 +385,7 @@ public class CuratorDb {
return readSlime(lastRunPath(id, type)).map(runSerializer::runFromSlime);
}
- public SortedMap<RunId, Run> readHistoricRuns(ApplicationId id, JobType type) {
+ public NavigableMap<RunId, Run> readHistoricRuns(ApplicationId id, JobType type) {
return readSlime(runsPath(id, type)).map(runSerializer::runsFromSlime).orElse(new TreeMap<>(comparing(RunId::number)));
}
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 dda1fb881a7..b84df02e583 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
@@ -22,6 +22,7 @@ import com.yahoo.vespa.hosted.controller.deployment.Versions;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.EnumMap;
+import java.util.NavigableMap;
import java.util.Optional;
import java.util.SortedMap;
import java.util.TreeMap;
@@ -90,8 +91,8 @@ class RunSerializer {
return runFromSlime(slime.get());
}
- SortedMap<RunId, Run> runsFromSlime(Slime slime) {
- SortedMap<RunId, Run> runs = new TreeMap<>(comparing(RunId::number));
+ NavigableMap<RunId, Run> runsFromSlime(Slime slime) {
+ NavigableMap<RunId, Run> runs = new TreeMap<>(comparing(RunId::number));
Inspector runArray = slime.get();
runArray.traverse((ArrayTraverser) (__, runObject) -> {
Run run = runFromSlime(runObject);
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 e3c048e865a..dd43195f67d 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
@@ -2,9 +2,10 @@
package com.yahoo.vespa.hosted.controller.proxy;
import com.google.inject.Inject;
-import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.component.AbstractComponent;
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.hosted.controller.api.integration.zone.ZoneRegistry;
@@ -23,8 +24,10 @@ import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLSession;
import java.io.IOException;
import java.io.InputStream;
+import java.io.UncheckedIOException;
import java.net.URI;
import java.time.Duration;
import java.util.ArrayList;
@@ -33,7 +36,9 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
+import java.util.stream.Collectors;
import static com.yahoo.yolean.Exceptions.uncheck;
@@ -43,33 +48,37 @@ import static com.yahoo.yolean.Exceptions.uncheck;
* @author bjorncs
*/
@SuppressWarnings("unused") // Injected
-public class ConfigServerRestExecutorImpl implements ConfigServerRestExecutor {
+public class ConfigServerRestExecutorImpl extends AbstractComponent implements ConfigServerRestExecutor {
private static final Logger log = Logger.getLogger(ConfigServerRestExecutorImpl.class.getName());
private static final Duration PROXY_REQUEST_TIMEOUT = Duration.ofSeconds(10);
private static final Set<String> HEADERS_TO_COPY = Set.of("X-HTTP-Method-Override", "Content-Type");
- private final ZoneRegistry zoneRegistry;
- private final ServiceIdentityProvider sslContextProvider;
+ private final CloseableHttpClient client;
@Inject
public ConfigServerRestExecutorImpl(ZoneRegistry zoneRegistry, ServiceIdentityProvider sslContextProvider) {
- this.zoneRegistry = zoneRegistry;
- this.sslContextProvider = sslContextProvider;
+ RequestConfig config = RequestConfig.custom()
+ .setConnectTimeout((int) PROXY_REQUEST_TIMEOUT.toMillis())
+ .setConnectionRequestTimeout((int) PROXY_REQUEST_TIMEOUT.toMillis())
+ .setSocketTimeout((int) PROXY_REQUEST_TIMEOUT.toMillis()).build();
+
+ this.client = createHttpClient(config, sslContextProvider,
+ new ControllerOrConfigserverHostnameVerifier(zoneRegistry));
}
@Override
public ProxyResponse handle(ProxyRequest proxyRequest) throws ProxyException {
- HostnameVerifier hostnameVerifier = createHostnameVerifier(proxyRequest.getZoneId());
- List<URI> allServers = getConfigserverEndpoints(proxyRequest.getZoneId());
+ // Make a local copy of the list as we want to manipulate it in case of ping problems.
+ List<URI> allServers = new ArrayList<>(proxyRequest.getTargets());
StringBuilder errorBuilder = new StringBuilder();
- if (queueFirstServerIfDown(allServers, hostnameVerifier)) {
+ if (queueFirstServerIfDown(allServers)) {
errorBuilder.append("Change ordering due to failed ping.");
}
for (URI uri : allServers) {
- Optional<ProxyResponse> proxyResponse = proxyCall(uri, proxyRequest, hostnameVerifier, errorBuilder);
+ Optional<ProxyResponse> proxyResponse = proxyCall(uri, proxyRequest, errorBuilder);
if (proxyResponse.isPresent()) {
return proxyResponse.get();
}
@@ -79,32 +88,14 @@ public class ConfigServerRestExecutorImpl implements ConfigServerRestExecutor {
+ errorBuilder.toString()));
}
- private List<URI> getConfigserverEndpoints(ZoneId zoneId) {
- // TODO: Use config server VIP for all zones that have one
- // Make a local copy of the list as we want to manipulate it in case of ping problems.
- if (zoneId.region().value().startsWith("aws-") || zoneId.region().value().contains("-aws-")) {
- return List.of(zoneRegistry.getConfigServerVipUri(zoneId));
- } else {
- return new ArrayList<>(zoneRegistry.getConfigServerUris(zoneId));
- }
- }
-
- private Optional<ProxyResponse> proxyCall(
- URI uri, ProxyRequest proxyRequest, HostnameVerifier hostnameVerifier, StringBuilder errorBuilder)
+ private Optional<ProxyResponse> proxyCall(URI uri, ProxyRequest proxyRequest, StringBuilder errorBuilder)
throws ProxyException {
final HttpRequestBase requestBase = createHttpBaseRequest(
proxyRequest.getMethod(), proxyRequest.createConfigServerRequestUri(uri), proxyRequest.getData());
// Empty list of headers to copy for now, add headers when needed, or rewrite logic.
copyHeaders(proxyRequest.getHeaders(), requestBase);
- RequestConfig config = RequestConfig.custom()
- .setConnectTimeout((int) PROXY_REQUEST_TIMEOUT.toMillis())
- .setConnectionRequestTimeout((int) PROXY_REQUEST_TIMEOUT.toMillis())
- .setSocketTimeout((int) PROXY_REQUEST_TIMEOUT.toMillis()).build();
- try (
- CloseableHttpClient client = createHttpClient(config, sslContextProvider, hostnameVerifier);
- CloseableHttpResponse response = client.execute(requestBase)
- ) {
+ try (CloseableHttpResponse response = client.execute(requestBase)) {
String content = getContent(response);
int status = response.getStatusLine().getStatusCode();
if (status / 100 == 5) {
@@ -182,7 +173,7 @@ public class ConfigServerRestExecutorImpl implements ConfigServerRestExecutor {
* if it is not responding, we try the other servers first. False positive/negatives are not critical,
* but will increase latency to some extent.
*/
- private boolean queueFirstServerIfDown(List<URI> allServers, HostnameVerifier hostnameVerifier) {
+ private boolean queueFirstServerIfDown(List<URI> allServers) {
if (allServers.size() < 2) {
return false;
}
@@ -194,10 +185,8 @@ public class ConfigServerRestExecutorImpl implements ConfigServerRestExecutor {
.setConnectTimeout(timeout)
.setConnectionRequestTimeout(timeout)
.setSocketTimeout(timeout).build();
- try (
- CloseableHttpClient client = createHttpClient(config, sslContextProvider, hostnameVerifier);
- CloseableHttpResponse response = client.execute(httpget)
- ) {
+ httpget.setConfig(config);
+ try (CloseableHttpResponse response = client.execute(httpget)) {
if (response.getStatusLine().getStatusCode() == 200) {
return false;
}
@@ -210,8 +199,13 @@ public class ConfigServerRestExecutorImpl implements ConfigServerRestExecutor {
return true;
}
- private HostnameVerifier createHostnameVerifier(ZoneId zoneId) {
- return new AthenzIdentityVerifier(Set.of(zoneRegistry.getConfigServerHttpsIdentity(zoneId)));
+ @Override
+ public void deconstruct() {
+ try {
+ client.close();
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
}
private static CloseableHttpClient createHttpClient(RequestConfig config,
@@ -222,7 +216,30 @@ public class ConfigServerRestExecutorImpl implements ConfigServerRestExecutor {
.setSslcontext(sslContextProvider.getIdentitySslContext())
.setSSLHostnameVerifier(hostnameVerifier)
.setDefaultRequestConfig(config)
+ .setMaxConnPerRoute(10)
+ .setMaxConnTotal(500)
+ .setConnectionTimeToLive(1, TimeUnit.MINUTES)
.build();
}
+ private static class ControllerOrConfigserverHostnameVerifier implements HostnameVerifier {
+
+ private final HostnameVerifier configserverVerifier;
+
+ ControllerOrConfigserverHostnameVerifier(ZoneRegistry registry) {
+ this.configserverVerifier = createConfigserverVerifier(registry);
+ }
+
+ private static HostnameVerifier createConfigserverVerifier(ZoneRegistry registry) {
+ Set<AthenzIdentity> configserverIdentities = registry.zones().all().zones().stream()
+ .map(zone -> registry.getConfigServerHttpsIdentity(zone.getId()))
+ .collect(Collectors.toSet());
+ return new AthenzIdentityVerifier(configserverIdentities);
+ }
+
+ @Override
+ public boolean verify(String hostname, SSLSession session) {
+ return "localhost".equals(hostname) || configserverVerifier.verify(hostname, session);
+ }
+ }
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequest.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequest.java
index 100292a0bdc..f398683567b 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequest.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequest.java
@@ -1,7 +1,6 @@
// 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.proxy;
-import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.container.jdisc.HttpRequest;
import java.io.InputStream;
@@ -26,36 +25,36 @@ public class ProxyRequest {
private final Map<String, List<String>> headers;
private final InputStream requestData;
- private final ZoneId zoneId;
- private final String proxyPath;
+ private final List<URI> targets;
+ private final String targetPath;
/**
* The constructor calls exception if the request is invalid.
*
* @param request the request from the jdisc framework.
- * @param zoneId the zone to proxy to.
- * @param proxyPath the path to proxy to.
+ * @param targets list of targets this request should be proxied to (targets are tried once in order until a response is returned).
+ * @param targetPath the path to proxy to.
* @throws ProxyException on errors
*/
- public ProxyRequest(HttpRequest request, ZoneId zoneId, String proxyPath) throws ProxyException {
+ public ProxyRequest(HttpRequest request, List<URI> targets, String targetPath) throws ProxyException {
this(request.getMethod(), request.getUri(), request.getJDiscRequest().headers(), request.getData(),
- zoneId, proxyPath);
+ targets, targetPath);
}
ProxyRequest(Method method, URI requestUri, Map<String, List<String>> headers, InputStream body,
- ZoneId zoneId, String proxyPath) throws ProxyException {
+ List<URI> targets, String targetPath) throws ProxyException {
Objects.requireNonNull(requestUri, "Request must be non-null");
- if (!requestUri.getPath().endsWith(proxyPath))
+ if (!requestUri.getPath().endsWith(targetPath))
throw new ProxyException(ErrorResponse.badRequest(String.format(
- "Request path '%s' does not end with proxy path '%s'", requestUri.getPath(), proxyPath)));
+ "Request path '%s' does not end with proxy path '%s'", requestUri.getPath(), targetPath)));
this.method = Objects.requireNonNull(method);
this.requestUri = Objects.requireNonNull(requestUri);
this.headers = Objects.requireNonNull(headers);
this.requestData = body;
- this.zoneId = Objects.requireNonNull(zoneId);
- this.proxyPath = proxyPath.startsWith("/") ? proxyPath : "/" + proxyPath;
+ this.targets = List.copyOf(targets);
+ this.targetPath = targetPath.startsWith("/") ? targetPath : "/" + targetPath;
}
@@ -71,23 +70,23 @@ public class ProxyRequest {
return requestData;
}
- public ZoneId getZoneId() {
- return zoneId;
+ public List<URI> getTargets() {
+ return targets;
}
public URI createConfigServerRequestUri(URI baseURI) {
try {
return new URI(baseURI.getScheme(), baseURI.getUserInfo(), baseURI.getHost(),
- baseURI.getPort(), proxyPath, requestUri.getQuery(), requestUri.getFragment());
+ baseURI.getPort(), targetPath, requestUri.getQuery(), requestUri.getFragment());
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
public URI getControllerPrefixUri() {
- String prefixPath = proxyPath.equals("/") && !requestUri.getPath().endsWith("/") ?
- requestUri.getPath() + proxyPath :
- requestUri.getPath().substring(0, requestUri.getPath().length() - proxyPath.length() + 1);
+ String prefixPath = targetPath.equals("/") && !requestUri.getPath().endsWith("/") ?
+ requestUri.getPath() + targetPath :
+ requestUri.getPath().substring(0, requestUri.getPath().length() - targetPath.length() + 1);
try {
return new URI(requestUri.getScheme(), requestUri.getUserInfo(), requestUri.getHost(),
requestUri.getPort(), prefixPath, null, null);
@@ -98,7 +97,7 @@ public class ProxyRequest {
@Override
public String toString() {
- return "[zone: " + zoneId + " request: " + proxyPath + "]";
+ return "[targets: " + targets + " request: " + targetPath + "]";
}
}
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 e55b08d0b4a..c8f5720327a 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
@@ -57,7 +57,6 @@ import com.yahoo.vespa.hosted.controller.api.integration.resource.CostInfo;
import com.yahoo.vespa.hosted.controller.api.integration.resource.MeteringInfo;
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.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.Change;
@@ -72,6 +71,7 @@ import com.yahoo.vespa.hosted.controller.application.JobStatus;
import com.yahoo.vespa.hosted.controller.application.RoutingPolicy;
import com.yahoo.vespa.hosted.controller.application.SystemApplication;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
+import com.yahoo.vespa.hosted.controller.deployment.DeploymentSteps;
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;
@@ -290,11 +290,9 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
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}/key")) return removeDeployKey(path.get("tenant"), path.get("application"), request);
- 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}/instance/{instance}")) return deleteInstance(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"), "all");
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/deploying/{choice}")) return cancelDeploy(path.get("tenant"), path.get("application"), 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"));
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);
@@ -718,35 +716,38 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
private void toSlime(Cursor object, Instance instance, DeploymentSpec deploymentSpec, HttpRequest request) {
object.setString("instance", instance.name().value());
- // Jobs sorted according to deployment spec
- List<JobStatus> jobStatus = controller.applications().deploymentTrigger()
- .steps(deploymentSpec)
- .sortedJobs(instance.deploymentJobs().jobStatus().values());
-
-
- Cursor deploymentJobsArray = object.setArray("deploymentJobs");
- for (JobStatus job : jobStatus) {
- Cursor jobObject = deploymentJobsArray.addObject();
- jobObject.setString("type", job.type().jobName());
- jobObject.setBool("success", job.isSuccess());
- job.lastTriggered().ifPresent(jobRun -> toSlime(jobRun, jobObject.setObject("lastTriggered")));
- job.lastCompleted().ifPresent(jobRun -> toSlime(jobRun, jobObject.setObject("lastCompleted")));
- job.firstFailing().ifPresent(jobRun -> toSlime(jobRun, jobObject.setObject("firstFailing")));
- job.lastSuccess().ifPresent(jobRun -> toSlime(jobRun, jobObject.setObject("lastSuccess")));
- }
- // Change blockers
- Cursor changeBlockers = object.setArray("changeBlockers");
- deploymentSpec.changeBlocker().forEach(changeBlocker -> {
- Cursor changeBlockerObject = changeBlockers.addObject();
- changeBlockerObject.setBool("versions", changeBlocker.blocksVersions());
- changeBlockerObject.setBool("revisions", changeBlocker.blocksRevisions());
- changeBlockerObject.setString("timeZone", changeBlocker.window().zone().getId());
- Cursor days = changeBlockerObject.setArray("days");
- changeBlocker.window().days().stream().map(DayOfWeek::getValue).forEach(days::addLong);
- Cursor hours = changeBlockerObject.setArray("hours");
- changeBlocker.window().hours().forEach(hours::addLong);
- });
+ if (deploymentSpec.instance(instance.name()).isPresent()) {
+ // Jobs sorted according to deployment spec
+ List<JobStatus> jobStatus = controller.applications().deploymentTrigger()
+ .steps(deploymentSpec.requireInstance(instance.name()))
+ .sortedJobs(instance.deploymentJobs().jobStatus().values());
+
+
+ Cursor deploymentJobsArray = object.setArray("deploymentJobs");
+ for (JobStatus job : jobStatus) {
+ Cursor jobObject = deploymentJobsArray.addObject();
+ jobObject.setString("type", job.type().jobName());
+ jobObject.setBool("success", job.isSuccess());
+ job.lastTriggered().ifPresent(jobRun -> toSlime(jobRun, jobObject.setObject("lastTriggered")));
+ job.lastCompleted().ifPresent(jobRun -> toSlime(jobRun, jobObject.setObject("lastCompleted")));
+ job.firstFailing().ifPresent(jobRun -> toSlime(jobRun, jobObject.setObject("firstFailing")));
+ job.lastSuccess().ifPresent(jobRun -> toSlime(jobRun, jobObject.setObject("lastSuccess")));
+ }
+
+ // Change blockers
+ Cursor changeBlockers = object.setArray("changeBlockers");
+ deploymentSpec.instance(instance.name()).ifPresent(spec -> spec.changeBlocker().forEach(changeBlocker -> {
+ Cursor changeBlockerObject = changeBlockers.addObject();
+ changeBlockerObject.setBool("versions", changeBlocker.blocksVersions());
+ changeBlockerObject.setBool("revisions", changeBlocker.blocksRevisions());
+ changeBlockerObject.setString("timeZone", changeBlocker.window().zone().getId());
+ Cursor days = changeBlockerObject.setArray("days");
+ changeBlocker.window().days().stream().map(DayOfWeek::getValue).forEach(days::addLong);
+ Cursor hours = changeBlockerObject.setArray("hours");
+ changeBlocker.window().hours().forEach(hours::addLong);
+ }));
+ }
// Rotation
Cursor globalRotationsArray = object.setArray("globalRotations");
@@ -773,9 +774,10 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
}
// Deployments sorted according to deployment spec
- List<Deployment> deployments = controller.applications().deploymentTrigger()
- .steps(deploymentSpec)
- .sortedDeployments(instance.deployments().values());
+ List<Deployment> deployments = deploymentSpec.instance(instance.name())
+ .map(spec -> new DeploymentSteps(spec, controller::system))
+ .map(steps -> steps.sortedDeployments(instance.deployments().values()))
+ .orElse(List.copyOf(instance.deployments().values()));
Cursor deploymentsArray = object.setArray("deployments");
for (Deployment deployment : deployments) {
@@ -823,36 +825,37 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
toSlime(object.setObject("outstandingChange"), application.outstandingChange());
}
- // Jobs sorted according to deployment spec
- List<JobStatus> jobStatus = controller.applications().deploymentTrigger()
- .steps(application.deploymentSpec())
- .sortedJobs(instance.deploymentJobs().jobStatus().values());
-
- object.setBool("deployedInternally", application.internal());
- Cursor deploymentsArray = object.setArray("deploymentJobs");
- for (JobStatus job : jobStatus) {
- Cursor jobObject = deploymentsArray.addObject();
- jobObject.setString("type", job.type().jobName());
- jobObject.setBool("success", job.isSuccess());
-
- job.lastTriggered().ifPresent(jobRun -> toSlime(jobRun, jobObject.setObject("lastTriggered")));
- job.lastCompleted().ifPresent(jobRun -> toSlime(jobRun, jobObject.setObject("lastCompleted")));
- job.firstFailing().ifPresent(jobRun -> toSlime(jobRun, jobObject.setObject("firstFailing")));
- job.lastSuccess().ifPresent(jobRun -> toSlime(jobRun, jobObject.setObject("lastSuccess")));
- }
+ if (application.deploymentSpec().instance(instance.name()).isPresent()) {
+ // Jobs sorted according to deployment spec
+ List<JobStatus> jobStatus = controller.applications().deploymentTrigger()
+ .steps(application.deploymentSpec().requireInstance(instance.name()))
+ .sortedJobs(instance.deploymentJobs().jobStatus().values());
+
+ Cursor deploymentsArray = object.setArray("deploymentJobs");
+ for (JobStatus job : jobStatus) {
+ Cursor jobObject = deploymentsArray.addObject();
+ jobObject.setString("type", job.type().jobName());
+ jobObject.setBool("success", job.isSuccess());
+
+ job.lastTriggered().ifPresent(jobRun -> toSlime(jobRun, jobObject.setObject("lastTriggered")));
+ job.lastCompleted().ifPresent(jobRun -> toSlime(jobRun, jobObject.setObject("lastCompleted")));
+ job.firstFailing().ifPresent(jobRun -> toSlime(jobRun, jobObject.setObject("firstFailing")));
+ job.lastSuccess().ifPresent(jobRun -> toSlime(jobRun, jobObject.setObject("lastSuccess")));
+ }
- // Change blockers
- Cursor changeBlockers = object.setArray("changeBlockers");
- application.deploymentSpec().changeBlocker().forEach(changeBlocker -> {
- Cursor changeBlockerObject = changeBlockers.addObject();
- changeBlockerObject.setBool("versions", changeBlocker.blocksVersions());
- changeBlockerObject.setBool("revisions", changeBlocker.blocksRevisions());
- changeBlockerObject.setString("timeZone", changeBlocker.window().zone().getId());
- Cursor days = changeBlockerObject.setArray("days");
- changeBlocker.window().days().stream().map(DayOfWeek::getValue).forEach(days::addLong);
- Cursor hours = changeBlockerObject.setArray("hours");
- changeBlocker.window().hours().forEach(hours::addLong);
- });
+ // Change blockers
+ Cursor changeBlockers = object.setArray("changeBlockers");
+ application.deploymentSpec().instance(instance.name()).ifPresent(spec -> spec.changeBlocker().forEach(changeBlocker -> {
+ Cursor changeBlockerObject = changeBlockers.addObject();
+ changeBlockerObject.setBool("versions", changeBlocker.blocksVersions());
+ changeBlockerObject.setBool("revisions", changeBlocker.blocksRevisions());
+ changeBlockerObject.setString("timeZone", changeBlocker.window().zone().getId());
+ Cursor days = changeBlockerObject.setArray("days");
+ changeBlocker.window().days().stream().map(DayOfWeek::getValue).forEach(days::addLong);
+ Cursor hours = changeBlockerObject.setArray("hours");
+ changeBlocker.window().hours().forEach(hours::addLong);
+ }));
+ }
// Compile version. The version that should be used when building an application
object.setString("compileVersion", compileVersion(application.id()).toFullString());
@@ -884,9 +887,11 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
}
// Deployments sorted according to deployment spec
- List<Deployment> deployments = controller.applications().deploymentTrigger()
- .steps(application.deploymentSpec())
- .sortedDeployments(instance.deployments().values());
+ List<Deployment> deployments =
+ application.deploymentSpec().instance(instance.name())
+ .map(spec -> new DeploymentSteps(spec, controller::system))
+ .map(steps -> steps.sortedDeployments(instance.deployments().values()))
+ .orElse(List.copyOf(instance.deployments().values()));
Cursor instancesArray = object.setArray("instances");
for (Deployment deployment : deployments) {
Cursor deploymentObject = instancesArray.addObject();
@@ -1116,16 +1121,15 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
Slime slime = new Slime();
Cursor array = slime.setObject().setArray("globalrotationoverride");
- Map<RoutingEndpoint, EndpointStatus> status = controller.applications().globalRotationStatus(deploymentId);
- for (RoutingEndpoint endpoint : status.keySet()) {
- EndpointStatus currentStatus = status.get(endpoint);
- array.addString(endpoint.upstreamName());
- Cursor statusObject = array.addObject();
- statusObject.setString("status", currentStatus.getStatus().name());
- statusObject.setString("reason", currentStatus.getReason() == null ? "" : currentStatus.getReason());
- statusObject.setString("agent", currentStatus.getAgent() == null ? "" : currentStatus.getAgent());
- statusObject.setLong("timestamp", currentStatus.getEpoch());
- }
+ controller.applications().globalRotationStatus(deploymentId)
+ .forEach((endpoint, status) -> {
+ array.addString(endpoint.upstreamName());
+ Cursor statusObject = array.addObject();
+ statusObject.setString("status", status.getStatus().name());
+ statusObject.setString("reason", status.getReason() == null ? "" : status.getReason());
+ statusObject.setString("agent", status.getAgent() == null ? "" : status.getAgent());
+ statusObject.setLong("timestamp", status.getEpoch());
+ });
return new SlimeJsonResponse(slime);
}
@@ -1472,7 +1476,6 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
applicationVersion = Optional.of(ApplicationVersion.from(toSourceRevision(sourceRevision),
buildNumber.asLong()));
applicationPackage = Optional.of(controller.applications().getApplicationPackage(applicationId,
- application.get().internal(),
applicationVersion.get()));
}
@@ -1496,7 +1499,6 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
applicationVersion = Optional.of(version);
vespaVersion = Optional.of(deployment.get().version());
applicationPackage = Optional.of(controller.applications().getApplicationPackage(applicationId,
- application.get().internal(),
applicationVersion.get()));
}
@@ -1569,12 +1571,6 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
private HttpResponse notifyJobCompletion(String tenant, String application, HttpRequest request) {
try {
DeploymentJobs.JobReport report = toJobReport(tenant, application, toSlime(request.getData()).get());
- if ( report.jobType() == JobType.component
- && controller.applications().requireApplication(TenantAndApplicationId.from(report.applicationId())).internal())
- throw new IllegalArgumentException(report.applicationId() + " is set up to be deployed from internally, and no " +
- "longer accepts submissions from Screwdriver v3 jobs. If you need to revert " +
- "to the old pipeline, please file a ticket at yo/vespa-support and request this.");
-
controller.applications().deploymentTrigger().notifyOfCompletion(report);
return new MessageResponse("ok");
} catch (IllegalStateException e) {
@@ -1613,14 +1609,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
ApplicationId id = ApplicationId.from(tenantName, applicationName, report.field("instance").asString());
JobType type = JobType.fromJobName(report.field("jobName").asString());
long buildNumber = report.field("buildNumber").asLong();
- if (type == JobType.component)
- return DeploymentJobs.JobReport.ofComponent(id,
- report.field("projectId").asLong(),
- buildNumber,
- jobError,
- toSourceRevision(report.field("sourceRevision")));
- else
- return DeploymentJobs.JobReport.ofJob(id, type, buildNumber, jobError);
+ return DeploymentJobs.JobReport.ofJob(id, type, buildNumber, jobError);
}
private static SourceRevision toSourceRevision(Inspector object) {
@@ -1940,8 +1929,6 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
ApplicationPackage applicationPackage = new ApplicationPackage(dataParts.get(EnvironmentResource.APPLICATION_ZIP));
if (DeploymentSpec.empty.equals(applicationPackage.deploymentSpec()))
throw new IllegalArgumentException("Missing required file 'deployment.xml'");
- if (applicationPackage.deploymentSpec().instances().size() != 1)
- throw new IllegalArgumentException("Only single-instance deployment specs are currently supported");
controller.applications().verifyApplicationIdentityConfiguration(TenantName.from(tenant),
applicationPackage,
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 23d2646acd7..9a9a9798c6d 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
@@ -22,10 +22,10 @@ import com.yahoo.vespa.hosted.controller.api.integration.deployment.SourceRevisi
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.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentSteps;
import com.yahoo.vespa.hosted.controller.deployment.JobController;
+import com.yahoo.vespa.hosted.controller.deployment.JobStatus;
import com.yahoo.vespa.hosted.controller.deployment.Run;
import com.yahoo.vespa.hosted.controller.deployment.RunLog;
import com.yahoo.vespa.hosted.controller.deployment.RunStatus;
@@ -47,7 +47,6 @@ import java.util.stream.Collectors;
import static com.yahoo.config.application.api.DeploymentSpec.UpgradePolicy.conservative;
import static com.yahoo.config.application.api.DeploymentSpec.UpgradePolicy.defaultPolicy;
-import static com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType.component;
import static com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType.stagingTest;
import static com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType.systemTest;
import static com.yahoo.vespa.hosted.controller.deployment.Step.Status.unfinished;
@@ -77,12 +76,13 @@ class JobControllerApiHandlerHelper {
Application application = controller.applications().requireApplication(TenantAndApplicationId.from(id));
Instance instance = application.require(id.instance());
Change change = application.change();
- DeploymentSteps steps = new DeploymentSteps(application.deploymentSpec(), controller::system);
+ DeploymentSteps steps = new DeploymentSteps(application.deploymentSpec().requireInstance(id.instance()), controller::system);
+ Map<JobType, JobStatus> status = controller.jobController().deploymentStatus(application).instanceJobs(id.instance());
// The logic for pending runs imitates DeploymentTrigger logic; not good, but the trigger wiring must be re-written to reuse :S
Map<JobType, Versions> pendingProduction =
steps.productionJobs().stream()
- .filter(type -> ! controller.applications().deploymentTrigger().isComplete(change, change, instance, type))
+ .filter(type -> ! controller.applications().deploymentTrigger().isComplete(change, change, instance, type, status.get(type)))
.collect(Collectors.toMap(type -> type,
type -> Versions.from(change,
application,
@@ -103,8 +103,8 @@ class JobControllerApiHandlerHelper {
Cursor lastVersionsObject = responseObject.setObject("lastVersions");
if (application.latestVersion().isPresent()) {
- lastPlatformToSlime(lastVersionsObject.setObject("platform"), controller, application, instance, change, steps);
- lastApplicationToSlime(lastVersionsObject.setObject("application"), application, instance, change, steps, controller);
+ lastPlatformToSlime(lastVersionsObject.setObject("platform"), controller, application, instance, status, change, steps);
+ lastApplicationToSlime(lastVersionsObject.setObject("application"), application, instance, status, change, steps, controller);
}
Cursor deployingObject = responseObject.setObject("deploying");
@@ -126,6 +126,7 @@ class JobControllerApiHandlerHelper {
pendingProduction,
running,
type,
+ status.get(type),
deployment);
});
});
@@ -136,6 +137,7 @@ class JobControllerApiHandlerHelper {
controller,
application,
instance,
+ status,
type,
steps,
pendingProduction,
@@ -159,11 +161,11 @@ class JobControllerApiHandlerHelper {
return new SlimeJsonResponse(slime);
}
- private static void lastPlatformToSlime(Cursor lastPlatformObject, Controller controller, Application application, Instance instance, Change change, DeploymentSteps steps) {
+ private static void lastPlatformToSlime(Cursor lastPlatformObject, Controller controller, Application application, Instance instance, Map<JobType, JobStatus> status, Change change, DeploymentSteps steps) {
VespaVersion lastVespa = controller.versionStatus().version(controller.systemVersion());
VespaVersion.Confidence targetConfidence = Map.of(defaultPolicy, normal,
conservative, high)
- .getOrDefault(application.deploymentSpec().upgradePolicy(), broken);
+ .getOrDefault(application.deploymentSpec().requireInstance(instance.name()).upgradePolicy(), broken);
for (VespaVersion version : controller.versionStatus().versions())
if ( ! version.versionNumber().isAfter(controller.systemVersion())
&& version.confidence().equalOrHigherThan(targetConfidence))
@@ -172,13 +174,15 @@ class JobControllerApiHandlerHelper {
Version lastPlatform = lastVespa.versionNumber();
lastPlatformObject.setString("platform", lastPlatform.toString());
lastPlatformObject.setLong("at", lastVespa.committedAt().toEpochMilli());
- long completed = steps.productionJobs().stream().filter(type -> controller.applications().deploymentTrigger().isComplete(Change.of(lastPlatform), change, instance, type)).count();
+ long completed = steps.productionJobs().stream().filter(type -> controller.applications().deploymentTrigger().isComplete(Change.of(lastPlatform), change, instance, type, status.get(type))).count();
if (Optional.of(lastPlatform).equals(change.platform()))
lastPlatformObject.setString("deploying", completed + " of " + steps.productionJobs().size() + " complete");
else if (completed == steps.productionJobs().size())
lastPlatformObject.setString("completed", completed + " of " + steps.productionJobs().size() + " complete");
- else if ( ! application.deploymentSpec().canUpgradeAt(controller.clock().instant())) {
- lastPlatformObject.setString("blocked", application.deploymentSpec().changeBlocker().stream()
+ else if ( ! application.deploymentSpec().instances().stream()
+ .allMatch(spec -> spec.canUpgradeAt(controller.clock().instant()))) {
+ lastPlatformObject.setString("blocked", application.deploymentSpec().instances().stream()
+ .flatMap(spec -> spec.changeBlocker().stream())
.filter(blocker -> blocker.blocksVersions())
.filter(blocker -> blocker.window().includes(controller.clock().instant()))
.findAny().map(blocker -> blocker.window().toString()).get());
@@ -190,18 +194,20 @@ class JobControllerApiHandlerHelper {
: "Waiting for " + application.change() + " to complete");
}
- private static void lastApplicationToSlime(Cursor lastApplicationObject, Application application, Instance instance, Change change, DeploymentSteps steps, Controller controller) {
+ private static void lastApplicationToSlime(Cursor lastApplicationObject, Application application, Instance instance, Map<JobType, JobStatus> status, Change change, DeploymentSteps steps, Controller controller) {
long completed;
ApplicationVersion lastApplication = application.latestVersion().get();
applicationVersionToSlime(lastApplicationObject.setObject("application"), lastApplication);
lastApplicationObject.setLong("at", lastApplication.buildTime().get().toEpochMilli());
- completed = steps.productionJobs().stream().filter(type -> controller.applications().deploymentTrigger().isComplete(Change.of(lastApplication), change, instance, type)).count();
+ completed = steps.productionJobs().stream().filter(type -> controller.applications().deploymentTrigger().isComplete(Change.of(lastApplication), change, instance, type, status.get(type))).count();
if (Optional.of(lastApplication).equals(change.application()))
lastApplicationObject.setString("deploying", completed + " of " + steps.productionJobs().size() + " complete");
else if (completed == steps.productionJobs().size())
lastApplicationObject.setString("completed", completed + " of " + steps.productionJobs().size() + " complete");
- else if ( ! application.deploymentSpec().canChangeRevisionAt(controller.clock().instant())) {
- lastApplicationObject.setString("blocked", application.deploymentSpec().changeBlocker().stream()
+ else if ( ! application.deploymentSpec().instances().stream()
+ .allMatch(spec -> spec.canChangeRevisionAt(controller.clock().instant()))) {
+ lastApplicationObject.setString("blocked", application.deploymentSpec().instances().stream()
+ .flatMap(spec -> spec.changeBlocker().stream())
.filter(blocker -> blocker.blocksRevisions())
.filter(blocker -> blocker.window().includes(controller.clock().instant()))
.findAny().map(blocker -> blocker.window().toString()).get());
@@ -212,32 +218,32 @@ class JobControllerApiHandlerHelper {
private static void deploymentToSlime(Cursor deploymentObject, Instance instance, Change change,
Map<JobType, Versions> pendingProduction, Map<JobType, Run> running,
- JobType type, Deployment deployment) {
+ JobType type, JobStatus jobStatus, Deployment deployment) {
deploymentObject.setLong("at", deployment.at().toEpochMilli());
deploymentObject.setString("platform", deployment.version().toString());
applicationVersionToSlime(deploymentObject.setObject("application"), deployment.applicationVersion());
- deploymentObject.setBool("verified", instance.deploymentJobs().statusOf(type)
- .flatMap(JobStatus::lastSuccess)
- .filter(run -> run.platform().equals(deployment.version())
- && run.application().equals(deployment.applicationVersion()))
- .isPresent());
+ deploymentObject.setBool("verified", jobStatus.lastSuccess()
+ .map(Run::versions)
+ .filter(run -> run.targetPlatform().equals(deployment.version())
+ && run.targetApplication().equals(deployment.applicationVersion()))
+ .isPresent());
if (running.containsKey(type))
deploymentObject.setString("status", running.get(type).steps().get(deployReal) == unfinished ? "deploying" : "verifying");
else if (change.hasTargets())
deploymentObject.setString("status", pendingProduction.containsKey(type) ? "pending" : "completed");
}
- private static void jobTypeToSlime(Cursor jobObject, Controller controller, Application application, Instance instance, JobType type, DeploymentSteps steps,
+ private static void jobTypeToSlime(Cursor jobObject, Controller controller, Application application, Instance instance, Map<JobType, JobStatus> status, JobType type, DeploymentSteps steps,
Map<JobType, Versions> pendingProduction, Map<JobType, Run> running, URI baseUriForJob) {
- instance.deploymentJobs().statusOf(type).ifPresent(status -> status.pausedUntil().ifPresent(until ->
+ instance.deploymentJobs().statusOf(type).ifPresent(jobStatus -> jobStatus.pausedUntil().ifPresent(until ->
jobObject.setLong("pausedUntil", until)));
int runs = 0;
Cursor runArray = jobObject.setArray("runs");
if (type.isTest()) {
Deque<List<JobType>> pending = new ArrayDeque<>();
pendingProduction.entrySet().stream()
- .filter(typeVersions -> ! controller.applications().deploymentTrigger().testedIn(instance, type, typeVersions.getValue()))
- .filter(typeVersions -> ! controller.applications().deploymentTrigger().alreadyTriggered(instance, typeVersions.getValue()))
+ .filter(typeVersions -> ! controller.applications().deploymentTrigger().testedIn(type, status.get(type), typeVersions.getValue()))
+ .filter(typeVersions -> ! controller.applications().deploymentTrigger().alreadyTriggered(status, typeVersions.getValue()))
.collect(groupingBy(Map.Entry::getValue,
LinkedHashMap::new,
Collectors.mapping(Map.Entry::getKey, toList())))
@@ -251,7 +257,7 @@ class JobControllerApiHandlerHelper {
Cursor runObject = runArray.addObject();
runObject.setString("status", "pending");
versionsToSlime(runObject, versions);
- if ( ! controller.applications().deploymentTrigger().triggerAt(controller.clock().instant(), type, versions, instance, application.deploymentSpec()))
+ if ( ! controller.applications().deploymentTrigger().triggerAt(controller.clock().instant(), type, status.get(type), versions, instance, application.deploymentSpec()))
runObject.setObject("tasks").setString("cooldown", "failed");
else
runObject.setObject("tasks").setString("capacity", "running");
@@ -267,18 +273,18 @@ class JobControllerApiHandlerHelper {
runObject.setString("status", "pending");
versionsToSlime(runObject, pendingProduction.get(type));
Cursor pendingObject = runObject.setObject("tasks");
- if (instance.deploymentJobs().statusOf(type).map(status -> status.pausedUntil().isPresent()).orElse(false))
+ if (instance.deploymentJobs().statusOf(type).map(jobStatus -> jobStatus.pausedUntil().isPresent()).orElse(false))
pendingObject.setString("paused", "pending");
- else if ( ! controller.applications().deploymentTrigger().triggerAt(controller.clock().instant(), type, versions, instance, application.deploymentSpec()))
+ else if ( ! controller.applications().deploymentTrigger().triggerAt(controller.clock().instant(), type, status.get(type), versions, instance, application.deploymentSpec()))
pendingObject.setString("cooldown", "failed");
else {
int pending = 0;
- if ( ! controller.applications().deploymentTrigger().alreadyTriggered(instance, versions)) {
- if ( ! controller.applications().deploymentTrigger().testedIn(instance, systemTest, versions)) {
+ if ( ! controller.applications().deploymentTrigger().alreadyTriggered(status, versions)) {
+ if ( ! controller.applications().deploymentTrigger().testedIn(systemTest, status.get(systemTest), versions)) {
pending++;
pendingObject.setString(shortNameOf(systemTest, controller.system()), statusOf(controller, instance.id(), systemTest, versions));
}
- if ( ! controller.applications().deploymentTrigger().testedIn(instance, stagingTest, versions)) {
+ if ( ! controller.applications().deploymentTrigger().testedIn(stagingTest, status.get(stagingTest), versions)) {
pending++;
pendingObject.setString(shortNameOf(stagingTest, controller.system()), statusOf(controller, instance.id(), stagingTest, versions));
}
@@ -459,15 +465,6 @@ class JobControllerApiHandlerHelper {
return new SlimeJsonResponse(slime);
}
- /** Unregisters the application from the internal deployment pipeline. */
- static HttpResponse unregisterResponse(JobController jobs, String tenantName, String applicationName) {
- TenantAndApplicationId id = TenantAndApplicationId.from(tenantName, applicationName);
- jobs.unregister(id);
- Slime slime = new Slime();
- slime.setObject().setString("message", "Unregistered '" + id + "' from internal deployment pipeline.");
- return new SlimeJsonResponse(slime);
- }
-
private static String nameOf(RunStatus status) {
switch (status) {
case running: return "running";
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandler.java
new file mode 100644
index 00000000000..6ffdea93a1c
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandler.java
@@ -0,0 +1,129 @@
+// 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.restapi.configserver;
+
+import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.config.provision.zone.ZoneList;
+import com.yahoo.container.jdisc.HttpRequest;
+import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.restapi.ErrorResponse;
+import com.yahoo.restapi.Path;
+import com.yahoo.restapi.SlimeJsonResponse;
+import com.yahoo.slime.Cursor;
+import com.yahoo.slime.Slime;
+import com.yahoo.vespa.hosted.controller.Controller;
+import com.yahoo.vespa.hosted.controller.api.integration.ServiceRegistry;
+import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry;
+import com.yahoo.vespa.hosted.controller.auditlog.AuditLoggingRequestHandler;
+import com.yahoo.vespa.hosted.controller.proxy.ConfigServerRestExecutor;
+import com.yahoo.vespa.hosted.controller.proxy.ProxyException;
+import com.yahoo.vespa.hosted.controller.proxy.ProxyRequest;
+import com.yahoo.yolean.Exceptions;
+
+import java.net.URI;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.stream.Stream;
+
+/**
+ * REST API for proxying operator APIs to config servers in a given zone.
+ *
+ * @author freva
+ */
+@SuppressWarnings("unused")
+public class ConfigServerApiHandler extends AuditLoggingRequestHandler {
+
+ private static final ZoneId CONTROLLER_ZONE = ZoneId.from("prod", "controller");
+ private static final URI CONTROLLER_URI = URI.create("https://localhost:4443/");
+ private static final String OPTIONAL_PREFIX = "/api";
+ private static final List<String> WHITELISTED_APIS = List.of("/flags/v1/", "/nodes/v2/", "/orchestrator/v1/");
+
+ private final ZoneRegistry zoneRegistry;
+ private final ConfigServerRestExecutor proxy;
+
+ public ConfigServerApiHandler(Context parentCtx, ServiceRegistry serviceRegistry,
+ ConfigServerRestExecutor proxy, Controller controller) {
+ super(parentCtx, controller.auditLogger());
+ this.zoneRegistry = serviceRegistry.zoneRegistry();
+ this.proxy = proxy;
+ }
+
+ @Override
+ public HttpResponse auditAndHandle(HttpRequest request) {
+ try {
+ switch (request.getMethod()) {
+ case GET:
+ return get(request);
+ case POST:
+ case PUT:
+ case DELETE:
+ case PATCH:
+ return proxy(request);
+ default:
+ return ErrorResponse.methodNotAllowed("Method '" + request.getMethod() + "' is unsupported");
+ }
+ } catch (IllegalArgumentException e) {
+ return ErrorResponse.badRequest(Exceptions.toMessageString(e));
+ } catch (RuntimeException e) {
+ log.log(Level.WARNING, "Unexpected error handling '" + request.getUri() + "', "
+ + Exceptions.toMessageString(e));
+ return ErrorResponse.internalServerError(Exceptions.toMessageString(e));
+ }
+ }
+
+ private HttpResponse get(HttpRequest request) {
+ Path path = new Path(request.getUri(), OPTIONAL_PREFIX);
+ if (path.matches("/configserver/v1")) {
+ return root(request);
+ }
+ return proxy(request);
+ }
+
+ private HttpResponse proxy(HttpRequest request) {
+ Path path = new Path(request.getUri(), OPTIONAL_PREFIX);
+ if ( ! path.matches("/configserver/v1/{environment}/{region}/{*}")) {
+ return ErrorResponse.notFoundError("Nothing at " + path);
+ }
+
+ ZoneId zoneId = ZoneId.from(path.get("environment"), path.get("region"));
+ if (! zoneRegistry.hasZone(zoneId) && ! CONTROLLER_ZONE.equals(zoneId)) {
+ throw new IllegalArgumentException("No such zone: " + zoneId.value());
+ }
+
+ String cfgPath = "/" + path.getRest();
+ if (WHITELISTED_APIS.stream().noneMatch(cfgPath::startsWith)) {
+ return ErrorResponse.forbidden("Cannot access '" + cfgPath +
+ "' through /configserver/v1, following APIs are permitted: " + String.join(", ", WHITELISTED_APIS));
+ }
+
+ try {
+ return proxy.handle(new ProxyRequest(request, List.of(getEndpoint(zoneId)), cfgPath));
+ } catch (ProxyException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private HttpResponse root(HttpRequest request) {
+ Slime slime = new Slime();
+ Cursor root = slime.setObject();
+ ZoneList zoneList = zoneRegistry.zones().reachable();
+
+ Cursor zones = root.setArray("zones");
+ Stream.concat(Stream.of(CONTROLLER_ZONE), zoneRegistry.zones().reachable().ids().stream())
+ .forEach(zone -> {
+ Cursor object = zones.addObject();
+ object.setString("environment", zone.environment().value());
+ object.setString("region", zone.region().value());
+ object.setString("uri", request.getUri().resolve(
+ "/configserver/v1/" + zone.environment().value() + "/" + zone.region().value()).toString());
+ });
+ return new SlimeJsonResponse(slime);
+ }
+
+ private HttpResponse notFound(Path path) {
+ return ErrorResponse.notFoundError("Nothing at " + path);
+ }
+
+ private URI getEndpoint(ZoneId zoneId) {
+ return CONTROLLER_ZONE.equals(zoneId) ? CONTROLLER_URI : zoneRegistry.getConfigServerVipUri(zoneId);
+ }
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/configserver/package-info.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/configserver/package-info.java
new file mode 100644
index 00000000000..9949c2d17bf
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/configserver/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.
+/**
+ * @author freva
+ */
+package com.yahoo.vespa.hosted.controller.restapi.configserver;
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 2adf6ce95e1..4ac7ff4d6d4 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
@@ -142,7 +142,7 @@ public class DeploymentApiHandler extends LoggingRequestHandler {
"/application/" +
instance.id().application().value()).toString());
object.setString("upgradePolicy", toString(controller.applications().requireApplication(TenantAndApplicationId.from(instance.id()))
- .deploymentSpec().upgradePolicy()));
+ .deploymentSpec().requireInstance(instance.name()).upgradePolicy()));
}
private static String toString(DeploymentSpec.UpgradePolicy upgradePolicy) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilter.java
index 8ee95675465..2a75c7953ca 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilter.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilter.java
@@ -11,6 +11,7 @@ import com.yahoo.restapi.Path;
import com.yahoo.vespa.athenz.api.AthenzDomain;
import com.yahoo.vespa.athenz.api.AthenzIdentity;
import com.yahoo.vespa.athenz.api.AthenzPrincipal;
+import com.yahoo.vespa.athenz.api.AthenzUser;
import com.yahoo.vespa.athenz.client.zms.ZmsClientException;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.TenantController;
@@ -96,9 +97,14 @@ public class AthenzRoleFilter extends JsonSecurityRequestFilterBase {
roleMemberships.add(Role.tenantPipeline(tenant.get().name(), application.get()));
if ( tenant.isPresent() && application.isPresent() && instance.isPresent()
+ && principal.getIdentity() instanceof AthenzUser
&& instance.get().value().equals(principal.getIdentity().getName()))
roleMemberships.add(Role.athenzUser(tenant.get().name(), application.get(), instance.get()));
+ if (athenz.hasSystemFlagsDeployAccess(identity)) {
+ roleMemberships.add(Role.systemFlagsDeployer());
+ }
+
return roleMemberships.isEmpty()
? Set.of(Role.everyone())
: Set.copyOf(roleMemberships);
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilter.java
index 72336b4accf..ec95f0bbb4c 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilter.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilter.java
@@ -39,7 +39,7 @@ public class ControllerAuthorizationFilter extends JsonSecurityRequestFilterBase
Optional<SecurityContext> securityContext = Optional.ofNullable((SecurityContext)request.getAttribute(SecurityContext.ATTRIBUTE_NAME));
if (securityContext.isEmpty())
- return Optional.of(new ErrorResponse(Response.Status.FORBIDDEN, "Access denied"));
+ return Optional.of(new ErrorResponse(Response.Status.UNAUTHORIZED, "Access denied - not authenticated"));
Action action = Action.from(HttpRequest.Method.valueOf(request.getMethod()));
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 752409d5694..8a3adcce30e 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
@@ -8,8 +8,10 @@ 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.restapi.ErrorResponse;
+import com.yahoo.restapi.MessageResponse;
import com.yahoo.restapi.Path;
-import com.yahoo.slime.ArrayTraverser;
+import com.yahoo.restapi.SlimeJsonResponse;
import com.yahoo.slime.Cursor;
import com.yahoo.slime.Inspector;
import com.yahoo.slime.Slime;
@@ -23,9 +25,6 @@ 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.role.Role;
import com.yahoo.vespa.hosted.controller.api.role.RoleDefinition;
-import com.yahoo.restapi.ErrorResponse;
-import com.yahoo.restapi.MessageResponse;
-import com.yahoo.restapi.SlimeJsonResponse;
import com.yahoo.vespa.hosted.controller.api.role.SimplePrincipal;
import com.yahoo.vespa.hosted.controller.restapi.application.EmptyResponse;
import com.yahoo.yolean.Exceptions;
@@ -191,8 +190,9 @@ public class UserApiHandler extends LoggingRequestHandler {
var user = new UserId(require("user", Inspector::asString, requestObject));
var roles = SlimeStream.fromArray(requestObject.field("roles"), Inspector::asString)
.map(roleName -> Roles.toRole(tenant, roleName))
- .peek(role -> users.addUsers(role, List.of(user)))
.collect(Collectors.toUnmodifiableList());
+
+ users.addToRoles(user, roles);
return new MessageResponse(user + " is now a member of " + roles.stream().map(Role::toString).collect(Collectors.joining(", ")));
}
@@ -217,11 +217,13 @@ public class UserApiHandler extends LoggingRequestHandler {
TenantName tenant = TenantName.from(tenantName);
String roleName = require("roleName", Inspector::asString, requestObject);
UserId user = new UserId(require("user", Inspector::asString, requestObject));
- Role role = Roles.toRole(tenant, roleName);
+ List<Role> roles = Collections.singletonList(Roles.toRole(tenant, roleName));
- removeTenantRoleMember(tenant, user, role);
+ enforceLastAdminOfTenant(tenant, user, roles);
+ removeDeveloperKey(tenant, user, roles);
+ users.removeFromRoles(user, roles);
- return new MessageResponse(user+" is no longer a member of "+role);
+ return new MessageResponse(user + " is no longer a member of " + roles.stream().map(Role::toString).collect(Collectors.joining(", ")));
}
private HttpResponse removeMultipleTenantRoleMembers(String tenantName, Inspector requestObject) {
@@ -231,24 +233,35 @@ public class UserApiHandler extends LoggingRequestHandler {
.map(roleName -> Roles.toRole(tenant, roleName))
.collect(Collectors.toUnmodifiableList());
- roles.forEach(role -> removeTenantRoleMember(tenant, user, role));
+ enforceLastAdminOfTenant(tenant, user, roles);
+ removeDeveloperKey(tenant, user, roles);
+ users.removeFromRoles(user, roles);
return new MessageResponse(user + " is no longer a member of " + roles.stream().map(Role::toString).collect(Collectors.joining(", ")));
}
- private void removeTenantRoleMember(TenantName tenantName, UserId user, Role role) {
- if ( role.definition() == RoleDefinition.administrator
- && Set.of(user.value()).equals(users.listUsers(role).stream().map(User::email).collect(Collectors.toSet())))
- throw new IllegalArgumentException("Can't remove the last administrator of a tenant.");
-
- if (role.definition().equals(RoleDefinition.developer))
- controller.tenants().lockIfPresent(tenantName, LockedTenant.Cloud.class, tenant -> {
- PublicKey key = tenant.get().developerKeys().inverse().get(new SimplePrincipal(user.value()));
- if (key != null)
- controller.tenants().store(tenant.withoutDeveloperKey(key));
- });
+ private void enforceLastAdminOfTenant(TenantName tenantName, UserId user, List<Role> roles) {
+ for (Role role : roles) {
+ if (role.definition().equals(RoleDefinition.administrator)) {
+ if (Set.of(user.value()).equals(users.listUsers(role).stream().map(User::email).collect(Collectors.toSet()))) {
+ throw new IllegalArgumentException("Can't remove the last administrator of a tenant.");
+ }
+ break;
+ }
+ }
+ }
- users.removeUsers(role, List.of(user));
+ private void removeDeveloperKey(TenantName tenantName, UserId user, List<Role> roles) {
+ for (Role role : roles) {
+ if (role.definition().equals(RoleDefinition.developer)) {
+ controller.tenants().lockIfPresent(tenantName, LockedTenant.Cloud.class, tenant -> {
+ PublicKey key = tenant.get().developerKeys().inverse().get(new SimplePrincipal(user.value()));
+ if (key != null)
+ controller.tenants().store(tenant.withoutDeveloperKey(key));
+ });
+ break;
+ }
+ }
}
private HttpResponse removeApplicationRoleMember(String tenantName, String applicationName, HttpRequest request) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiHandler.java
index 53373bb228a..abbbbef82c7 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
@@ -9,6 +9,7 @@ import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.container.jdisc.LoggingRequestHandler;
import com.yahoo.slime.Cursor;
import com.yahoo.slime.Slime;
+import com.yahoo.vespa.hosted.controller.api.integration.ServiceRegistry;
import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry;
import com.yahoo.restapi.ErrorResponse;
import com.yahoo.restapi.Path;
@@ -32,9 +33,9 @@ public class ZoneApiHandler extends LoggingRequestHandler {
private final ZoneRegistry zoneRegistry;
- public ZoneApiHandler(LoggingRequestHandler.Context parentCtx, ZoneRegistry zoneRegistry) {
+ public ZoneApiHandler(LoggingRequestHandler.Context parentCtx, ServiceRegistry serviceRegistry) {
super(parentCtx);
- this.zoneRegistry = zoneRegistry;
+ this.zoneRegistry = serviceRegistry.zoneRegistry();
}
@Override
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 be601511763..a127a44efb2 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
@@ -12,6 +12,7 @@ import com.yahoo.restapi.SlimeJsonResponse;
import com.yahoo.slime.Cursor;
import com.yahoo.slime.Slime;
import com.yahoo.vespa.hosted.controller.Controller;
+import com.yahoo.vespa.hosted.controller.api.integration.ServiceRegistry;
import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry;
import com.yahoo.vespa.hosted.controller.auditlog.AuditLoggingRequestHandler;
import com.yahoo.vespa.hosted.controller.proxy.ConfigServerRestExecutor;
@@ -19,6 +20,8 @@ import com.yahoo.vespa.hosted.controller.proxy.ProxyException;
import com.yahoo.vespa.hosted.controller.proxy.ProxyRequest;
import com.yahoo.yolean.Exceptions;
+import java.net.URI;
+import java.util.List;
import java.util.logging.Level;
/**
@@ -34,10 +37,10 @@ public class ZoneApiHandler extends AuditLoggingRequestHandler {
private final ZoneRegistry zoneRegistry;
private final ConfigServerRestExecutor proxy;
- public ZoneApiHandler(LoggingRequestHandler.Context parentCtx, ZoneRegistry zoneRegistry,
+ public ZoneApiHandler(LoggingRequestHandler.Context parentCtx, ServiceRegistry serviceRegistry,
ConfigServerRestExecutor proxy, Controller controller) {
super(parentCtx, controller.auditLogger());
- this.zoneRegistry = zoneRegistry;
+ this.zoneRegistry = serviceRegistry.zoneRegistry();
this.proxy = proxy;
}
@@ -82,7 +85,7 @@ public class ZoneApiHandler extends AuditLoggingRequestHandler {
throw new IllegalArgumentException("No such zone: " + zoneId.value());
}
try {
- return proxy.handle(new ProxyRequest(request, zoneId, path.getRest()));
+ return proxy.handle(new ProxyRequest(request, getConfigserverEndpoints(zoneId), path.getRest()));
} catch (ProxyException e) {
throw new RuntimeException(e);
}
@@ -110,4 +113,14 @@ public class ZoneApiHandler extends AuditLoggingRequestHandler {
private HttpResponse notFound(Path path) {
return ErrorResponse.notFoundError("Nothing at " + path);
}
+
+ private List<URI> getConfigserverEndpoints(ZoneId zoneId) {
+ // TODO: Use config server VIP for all zones that have one
+ if (zoneId.region().value().startsWith("aws-") || zoneId.region().value().contains("-aws-")) {
+ return List.of(zoneRegistry.getConfigServerVipUri(zoneId));
+ } else {
+ return zoneRegistry.getConfigServerUris(zoneId);
+ }
+ }
+
}
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 d0cf5aac2d9..296639245a3 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
@@ -45,9 +45,7 @@ public class VespaVersion implements Comparable<VespaVersion> {
public static Confidence confidenceFrom(DeploymentStatistics statistics, Controller controller) {
// 'production on this': All deployment jobs upgrading to this version have completed without failure
- ApplicationList productionOnThis = ApplicationList.from(statistics.production(), controller.applications())
- .notUpgradingTo(statistics.version())
- .notFailingUpgrade();
+ ApplicationList productionOnThis = ApplicationList.from(statistics.production(), controller.applications()).not().upgradingTo(statistics.version()).not().failingUpgrade();
ApplicationList failingOnThis = ApplicationList.from(statistics.failing(), controller.applications());
ApplicationList all = ApplicationList.from(controller.applications().asList())
.withProductionDeployment();
@@ -162,8 +160,8 @@ public class VespaVersion implements Comparable<VespaVersion> {
private static boolean nonCanaryApplicationsBroken(Version version,
ApplicationList failingOnThis,
ApplicationList productionOnThis) {
- ApplicationList failingNonCanaries = failingOnThis.without(UpgradePolicy.canary).startedFailingOn(version);
- ApplicationList productionNonCanaries = productionOnThis.without(UpgradePolicy.canary);
+ ApplicationList failingNonCanaries = failingOnThis.not().with(UpgradePolicy.canary).startedFailingOn(version);
+ ApplicationList productionNonCanaries = productionOnThis.not().with(UpgradePolicy.canary);
if (productionNonCanaries.size() + failingNonCanaries.size() == 0) return false;
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 c374787aaa4..dbe451fd433 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
@@ -30,7 +30,6 @@ import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobError;
import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics;
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.DeploymentContext;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester;
import com.yahoo.vespa.hosted.controller.integration.ZoneApiMock;
@@ -81,7 +80,7 @@ public class ControllerTest {
// staging job - succeeding
Version version1 = tester.configServer().initialVersion();
- var context = tester.deploymentContext();
+ var context = tester.newDeploymentContext();
context.submit(applicationPackage);
assertEquals("Application version is known from completion of initial job",
ApplicationVersion.from(DeploymentContext.defaultSourceRevision, 1, "a@b", new Version("6.1"), Instant.ofEpochSecond(1)),
@@ -384,7 +383,6 @@ public class ControllerTest {
var west = ZoneId.from("prod", "us-west-1");
var central = ZoneId.from("prod", "us-central-1");
var east = ZoneId.from("prod", "us-east-3");
- var buildNumber = BuildJob.defaultBuildNumber;
// Application is deployed with endpoint pointing to 2/3 zones
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
@@ -503,7 +501,7 @@ public class ControllerTest {
@Test
public void testUnassignRotations() {
- var context = tester.deploymentContext();
+ var context = tester.newDeploymentContext();
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
.environment(Environment.prod)
.endpoint("default", "qrs", "us-west-1", "us-central-1")
@@ -635,7 +633,7 @@ public class ControllerTest {
.build();
// Create application
- var context = tester.deploymentContext();
+ var context = tester.newDeploymentContext();
// Direct deploy is allowed when deployDirectly is true
ZoneId zone = ZoneId.from("prod", "cd-us-central-1");
@@ -666,7 +664,7 @@ public class ControllerTest {
.build();
// Create application
- var context = tester.deploymentContext();
+ var context = tester.newDeploymentContext();
ZoneId zone = ZoneId.from("dev", "us-east-1");
// Deploy
@@ -680,7 +678,7 @@ public class ControllerTest {
@Test
public void testSuspension() {
- var context = tester.deploymentContext();
+ var context = tester.newDeploymentContext();
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
.environment(Environment.prod)
.region("us-west-1")
@@ -704,7 +702,7 @@ public class ControllerTest {
// second time will not fail
@Test
public void testDeletingApplicationThatHasAlreadyBeenDeleted() {
- var context = tester.deploymentContext();
+ var context = tester.newDeploymentContext();
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
.environment(Environment.prod)
.region("us-east-3")
@@ -723,12 +721,12 @@ public class ControllerTest {
.environment(Environment.prod)
.region("us-west-1")
.build(true);
- tester.deploymentContext().submit(applicationPackage);
+ tester.newDeploymentContext().submit(applicationPackage);
}
@Test
public void testDeployApplicationWithWarnings() {
- var context = tester.deploymentContext();
+ var context = tester.newDeploymentContext();
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
.environment(Environment.prod)
.region("us-west-1")
@@ -790,7 +788,7 @@ public class ControllerTest {
ZoneApiMock.fromId("prod.us-west-1"),
ZoneApiMock.newBuilder().with(CloudName.from("aws")).withId("prod.aws-us-east-1").build()
);
- var context = tester.deploymentContext();
+ var context = tester.newDeploymentContext();
var applicationPackage = new ApplicationPackageBuilder()
.region("aws-us-east-1")
.region("us-west-1")
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 351b139f747..ff6a5d3795f 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
@@ -19,10 +19,8 @@ import com.yahoo.vespa.flags.InMemoryFlagSource;
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.athenz.AthenzClientFactoryMock;
import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzDbMock;
-import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.api.integration.dns.MemoryNameService;
import com.yahoo.vespa.hosted.controller.api.integration.dns.Record;
import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordName;
@@ -37,6 +35,7 @@ import com.yahoo.vespa.hosted.controller.integration.ServiceRegistryMock;
import com.yahoo.vespa.hosted.controller.integration.ZoneRegistryMock;
import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
import com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb;
+import com.yahoo.vespa.hosted.controller.restapi.ContainerTester;
import com.yahoo.vespa.hosted.controller.security.AthenzCredentials;
import com.yahoo.vespa.hosted.controller.security.AthenzTenantSpec;
import com.yahoo.vespa.hosted.controller.security.Credentials;
@@ -73,9 +72,9 @@ public final class ControllerTester {
public static final int availableRotations = 10;
+ private final boolean inContainer;
private final AthenzDbMock athenzDb;
private final ManualClock clock;
- private final ZoneRegistryMock zoneRegistry;
private final ServiceRegistryMock serviceRegistry;
private final CuratorDb curator;
private final RotationsConfig rotationsConfig;
@@ -88,7 +87,6 @@ public final class ControllerTester {
public ControllerTester(RotationsConfig rotationsConfig, MockCuratorDb curatorDb) {
this(new AthenzDbMock(),
- new ZoneRegistryMock(),
curatorDb,
rotationsConfig,
new ServiceRegistryMock());
@@ -106,13 +104,12 @@ public final class ControllerTester {
this(defaultRotationsConfig(), new MockCuratorDb());
}
- private ControllerTester(AthenzDbMock athenzDb,
- ZoneRegistryMock zoneRegistry,
+ private ControllerTester(AthenzDbMock athenzDb, boolean inContainer,
CuratorDb curator, RotationsConfig rotationsConfig,
ServiceRegistryMock serviceRegistry, Controller controller) {
this.athenzDb = athenzDb;
+ this.inContainer = inContainer;
this.clock = serviceRegistry.clock();
- this.zoneRegistry = zoneRegistry;
this.serviceRegistry = serviceRegistry;
this.curator = curator;
this.rotationsConfig = rotationsConfig;
@@ -127,13 +124,23 @@ public final class ControllerTester {
}
private ControllerTester(AthenzDbMock athenzDb,
- ZoneRegistryMock zoneRegistry,
CuratorDb curator, RotationsConfig rotationsConfig,
ServiceRegistryMock serviceRegistry) {
- this(athenzDb, zoneRegistry, curator, rotationsConfig, serviceRegistry,
- createController(curator, rotationsConfig, zoneRegistry, athenzDb, serviceRegistry));
+ this(athenzDb, false, curator, rotationsConfig, serviceRegistry,
+ createController(curator, rotationsConfig, athenzDb, serviceRegistry));
+ }
+
+ /** Creates a ControllerTester built on the ContainerTester's controller. This controller can not be recreated. */
+ public ControllerTester(ContainerTester tester) {
+ this(tester.athenzClientFactory().getSetup(),
+ true,
+ tester.controller().curator(),
+ null,
+ tester.serviceRegistry(),
+ tester.controller());
}
+
public void configureDefaultLogHandler(Consumer<Handler> configureFunc) {
Arrays.stream(Logger.getLogger("").getHandlers())
// Do not mess with log configuration if a custom one has been set
@@ -141,13 +148,6 @@ public final class ControllerTester {
.forEach(configureFunc);
}
- public static BuildService.BuildJob buildJob(ApplicationId id, JobType jobType) {
- if (jobType == JobType.component)
- throw new AssertionError("Not supposed to happen");
-
- return BuildService.BuildJob.of(id, 0, jobType.jobName());
- }
-
public Controller controller() { return controller; }
public CuratorDb curator() { return curator; }
@@ -158,7 +158,7 @@ public final class ControllerTester {
public MemoryNameService nameService() { return serviceRegistry.nameServiceMock(); }
- public ZoneRegistryMock zoneRegistry() { return zoneRegistry; }
+ public ZoneRegistryMock zoneRegistry() { return serviceRegistry.zoneRegistry(); }
public ConfigServerMock configServer() { return serviceRegistry.configServerMock(); }
@@ -179,7 +179,9 @@ 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, zoneRegistry, athenzDb, serviceRegistry);
+ if (inContainer)
+ throw new UnsupportedOperationException("Cannot recreate this controller");
+ controller = createController(curator, rotationsConfig, athenzDb, serviceRegistry);
}
/** Creates the given tenant and application and deploys it */
@@ -353,12 +355,10 @@ public final class ControllerTester {
}
private static Controller createController(CuratorDb curator, RotationsConfig rotationsConfig,
- ZoneRegistryMock zoneRegistryMock,
AthenzDbMock athensDb,
ServiceRegistryMock serviceRegistry) {
Controller controller = new Controller(curator,
rotationsConfig,
- zoneRegistryMock,
new AthenzFacade(new AthenzClientFactoryMock(athensDb)),
() -> "test-controller",
new InMemoryFlagSource(),
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 3e96d2f6972..ea97e3e6c71 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
@@ -41,6 +41,10 @@ public class EndpointTest {
"https://cd--a1--t1.global.vespa.oath.cloud:4443/",
Endpoint.of(app1).named(endpointId).on(Port.tls(4443)).in(SystemName.cd),
+ // Main endpoint in CD
+ "https://cd--i2--a2--t2.global.vespa.oath.cloud:4443/",
+ Endpoint.of(app2).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),
@@ -50,11 +54,11 @@ public class EndpointTest {
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/",
+ "https://i2.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/",
+ "https://r2.i2.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
@@ -82,6 +86,10 @@ public class EndpointTest {
Endpoint.of(app1).named(endpointId).on(Port.tls(4443)).in(SystemName.main),
// Main endpoint in CD
+ "https://cd--i2--a2--t2.global.vespa.oath.cloud:4443/",
+ Endpoint.of(app2).named(endpointId).on(Port.tls(4443)).in(SystemName.cd),
+
+ // 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),
@@ -94,11 +102,11 @@ public class EndpointTest {
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/",
+ "https://i2.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/",
+ "https://r2.i2.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
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/BuildJob.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/BuildJob.java
deleted file mode 100644
index 63d84926144..00000000000
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/BuildJob.java
+++ /dev/null
@@ -1,128 +0,0 @@
-// 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.deployment;
-
-import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.vespa.hosted.controller.Application;
-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.application.ApplicationPackage;
-import com.yahoo.vespa.hosted.controller.application.DeploymentJobs;
-import com.yahoo.vespa.hosted.controller.integration.ArtifactRepositoryMock;
-import com.yahoo.vespa.hosted.controller.restapi.ContainerControllerTester;
-
-import java.util.Objects;
-import java.util.Optional;
-import java.util.function.Consumer;
-
-import static com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType.component;
-
-public class BuildJob {
-
- public static final long defaultBuildNumber = 42;
-
- private JobType job;
- private ApplicationId applicationId;
- private Optional<DeploymentJobs.JobError> jobError = Optional.empty();
- private Optional<SourceRevision> sourceRevision = Optional.of(DeploymentContext.defaultSourceRevision);
- private long projectId;
- private long buildNumber = defaultBuildNumber;
-
- private final Consumer<DeploymentJobs.JobReport> reportConsumer;
- private final ArtifactRepositoryMock artifactRepository;
-
- public BuildJob(Consumer<DeploymentJobs.JobReport> reportConsumer, ArtifactRepositoryMock artifactRepository) {
- Objects.requireNonNull(reportConsumer, "reportConsumer cannot be null");
- Objects.requireNonNull(artifactRepository, "artifactRepository cannot be null");
- this.reportConsumer = reportConsumer;
- this.artifactRepository = artifactRepository;
- }
-
- public BuildJob type(JobType job) {
- this.job = job;
- return this;
- }
-
- public BuildJob application(Application application) {
- if (application.projectId().isPresent())
- this.projectId = application.projectId().getAsLong();
-
- return application(application.id().defaultInstance());
- }
-
- public BuildJob application(ApplicationId applicationId) {
- this.applicationId = applicationId;
- return this;
- }
-
- public BuildJob error(DeploymentJobs.JobError jobError) {
- this.jobError = Optional.of(jobError);
- return this;
- }
-
- public BuildJob sourceRevision(SourceRevision sourceRevision) {
- this.sourceRevision = Optional.of(sourceRevision);
- return this;
- }
-
- public BuildJob buildNumber(long buildNumber) {
- this.buildNumber = requireBuildNumber(buildNumber);
- return this;
- }
-
- public BuildJob nextBuildNumber(int increment) {
- return buildNumber(buildNumber + requireBuildNumber(increment));
- }
-
- public BuildJob nextBuildNumber() {
- return nextBuildNumber(1);
- }
-
- public BuildJob projectId(long projectId) {
- this.projectId = projectId;
- return this;
- }
-
- public BuildJob success(boolean success) {
- this.jobError = success ? Optional.empty() : Optional.of(DeploymentJobs.JobError.unknown);
- return this;
- }
-
- public BuildJob unsuccessful() {
- return success(false);
- }
-
- /** Create a job report for this build job */
- public DeploymentJobs.JobReport report() {
- return job == component ? DeploymentJobs.JobReport.ofComponent(applicationId, projectId, buildNumber, jobError, sourceRevision.get())
- : DeploymentJobs.JobReport.ofJob(applicationId, job, buildNumber, jobError);
- }
-
- /** Upload given application package to artifact repository as part of this job */
- public BuildJob uploadArtifact(ApplicationPackage applicationPackage) {
- Objects.requireNonNull(job, "job cannot be null");
- Objects.requireNonNull(applicationId, "applicationId cannot be null");
- if (job != component) {
- throw new IllegalStateException(job + " cannot upload artifact");
- }
- artifactRepository.put(applicationId, applicationPackage, ApplicationVersion.from(sourceRevision.get(), buildNumber).id());
- return this;
- }
-
- /** Send report for this build job to the controller */
- public void submit() {
- if (job == component &&
- !artifactRepository.contains(applicationId, ApplicationVersion.from(sourceRevision.get(), buildNumber).id())) {
- throw new IllegalStateException(job + " must upload artifact before reporting completion");
- }
- reportConsumer.accept(report());
- }
-
- private static long requireBuildNumber(long n) {
- if (n <= 0) {
- throw new IllegalArgumentException("Build number must be positive");
- }
- return n;
- }
-
-}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java
index a5840cea3bd..d181ab8d38f 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java
@@ -57,12 +57,13 @@ import static org.junit.Assert.assertTrue;
* A deployment context for an application. This allows fine-grained control of the deployment of an application's
* instances.
*
- * References to this should be acquired through {@link DeploymentTester#deploymentContext}.
+ * References to this should be acquired through {@link DeploymentTester#newDeploymentContext}.
*
* Tester code that is not specific to deployments should be added to either {@link ControllerTester} or
* {@link DeploymentTester} instead of this class.
*
* @author mpolden
+ * @author jonmv
*/
public class DeploymentContext {
@@ -118,9 +119,7 @@ public class DeploymentContext {
try {
var tenant = tester.createTenant(instanceId.tenant().value());
tester.createApplication(tenant, instanceId.application().value(), instanceId.instance().value());
- } catch (IllegalArgumentException ignored) {
- // TODO(mpolden): Application already exists. Remove this once InternalDeploymentTester stops implicitly creating applications
- }
+ } catch (IllegalArgumentException ignored) { } // Tenant and or application may already exist with custom setup.
}
public Application application() {
@@ -139,6 +138,8 @@ public class DeploymentContext {
return instanceId;
}
+ public TesterId testerId() { return testerId; }
+
public DeploymentId deploymentIdIn(ZoneId zone) {
return new DeploymentId(instanceId, zone);
}
@@ -171,10 +172,11 @@ public class DeploymentContext {
.allMatch(deployments -> deployments.stream()
.allMatch(deployment -> deployment.version().equals(version))));
- for (JobType type : new DeploymentSteps(application().deploymentSpec(), tester.controller()::system).productionJobs())
- assertTrue(tester.configServer().nodeRepository()
- .list(type.zone(tester.controller().system()), applicationId.defaultInstance()).stream() // TODO jonmv: support more
- .allMatch(node -> node.currentVersion().equals(version)));
+ for (var spec : application().deploymentSpec().instances())
+ for (JobType type : new DeploymentSteps(spec, tester.controller()::system).productionJobs())
+ assertTrue(tester.configServer().nodeRepository()
+ .list(type.zone(tester.controller().system()), applicationId.defaultInstance()).stream() // TODO jonmv: support more
+ .allMatch(node -> node.currentVersion().equals(version)));
assertFalse(application().change().hasTargets());
return this;
@@ -205,7 +207,7 @@ public class DeploymentContext {
var projectId = tester.controller().applications()
.requireApplication(applicationId)
.projectId()
- .orElseThrow(() -> new IllegalArgumentException("No project ID set for " + applicationId));
+ .orElse(1000); // These are really set through submission, so just pick one if it hasn't been set.
lastSubmission = jobs.submit(applicationId, sourceRevision, "a@b", projectId, applicationPackage, new byte[0]);
return this;
}
@@ -266,7 +268,7 @@ public class DeploymentContext {
triggerJobs();
}
else
- throw new AssertionError("Job '" + run.id().type() + "' was run twice for '" + instanceId + "'");
+ throw new AssertionError("Job '" + run.id() + "' was run twice");
assertFalse("Change should have no targets, but was " + application().change(), application().change().hasTargets());
if (!deferDnsUpdates) {
@@ -355,7 +357,6 @@ public class DeploymentContext {
/** Deploy default application package, start a run for that change and return its ID */
public RunId newRun(JobType type) {
- assertFalse(application().internal()); // Use this only once per test.
submit();
readyJobsTrigger.maintain();
@@ -455,7 +456,7 @@ public class DeploymentContext {
zone.region().value(),
zone.environment().value()),
"host1",
- false,
+ true,
String.format("cluster1.%s.%s.%s.%s",
id.application().value(),
id.tenant().value(),
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 0ca035b85b2..af58f8e825e 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
@@ -1,7 +1,6 @@
// 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.deployment;
-import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.log.LogLevel;
@@ -14,13 +13,9 @@ import com.yahoo.vespa.hosted.controller.Instance;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzDbMock;
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.vespa.hosted.controller.api.integration.deployment.SourceRevision;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.TesterId;
import com.yahoo.vespa.hosted.controller.api.integration.routing.RoutingGeneratorMock;
import com.yahoo.vespa.hosted.controller.api.integration.stubs.MockTesterCloud;
-import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.integration.ConfigServerMock;
import com.yahoo.vespa.hosted.controller.maintenance.JobControl;
@@ -36,12 +31,8 @@ import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Collections;
-import java.util.Optional;
import java.util.logging.Logger;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
/**
@@ -59,7 +50,6 @@ public class DeploymentTester {
public static final ApplicationId instanceId = appId.defaultInstance();
public static final TesterId testerId = TesterId.of(instanceId);
- private final DeploymentContext defaultContext;
private final ControllerTester tester;
private final JobController jobs;
private final RoutingGeneratorMock routing;
@@ -104,7 +94,6 @@ public class DeploymentTester {
outstandingChangeDeployer = new OutstandingChangeDeployer(tester.controller(), maintenanceInterval, jobControl);
nameServiceDispatcher = new NameServiceDispatcher(tester.controller(), maintenanceInterval, jobControl,
Integer.MAX_VALUE);
- defaultContext = newDeploymentContext(instanceId);
routing.putEndpoints(new DeploymentId(null, null), Collections.emptyList()); // Turn off default behaviour for the mock.
// Get deployment job logs to stderr.
@@ -139,9 +128,9 @@ public class DeploymentTester {
return this;
}
- /** Returns the default deployment context owned by this */
- public DeploymentContext deploymentContext() {
- return defaultContext;
+ /** Create the deployment context for the default instance id */
+ public DeploymentContext newDeploymentContext() {
+ return newDeploymentContext(instanceId);
}
/** Create a new deployment context for given application */
@@ -159,29 +148,6 @@ public class DeploymentTester {
return newDeploymentContext(tenantName, applicationName, instanceName).application();
}
- /** Submits a new application, and returns the version of the new submission. */
- public ApplicationVersion newSubmission(TenantAndApplicationId id, ApplicationPackage applicationPackage, SourceRevision sourceRevision) {
- return newDeploymentContext(id.defaultInstance()).submit(applicationPackage, sourceRevision).lastSubmission().get();
- }
-
- public ApplicationVersion newSubmission(TenantAndApplicationId id, ApplicationPackage applicationPackage) {
- return newSubmission(id, applicationPackage, DeploymentContext.defaultSourceRevision);
- }
-
- /**
- * Submits a new application package, and returns the version of the new submission.
- */
- public ApplicationVersion newSubmission(ApplicationPackage applicationPackage) {
- return newSubmission(appId, applicationPackage);
- }
-
- /**
- * Submits a new application, and returns the version of the new submission.
- */
- public ApplicationVersion newSubmission() {
- return defaultContext.submit().lastSubmission().get();
- }
-
/**
* Sets a single endpoint in the routing mock; this matches that required for the tester.
*/
@@ -189,34 +155,6 @@ public class DeploymentTester {
newDeploymentContext(id).setEndpoints(zone);
}
- /** Completely deploys the given application version, assuming it is the last to be submitted. */
- public void deployNewSubmission(ApplicationVersion version) {
- deployNewSubmission(appId, version);
- }
-
- /** Completely deploys the given application version, assuming it is the last to be submitted. */
- public void deployNewSubmission(TenantAndApplicationId id, ApplicationVersion version) {
- var context = newDeploymentContext(id.defaultInstance());
- var application = context.application();
- assertFalse(application.instances().values().stream()
- .anyMatch(instance -> instance.deployments().values().stream()
- .anyMatch(deployment -> deployment.applicationVersion().equals(version))));
- assertEquals(version, application.change().application().get());
- assertFalse(application.change().platform().isPresent());
- context.completeRollout();
- assertFalse(context.application().change().hasTargets());
- }
-
- /** Completely deploys the given, new platform. */
- public void deployNewPlatform(Version version) {
- deployNewPlatform(appId, version);
- }
-
- /** Completely deploys the given, new platform. */
- public void deployNewPlatform(TenantAndApplicationId id, Version version) {
- newDeploymentContext(id.defaultInstance()).deployPlatform(version);
- }
-
/** Aborts and finishes all running jobs. */
public void abortAll() {
triggerJobs();
@@ -234,63 +172,4 @@ public class DeploymentTester {
return triggered;
}
- /** Starts a manual deployment of the given package, and then runs the whole of the given job, successfully. */
- public void runJob(ApplicationId instanceId, JobType type, ApplicationPackage applicationPackage) {
- jobs.deploy(instanceId, type, Optional.empty(), applicationPackage);
- newDeploymentContext(instanceId).runJob(type);
- }
-
- /** Pulls the ready job trigger, and then runs the whole of the given job, successfully. */
- public void runJob(JobType type) {
- defaultContext.runJob(type);
- }
-
- /** Pulls the ready job trigger, and then runs the whole of the given job, successfully. */
- public void runJob(ApplicationId instanceId, JobType type) {
- if (type.environment().isManuallyDeployed())
- throw new IllegalArgumentException("Use overload with application package for dev/perf jobs");
- newDeploymentContext(instanceId).runJob(type);
- }
-
- public void failDeployment(JobType type) {
- defaultContext.failDeployment(type);
- }
-
- public void failDeployment(ApplicationId instanceId, JobType type) {
- newDeploymentContext(instanceId).failDeployment(type);
- }
-
- public void timeOutUpgrade(JobType type) {
- defaultContext.timeOutUpgrade(type);
- }
-
- public void timeOutUpgrade(ApplicationId instanceId, JobType type) {
- newDeploymentContext(instanceId).timeOutConvergence(type);
- }
-
- public void timeOutConvergence(JobType type) {
- defaultContext.timeOutConvergence(type);
- }
-
- public void timeOutConvergence(ApplicationId instanceId, JobType type) {
- newDeploymentContext(instanceId).timeOutConvergence(type);
- }
-
- public RunId startSystemTestTests() {
- return defaultContext.startSystemTestTests();
- }
-
- /** Creates and submits a new application, and then starts the job of the given type. Use only once per test. */
- public RunId newRun(JobType type) {
- return defaultContext.newRun(type);
- }
-
- public void assertRunning(JobType type) {
- assertRunning(instanceId, type);
- }
-
- public void assertRunning(ApplicationId id, JobType type) {
- assertTrue(jobs.active().stream().anyMatch(run -> run.id().application().equals(id) && run.id().type() == type));
- }
-
}
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 07cddbe2c7e..8fb766164c2 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
@@ -59,7 +59,7 @@ public class DeploymentTriggerTest {
.build();
// Deploy completely once
- var app = tester.deploymentContext().submit(applicationPackage).deploy();
+ var app = tester.newDeploymentContext().submit(applicationPackage).deploy();
// New version is released
Version version = Version.fromString("6.3");
@@ -90,6 +90,7 @@ public class DeploymentTriggerTest {
tester.applications().lockApplicationOrThrow(app.application().id(), locked ->
tester.applications().store(locked.withProjectId(OptionalLong.empty())));
app.timeOutConvergence(productionUsWest1);
+ tester.triggerJobs();
assertEquals("Job is not triggered when no projectId is present", 0, tester.jobs().active().size());
}
@@ -141,7 +142,7 @@ public class DeploymentTriggerTest {
@Test
public void abortsJobsOnNewApplicationChange() {
- var app = tester.deploymentContext();
+ var app = tester.newDeploymentContext();
app.submit()
.runJob(systemTest)
.runJob(stagingTest);
@@ -188,7 +189,7 @@ public class DeploymentTriggerTest {
.region("us-central-1")
.delay(Duration.ofMinutes(10)) // Delays after last region are valid, but have no effect
.build();
- var app = tester.deploymentContext().submit(applicationPackage);
+ var app = tester.newDeploymentContext().submit(applicationPackage);
// Test jobs pass
app.runJob(systemTest).runJob(stagingTest);
@@ -242,7 +243,7 @@ public class DeploymentTriggerTest {
.region("eu-west-1")
.build();
- var app = tester.deploymentContext().submit(applicationPackage);
+ var app = tester.newDeploymentContext().submit(applicationPackage);
// Test jobs pass
app.runJob(systemTest).runJob(stagingTest);
@@ -279,7 +280,7 @@ public class DeploymentTriggerTest {
.region("us-central-1")
.parallel("us-west-1", "us-east-3")
.build();
- var application = tester.deploymentContext().submit().deploy();
+ var application = tester.newDeploymentContext().submit().deploy();
// The first production zone is suspended:
tester.configServer().setSuspended(application.deploymentIdIn(ZoneId.from("prod", "us-central-1")), true);
@@ -317,7 +318,7 @@ public class DeploymentTriggerTest {
.region("us-east-3")
.build();
- var app = tester.deploymentContext().submit(applicationPackage).deploy();
+ var app = tester.newDeploymentContext().submit(applicationPackage).deploy();
tester.clock().advance(Duration.ofHours(1)); // --------------- Enter block window: 18:30
@@ -352,7 +353,7 @@ public class DeploymentTriggerTest {
.region("us-west-1")
.region("us-east-3")
.build();
- var app = tester.deploymentContext().submit(applicationPackage).deploy();
+ var app = tester.newDeploymentContext().submit(applicationPackage).deploy();
// Application on (6.1, 1.0.1)
Version v1 = Version.fromString("6.1");
@@ -399,7 +400,7 @@ public class DeploymentTriggerTest {
.region("us-west-1")
.region("us-east-3")
.build();
- var app = tester.deploymentContext().submit(applicationPackage).deploy();
+ var app = tester.newDeploymentContext().submit(applicationPackage).deploy();
tester.controllerTester().upgradeSystem(new Version("9.8.7"));
tester.upgrader().maintain();
@@ -441,7 +442,7 @@ public class DeploymentTriggerTest {
.region("us-central-1")
.region("eu-west-1")
.build();
- var app = tester.deploymentContext().submit(applicationPackage).deploy();
+ var app = tester.newDeploymentContext().submit(applicationPackage).deploy();
// productionUsCentral1 fails after deployment, causing a mismatch between deployed and successful state.
app.submit(applicationPackage)
@@ -561,7 +562,7 @@ public class DeploymentTriggerTest {
.parallel("eu-west-1", "us-east-3")
.build();
// Application version 1 and platform version 6.1.
- var app = tester.deploymentContext().submit(applicationPackage).deploy();
+ var app = tester.newDeploymentContext().submit(applicationPackage).deploy();
// Success in first prod zone, change cancelled between triggering and completion of eu west job.
// One of the parallel zones get a deployment, but both fail their jobs.
@@ -613,7 +614,7 @@ public class DeploymentTriggerTest {
.build();
// Deploy completely on default application and platform versions
- var app = tester.deploymentContext().submit(applicationPackage).deploy();
+ var app = tester.newDeploymentContext().submit(applicationPackage).deploy();
// New application change is deployed and fails in system-test for a while
app.submit(applicationPackage).runJob(stagingTest).failDeployment(systemTest);
@@ -666,7 +667,7 @@ public class DeploymentTriggerTest {
// Initial failure
Instant initialFailure = tester.clock().instant().truncatedTo(MILLIS);
- var app = tester.deploymentContext().submit(applicationPackage);
+ var app = tester.newDeploymentContext().submit(applicationPackage);
app.failDeployment(systemTest);
assertEquals("Failure age is right at initial failure",
initialFailure, app.instance().deploymentJobs().jobStatus().get(systemTest).firstFailing().get().at());
@@ -710,7 +711,7 @@ public class DeploymentTriggerTest {
.region("us-west-1")
.build();
Version version1 = tester.controller().versionStatus().systemVersion().get().versionNumber();
- var app1 = tester.deploymentContext();
+ var app1 = tester.newDeploymentContext();
// First deployment: An application change
app1.submit(applicationPackage).deploy();
@@ -855,21 +856,22 @@ public class DeploymentTriggerTest {
@Test
public void testUserInstancesNotInDeploymentSpec() {
- var app = tester.deploymentContext();
+ var app = tester.newDeploymentContext();
tester.controller().applications().createInstance(app.application().id().instance("user"));
app.submit().deploy();
}
@Test
- @Ignore
public void testMultipleInstances() {
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
.instances("instance1,instance2")
.environment(Environment.prod)
.region("us-east-3")
.build();
- var app = tester.deploymentContext().submit(applicationPackage); // TODO jonmv: support instances in deployment context>
- app.deploy();
+ var app = tester.newDeploymentContext("tenant1", "application1", "instance1").submit(applicationPackage); // TODO jonmv: support instances in deployment context>
+ var otherInstance = tester.newDeploymentContext("tenant1", "application1", "instance2");
+ app.runJob(systemTest).runJob(stagingTest).runJob(productionUsEast3);
+ otherInstance.runJob(systemTest).runJob(stagingTest).runJob(productionUsEast3);
assertEquals(2, app.application().instances().size());
assertEquals(2, app.application().productionDeployments().values().stream()
.mapToInt(Collection::size)
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 7be2b6a9797..a1f0f924dcf 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
@@ -53,7 +53,6 @@ import static com.yahoo.vespa.hosted.controller.api.integration.LogEntry.Type.wa
import static com.yahoo.vespa.hosted.controller.deployment.DeploymentTester.instanceId;
import static com.yahoo.vespa.hosted.controller.deployment.DeploymentContext.applicationPackage;
import static com.yahoo.vespa.hosted.controller.deployment.DeploymentContext.publicCdApplicationPackage;
-import static com.yahoo.vespa.hosted.controller.deployment.DeploymentTester.testerId;
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;
@@ -70,10 +69,12 @@ import static org.junit.Assert.fail;
public class InternalStepRunnerTest {
private DeploymentTester tester;
+ private DeploymentContext app;
@Before
public void setup() {
tester = new DeploymentTester();
+ app = tester.newDeploymentContext();
}
private SystemName system() {
@@ -82,38 +83,39 @@ public class InternalStepRunnerTest {
@Test
public void canRegisterAndRunDirectly() {
- tester.deploymentContext().submit().deploy();
+ app.submit().deploy();
}
@Test
public void testerHasAthenzIdentity() {
- tester.newRun(JobType.stagingTest);
+ app.submit();
+ tester.triggerJobs();
tester.runner().run();
DeploymentSpec spec = tester.configServer()
- .application(DeploymentTester.testerId.id(), JobType.stagingTest.zone(system())).get()
+ .application(app.testerId().id(), JobType.stagingTest.zone(system())).get()
.applicationPackage().deploymentSpec();
+ assertTrue(spec.instance(app.testerId().id().instance()).isPresent());
assertEquals("domain", spec.athenzDomain().get().value());
- ZoneId zone = JobType.stagingTest.zone(system());
- assertEquals("service", spec.athenzService(zone.environment(), zone.region()).get().value());
+ assertEquals("service", spec.athenzService().get().value());
}
@Test
public void refeedRequirementBlocksDeployment() {
- RunId id = tester.newRun(JobType.stagingTest);
+ RunId id = app.newRun(JobType.stagingTest);
- tester.setEndpoints(testerId.id(), JobType.stagingTest.zone(system()));
+ tester.setEndpoints(app.testerId().id(), JobType.stagingTest.zone(system()));
tester.runner().run();
assertEquals(unfinished, tester.jobs().run(id).get().steps().get(Step.installInitialReal));
- tester.setEndpoints(instanceId, JobType.stagingTest.zone(system()));
- tester.configServer().convergeServices(instanceId, JobType.stagingTest.zone(system()));
+ tester.setEndpoints(app.instanceId(), JobType.stagingTest.zone(system()));
+ tester.configServer().convergeServices(app.instanceId(), JobType.stagingTest.zone(system()));
tester.configServer().setConfigChangeActions(new ConfigChangeActions(Collections.emptyList(),
- singletonList(new RefeedAction("Refeed",
- false,
- "doctype",
- "cluster",
- Collections.emptyList(),
- singletonList("Refeed it!")))));
+ singletonList(new RefeedAction("Refeed",
+ false,
+ "doctype",
+ "cluster",
+ Collections.emptyList(),
+ singletonList("Refeed it!")))));
tester.runner().run();
assertEquals(failed, tester.jobs().run(id).get().steps().get(Step.deployReal));
@@ -121,11 +123,11 @@ public class InternalStepRunnerTest {
@Test
public void restartsServicesAndWaitsForRestartAndReboot() {
- RunId id = tester.newRun(JobType.productionUsCentral1);
+ RunId id = app.newRun(JobType.productionUsCentral1);
ZoneId zone = id.type().zone(system());
HostName host = tester.configServer().hostFor(instanceId, zone);
- tester.setEndpoints(testerId.id(), JobType.productionUsCentral1.zone(system()));
+ tester.setEndpoints(app.testerId().id(), JobType.productionUsCentral1.zone(system()));
tester.runner().run();
tester.configServer().setConfigChangeActions(new ConfigChangeActions(singletonList(new RestartAction("cluster",
@@ -140,11 +142,11 @@ public class InternalStepRunnerTest {
tester.runner().run();
assertEquals(succeeded, tester.jobs().run(id).get().steps().get(Step.deployReal));
- tester.configServer().convergeServices(instanceId, zone);
+ tester.configServer().convergeServices(app.instanceId(), zone);
assertEquals(unfinished, tester.jobs().run(id).get().steps().get(Step.installReal));
- tester.configServer().nodeRepository().doRestart(new DeploymentId(instanceId, zone), Optional.of(host));
- tester.configServer().nodeRepository().requestReboot(new DeploymentId(instanceId, zone), Optional.of(host));
+ tester.configServer().nodeRepository().doRestart(app.deploymentIdIn(zone), Optional.of(host));
+ tester.configServer().nodeRepository().requestReboot(app.deploymentIdIn(zone), Optional.of(host));
tester.runner().run();
assertEquals(unfinished, tester.jobs().run(id).get().steps().get(Step.installReal));
@@ -155,85 +157,85 @@ public class InternalStepRunnerTest {
@Test
public void waitsForEndpointsAndTimesOut() {
- tester.newRun(JobType.systemTest);
+ app.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(system()));
- tester.setEndpoints(instanceId, JobType.stagingTest.zone(system()));
+ tester.setEndpoints(app.testerId().id(), JobType.systemTest.zone(system()));
+ tester.setEndpoints(app.instanceId(), JobType.stagingTest.zone(system()));
tester.runner().run();
- tester.configServer().convergeServices(instanceId, JobType.stagingTest.zone(system()));
+ tester.configServer().convergeServices(app.instanceId(), JobType.stagingTest.zone(system()));
tester.runner().run();
- tester.configServer().convergeServices(instanceId, JobType.systemTest.zone(system()));
- tester.configServer().convergeServices(testerId.id(), JobType.systemTest.zone(system()));
- tester.configServer().convergeServices(instanceId, JobType.stagingTest.zone(system()));
- tester.configServer().convergeServices(testerId.id(), JobType.stagingTest.zone(system()));
+ tester.configServer().convergeServices(app.instanceId(), JobType.systemTest.zone(system()));
+ tester.configServer().convergeServices(app.testerId().id(), JobType.systemTest.zone(system()));
+ tester.configServer().convergeServices(app.instanceId(), JobType.stagingTest.zone(system()));
+ tester.configServer().convergeServices(app.testerId().id(), JobType.stagingTest.zone(system()));
tester.runner().run();
tester.clock().advance(InternalStepRunner.endpointTimeout.plus(Duration.ofSeconds(1)));
tester.runner().run();
- assertEquals(failed, tester.jobs().last(instanceId, JobType.systemTest).get().steps().get(Step.installReal));
- assertEquals(failed, tester.jobs().last(instanceId, JobType.stagingTest).get().steps().get(Step.installTester));
+ assertEquals(failed, tester.jobs().last(app.instanceId(), JobType.systemTest).get().steps().get(Step.installReal));
+ assertEquals(failed, tester.jobs().last(app.instanceId(), JobType.stagingTest).get().steps().get(Step.installTester));
}
@Test
public void installationFailsIfDeploymentExpires() {
- tester.newRun(JobType.systemTest);
+ app.newRun(JobType.systemTest);
tester.runner().run();
- tester.configServer().convergeServices(instanceId, JobType.systemTest.zone(system()));
- tester.setEndpoints(instanceId, JobType.systemTest.zone(system()));
+ tester.configServer().convergeServices(app.instanceId(), JobType.systemTest.zone(system()));
+ tester.setEndpoints(app.instanceId(), JobType.systemTest.zone(system()));
tester.runner().run();
- assertEquals(succeeded, tester.jobs().last(instanceId, JobType.systemTest).get().steps().get(Step.installReal));
+ assertEquals(succeeded, tester.jobs().last(app.instanceId(), JobType.systemTest).get().steps().get(Step.installReal));
- tester.applications().deactivate(instanceId, JobType.systemTest.zone(system()));
+ tester.applications().deactivate(app.instanceId(), JobType.systemTest.zone(system()));
tester.runner().run();
- assertEquals(failed, tester.jobs().last(instanceId, JobType.systemTest).get().steps().get(Step.installTester));
- assertTrue(tester.jobs().last(instanceId, JobType.systemTest).get().hasEnded());
- assertTrue(tester.jobs().last(instanceId, JobType.systemTest).get().hasFailed());
+ assertEquals(failed, tester.jobs().last(app.instanceId(), JobType.systemTest).get().steps().get(Step.installTester));
+ assertTrue(tester.jobs().last(app.instanceId(), JobType.systemTest).get().hasEnded());
+ assertTrue(tester.jobs().last(app.instanceId(), JobType.systemTest).get().hasFailed());
}
@Test
public void startTestsFailsIfDeploymentExpires() {
- tester.newRun(JobType.systemTest);
+ app.newRun(JobType.systemTest);
tester.runner().run();
- tester.configServer().convergeServices(instanceId, JobType.systemTest.zone(system()));
- tester.configServer().convergeServices(testerId.id(), JobType.systemTest.zone(system()));
+ tester.configServer().convergeServices(app.instanceId(), JobType.systemTest.zone(system()));
+ tester.configServer().convergeServices(app.testerId().id(), JobType.systemTest.zone(system()));
tester.runner().run();
- tester.applications().deactivate(instanceId, JobType.systemTest.zone(system()));
+ tester.applications().deactivate(app.instanceId(), JobType.systemTest.zone(system()));
tester.runner().run();
- assertEquals(unfinished, tester.jobs().last(instanceId, JobType.systemTest).get().steps().get(Step.startTests));
+ assertEquals(unfinished, tester.jobs().last(app.instanceId(), JobType.systemTest).get().steps().get(Step.startTests));
}
@Test
public void alternativeEndpointsAreDetected() {
- tester.newRun(JobType.systemTest);
+ app.newRun(JobType.systemTest);
tester.runner().run();;
- tester.configServer().convergeServices(instanceId, JobType.systemTest.zone(system()));
- tester.configServer().convergeServices(testerId.id(), JobType.systemTest.zone(system()));
- assertEquals(unfinished, tester.jobs().last(instanceId, JobType.systemTest).get().steps().get(Step.installReal));
- assertEquals(unfinished, tester.jobs().last(instanceId, JobType.systemTest).get().steps().get(Step.installTester));
-
- tester.controller().curator().writeRoutingPolicies(instanceId, Set.of(new RoutingPolicy(instanceId,
+ tester.configServer().convergeServices(app.instanceId(), JobType.systemTest.zone(system()));
+ tester.configServer().convergeServices(app.testerId().id(), JobType.systemTest.zone(system()));
+ assertEquals(unfinished, tester.jobs().last(app.instanceId(), JobType.systemTest).get().steps().get(Step.installReal));
+ assertEquals(unfinished, tester.jobs().last(app.instanceId(), JobType.systemTest).get().steps().get(Step.installTester));
+
+ tester.controller().curator().writeRoutingPolicies(app.instanceId(), Set.of(new RoutingPolicy(app.instanceId(),
+ ClusterSpec.Id.from("default"),
+ JobType.systemTest.zone(system()),
+ HostName.from("host"),
+ Optional.empty(),
+ emptySet())));
+ tester.controller().curator().writeRoutingPolicies(app.testerId().id(), Set.of(new RoutingPolicy(app.testerId().id(),
ClusterSpec.Id.from("default"),
JobType.systemTest.zone(system()),
HostName.from("host"),
Optional.empty(),
emptySet())));
- tester.controller().curator().writeRoutingPolicies(testerId.id(), Set.of(new RoutingPolicy(testerId.id(),
- ClusterSpec.Id.from("default"),
- JobType.systemTest.zone(system()),
- HostName.from("host"),
- Optional.empty(),
- emptySet())));
tester.runner().run();;
- assertEquals(succeeded, tester.jobs().last(instanceId, JobType.systemTest).get().steps().get(Step.installReal));
- assertEquals(succeeded, tester.jobs().last(instanceId, JobType.systemTest).get().steps().get(Step.installTester));
+ assertEquals(succeeded, tester.jobs().last(app.instanceId(), JobType.systemTest).get().steps().get(Step.installReal));
+ assertEquals(succeeded, tester.jobs().last(app.instanceId(), JobType.systemTest).get().steps().get(Step.installTester));
}
@Test
public void testsFailIfTesterRestarts() {
- RunId id = tester.startSystemTestTests();
+ RunId id = app.startSystemTestTests();
tester.cloud().set(TesterCloud.Status.NOT_STARTED);
tester.runner().run();
assertEquals(failed, tester.jobs().run(id).get().steps().get(Step.endTests));
@@ -241,7 +243,7 @@ public class InternalStepRunnerTest {
@Test
public void testsFailIfTestsFailRemotely() {
- RunId id = tester.startSystemTestTests();
+ RunId id = app.startSystemTestTests();
tester.cloud().add(new LogEntry(123, Instant.ofEpochMilli(321), error, "Failure!"));
tester.cloud().set(TesterCloud.Status.FAILURE);
@@ -255,7 +257,7 @@ public class InternalStepRunnerTest {
@Test
public void testsFailIfTestsErr() {
- RunId id = tester.startSystemTestTests();
+ RunId id = app.startSystemTestTests();
tester.cloud().add(new LogEntry(0, Instant.ofEpochMilli(123), error, "Error!"));
tester.cloud().set(TesterCloud.Status.ERROR);
@@ -269,13 +271,13 @@ public class InternalStepRunnerTest {
@Test
public void testsSucceedWhenTheyDoRemotely() {
- RunId id = tester.startSystemTestTests();
+ RunId id = app.startSystemTestTests();
tester.runner().run();
assertEquals(unfinished, tester.jobs().run(id).get().steps().get(Step.endTests));
- assertEquals(URI.create(tester.routing().endpoints(new DeploymentId(testerId.id(), JobType.systemTest.zone(system()))).get(0).endpoint()),
+ assertEquals(URI.create(tester.routing().endpoints(new DeploymentId(app.testerId().id(), JobType.systemTest.zone(system()))).get(0).endpoint()),
tester.cloud().testerUrl());
Inspector configObject = SlimeUtils.jsonToSlime(tester.cloud().config()).get();
- assertEquals(instanceId.serializedForm(), configObject.field("application").asString());
+ assertEquals(app.instanceId().serializedForm(), configObject.field("application").asString());
assertEquals(JobType.systemTest.zone(system()).value(), configObject.field("zone").asString());
assertEquals(system().value(), configObject.field("system").asString());
assertEquals(1, configObject.field("endpoints").children());
@@ -308,36 +310,36 @@ public class InternalStepRunnerTest {
@Test
public void deployToDev() {
ZoneId zone = JobType.devUsEast1.zone(system());
- tester.jobs().deploy(instanceId, JobType.devUsEast1, Optional.empty(), applicationPackage);
+ tester.jobs().deploy(app.instanceId(), JobType.devUsEast1, Optional.empty(), applicationPackage);
tester.runner().run();
- RunId id = tester.jobs().last(instanceId, JobType.devUsEast1).get().id();
+ RunId id = tester.jobs().last(app.instanceId(), JobType.devUsEast1).get().id();
assertEquals(unfinished, tester.jobs().run(id).get().steps().get(Step.installReal));
Version version = new Version("7.8.9");
Future<?> concurrentDeployment = Executors.newSingleThreadExecutor().submit(() -> {
- tester.jobs().deploy(instanceId, JobType.devUsEast1, Optional.of(version), applicationPackage);
+ tester.jobs().deploy(app.instanceId(), JobType.devUsEast1, Optional.of(version), applicationPackage);
});
while ( ! concurrentDeployment.isDone())
tester.runner().run();
- assertEquals(id.number() + 1, tester.jobs().last(instanceId, JobType.devUsEast1).get().id().number());
+ assertEquals(id.number() + 1, tester.jobs().last(app.instanceId(), JobType.devUsEast1).get().id().number());
ApplicationPackage otherPackage = new ApplicationPackageBuilder().region("us-central-1").build();
- tester.jobs().deploy(instanceId, JobType.perfUsEast3, Optional.empty(), otherPackage);
+ tester.jobs().deploy(app.instanceId(), JobType.perfUsEast3, Optional.empty(), otherPackage);
tester.runner().run(); // Job run order determined by JobType enum order per application.
- tester.configServer().convergeServices(instanceId, zone);
- tester.setEndpoints(instanceId, zone);
+ tester.configServer().convergeServices(app.instanceId(), zone);
+ tester.setEndpoints(app.instanceId(), zone);
assertEquals(unfinished, tester.jobs().run(id).get().steps().get(Step.installReal));
- assertEquals(applicationPackage.hash(), tester.configServer().application(instanceId, zone).get().applicationPackage().hash());
- assertEquals(otherPackage.hash(), tester.configServer().application(instanceId, JobType.perfUsEast3.zone(system())).get().applicationPackage().hash());
+ assertEquals(applicationPackage.hash(), tester.configServer().application(app.instanceId(), zone).get().applicationPackage().hash());
+ assertEquals(otherPackage.hash(), tester.configServer().application(app.instanceId(), JobType.perfUsEast3.zone(system())).get().applicationPackage().hash());
- tester.configServer().setVersion(instanceId, zone, version);
+ tester.configServer().setVersion(app.instanceId(), zone, version);
tester.runner().run();
assertEquals(1, tester.jobs().active().size());
- assertEquals(version, tester.instance(instanceId).deployments().get(zone).version());
+ assertEquals(version, tester.instance(app.instanceId()).deployments().get(zone).version());
try {
- tester.jobs().deploy(instanceId, JobType.productionApNortheast1, Optional.empty(), applicationPackage);
+ tester.jobs().deploy(app.instanceId(), JobType.productionApNortheast1, Optional.empty(), applicationPackage);
fail("Deployments outside dev should not be allowed.");
}
catch (IllegalArgumentException expected) { }
@@ -345,10 +347,10 @@ public class InternalStepRunnerTest {
@Test
public void notificationIsSent() {
- tester.startSystemTestTests();
+ app.startSystemTestTests();
tester.cloud().set(TesterCloud.Status.NOT_STARTED);
tester.runner().run();
- MockMailer mailer = ((MockMailer) tester.controller().serviceRegistry().mailer());
+ MockMailer mailer = tester.controllerTester().serviceRegistry().mailer();
assertEquals(1, mailer.inbox("a@b").size());
assertEquals("Vespa application tenant.application: System test failing due to system error",
mailer.inbox("a@b").get(0).subject());
@@ -359,7 +361,7 @@ public class InternalStepRunnerTest {
@Test
public void vespaLogIsCopied() {
- RunId id = tester.startSystemTestTests();
+ RunId id = app.startSystemTestTests();
tester.cloud().set(TesterCloud.Status.ERROR);
tester.configServer().setLogStream(vespaLog);
long lastId = tester.jobs().details(id).get().lastId().getAsLong();
@@ -384,11 +386,11 @@ public class InternalStepRunnerTest {
public void certificateTimeoutAbortsJob() {
tester.controllerTester().zoneRegistry().setSystemName(SystemName.PublicCd);
tester.controllerTester().zoneRegistry().setZones(ZoneApiMock.fromId("prod.aws-us-east-1c"));
- RunId id = tester.startSystemTestTests();
+ RunId id = app.startSystemTestTests();
List<X509Certificate> trusted = new ArrayList<>(publicCdApplicationPackage.trustedCertificates());
trusted.add(tester.jobs().run(id).get().testerCertificate().get());
- assertEquals(trusted, tester.configServer().application(instanceId, id.type().zone(system())).get().applicationPackage().trustedCertificates());
+ assertEquals(trusted, tester.configServer().application(app.instanceId(), id.type().zone(system())).get().applicationPackage().trustedCertificates());
tester.clock().advance(InternalStepRunner.certificateTimeout.plus(Duration.ofSeconds(1)));
tester.runner().run();
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ApplicationStoreMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ApplicationStoreMock.java
index d915fe06720..3c33051e98b 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ApplicationStoreMock.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ApplicationStoreMock.java
@@ -36,7 +36,11 @@ public class ApplicationStoreMock implements ApplicationStore {
@Override
public byte[] get(TenantName tenant, ApplicationName application, ApplicationVersion applicationVersion) {
- return requireNonNull(store.get(appId(tenant, application)).get(applicationVersion));
+ byte[] bytes = store.get(appId(tenant, application)).get(applicationVersion);
+ if (bytes == null)
+ throw new IllegalArgumentException("No application package found for " + tenant + "." + application +
+ " with version " + applicationVersion.id());
+ return bytes;
}
@Override
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java
index a3c06c75a26..eced161cebc 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java
@@ -1,50 +1,33 @@
// 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.google.inject.Inject;
+import com.yahoo.cloud.config.ConfigserverConfig;
import com.yahoo.component.AbstractComponent;
+import com.yahoo.config.provision.SystemName;
import com.yahoo.test.ManualClock;
-import com.yahoo.vespa.hosted.controller.api.integration.BuildService;
-import com.yahoo.vespa.hosted.controller.api.integration.RunDataStore;
import com.yahoo.vespa.hosted.controller.api.integration.ServiceRegistry;
-import com.yahoo.vespa.hosted.controller.api.integration.aws.AwsEventFetcher;
import com.yahoo.vespa.hosted.controller.api.integration.aws.MockAwsEventFetcher;
import com.yahoo.vespa.hosted.controller.api.integration.certificates.ApplicationCertificateMock;
-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.dns.MemoryNameService;
-import com.yahoo.vespa.hosted.controller.api.integration.dns.NameService;
-import com.yahoo.vespa.hosted.controller.api.integration.entity.EntityService;
import com.yahoo.vespa.hosted.controller.api.integration.entity.MemoryEntityService;
-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.IssueHandler;
-import com.yahoo.vespa.hosted.controller.api.integration.organization.Mailer;
import com.yahoo.vespa.hosted.controller.api.integration.organization.MockBilling;
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.organization.OwnershipIssues;
-import com.yahoo.vespa.hosted.controller.api.integration.resource.CostReportConsumer;
import com.yahoo.vespa.hosted.controller.api.integration.resource.CostReportConsumerMock;
-import com.yahoo.vespa.hosted.controller.api.integration.resource.MeteringClient;
import com.yahoo.vespa.hosted.controller.api.integration.resource.MockTenantCost;
-import com.yahoo.vespa.hosted.controller.api.integration.resource.TenantCost;
import com.yahoo.vespa.hosted.controller.api.integration.routing.GlobalRoutingService;
import com.yahoo.vespa.hosted.controller.api.integration.routing.MemoryGlobalRoutingService;
import com.yahoo.vespa.hosted.controller.api.integration.routing.RoutingGenerator;
import com.yahoo.vespa.hosted.controller.api.integration.routing.RoutingGeneratorMock;
import com.yahoo.vespa.hosted.controller.api.integration.stubs.DummyOwnershipIssues;
import com.yahoo.vespa.hosted.controller.api.integration.stubs.LoggingDeploymentIssues;
-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.MockMeteringClient;
import com.yahoo.vespa.hosted.controller.api.integration.stubs.MockRunDataStore;
import com.yahoo.vespa.hosted.controller.api.integration.stubs.MockTesterCloud;
-import java.time.Clock;
-
/**
* A mock implementation of a {@link ServiceRegistry} for testing purposes.
*
@@ -53,8 +36,8 @@ import java.time.Clock;
public class ServiceRegistryMock extends AbstractComponent implements ServiceRegistry {
private final ManualClock clock = new ManualClock();
- private final ZoneRegistryMock zoneRegistryMock = new ZoneRegistryMock();
- private final ConfigServerMock configServerMock = new ConfigServerMock(zoneRegistryMock);
+ private final ZoneRegistryMock zoneRegistryMock;
+ private final ConfigServerMock configServerMock;
private final MemoryNameService memoryNameService = new MemoryNameService();
private final MemoryGlobalRoutingService memoryGlobalRoutingService = new MemoryGlobalRoutingService();
private final RoutingGeneratorMock routingGeneratorMock = new RoutingGeneratorMock(RoutingGeneratorMock.TEST_ENDPOINTS);
@@ -73,9 +56,22 @@ public class ServiceRegistryMock extends AbstractComponent implements ServiceReg
private final MockTesterCloud mockTesterCloud = new MockTesterCloud();
private final ApplicationStoreMock applicationStoreMock = new ApplicationStoreMock();
private final MockRunDataStore mockRunDataStore = new MockRunDataStore();
- private final MockBuildService mockBuildService = new MockBuildService();
private final MockTenantCost mockTenantCost = new MockTenantCost();
+ public ServiceRegistryMock(SystemName system) {
+ this.zoneRegistryMock = new ZoneRegistryMock(system);
+ this.configServerMock = new ConfigServerMock(zoneRegistryMock);
+ }
+
+ @Inject
+ public ServiceRegistryMock(ConfigserverConfig config) {
+ this(SystemName.from(config.system()));
+ }
+
+ public ServiceRegistryMock() {
+ this(SystemName.main);
+ }
+
@Override
public ConfigServer configServer() {
return configServerMock;
@@ -97,62 +93,62 @@ public class ServiceRegistryMock extends AbstractComponent implements ServiceReg
}
@Override
- public Mailer mailer() {
+ public MockMailer mailer() {
return mockMailer;
}
@Override
- public ApplicationCertificateProvider applicationCertificateProvider() {
+ public ApplicationCertificateMock applicationCertificateProvider() {
return applicationCertificateMock;
}
@Override
- public MeteringClient meteringService() {
+ public MockMeteringClient meteringService() {
return mockMeteringClient;
}
@Override
- public ContactRetriever contactRetriever() {
+ public MockContactRetriever contactRetriever() {
return mockContactRetriever;
}
@Override
- public IssueHandler issueHandler() {
+ public MockIssueHandler issueHandler() {
return mockIssueHandler;
}
@Override
- public OwnershipIssues ownershipIssues() {
+ public DummyOwnershipIssues ownershipIssues() {
return dummyOwnershipIssues;
}
@Override
- public DeploymentIssues deploymentIssues() {
+ public LoggingDeploymentIssues deploymentIssues() {
return loggingDeploymentIssues;
}
@Override
- public EntityService entityService() {
+ public MemoryEntityService entityService() {
return memoryEntityService;
}
@Override
- public CostReportConsumer costReportConsumer() {
+ public CostReportConsumerMock costReportConsumer() {
return costReportConsumerMock;
}
@Override
- public Billing billingService() {
+ public MockBilling billingService() {
return mockBilling;
}
@Override
- public AwsEventFetcher eventFetcherService() {
+ public MockAwsEventFetcher eventFetcherService() {
return mockAwsEventFetcher;
}
@Override
- public ArtifactRepository artifactRepository() {
+ public ArtifactRepositoryMock artifactRepository() {
return artifactRepositoryMock;
}
@@ -162,29 +158,25 @@ public class ServiceRegistryMock extends AbstractComponent implements ServiceReg
}
@Override
- public ApplicationStore applicationStore() {
+ public ApplicationStoreMock applicationStore() {
return applicationStoreMock;
}
@Override
- public RunDataStore runDataStore() {
+ public MockRunDataStore runDataStore() {
return mockRunDataStore;
}
@Override
- public BuildService buildService() {
- return mockBuildService;
- }
-
- @Override
- public NameService nameService() {
+ public MemoryNameService nameService() {
return memoryNameService;
}
@Override
- public TenantCost tenantCost() { return mockTenantCost;}
+ public MockTenantCost tenantCost() { return mockTenantCost;}
- public ZoneRegistryMock zoneRegistryMock() {
+ @Override
+ public ZoneRegistryMock zoneRegistry() {
return zoneRegistryMock;
}
@@ -212,10 +204,6 @@ public class ServiceRegistryMock extends AbstractComponent implements ServiceReg
return artifactRepositoryMock;
}
- public MockBuildService buildServiceMock() {
- return mockBuildService;
- }
-
public ApplicationCertificateMock applicationCertificateMock() {
return applicationCertificateMock;
}
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 32bbf3ceb9b..07db06164c6 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
@@ -1,7 +1,6 @@
// 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.google.common.collect.ImmutableList;
import com.google.inject.Inject;
import com.yahoo.cloud.config.ConfigserverConfig;
import com.yahoo.component.AbstractComponent;
@@ -24,8 +23,6 @@ import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry;
import java.net.URI;
import java.time.Duration;
-import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -38,20 +35,11 @@ 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<ZoneApi> zones = new ArrayList<>();
+ private List<ZoneApi> zones = List.of();
private SystemName system;
private UpgradePolicy upgradePolicy = null;
private Map<CloudName, UpgradePolicy> osUpgradePolicies = new HashMap<>();
- @Inject
- public ZoneRegistryMock(ConfigserverConfig config) {
- this(SystemName.from(config.system()));
- }
-
- public ZoneRegistryMock() {
- this(SystemName.main);
- }
-
/**
* This sets the default list of zones contained in this. If your test need a particular set of zones, use
* {@link #setZones(List)} instead of changing the default set.}
@@ -136,7 +124,7 @@ public class ZoneRegistryMock extends AbstractComponent implements ZoneRegistry
@Override
public List<UpgradePolicy> osUpgradePolicies() {
- return ImmutableList.copyOf(osUpgradePolicies.values());
+ return List.copyOf(osUpgradePolicies.values());
}
@Override
@@ -176,7 +164,9 @@ public class ZoneRegistryMock extends AbstractComponent implements ZoneRegistry
@Override
public List<URI> getConfigServerUris(ZoneId zoneId) {
- return Collections.singletonList(URI.create(String.format("https://cfg.%s.test:4443/", zoneId.value())));
+ return List.of(
+ URI.create(String.format("https://cfg1.%s.test:4443/", zoneId.value())),
+ URI.create(String.format("https://cfg2.%s.test:4443/", zoneId.value())));
}
@Override
@@ -186,11 +176,9 @@ public class ZoneRegistryMock extends AbstractComponent implements ZoneRegistry
@Override
public List<URI> getConfigServerApiUris(ZoneId zoneId) {
- List<URI> uris = new ArrayList<URI>();
- uris.add(URI.create(String.format("https://cfg.%s.test:4443/", zoneId.value())));
- uris.add(URI.create(String.format("https://cfg.%s.test.vip:4443/", zoneId.value())));
-
- return uris;
+ return List.of(
+ URI.create(String.format("https://cfg.%s.test:4443/", zoneId.value())),
+ URI.create(String.format("https://cfg.%s.test.vip:4443/", zoneId.value())));
}
@Override
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmerTest.java
index 88d1626b834..8997f34fb98 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmerTest.java
@@ -2,15 +2,12 @@
package com.yahoo.vespa.hosted.controller.maintenance;
import com.yahoo.config.provision.InstanceName;
-import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.LockedTenant;
import com.yahoo.vespa.hosted.controller.api.integration.organization.ApplicationSummary;
import com.yahoo.vespa.hosted.controller.api.integration.organization.Contact;
import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId;
import com.yahoo.vespa.hosted.controller.api.integration.organization.OwnershipIssues;
import com.yahoo.vespa.hosted.controller.api.integration.organization.User;
-import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
-import com.yahoo.vespa.hosted.controller.deployment.DeploymentContext;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester;
import com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb;
import com.yahoo.vespa.hosted.controller.tenant.UserTenant;
@@ -20,7 +17,6 @@ import org.junit.Test;
import java.time.Duration;
import java.util.List;
import java.util.Optional;
-import java.util.function.Supplier;
import static com.yahoo.vespa.hosted.controller.deployment.DeploymentTester.appId;
import static org.junit.Assert.assertEquals;
@@ -46,20 +42,18 @@ public class ApplicationOwnershipConfirmerTest {
@Test
public void testConfirmation() {
Optional<Contact> contact = Optional.of(tester.controllerTester().serviceRegistry().contactRetrieverMock().contact());
+ var propertyApp = tester.newDeploymentContext();
tester.controller().tenants().lockOrThrow(appId.tenant(), LockedTenant.Athenz.class, tenant ->
tester.controller().tenants().store(tenant.with(contact.get())));
- Supplier<Application> propertyApp = tester::application;
- tester.deployNewSubmission(tester.newSubmission());
+ propertyApp.submit().deploy();
UserTenant user = UserTenant.create("by-user", contact);
tester.controller().tenants().createUser(user);
- tester.createApplication(user.name().value(), "application", "default");
- TenantAndApplicationId userAppId = TenantAndApplicationId.from("by-user", "application");
- Supplier<Application> userApp = () -> tester.controller().applications().requireApplication(userAppId);
- tester.deployNewSubmission(userAppId, tester.newSubmission(userAppId, DeploymentContext.applicationPackage));
+ var userApp = tester.newDeploymentContext("by-user", "application", "default");
+ userApp.submit().deploy();
- assertFalse("No issue is initially stored for a new application.", propertyApp.get().ownershipIssueId().isPresent());
- assertFalse("No issue is initially stored for a new application.", userApp.get().ownershipIssueId().isPresent());
+ assertFalse("No issue is initially stored for a new application.", propertyApp.application().ownershipIssueId().isPresent());
+ assertFalse("No issue is initially stored for a new application.", userApp.application().ownershipIssueId().isPresent());
assertFalse("No escalation has been attempted for a new application", issues.escalatedToContact || issues.escalatedToTerminator);
// Set response from the issue mock, which will be obtained by the maintainer on issue filing.
@@ -67,14 +61,14 @@ public class ApplicationOwnershipConfirmerTest {
issues.response = issueId;
confirmer.maintain();
- assertFalse("No issue is stored for an application newer than 3 months.", propertyApp.get().ownershipIssueId().isPresent());
- assertFalse("No issue is stored for an application newer than 3 months.", userApp.get().ownershipIssueId().isPresent());
+ assertFalse("No issue is stored for an application newer than 3 months.", propertyApp.application().ownershipIssueId().isPresent());
+ assertFalse("No issue is stored for an application newer than 3 months.", userApp.application().ownershipIssueId().isPresent());
tester.clock().advance(Duration.ofDays(91));
confirmer.maintain();
- assertEquals("Confirmation issue has been filed for property owned application.", issueId, propertyApp.get().ownershipIssueId());
- assertEquals("Confirmation issue has been filed for user owned application.", issueId, userApp.get().ownershipIssueId());
+ assertEquals("Confirmation issue has been filed for property owned application.", issueId, propertyApp.application().ownershipIssueId());
+ assertEquals("Confirmation issue has been filed for user owned application.", issueId, userApp.application().ownershipIssueId());
assertTrue(issues.escalatedToTerminator);
assertTrue("Both applications have had their responses ensured.", issues.escalatedToContact);
@@ -82,14 +76,14 @@ public class ApplicationOwnershipConfirmerTest {
issues.response = Optional.empty();
confirmer.maintain();
- assertEquals("Confirmation issue reference is not updated when no issue id is returned.", issueId, propertyApp.get().ownershipIssueId());
- assertEquals("Confirmation issue reference is not updated when no issue id is returned.", issueId, userApp.get().ownershipIssueId());
+ assertEquals("Confirmation issue reference is not updated when no issue id is returned.", issueId, propertyApp.application().ownershipIssueId());
+ assertEquals("Confirmation issue reference is not updated when no issue id is returned.", issueId, userApp.application().ownershipIssueId());
// The user deletes all production deployments — see that the issue is forgotten.
- assertEquals("Confirmation issue for user is still open.", issueId, userApp.get().ownershipIssueId());
- userApp.get().productionDeployments().values().stream().flatMap(List::stream)
- .forEach(deployment -> tester.controller().applications().deactivate(userAppId.defaultInstance(), deployment.zone()));
- assertTrue("No production deployments are listed for user.", userApp.get().require(InstanceName.defaultName()).productionDeployments().isEmpty());
+ assertEquals("Confirmation issue for user is still open.", issueId, userApp.application().ownershipIssueId());
+ userApp.application().productionDeployments().values().stream().flatMap(List::stream)
+ .forEach(deployment -> tester.controller().applications().deactivate(userApp.instanceId(), deployment.zone()));
+ assertTrue("No production deployments are listed for user.", userApp.application().require(InstanceName.defaultName()).productionDeployments().isEmpty());
confirmer.maintain();
// Time has passed, and a new confirmation issue is in order for the property which is still in production.
@@ -97,13 +91,13 @@ public class ApplicationOwnershipConfirmerTest {
issues.response = issueId2;
confirmer.maintain();
- assertEquals("A new confirmation issue id is stored when something is returned to the maintainer.", issueId2, propertyApp.get().ownershipIssueId());
- assertEquals("Confirmation issue for application without production deployments has not been filed.", issueId, userApp.get().ownershipIssueId());
+ assertEquals("A new confirmation issue id is stored when something is returned to the maintainer.", issueId2, propertyApp.application().ownershipIssueId());
+ assertEquals("Confirmation issue for application without production deployments has not been filed.", issueId, userApp.application().ownershipIssueId());
- assertFalse("No owner is stored for application", propertyApp.get().owner().isPresent());
+ assertFalse("No owner is stored for application", propertyApp.application().owner().isPresent());
issues.owner = Optional.of(User.from("username"));
confirmer.maintain();
- assertEquals("Owner has been added to application", propertyApp.get().owner().get().username(), "username");
+ assertEquals("Owner has been added to application", propertyApp.application().owner().get().username(), "username");
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirerTest.java
index 4df1336ac1f..258dad3b6d6 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirerTest.java
@@ -35,35 +35,32 @@ public class DeploymentExpirerTest {
);
DeploymentExpirer expirer = new DeploymentExpirer(tester.controller(), Duration.ofDays(10),
new JobControl(new MockCuratorDb()));
- Application devApp = tester.createApplication("tenant1", "app1", "default");
- Application prodApp = tester.createApplication("tenant2", "app2", "default");
+ var devApp = tester.newDeploymentContext("tenant1", "app1", "default");
+ var prodApp = tester.newDeploymentContext("tenant2", "app2", "default");
ApplicationPackage appPackage = new ApplicationPackageBuilder()
.region("us-west-1")
.build();
- Instance devInstance = tester.instance(devApp.id().defaultInstance());
- Instance prodInstance = tester.instance(prodApp.id().defaultInstance());
-
// Deploy dev
- tester.runJob(devInstance.id(), JobType.devUsEast1, appPackage);
+ devApp.runJob(JobType.devUsEast1, appPackage);
// Deploy prod
- tester.deployNewSubmission(prodApp.id(), tester.newSubmission(prodApp.id(), appPackage));
+ prodApp.submit(appPackage).deploy();
- assertEquals(1, permanentDeployments(devInstance).size());
- assertEquals(1, permanentDeployments(prodInstance).size());
+ assertEquals(1, permanentDeployments(devApp.instance()).size());
+ assertEquals(1, permanentDeployments(prodApp.instance()).size());
// Not expired at first
expirer.maintain();
- assertEquals(1, permanentDeployments(devInstance).size());
- assertEquals(1, permanentDeployments(prodInstance).size());
+ assertEquals(1, permanentDeployments(devApp.instance()).size());
+ assertEquals(1, permanentDeployments(prodApp.instance()).size());
// The dev application is removed
tester.clock().advance(Duration.ofDays(15));
expirer.maintain();
- assertEquals(0, permanentDeployments(devInstance).size());
- assertEquals(1, permanentDeployments(prodInstance).size());
+ assertEquals(0, permanentDeployments(devApp.instance()).size());
+ assertEquals(1, permanentDeployments(prodApp.instance()).size());
}
private List<Deployment> permanentDeployments(Instance instance) {
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporterTest.java
index 070171ad399..0afe7377d40 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporterTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporterTest.java
@@ -13,6 +13,7 @@ import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
import com.yahoo.vespa.hosted.controller.application.Change;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder;
+import com.yahoo.vespa.hosted.controller.deployment.DeploymentContext;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester;
import com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb;
import com.yahoo.vespa.hosted.controller.versions.VespaVersion;
@@ -65,33 +66,28 @@ public class DeploymentIssueReporterTest {
tester.controllerTester().upgradeSystem(Version.fromString("6.2"));
// Create and deploy one application for each of three tenants.
- Application app1 = tester.createApplication("application1", "tenant1", "default");
- Application app2 = tester.createApplication("application2", "tenant2", "default");
- Application app3 = tester.createApplication("application3", "tenant3", "default");
+ var app1 = tester.newDeploymentContext("application1", "tenant1", "default");
+ var app2 = tester.newDeploymentContext("application2", "tenant2", "default");
+ var app3 = tester.newDeploymentContext("application3", "tenant3", "default");
Contact contact = tester.controllerTester().serviceRegistry().contactRetrieverMock().contact();
- tester.controller().tenants().lockOrThrow(app1.id().tenant(), LockedTenant.Athenz.class, tenant ->
+ tester.controller().tenants().lockOrThrow(app1.instanceId().tenant(), LockedTenant.Athenz.class, tenant ->
tester.controller().tenants().store(tenant.with(contact)));
- tester.controller().tenants().lockOrThrow(app2.id().tenant(), LockedTenant.Athenz.class, tenant ->
+ tester.controller().tenants().lockOrThrow(app2.instanceId().tenant(), LockedTenant.Athenz.class, tenant ->
tester.controller().tenants().store(tenant.with(contact)));
- tester.controller().tenants().lockOrThrow(app3.id().tenant(), LockedTenant.Athenz.class, tenant ->
+ tester.controller().tenants().lockOrThrow(app3.instanceId().tenant(), LockedTenant.Athenz.class, tenant ->
tester.controller().tenants().store(tenant.with(contact)));
// NOTE: All maintenance should be idempotent within a small enough time interval, so maintain is called twice in succession throughout.
// app 1 fails staging tests.
- tester.newSubmission(app1.id(), applicationPackage);
- tester.runJob(app1.id().defaultInstance(), systemTest);
- tester.timeOutConvergence(app1.id().defaultInstance(), stagingTest);
+ app1.submit(applicationPackage).runJob(systemTest).timeOutConvergence(stagingTest);
// app2 is successful, but will fail later.
- tester.deployNewSubmission(app2.id(), tester.newSubmission(app2.id(), applicationPackage));
+ app2.submit(applicationPackage).deploy();
// app 3 fails a production job.
- tester.newSubmission(app3.id(), applicationPackage);
- tester.runJob(app3.id().defaultInstance(), systemTest);
- tester.runJob(app3.id().defaultInstance(), stagingTest);
- tester.failDeployment(app3.id().defaultInstance(), productionUsWest1);
+ app3.submit(applicationPackage).runJob(systemTest).runJob(stagingTest).failDeployment(productionUsWest1);
reporter.maintain();
reporter.maintain();
@@ -103,53 +99,52 @@ public class DeploymentIssueReporterTest {
reporter.maintain();
reporter.maintain();
- assertTrue("One issue is produced for app1.", issues.isOpenFor(app1.id()));
- assertFalse("No issues are produced for app2.", issues.isOpenFor(app2.id()));
- assertTrue("One issue is produced for app3.", issues.isOpenFor(app3.id()));
+ assertTrue("One issue is produced for app1.", issues.isOpenFor(app1.application().id()));
+ assertFalse("No issues are produced for app2.", issues.isOpenFor(app2.application().id()));
+ assertTrue("One issue is produced for app3.", issues.isOpenFor(app3.application().id()));
// app3 closes their issue prematurely; see that it is refiled.
- issues.closeFor(app3.id());
- assertFalse("No issue is open for app3.", issues.isOpenFor(app3.id()));
+ issues.closeFor(app3.application().id());
+ assertFalse("No issue is open for app3.", issues.isOpenFor(app3.application().id()));
reporter.maintain();
reporter.maintain();
- assertTrue("Issue is re-filed for app3.", issues.isOpenFor(app3.id()));
+ assertTrue("Issue is re-filed for app3.", issues.isOpenFor(app3.application().id()));
// Some time passes; tenant1 leaves her issue unattended, while tenant3 starts work and updates the issue.
tester.clock().advance(maxInactivity.plus(maxFailureAge));
- issues.touchFor(app3.id());
+ issues.touchFor(app3.application().id());
reporter.maintain();
reporter.maintain();
- assertEquals("The issue for app1 is escalated once.", 1, issues.escalationLevelFor(app1.id()));
+ assertEquals("The issue for app1 is escalated once.", 1, issues.escalationLevelFor(app1.application().id()));
// app3 fixes their problems, but the ticket for app3 is left open; see the resolved ticket is not escalated when another escalation period has passed.
- tester.runJob(app3.id().defaultInstance(), productionUsWest1);
+ app3.runJob(productionUsWest1);
tester.clock().advance(maxInactivity.plus(Duration.ofDays(1)));
reporter.maintain();
reporter.maintain();
assertFalse("We no longer have a platform issue.", issues.platformIssue());
- assertEquals("The issue for app1 is escalated once more.", 2, issues.escalationLevelFor(app1.id()));
- assertEquals("The issue for app3 is not escalated.", 0, issues.escalationLevelFor(app3.id()));
+ assertEquals("The issue for app1 is escalated once more.", 2, issues.escalationLevelFor(app1.application().id()));
+ assertEquals("The issue for app3 is not escalated.", 0, issues.escalationLevelFor(app3.application().id()));
// app3 now has a new failure past max failure age; see that a new issue is filed.
- tester.newSubmission(app3.id(), applicationPackage);
- tester.failDeployment(app3.id().defaultInstance(), systemTest);
+ app3.submit(applicationPackage).failDeployment(systemTest);
tester.clock().advance(maxInactivity.plus(maxFailureAge));
reporter.maintain();
reporter.maintain();
- assertTrue("A new issue is filed for app3.", issues.isOpenFor(app3.id()));
+ assertTrue("A new issue is filed for app3.", issues.isOpenFor(app3.application().id()));
- // App2 is changed to be a canary
- tester.deployNewSubmission(app2.id(), tester.newSubmission(app2.id(), canaryPackage));
- assertEquals(canary, tester.applications().requireApplication(app2.id()).deploymentSpec().requireInstance("default").upgradePolicy());
- assertEquals(Change.empty(), tester.applications().requireApplication(app2.id()).change());
+ // app2 is changed to be a canary
+ app2.submit(canaryPackage).deploy();
+ assertEquals(canary, app2.application().deploymentSpec().requireInstance("default").upgradePolicy());
+ assertEquals(Change.empty(), app2.application().change());
// Bump system version to upgrade canary app2.
Version version = Version.fromString("6.3");
@@ -157,7 +152,7 @@ public class DeploymentIssueReporterTest {
tester.upgrader().maintain();
assertEquals(version, tester.controller().versionStatus().systemVersion().get().versionNumber());
- tester.timeOutUpgrade(app2.id().defaultInstance(), systemTest);
+ app2.timeOutUpgrade(systemTest);
tester.controllerTester().upgradeSystem(version);
assertEquals(VespaVersion.Confidence.broken, tester.controller().versionStatus().systemVersion().get().confidence());
@@ -169,7 +164,7 @@ public class DeploymentIssueReporterTest {
reporter.maintain();
reporter.maintain();
assertTrue("We get a platform issue when confidence is broken", issues.platformIssue());
- assertFalse("No deployment issue is filed for app2, which has a version upgrade failure.", issues.isOpenFor(app2.id()));
+ assertFalse("No deployment issue is filed for app2, which has a version upgrade failure.", issues.isOpenFor(app2.application().id()));
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainerTest.java
index 4e4fbe00bb7..06a815819f4 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainerTest.java
@@ -35,7 +35,7 @@ public class DeploymentMetricsMaintainerTest {
@Test
public void updates_metrics() {
- var application = tester.deploymentContext();
+ var application = tester.newDeploymentContext();
application.runJob(JobType.devUsEast1, new ApplicationPackage(new byte[0]), Version.fromString("7.1"));
DeploymentMetricsMaintainer maintainer = maintainer(tester.controller());
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunnerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunnerTest.java
index 0f6aec804e2..f7d451ab931 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunnerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunnerTest.java
@@ -4,6 +4,7 @@ package com.yahoo.vespa.hosted.controller.maintenance;
import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
+import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobId;
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.application.ApplicationPackage;
@@ -189,7 +190,7 @@ public class JobRunnerTest {
// Start a third run, then unregister and wait for data to be deleted.
jobs.start(id, systemTest, versions);
- jobs.unregister(appId);
+ tester.applications().deleteInstance(id);
runner.maintain();
assertFalse(jobs.last(id, systemTest).isPresent());
assertTrue(jobs.runs(id, systemTest).isEmpty());
@@ -242,24 +243,74 @@ public class JobRunnerTest {
inThreadExecutor(), (id, step) -> Optional.of(running));
TenantAndApplicationId appId = tester.createApplication("tenant", "real", "default").id();
- ApplicationId id = appId.defaultInstance();
+ ApplicationId instanceId = appId.defaultInstance();
+ JobId jobId = new JobId(instanceId, systemTest);
jobs.submit(appId, versions.targetApplication().source().get(), "a@b", 2, applicationPackage, new byte[0]);
+ assertFalse(jobs.lastSuccess(jobId).isPresent());
for (int i = 0; i < jobs.historyLength(); i++) {
- jobs.start(id, systemTest, versions);
+ jobs.start(instanceId, systemTest, versions);
runner.run();
}
- assertEquals(256, jobs.runs(id, systemTest).size());
- assertTrue(jobs.details(new RunId(id, systemTest, 1)).isPresent());
+ assertEquals(256, jobs.runs(jobId).size());
+ assertTrue(jobs.details(new RunId(instanceId, systemTest, 1)).isPresent());
- jobs.start(id, systemTest, versions);
+ jobs.start(instanceId, systemTest, versions);
runner.run();
- assertEquals(256, jobs.runs(id, systemTest).size());
- assertEquals(2, jobs.runs(id, systemTest).keySet().iterator().next().number());
- assertFalse(jobs.details(new RunId(id, systemTest, 1)).isPresent());
- assertTrue(jobs.details(new RunId(id, systemTest, 257)).isPresent());
+ assertEquals(256, jobs.runs(jobId).size());
+ assertEquals(2, jobs.runs(jobId).keySet().iterator().next().number());
+ assertFalse(jobs.details(new RunId(instanceId, systemTest, 1)).isPresent());
+ assertTrue(jobs.details(new RunId(instanceId, systemTest, 257)).isPresent());
+
+ JobRunner failureRunner = new JobRunner(tester.controller(), Duration.ofDays(1), new JobControl(tester.controller().curator()),
+ inThreadExecutor(), (id, step) -> Optional.of(error));
+
+ // Make all but the oldest of the 256 jobs a failure.
+ for (int i = 0; i < jobs.historyLength() - 1; i++) {
+ jobs.start(instanceId, systemTest, versions);
+ failureRunner.run();
+ }
+ assertEquals(256, jobs.runs(jobId).size());
+ assertEquals(257, jobs.runs(jobId).keySet().iterator().next().number());
+ assertEquals(257, jobs.lastSuccess(jobId).get().id().number());
+ assertEquals(258, jobs.firstFailing(jobId).get().id().number());
+
+ // Oldest success is kept even though it would normally overflow.
+ jobs.start(instanceId, systemTest, versions);
+ failureRunner.run();
+ assertEquals(257, jobs.runs(jobId).size());
+ assertEquals(257, jobs.runs(jobId).keySet().iterator().next().number());
+ assertEquals(257, jobs.lastSuccess(jobId).get().id().number());
+ assertEquals(258, jobs.firstFailing(jobId).get().id().number());
+
+ // First failure after the last success is also kept.
+ jobs.start(instanceId, systemTest, versions);
+ failureRunner.run();
+ assertEquals(258, jobs.runs(jobId).size());
+ assertEquals(257, jobs.runs(jobId).keySet().iterator().next().number());
+ assertEquals(258, jobs.runs(jobId).keySet().stream().skip(1).iterator().next().number());
+ assertEquals(257, jobs.lastSuccess(jobId).get().id().number());
+ assertEquals(258, jobs.firstFailing(jobId).get().id().number());
+
+ // No other jobs are kept with repeated failures.
+ jobs.start(instanceId, systemTest, versions);
+ failureRunner.run();
+ assertEquals(258, jobs.runs(jobId).size());
+ assertEquals(257, jobs.runs(jobId).keySet().iterator().next().number());
+ assertEquals(258, jobs.runs(jobId).keySet().stream().skip(1).iterator().next().number());
+ assertEquals(260, jobs.runs(jobId).keySet().stream().skip(2).iterator().next().number());
+ assertEquals(257, jobs.lastSuccess(jobId).get().id().number());
+ assertEquals(258, jobs.firstFailing(jobId).get().id().number());
+
+ // history length returns to 256 when a new success is recorded.
+ jobs.start(instanceId, systemTest, versions);
+ runner.run();
+ assertEquals(256, jobs.runs(jobId).size());
+ assertEquals(261, jobs.runs(jobId).keySet().iterator().next().number());
+ assertEquals(516, jobs.lastSuccess(jobId).get().id().number());
+ assertFalse(jobs.firstFailing(jobId).isPresent());
}
@Test
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 31f55be92a9..7c87cbf3610 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
@@ -78,7 +78,7 @@ public class MetricsReporterTest {
MetricsReporter reporter = createReporter(tester.controller());
- var context = tester.deploymentContext()
+ var context = tester.newDeploymentContext()
.submit(applicationPackage)
.deploy();
reporter.maintain();
@@ -122,7 +122,7 @@ public class MetricsReporterTest {
.build();
MetricsReporter reporter = createReporter(tester.controller());
- var context = tester.deploymentContext();
+ var context = tester.newDeploymentContext();
// Initial deployment without failures
context.submit(applicationPackage).deploy();
@@ -174,7 +174,7 @@ public class MetricsReporterTest {
.region("us-east-3")
.build();
MetricsReporter reporter = createReporter(tester.controller());
- var context = tester.deploymentContext();
+ var context = tester.newDeploymentContext();
tester.configServer().generateWarnings(context.deploymentIdIn(ZoneId.from("prod", "us-west-1")), 3);
tester.configServer().generateWarnings(context.deploymentIdIn(ZoneId.from("prod", "us-west-1")), 4);
context.submit(applicationPackage).deploy();
@@ -186,7 +186,7 @@ public class MetricsReporterTest {
public void build_time_reporting() {
var tester = new DeploymentTester();
var applicationPackage = new ApplicationPackageBuilder().region("us-west-1").build();
- var context = tester.deploymentContext()
+ var context = tester.newDeploymentContext()
.submit(applicationPackage)
.deploy();
assertEquals(1000, context.lastSubmission().get().buildTime().get().toEpochMilli());
@@ -207,7 +207,7 @@ public class MetricsReporterTest {
.region("us-east-3")
.build();
MetricsReporter reporter = createReporter(tester.controller());
- var context = tester.deploymentContext()
+ var context = tester.newDeploymentContext()
.deferDnsUpdates();
reporter.maintain();
assertEquals("Queue is empty initially", 0, metrics.getMetric(MetricsReporter.NAME_SERVICE_REQUESTS_QUEUED).intValue());
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployerTest.java
index 0be873f80ed..11c77304dd0 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployerTest.java
@@ -37,45 +37,40 @@ public class OutstandingChangeDeployerTest {
.region("us-west-1")
.build();
- Application app1 = tester.createApplication("tenant", "app1", "default");
- tester.deployNewSubmission(app1.id(), tester.newSubmission(app1.id(), applicationPackage));
+ var app1 = tester.newDeploymentContext("tenant", "app1", "default").submit(applicationPackage).deploy();
Version version = new Version(6, 2);
- tester.deploymentTrigger().triggerChange(app1.id(), Change.of(version));
+ tester.deploymentTrigger().triggerChange(app1.application().id(), Change.of(version));
tester.deploymentTrigger().triggerReadyJobs();
- assertEquals(Change.of(version), tester.application(app1.id()).change());
- assertFalse(tester.application(app1.id()).outstandingChange().hasTargets());
+ assertEquals(Change.of(version), app1.application().change());
+ assertFalse(app1.application().outstandingChange().hasTargets());
- assertEquals(1, tester.application(app1.id()).latestVersion().get().buildNumber().getAsLong());
- tester.newSubmission(app1.id(), applicationPackage, new SourceRevision("repository1", "master", "cafed00d"));
+ assertEquals(1, app1.application().latestVersion().get().buildNumber().getAsLong());
+ app1.submit(applicationPackage, new SourceRevision("repository1", "master", "cafed00d"));
- ApplicationId instanceId = app1.id().defaultInstance();
- assertTrue(tester.application(app1.id()).outstandingChange().hasTargets());
- assertEquals("1.0.2-cafed00d", tester.application(app1.id()).outstandingChange().application().get().id());
- tester.assertRunning(instanceId, JobType.systemTest);
- tester.assertRunning(instanceId, JobType.stagingTest);
+ assertTrue(app1.application().outstandingChange().hasTargets());
+ assertEquals("1.0.2-cafed00d", app1.application().outstandingChange().application().get().id());
+ app1.assertRunning(JobType.systemTest);
+ app1.assertRunning(JobType.stagingTest);
assertEquals(2, tester.jobs().active().size());
deployer.maintain();
- tester.deploymentTrigger().triggerReadyJobs();
+ tester.triggerJobs();
assertEquals("No effect as job is in progress", 2, tester.jobs().active().size());
- assertEquals("1.0.2-cafed00d", tester.application(app1.id()).outstandingChange().application().get().id());
+ assertEquals("1.0.2-cafed00d", app1.application().outstandingChange().application().get().id());
- tester.runJob(instanceId, JobType.systemTest);
- tester.runJob(instanceId, JobType.stagingTest);
- tester.runJob(instanceId, JobType.productionUsWest1);
- tester.runJob(instanceId, JobType.systemTest);
- tester.runJob(instanceId, JobType.stagingTest);
+ app1.runJob(JobType.systemTest).runJob(JobType.stagingTest).runJob(JobType.productionUsWest1)
+ .runJob(JobType.stagingTest).runJob(JobType.systemTest);
assertEquals("Upgrade done", 0, tester.jobs().active().size());
deployer.maintain();
- tester.deploymentTrigger().triggerReadyJobs();
- assertEquals("1.0.2-cafed00d", tester.application(app1.id()).change().application().get().id());
+ tester.triggerJobs();
+ assertEquals("1.0.2-cafed00d", app1.application().change().application().get().id());
List<Run> runs = tester.jobs().active();
assertEquals(1, runs.size());
- tester.assertRunning(instanceId, JobType.productionUsWest1);
- assertFalse(tester.application(app1.id()).outstandingChange().hasTargets());
+ app1.assertRunning(JobType.productionUsWest1);
+ assertFalse(app1.application().outstandingChange().hasTargets());
}
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPoliciesTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPoliciesTest.java
index f5f1605a699..bc184715589 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPoliciesTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RoutingPoliciesTest.java
@@ -14,7 +14,6 @@ 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.DeploymentContext;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester;
import org.junit.Test;
@@ -53,7 +52,6 @@ public class RoutingPoliciesTest {
@Test
public void maintains_global_routing_policies() {
- long buildNumber = BuildJob.defaultBuildNumber;
int clustersPerZone = 2;
int numberOfDeployments = 2;
var applicationPackage = new ApplicationPackageBuilder()
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java
index 58a49307733..36039c47025 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java
@@ -461,7 +461,7 @@ public class UpgraderTest {
.region("us-west-1")
.build();
- var app = tester.deploymentContext().submit(applicationPackage).deploy();
+ var app = tester.newDeploymentContext().submit(applicationPackage).deploy();
// New version is released
version = Version.fromString("6.3");
@@ -504,7 +504,7 @@ public class UpgraderTest {
.region("us-east-3")
.build();
- var app = tester.deploymentContext().submit(applicationPackage).deploy();
+ var app = tester.newDeploymentContext().submit(applicationPackage).deploy();
// New version is released
version = Version.fromString("6.3");
@@ -541,7 +541,7 @@ public class UpgraderTest {
.region("us-east-3")
.build();
- var app = tester.deploymentContext().submit(applicationPackage).deploy();
+ var app = tester.newDeploymentContext().submit(applicationPackage).deploy();
// New version is released
version = Version.fromString("6.3");
@@ -662,7 +662,7 @@ public class UpgraderTest {
// Setup applications
var canary0 = createAndDeploy("canary0", "canary");
- var default0 = tester.deploymentContext().submit(version6ApplicationPackage).deploy();
+ var default0 = tester.newDeploymentContext().submit(version6ApplicationPackage).deploy();
// New major version is released
version = Version.fromString("7.0");
@@ -690,7 +690,7 @@ public class UpgraderTest {
// Setup applications
var canary0 = createAndDeploy("canary", "canary");
- var default0 = tester.deploymentContext().submit().deploy();
+ var default0 = tester.newDeploymentContext().submit().deploy();
tester.applications().lockApplicationOrThrow(default0.application().id(),
a -> tester.applications().store(a.withMajorVersion(6)));
assertEquals(OptionalInt.of(6), default0.application().majorVersion());
@@ -819,7 +819,7 @@ public class UpgraderTest {
.region("us-east-3")
.build();
- var app = tester.deploymentContext().submit(applicationPackage).deploy();
+ var app = tester.newDeploymentContext().submit(applicationPackage).deploy();
// Application upgrade starts.
app.submit(applicationPackage);
@@ -861,7 +861,7 @@ public class UpgraderTest {
.region("us-east-3")
.build();
- var app = tester.deploymentContext().submit(applicationPackage).deploy();
+ var app = tester.newDeploymentContext().submit(applicationPackage).deploy();
// Application revision starts rolling out.
@@ -895,7 +895,7 @@ public class UpgraderTest {
tester.controllerTester().upgradeSystem(version0);
// Create an application with pinned platform version.
- var context = tester.deploymentContext();
+ var context = tester.newDeploymentContext();
tester.deploymentTrigger().forceChange(context.application().id(), Change.empty().withPin());
context.submit().deploy();
@@ -993,7 +993,7 @@ public class UpgraderTest {
.region("us-west-1")
.region("us-east-3")
.build();
- var application = tester.deploymentContext().submit(applicationPackage).deploy();
+ var application = tester.newDeploymentContext().submit(applicationPackage).deploy();
// Next version is released and 2/3 deployments upgrade
Version v2 = Version.fromString("6.2");
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/metric/ConfigServerMetricsTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/metric/ConfigServerMetricsTest.java
index 23a6c6286c0..23e86320a78 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/metric/ConfigServerMetricsTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/metric/ConfigServerMetricsTest.java
@@ -1,6 +1,7 @@
package com.yahoo.vespa.hosted.controller.metric;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.ClusterMetrics;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
@@ -31,7 +32,7 @@ public class ConfigServerMetricsTest {
@Before
public void before() {
- configServer = new ConfigServerMock(new ZoneRegistryMock());
+ configServer = new ConfigServerMock(new ZoneRegistryMock(SystemName.main));
service = new ConfigServerMetrics(configServer);
}
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 186e982fd75..7536272d6d9 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java
@@ -6,6 +6,7 @@ import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.application.api.ValidationOverrides;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ClusterSpec;
+import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.security.KeyUtils;
import com.yahoo.vespa.config.SlimeUtils;
@@ -74,8 +75,13 @@ public class ApplicationSerializerTest {
@Test
public void testSerialization() {
- DeploymentSpec deploymentSpec = DeploymentSpec.fromXml("<deployment version='1.0'>" +
- " <staging/>" +
+ DeploymentSpec deploymentSpec = DeploymentSpec.fromXml("<deployment version='1.0'>\n" +
+ " <staging/>\n" +
+ " <instance id=\"i1\">\n" +
+ " <prod global-service-id=\"default\">\n" +
+ " <region active=\"true\">us-west-1</region>\n" +
+ " </prod>\n" +
+ " </instance>\n" +
"</deployment>");
ValidationOverrides validationOverrides = ValidationOverrides.fromXml("<validation-overrides version='1.0'>" +
" <allow until='2017-06-15'>deployment-removal</allow>" +
@@ -100,9 +106,6 @@ public class ApplicationSerializerTest {
List<JobStatus> statusList = new ArrayList<>();
- JobStatus.JobRun componentJob = JobStatus.JobRun.triggering(Version.emptyVersion, applicationVersion1, empty(),
- empty(), "New commit", Instant.ofEpochMilli(400))
- .completion(100, Instant.ofEpochMilli(500));
statusList.add(JobStatus.initial(JobType.systemTest)
.withTriggering(Version.fromString("5.6.7"), ApplicationVersion.unknown, empty(), "Test", Instant.ofEpochMilli(7))
.withCompletion(30, empty(), Instant.ofEpochMilli(8))
@@ -127,7 +130,7 @@ public class ApplicationSerializerTest {
List<Instance> instances = List.of(new Instance(id1,
deployments,
deploymentJobs,
- List.of(AssignedRotation.fromStrings("foo", "default", "my-rotation", Set.of())),
+ List.of(AssignedRotation.fromStrings("foo", "default", "my-rotation", Set.of("us-west-1"))),
rotationStatus),
new Instance(id3,
List.of(),
@@ -148,7 +151,6 @@ public class ApplicationSerializerTest {
new ApplicationMetrics(0.5, 0.9),
Set.of(publicKey, otherPublicKey),
projectId,
- true,
Optional.of(applicationVersion1),
instances);
@@ -162,7 +164,6 @@ public class ApplicationSerializerTest {
assertEquals(original.validationOverrides().xmlForm(), serialized.validationOverrides().xmlForm());
assertEquals(original.projectId(), serialized.projectId());
- assertEquals(original.internal(), serialized.internal());
assertEquals(original.deploymentIssueId(), serialized.deploymentIssueId());
assertEquals(0, serialized.require(id3.instance()).deployments().size());
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequestTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequestTest.java
index b9d68c2a3da..d8373cb8928 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequestTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequestTest.java
@@ -1,13 +1,13 @@
// 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.proxy;
-import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.jdisc.http.HttpRequest;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import java.net.URI;
+import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertEquals;
@@ -23,7 +23,7 @@ public class ProxyRequestTest {
@Test
public void testEmpty() throws Exception {
exception.expectMessage("Request must be non-null");
- new ProxyRequest(HttpRequest.Method.GET, null, Map.of(), null, ZoneId.from("dev", "us-north-1"), "/zone/v2");
+ new ProxyRequest(HttpRequest.Method.GET, null, Map.of(), null, List.of(), "/zone/v2");
}
@Test
@@ -69,6 +69,6 @@ public class ProxyRequestTest {
private static ProxyRequest testRequest(String url, String pathPrefix) throws ProxyException {
return new ProxyRequest(
- HttpRequest.Method.GET, URI.create(url), Map.of(), null, ZoneId.from("dev", "us-north-1"), pathPrefix);
+ HttpRequest.Method.GET, URI.create(url), Map.of(), null, List.of(), pathPrefix);
}
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponseTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponseTest.java
index efe7e17c58e..0aac59321b5 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponseTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponseTest.java
@@ -1,13 +1,13 @@
// 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.proxy;
-import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.jdisc.http.HttpRequest;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
import java.net.URI;
import java.nio.charset.StandardCharsets;
+import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertEquals;
@@ -20,7 +20,7 @@ public class ProxyResponseTest {
@Test
public void testRewriteUrl() throws Exception {
ProxyRequest request = new ProxyRequest(HttpRequest.Method.GET, URI.create("http://domain.tld/zone/v2/dev/us-north-1/configserver"),
- Map.of(), null, ZoneId.from("dev", "us-north-1"), "configserver");
+ Map.of(), null, List.of(), "configserver");
ProxyResponse proxyResponse = new ProxyResponse(
request,
"response link is http://configserver:1234/bla/bla/",
@@ -38,7 +38,7 @@ public class ProxyResponseTest {
@Test
public void testRewriteSecureUrl() throws Exception {
ProxyRequest request = new ProxyRequest(HttpRequest.Method.GET, URI.create("https://domain.tld/zone/v2/prod/eu-south-3/configserver"),
- Map.of(), null, ZoneId.from("prod", "eu-south-3"), "configserver");
+ Map.of(), null, List.of(), "configserver");
ProxyResponse proxyResponse = new ProxyResponse(
request,
"response link is http://configserver:1234/bla/bla/",
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
deleted file mode 100644
index 110aaf2b1a6..00000000000
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerControllerTester.java
+++ /dev/null
@@ -1,166 +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;
-
-import com.yahoo.application.container.JDisc;
-import com.yahoo.application.container.handler.Request;
-import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.config.provision.Environment;
-import com.yahoo.config.provision.TenantName;
-import com.yahoo.config.provision.zone.ZoneId;
-import com.yahoo.vespa.athenz.api.AthenzDomain;
-import com.yahoo.vespa.athenz.api.AthenzPrincipal;
-import com.yahoo.vespa.athenz.api.AthenzUser;
-import com.yahoo.vespa.athenz.api.OktaAccessToken;
-import com.yahoo.vespa.athenz.api.OktaIdentityToken;
-import com.yahoo.vespa.hosted.controller.Application;
-import com.yahoo.vespa.hosted.controller.Controller;
-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.identifiers.ScrewdriverId;
-import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
-import com.yahoo.vespa.hosted.controller.api.integration.stubs.MockBuildService;
-import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
-import com.yahoo.vespa.hosted.controller.application.DeploymentJobs;
-import com.yahoo.vespa.hosted.controller.api.integration.athenz.ApplicationAction;
-import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
-import com.yahoo.vespa.hosted.controller.athenz.HostedAthenzIdentities;
-import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactoryMock;
-import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzDbMock;
-import com.yahoo.vespa.hosted.controller.deployment.BuildJob;
-import com.yahoo.vespa.hosted.controller.deployment.DeploymentSteps;
-import com.yahoo.vespa.hosted.controller.maintenance.JobControl;
-import com.yahoo.vespa.hosted.controller.maintenance.Upgrader;
-import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
-import com.yahoo.vespa.hosted.controller.security.AthenzCredentials;
-import com.yahoo.vespa.hosted.controller.security.AthenzTenantSpec;
-
-import java.io.File;
-import java.time.Duration;
-import java.util.Optional;
-
-import static com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType.component;
-import static org.junit.Assert.assertFalse;
-
-/**
- * Provides testing of controller functionality accessed through the container
- *
- * @author bratseth
- */
-public class ContainerControllerTester {
-
- private final ContainerTester containerTester;
- private final Upgrader upgrader;
-
- public ContainerControllerTester(JDisc container, String responseFilePath) {
- containerTester = new ContainerTester(container, responseFilePath);
- CuratorDb curatorDb = controller().curator();
- upgrader = new Upgrader(controller(), Duration.ofDays(1), new JobControl(curatorDb), curatorDb);
- upgrader.setUpgradesPerMinute(100); // Anything to make it more than one per maintenance interval.
- }
-
- public Controller controller() { return containerTester.controller(); }
-
- public Upgrader upgrader() { return upgrader; }
-
- /** Returns the wrapped generic container tester */
- public ContainerTester containerTester() { return containerTester; }
-
- public Application createApplication() {
- return createApplication("domain1","tenant1", "application1", "default");
- }
-
- 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 OktaIdentityToken("okta-identity-token"), new OktaAccessToken("okta-access-token"));
- AthenzTenantSpec tenantSpec = new AthenzTenantSpec(TenantName.from(tenant),
- domain1,
- new Property("property1"),
- Optional.of(new PropertyId("1234")));
- controller().tenants().create(tenantSpec, credentials);
-
- TenantAndApplicationId id = TenantAndApplicationId.from(tenant, application);
- controller().applications().createApplication(id, Optional.of(credentials));
- controller().applications().createInstance(id.instance(instance));
- return controller().applications().requireApplication(id);
- }
-
- public void deploy(ApplicationId id, ApplicationPackage applicationPackage, ZoneId zone) {
- controller().applications().deploy(id, zone, Optional.of(applicationPackage),
- new DeployOptions(false, Optional.empty(), false, false));
- }
-
- public void deployCompletely(Application application, ApplicationPackage applicationPackage, long projectId,
- boolean failStaging) {
- jobCompletion(JobType.component).application(application)
- .projectId(projectId)
- .uploadArtifact(applicationPackage)
- .submit();
- DeploymentSteps steps = controller().applications().deploymentTrigger().steps(applicationPackage.deploymentSpec());
- // TODO jonmv: Connect instances from deployment spec to deployments below.
- boolean succeeding = true;
- for (var job : steps.jobs()) {
- if (!succeeding) return;
- var zone = job.zone(controller().system());
- deploy(application.id().defaultInstance(), applicationPackage, zone);
- if (failStaging && zone.environment() == Environment.staging) {
- succeeding = false;
- }
- if (zone.environment().isTest()) {
- controller().applications().deactivate(application.id().defaultInstance(), zone);
- }
- jobCompletion(job).application(application).success(succeeding).projectId(projectId).submit();
- }
- }
-
- /** Notify the controller about a job completing */
- public BuildJob jobCompletion(JobType job) {
- return new BuildJob(this::notifyJobCompletion, containerTester.serviceRegistry().artifactRepositoryMock()).type(job);
- }
-
- // ---- Delegators:
-
- public void assertResponse(Request request, File expectedResponse) {
- containerTester.assertResponse(request, expectedResponse);
- }
-
- public void assertResponse(Request request, String expectedResponse, int expectedStatusCode) {
- containerTester.assertResponse(() -> request, expectedResponse, expectedStatusCode);
- }
-
- /*
- * Authorize action on tenantDomain/application for a given screwdriverId
- */
- public void authorize(AthenzDomain tenantDomain, ScrewdriverId screwdriverId, ApplicationAction action, TenantAndApplicationId id) {
- AthenzClientFactoryMock mock = (AthenzClientFactoryMock) containerTester.container().components()
- .getComponent(AthenzClientFactoryMock.class.getName());
-
- mock.getSetup()
- .domains.get(tenantDomain)
- .applications.get(new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId(id.application().value()))
- .addRoleMember(action, HostedAthenzIdentities.from(screwdriverId));
- }
-
- private void notifyJobCompletion(DeploymentJobs.JobReport report) {
- MockBuildService buildService = containerTester.serviceRegistry().buildServiceMock();
- if (report.jobType() != component && ! buildService.remove(report.buildJob()))
- throw new IllegalArgumentException(report.jobType() + " is not running for " + report.applicationId());
- assertFalse("Unexpected entry '" + report.jobType() + "@" + report.projectId() + " in: " + buildService.jobs(),
- buildService.remove(report.buildJob()));
- controller().applications().deploymentTrigger().notifyOfCompletion(report);
- controller().applications().deploymentTrigger().triggerReadyJobs();
- }
-
- private AthenzDomain addTenantAthenzDomain(String domainName, String userName) {
- AthenzClientFactoryMock mock = (AthenzClientFactoryMock) containerTester.container().components()
- .getComponent(AthenzClientFactoryMock.class.getName());
- AthenzDomain athensDomain = new AthenzDomain(domainName);
- AthenzDbMock.Domain domain = mock.getSetup().getOrCreateDomain(athensDomain);
- domain.markAsVespaTenant();
- domain.admin(new AthenzUser(userName));
- return athensDomain;
- }
-
-}
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 300eddd6291..df8787a2a4b 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,12 +6,19 @@ 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.ApplicationName;
import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.container.http.filter.FilterChainRepository;
import com.yahoo.jdisc.http.filter.SecurityRequestFilter;
import com.yahoo.jdisc.http.filter.SecurityRequestFilterChain;
+import com.yahoo.vespa.athenz.api.AthenzDomain;
+import com.yahoo.vespa.athenz.api.AthenzIdentity;
import com.yahoo.vespa.hosted.controller.Controller;
+import com.yahoo.vespa.hosted.controller.api.identifiers.ScrewdriverId;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.ApplicationAction;
+import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactoryMock;
import com.yahoo.vespa.hosted.controller.application.SystemApplication;
+import com.yahoo.vespa.hosted.controller.athenz.HostedAthenzIdentities;
import com.yahoo.vespa.hosted.controller.integration.ConfigServerMock;
import com.yahoo.vespa.hosted.controller.integration.ServiceRegistryMock;
import com.yahoo.vespa.hosted.controller.versions.ControllerVersion;
@@ -58,24 +65,19 @@ public class ContainerTester {
return serviceRegistry().configServerMock();
}
- public ServiceRegistryMock serviceRegistry() {
- return (ServiceRegistryMock) container.components().getComponent(ServiceRegistryMock.class.getName());
+ public AthenzClientFactoryMock athenzClientFactory() {
+ return (AthenzClientFactoryMock) container.components().getComponent(AthenzClientFactoryMock.class.getName());
}
- public void computeVersionStatus() {
- controller().updateVersionStatus(VersionStatus.compute(controller()));
+ public ServiceRegistryMock serviceRegistry() {
+ return (ServiceRegistryMock) container.components().getComponent(ServiceRegistryMock.class.getName());
}
- public void upgradeSystem(Version version) {
- var controllerVersion = new ControllerVersion(version, "badc0ffee", Instant.EPOCH);
- controller().curator().writeControllerVersion(controller().hostname(), controllerVersion);
- for (ZoneApi zone : controller().zoneRegistry().zones().all().zones()) {
- for (SystemApplication application : SystemApplication.all()) {
- configServer().setVersion(application.id(), zone.getId(), controllerVersion.version());
- configServer().convergeServices(application.id(), zone.getId());
- }
- }
- computeVersionStatus();
+ public void authorize(AthenzDomain tenantDomain, AthenzIdentity identity, ApplicationAction action, ApplicationName application) {
+ athenzClientFactory().getSetup()
+ .domains.get(tenantDomain)
+ .applications.get(new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId(application.value()))
+ .addRoleMember(action, identity);
}
public void assertResponse(Supplier<Request> request, File responseFile) {
@@ -128,6 +130,10 @@ public class ContainerTester {
assertResponse(() -> request, expectedResponse, 200);
}
+ public void assertResponse(Request request, String expectedResponse, int expectedStatusCode) {
+ assertResponse(() -> request, expectedResponse, expectedStatusCode);
+ }
+
public void assertResponse(Supplier<Request> request, String expectedResponse, int expectedStatusCode) {
assertResponse(request,
(response) -> assertEquals(expectedResponse, new String(response.getBody(), StandardCharsets.UTF_8)),
@@ -139,8 +145,8 @@ public class ContainerTester {
FilterResult filterResult = invokeSecurityFilters(request);
request = filterResult.request;
Response response = filterResult.response != null ? filterResult.response : container.handleRequest(request);
- assertEquals("Status code", expectedStatusCode, response.getStatus());
responseAssertion.accept(response);
+ assertEquals("Status code", expectedStatusCode, response.getStatus());
}
// Hack to run request filters as part of the request processing chain.
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 fb0e92ab7f4..dc6abcb2616 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
@@ -34,12 +34,16 @@ import static org.junit.Assert.assertEquals;
*/
public class ControllerContainerTest {
+ private static final AthenzUser hostedOperator = AthenzUser.fromUserId("alice");
private static final AthenzUser defaultUser = AthenzUser.fromUserId("bob");
protected JDisc container;
@Before
- public void startContainer() { container = JDisc.fromServicesXml(controllerServicesXml(), Networking.disable); }
+ public void startContainer() {
+ container = JDisc.fromServicesXml(controllerServicesXml(), Networking.disable);
+ addUserToHostedOperatorRole(hostedOperator);
+ }
@After
public void stopContainer() { container.close(); }
@@ -66,7 +70,6 @@ public class ControllerContainerTest {
" <component id='com.yahoo.vespa.curator.mock.MockCurator'/>\n" +
" <component id='com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb'/>\n" +
" <component id='com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactoryMock'/>\n" +
- " <component id='com.yahoo.vespa.hosted.controller.integration.ZoneRegistryMock'/>\n" +
" <component id='com.yahoo.vespa.hosted.controller.integration.ServiceRegistryMock'/>\n" +
" <component id='com.yahoo.vespa.hosted.controller.Controller'/>\n" +
" <component id='com.yahoo.vespa.hosted.controller.integration.ConfigServerProxyMock'/>\n" +
@@ -92,6 +95,12 @@ public class ControllerContainerTest {
" <binding>http://*/zone/v2</binding>\n" +
" <binding>http://*/zone/v2/*</binding>\n" +
" </handler>\n" +
+ " <handler id='com.yahoo.vespa.hosted.controller.restapi.configserver.ConfigServerApiHandler'>\n" +
+ " <binding>http://*/configserver/v1</binding>\n" +
+ " <binding>http://*/configserver/v1/*</binding>\n" +
+ " <binding>http://*/api/configserver/v1</binding>\n" +
+ " <binding>http://*/api/configserver/v1/*</binding>\n" +
+ " </handler>\n" +
" <handler id='com.yahoo.vespa.hosted.controller.restapi.flags.AuditedFlagsHandler'>\n" +
" <binding>http://*/flags/v1</binding>\n" +
" <binding>http://*/flags/v1/*</binding>\n" +
@@ -147,10 +156,18 @@ public class ControllerContainerTest {
return addIdentityToRequest(new Request(uri), defaultUser);
}
- protected static Request authenticatedRequest(String uri, byte[] body, Request.Method method) {
+ protected static Request authenticatedRequest(String uri, String body, Request.Method method) {
return addIdentityToRequest(new Request(uri, body, method), defaultUser);
}
+ protected static Request operatorRequest(String uri) {
+ return addIdentityToRequest(new Request(uri), hostedOperator);
+ }
+
+ protected static Request operatorRequest(String uri, String body, Request.Method method) {
+ return addIdentityToRequest(new Request(uri, body, method), hostedOperator);
+ }
+
protected static Request addIdentityToRequest(Request request, AthenzIdentity identity) {
request.getHeaders().put(IDENTITY_HEADER_NAME, identity.getFullName());
return request;
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 cf28845b4e1..b5b7c1d0ad0 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
@@ -7,24 +7,21 @@ import com.yahoo.application.container.handler.Request;
import com.yahoo.component.Version;
import com.yahoo.config.application.api.ValidationId;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.AthenzService;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.RegionName;
-import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.zone.ZoneId;
-import com.yahoo.slime.Cursor;
-import com.yahoo.slime.Slime;
-import com.yahoo.test.ManualClock;
import com.yahoo.vespa.athenz.api.AthenzDomain;
import com.yahoo.vespa.athenz.api.AthenzIdentity;
import com.yahoo.vespa.athenz.api.AthenzUser;
import com.yahoo.vespa.athenz.api.OktaAccessToken;
import com.yahoo.vespa.athenz.api.OktaIdentityToken;
-import com.yahoo.vespa.config.SlimeUtils;
import com.yahoo.vespa.hosted.controller.Application;
+import com.yahoo.vespa.hosted.controller.ControllerTester;
import com.yahoo.vespa.hosted.controller.Instance;
import com.yahoo.vespa.hosted.controller.LockedTenant;
import com.yahoo.vespa.hosted.controller.api.application.v4.EnvironmentResource;
@@ -35,7 +32,6 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId;
import com.yahoo.vespa.hosted.controller.api.identifiers.ScrewdriverId;
import com.yahoo.vespa.hosted.controller.api.identifiers.UserId;
import com.yahoo.vespa.hosted.controller.api.integration.athenz.ApplicationAction;
-import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactoryMock;
import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzDbMock;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerException;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
@@ -54,7 +50,6 @@ import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
import com.yahoo.vespa.hosted.controller.application.Change;
import com.yahoo.vespa.hosted.controller.application.ClusterInfo;
import com.yahoo.vespa.hosted.controller.application.Deployment;
-import com.yahoo.vespa.hosted.controller.application.DeploymentJobs;
import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics;
import com.yahoo.vespa.hosted.controller.application.EndpointId;
import com.yahoo.vespa.hosted.controller.application.JobStatus;
@@ -62,15 +57,13 @@ import com.yahoo.vespa.hosted.controller.application.RoutingPolicy;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.athenz.HostedAthenzIdentities;
import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder;
-import com.yahoo.vespa.hosted.controller.deployment.BuildJob;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentContext;
+import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentTrigger;
import com.yahoo.vespa.hosted.controller.integration.ConfigServerMock;
-import com.yahoo.vespa.hosted.controller.integration.ServiceRegistryMock;
import com.yahoo.vespa.hosted.controller.maintenance.JobControl;
import com.yahoo.vespa.hosted.controller.maintenance.RotationStatusUpdater;
import com.yahoo.vespa.hosted.controller.metric.ApplicationMetrics;
-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;
@@ -80,8 +73,6 @@ import org.junit.Before;
import org.junit.Test;
import java.io.File;
-import java.io.IOException;
-import java.io.UncheckedIOException;
import java.math.BigDecimal;
import java.net.URI;
import java.nio.charset.StandardCharsets;
@@ -106,9 +97,7 @@ import static com.yahoo.application.container.handler.Request.Method.POST;
import static com.yahoo.application.container.handler.Request.Method.PUT;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
/**
@@ -154,24 +143,20 @@ public class ApplicationApiTest extends ControllerContainerTest {
private static final UserId HOSTED_VESPA_OPERATOR = new UserId("johnoperator");
private static final OktaIdentityToken OKTA_IT = new OktaIdentityToken("okta-it");
private static final OktaAccessToken OKTA_AT = new OktaAccessToken("okta-at");
- private static final ZoneId TEST_ZONE = ZoneId.from(Environment.test, RegionName.from("us-east-1"));
- private static final ZoneId STAGING_ZONE = ZoneId.from(Environment.staging, RegionName.from("us-east-3"));
- private ContainerControllerTester controllerTester;
private ContainerTester tester;
+ private DeploymentTester deploymentTester;
@Before
public void before() {
- controllerTester = new ContainerControllerTester(container, responseFiles);
- tester = controllerTester.containerTester();
+ tester = new ContainerTester(container, responseFiles);
+ deploymentTester = new DeploymentTester(new ControllerTester(tester));
+ deploymentTester.controllerTester().computeVersionStatus();
}
@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)
// GET API root
@@ -253,95 +238,80 @@ public class ApplicationApiTest extends ControllerContainerTest {
addUserToHostedOperatorRole(HostedAthenzIdentities.from(HOSTED_VESPA_OPERATOR));
- // POST (deploy) an application to a zone - manual user deployment (includes a content hash for verification)
+ ApplicationId id = ApplicationId.from("tenant1", "application1", "instance1");
+ var app1 = deploymentTester.newDeploymentContext(id);
+
+ // POST (deploy) an application to start a manual deployment to dev
MultiPartStreamer entity = createApplicationDeployData(applicationPackageInstance1, true);
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/dev/region/us-west-1/instance/instance1/deploy", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploy/dev-us-east-1/", POST)
.data(entity)
- .header("X-Content-Hash", Base64.getEncoder().encodeToString(Signatures.sha256Digest(entity::data)))
.userIdentity(USER_ID),
- new File("deploy-result.json"));
+ "{\"message\":\"Deployment started in run 1 of dev-us-east-1 for tenant1.application1.instance1. This may take about 15 minutes the first time.\",\"run\":1}");
+ app1.runJob(JobType.devUsEast1);
- // POST (deploy) an application to a zone. This simulates calls done by our tenant pipeline.
- ApplicationId id = ApplicationId.from("tenant1", "application1", "instance1");
- long screwdriverProjectId = 123;
+ // POST an application package is allowed under user instance
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/otheruser/deploy/dev-us-east-1", POST)
+ .userIdentity(OTHER_USER_ID)
+ .data(createApplicationDeployData(applicationPackageInstance1, false)),
+ new File("deployment-job-accepted-2.json"));
+
+ // DELETE a dev deployment is allowed under user instance
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/otheruser/environment/dev/region/us-east-1", DELETE)
+ .userIdentity(OTHER_USER_ID),
+ "{\"message\":\"Deactivated tenant1.application1.otheruser in dev.us-east-1\"}");
+
+ // DELETE a user instance
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/otheruser", DELETE)
+ .userIdentity(USER_ID)
+ .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT),
+ "{\"message\":\"Deleted instance tenant1.application1.otheruser\"}");
addScrewdriverUserToDeployRole(SCREWDRIVER_ID,
ATHENZ_TENANT_DOMAIN,
- new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId(id.application().value())); // (Necessary but not provided in this API)
-
- // Pipeline notifies about completed component job
- controllerTester.jobCompletion(JobType.component)
- .application(id)
- .projectId(screwdriverProjectId)
- .uploadArtifact(applicationPackageInstance1)
- .submit();
-
- // ... systemtest
- 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/instance1", DELETE)
- .screwdriverIdentity(SCREWDRIVER_ID),
- "{\"message\":\"Deactivated tenant1.application1.instance1 in test.us-east-1\"}");
+ id.application());
- controllerTester.jobCompletion(JobType.systemTest)
- .application(id)
- .projectId(screwdriverProjectId)
- .submit();
-
- // ... staging
- 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/instance1", DELETE)
- .screwdriverIdentity(SCREWDRIVER_ID),
- "{\"message\":\"Deactivated tenant1.application1.instance1 in staging.us-east-3\"}");
- controllerTester.jobCompletion(JobType.stagingTest)
- .application(id)
- .projectId(screwdriverProjectId)
- .submit();
+ // POST an application package and a test jar, submitting a new application for production deployment.
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/submit", POST)
+ .screwdriverIdentity(SCREWDRIVER_ID)
+ .data(createApplicationSubmissionData(applicationPackageInstance1, 123)),
+ "{\"message\":\"Application package version: 1.0.1-commit1, source revision of repository 'repository1', branch 'master' with commit 'commit1', by a@b, built against 6.1 at 1970-01-01T00:00:01Z\"}");
- // ... prod zone
- 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"));
- controllerTester.jobCompletion(JobType.productionUsCentral1)
- .application(id)
- .projectId(screwdriverProjectId)
- .unsuccessful()
- .submit();
+ app1.runJob(JobType.systemTest).runJob(JobType.stagingTest).runJob(JobType.productionUsCentral1);
// POST an application deployment to a production zone - operator emergency deployment - fails since package is unknown
entity = createApplicationDeployData(Optional.empty(),
- Optional.of(ApplicationVersion.from(DeploymentContext.defaultSourceRevision,
- BuildJob.defaultBuildNumber - 1)),
+ Optional.of(ApplicationVersion.from(DeploymentContext.defaultSourceRevision, 666)),
true);
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.instance1 with version 1.0.41-commit1\"}",
+ "{\"error-code\":\"BAD_REQUEST\",\"message\":\"No application package found for tenant1.application1 with version 1.0.666-commit1\"}",
400);
// POST an application deployment to a production zone - operator emergency deployment - works with known package
entity = createApplicationDeployData(Optional.empty(),
- Optional.of(ApplicationVersion.from(DeploymentContext.defaultSourceRevision,
- BuildJob.defaultBuildNumber)),
+ Optional.of(ApplicationVersion.from(DeploymentContext.defaultSourceRevision, 1)),
true);
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"));
+ // POST an application deployment to a production zone - operator emergency deployment - chooses latest package without arguments
+ entity = createApplicationDeployData(Optional.empty(), true);
+ 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"));
+
// POST (create) another application
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
.instances("instance1")
.globalServiceId("foo")
.environment(Environment.prod)
.region("us-west-1")
+ .region("us-east-3")
.allow(ValidationId.globalEndpointChange)
.build();
@@ -350,20 +320,22 @@ public class ApplicationApiTest extends ControllerContainerTest {
.oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT),
new File("instance-reference-2.json"));
- ApplicationId app2 = ApplicationId.from("tenant2", "application2", "default");
- long screwdriverProjectId2 = 456;
+ ApplicationId id2 = ApplicationId.from("tenant2", "application2", "instance1");
+ var app2 = deploymentTester.newDeploymentContext(id2);
addScrewdriverUserToDeployRole(SCREWDRIVER_ID,
ATHENZ_TENANT_DOMAIN_2,
- new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId(app2.application().value()));
+ id2.application());
// Trigger upgrade and then application change
- controllerTester.controller().applications().deploymentTrigger().triggerChange(TenantAndApplicationId.from(app2), Change.of(Version.fromString("7.0")));
+ deploymentTester.applications().deploymentTrigger().triggerChange(TenantAndApplicationId.from(id2), Change.of(Version.fromString("7.0")));
+
+ // POST an application package and a test jar, submitting a new application for production deployment.
+ tester.assertResponse(request("/application/v4/tenant/tenant2/application/application2/submit", POST)
+ .screwdriverIdentity(SCREWDRIVER_ID)
+ .data(createApplicationSubmissionData(applicationPackage, 1000)),
+ "{\"message\":\"Application package version: 1.0.1-commit1, source revision of repository 'repository1', branch 'master' with commit 'commit1', by a@b, built against 6.1 at 1970-01-01T00:00:01Z\"}");
- controllerTester.jobCompletion(JobType.component)
- .application(app2)
- .projectId(screwdriverProjectId2)
- .uploadArtifact(applicationPackage)
- .submit();
+ deploymentTester.triggerJobs();
// GET application having both change and outstanding change
tester.assertResponse(request("/application/v4/tenant/tenant2/application/application2", GET)
@@ -375,7 +347,6 @@ public class ApplicationApiTest extends ControllerContainerTest {
.screwdriverIdentity(SCREWDRIVER_ID),
new File("application2.json"));
-
// PATCH in a major version override
tester.assertResponse(request("/application/v4/tenant/tenant2/application/application2", PATCH)
.userIdentity(USER_ID)
@@ -415,16 +386,22 @@ public class ApplicationApiTest extends ControllerContainerTest {
.userIdentity(USER_ID),
new File("application2.json"));
- // DELETE application
+ // DELETE instance 1 of 2
+ tester.assertResponse(request("/application/v4/tenant/tenant2/application/application2/instance/default", DELETE)
+ .userIdentity(USER_ID)
+ .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT),
+ "{\"message\":\"Deleted instance tenant2.application2.default\"}");
+
+ // DELETE application with only one instance left
tester.assertResponse(request("/application/v4/tenant/tenant2/application/application2", DELETE)
.userIdentity(USER_ID)
.oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT),
"{\"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);
+ deploymentTester.upgrader().overrideConfidence(Version.fromString("6.1"), VespaVersion.Confidence.broken);
+ deploymentTester.controllerTester().computeVersionStatus();
+ setDeploymentMaintainedInfo();
setZoneInRotation("rotation-fqdn-1", ZoneId.from("prod", "us-central-1"));
// GET tenant application deployments
@@ -436,7 +413,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
.userIdentity(USER_ID),
new File("deployment.json"));
- addIssues(controllerTester, TenantAndApplicationId.from("tenant1", "application1"));
+ addIssues(deploymentTester, TenantAndApplicationId.from("tenant1", "application1"));
// 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)
@@ -471,7 +448,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
// DELETE (cancel) ongoing change
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.1-commit1' to 'no change' for application 'tenant1.application1'\"}");
// DELETE (cancel) again is a no-op
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying", DELETE)
@@ -557,7 +534,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
"{\"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.serviceRegistry().configServerMock().nodeRepository().addFixedNodes(ZoneId.from("prod", "us-central-1"));
+ deploymentTester.configServer().nodeRepository().addFixedNodes(ZoneId.from("prod", "us-central-1"));
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/instance1/restart?hostname=hostA", POST)
.screwdriverIdentity(SCREWDRIVER_ID),
"{\"message\":\"Requested restart of tenant1.application1.instance1 in prod.us-central-1\"}", 200);
@@ -583,9 +560,9 @@ public class ApplicationApiTest extends ControllerContainerTest {
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/instance1", DELETE)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/dev/region/us-east-1/instance/instance1", DELETE)
.userIdentity(USER_ID),
- "{\"message\":\"Deactivated tenant1.application1.instance1 in dev.us-west-1\"}");
+ "{\"message\":\"Deactivated tenant1.application1.instance1 in dev.us-east-1\"}");
// DELETE (deactivate) a deployment - prod
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/instance1", DELETE)
@@ -625,38 +602,6 @@ public class ApplicationApiTest extends ControllerContainerTest {
ZoneId.from("dev", "us-east-1"));
// teardown for test config tests
- // POST an application package to start a deployment to dev
- 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 is allowed under user instance
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/otheruser/deploy/dev-us-east-1", POST)
- .userIdentity(OTHER_USER_ID)
- .data(createApplicationDeployData(applicationPackage, false)),
- new File("deployment-job-accepted-2.json"));
-
- // DELETE a dev deployment is allowed under user instance
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/otheruser/environment/dev/region/us-east-1", DELETE)
- .userIdentity(OTHER_USER_ID),
- "{\"message\":\"Deactivated tenant1.application1.otheruser in dev.us-east-1\"}");
-
- // 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/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\"}");
-
- // GET application package
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/package", GET).userIdentity(HOSTED_VESPA_OPERATOR),
- (response) -> {
- assertEquals("attachment; filename=\"tenant1.application1-build43.zip\"", response.getHeaders().getFirst("Content-Disposition"));
- assertArrayEquals(applicationPackage.zippedContent(), response.getBody());
- },
- 200);
-
// Second attempt has a service under a different domain than the tenant of the application, and fails.
ApplicationPackage packageWithServiceForWrongDomain = new ApplicationPackageBuilder()
.instances("instance1")
@@ -664,39 +609,40 @@ 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();
- configureAthenzIdentity(new com.yahoo.vespa.athenz.api.AthenzService(ATHENZ_TENANT_DOMAIN_2, "service"), true);
+ allowLaunchOfService(new com.yahoo.vespa.athenz.api.AthenzService(ATHENZ_TENANT_DOMAIN_2, "service"));
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/submit", POST)
.screwdriverIdentity(SCREWDRIVER_ID)
- .data(createApplicationSubmissionData(packageWithServiceForWrongDomain)),
+ .data(createApplicationSubmissionData(packageWithServiceForWrongDomain, 123)),
"{\"error-code\":\"BAD_REQUEST\",\"message\":\"Athenz domain in deployment.xml: [domain2] must match tenant domain: [domain1]\"}", 400);
- // Third attempt finally has a service under the domain of the tenant, and succeeds.
+ // Third attempt has a service under the domain of the tenant, and also succeeds.
ApplicationPackage packageWithService = new ApplicationPackageBuilder()
.instances("instance1")
.globalServiceId("foo")
.environment(Environment.prod)
.athenzIdentity(com.yahoo.config.provision.AthenzDomain.from(ATHENZ_TENANT_DOMAIN.getName()), AthenzService.from("service"))
- .region("us-west-1")
+ .region("us-central-1")
+ .parallel("us-west-1", "us-east-3")
.build();
- configureAthenzIdentity(new com.yahoo.vespa.athenz.api.AthenzService(ATHENZ_TENANT_DOMAIN, "service"), true);
+ allowLaunchOfService(new com.yahoo.vespa.athenz.api.AthenzService(ATHENZ_TENANT_DOMAIN, "service"));
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\"}");
+ .data(createApplicationSubmissionData(packageWithService, 123)),
+ "{\"message\":\"Application package version: 1.0.2-commit1, source revision of repository 'repository1', branch 'master' with commit 'commit1', by a@b, built against 6.1 at 1970-01-01T00:00:01Z\"}");
// GET last submitted application package
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/package", GET).userIdentity(HOSTED_VESPA_OPERATOR),
(response) -> {
- assertEquals("attachment; filename=\"tenant1.application1-build44.zip\"", response.getHeaders().getFirst("Content-Disposition"));
+ assertEquals("attachment; filename=\"tenant1.application1-build2.zip\"", response.getHeaders().getFirst("Content-Disposition"));
assertArrayEquals(packageWithService.zippedContent(), response.getBody());
},
200);
// GET application package for previous build
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/package?build=43", GET).userIdentity(HOSTED_VESPA_OPERATOR),
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/package?build=1", GET).userIdentity(HOSTED_VESPA_OPERATOR),
(response) -> {
- assertEquals("attachment; filename=\"tenant1.application1-build43.zip\"", response.getHeaders().getFirst("Content-Disposition"));
- assertArrayEquals(applicationPackage.zippedContent(), response.getBody());
+ assertEquals("attachment; filename=\"tenant1.application1-build1.zip\"", response.getHeaders().getFirst("Content-Disposition"));
+ assertArrayEquals(applicationPackageInstance1.zippedContent(), response.getBody());
},
200);
@@ -704,40 +650,30 @@ public class ApplicationApiTest extends ControllerContainerTest {
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)),
+ .data(createApplicationSubmissionData(packageWithService, 123)),
"{\"error-code\":\"BAD_REQUEST\",\"message\":\"Value of X-Content-Hash header does not match computed content hash\"}", 400);
// Fifth attempt has the right content hash in a header, and succeeds.
- MultiPartStreamer streamer = createApplicationSubmissionData(packageWithService);
+ MultiPartStreamer streamer = createApplicationSubmissionData(packageWithService, 123);
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\"}");
+ "{\"message\":\"Application package version: 1.0.3-commit1, source revision of repository 'repository1', branch 'master' with commit 'commit1', by a@b, built against 6.1 at 1970-01-01T00:00:01Z\"}");
- // Sixth attempt has a multi-instance deployment spec, and fails.
+ // Sixth attempt has a multi-instance deployment spec, and is accepted.
ApplicationPackage multiInstanceSpec = new ApplicationPackageBuilder()
.instances("instance1,instance2")
.environment(Environment.prod)
- .region("us-west-1")
+ .region("us-central-1")
+ .parallel("us-west-1", "us-east-3")
+ .endpoint("default", "foo", "us-central-1", "us-west-1", "us-east-3")
.build();
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/submit", POST)
.screwdriverIdentity(SCREWDRIVER_ID)
- .data(createApplicationSubmissionData(multiInstanceSpec)),
- "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Only single-instance deployment specs are currently supported\"}", 400);
+ .data(createApplicationSubmissionData(multiInstanceSpec, 123)),
+ "{\"message\":\"Application package version: 1.0.4-commit1, source revision of repository 'repository1', branch 'master' with commit 'commit1', by a@b, built against 6.1 at 1970-01-01T00:00:01Z\"}");
- 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,
- 123,
- Optional.empty(),
- DeploymentContext.defaultSourceRevision))),
- "{\"error-code\":\"BAD_REQUEST\",\"message\":\"" + app1 + " is set up to be deployed from internally," +
- " and no longer accepts submissions from Screwdriver v3 jobs. If you need to revert " +
- "to the old pipeline, please file a ticket at yo/vespa-support and request this.\"}",
- 400);
// GET deployment job overview, after triggering system and staging test jobs.
assertEquals(2, tester.controller().applications().deploymentTrigger().triggerReadyJobs());
@@ -758,21 +694,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
// DELETE a running job to have it aborted.
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.instance1\"}");
-
- // DELETE submission to unsubscribe from continuous deployment.
- 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.\"}");
-
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/jobreport", POST)
- .screwdriverIdentity(SCREWDRIVER_ID)
- .data(asJson(DeploymentJobs.JobReport.ofComponent(app1,
- 1234,
- 123,
- Optional.empty(),
- DeploymentContext.defaultSourceRevision))),
- "{\"message\":\"ok\"}");
+ "{\"message\":\"Aborting run 2 of staging-test for tenant1.application1.instance1\"}");
// PUT (create) the authenticated user
byte[] data = new byte[0];
@@ -804,10 +726,10 @@ public class ApplicationApiTest extends ControllerContainerTest {
.userIdentity(USER_ID)
.oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT),
"{\"message\":\"Deleted instance tenant1.application1.instance1\"}");
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/otheruser", DELETE)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance2", DELETE)
.userIdentity(USER_ID)
.oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT),
- "{\"message\":\"Deleted instance tenant1.application1.otheruser\"}");
+ "{\"message\":\"Deleted instance tenant1.application1.instance2\"}");
// DELETE a tenant
tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE).userIdentity(USER_ID)
@@ -815,8 +737,8 @@ public class ApplicationApiTest extends ControllerContainerTest {
new File("tenant-without-applications.json"));
}
- private void addIssues(ContainerControllerTester tester, TenantAndApplicationId id) {
- tester.controller().applications().lockApplicationOrThrow(id, application ->
+ private void addIssues(DeploymentTester tester, TenantAndApplicationId id) {
+ tester.applications().lockApplicationOrThrow(id, application ->
tester.controller().applications().store(application.withDeploymentIssueId(IssueId.from("123"))
.withOwnershipIssueId(IssueId.from("321"))
.withOwner(User.from("owner-username"))));
@@ -825,7 +747,6 @@ public class ApplicationApiTest extends ControllerContainerTest {
@Test
public void testRotationOverride() {
// Setup
- tester.computeVersionStatus();
createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID);
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
.instances("instance1")
@@ -835,21 +756,8 @@ public class ApplicationApiTest extends ControllerContainerTest {
.build();
// Create tenant and deploy
- ApplicationId id = createTenantAndApplication();
- long projectId = 1;
- MultiPartStreamer deployData = createApplicationDeployData(Optional.of(applicationPackage), false);
- startAndTestChange(controllerTester, id, projectId, applicationPackage, deployData, 100);
-
- // us-west-1
- 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"));
- controllerTester.jobCompletion(JobType.productionUsWest1)
- .application(id)
- .projectId(projectId)
- .submit();
- setZoneInRotation("rotation-fqdn-1", ZoneId.from("prod", "us-west-1"));
+ var app = deploymentTester.newDeploymentContext(createTenantAndApplication());
+ app.submit(applicationPackage).runJob(JobType.systemTest).runJob(JobType.stagingTest).runJob(JobType.productionUsWest1);
// Invalid application fails
tester.assertResponse(request("/application/v4/tenant/tenant2/application/application2/environment/prod/region/us-west-1/instance/default/global-rotation", GET)
@@ -897,7 +805,6 @@ public class ApplicationApiTest extends ControllerContainerTest {
@Test
public void multiple_endpoints() {
// Setup
- tester.computeVersionStatus();
createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID);
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
.instances("instance1")
@@ -909,20 +816,9 @@ public class ApplicationApiTest extends ControllerContainerTest {
.build();
// Create tenant and deploy
- ApplicationId id = createTenantAndApplication();
- long projectId = 1;
- MultiPartStreamer deployData = createApplicationDeployData(Optional.empty(), false);
- startAndTestChange(controllerTester, id, projectId, applicationPackage, deployData, 100);
- for (var job : List.of(JobType.productionUsWest1, JobType.productionUsEast3, JobType.productionEuWest1)) {
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/environment/prod/region/" + job.zone(SystemName.main).region().value() + "/deploy", POST)
- .data(deployData)
- .screwdriverIdentity(SCREWDRIVER_ID),
- new File("deploy-result.json"));
- controllerTester.jobCompletion(job)
- .application(id)
- .projectId(projectId)
- .submit();
- }
+ var app = deploymentTester.newDeploymentContext("tenant1", "application1", "instance1");
+ app.submit(applicationPackage).deploy();
+
setZoneInRotation("rotation-fqdn-2", ZoneId.from("prod", "us-west-1"));
setZoneInRotation("rotation-fqdn-2", ZoneId.from("prod", "us-east-3"));
setZoneInRotation("rotation-fqdn-1", ZoneId.from("prod", "eu-west-1"));
@@ -955,7 +851,6 @@ public class ApplicationApiTest extends ControllerContainerTest {
@Test
public void testDeployDirectly() {
// Setup
- tester.computeVersionStatus();
createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID);
addUserToHostedOperatorRole(HostedAthenzIdentities.from(HOSTED_VESPA_OPERATOR));
@@ -974,7 +869,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
// Grant deploy access
addScrewdriverUserToDeployRole(SCREWDRIVER_ID,
ATHENZ_TENANT_DOMAIN,
- new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId("application1"));
+ ApplicationName.from("application1"));
// POST (deploy) an application to a prod zone - allowed when project ID is not specified
MultiPartStreamer entity = createApplicationDeployData(applicationPackageInstance1, true);
@@ -990,7 +885,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
.userIdentity(HOSTED_VESPA_OPERATOR),
"{\"error-code\":\"BAD_REQUEST\",\"message\":\"Deployment of system applications during a system upgrade is not allowed\"}",
400);
- tester.upgradeSystem(tester.controller().versionStatus().controllerVersion().get().versionNumber());
+ deploymentTester.controllerTester().upgradeSystem(deploymentTester.controller().versionStatus().controllerVersion().get().versionNumber());
tester.assertResponse(request("/application/v4/tenant/hosted-vespa/application/routing/environment/prod/region/us-central-1/instance/default/deploy", POST)
.data(noAppEntity)
.userIdentity(HOSTED_VESPA_OPERATOR),
@@ -1005,27 +900,14 @@ public class ApplicationApiTest extends ControllerContainerTest {
@Test
public void testSortsDeploymentsAndJobs() {
- tester.computeVersionStatus();
-
// Deploy
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
.instances("instance1")
.region("us-east-3")
.build();
- ApplicationId id = createTenantAndApplication();
- long projectId = 1;
- MultiPartStreamer deployData = createApplicationDeployData(Optional.empty(), false);
- startAndTestChange(controllerTester, id, projectId, applicationPackage, deployData, 100);
-
- // us-east-3
- 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"));
- controllerTester.jobCompletion(JobType.productionUsEast3)
- .application(id)
- .projectId(projectId)
- .submit();
+
+ var app = deploymentTester.newDeploymentContext("tenant1", "application1", "instance1");
+ app.submit(applicationPackage).deploy();
// New zone is added before us-east-3
applicationPackage = new ApplicationPackageBuilder()
@@ -1035,31 +917,13 @@ public class ApplicationApiTest extends ControllerContainerTest {
.region("us-west-1")
.region("us-east-3")
.build();
- startAndTestChange(controllerTester, id, projectId, applicationPackage, deployData, 101);
-
- // us-west-1
- 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"));
- controllerTester.jobCompletion(JobType.productionUsWest1)
- .application(id)
- .projectId(projectId)
- .submit();
+ app.submit(applicationPackage).runJob(JobType.systemTest).runJob(JobType.stagingTest).runJob(JobType.productionUsWest1);
setZoneInRotation("rotation-fqdn-1", ZoneId.from("prod", "us-west-1"));
- // us-east-3
- 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"));
- controllerTester.jobCompletion(JobType.productionUsEast3)
- .application(id)
- .projectId(projectId)
- .submit();
+ app.runJob(JobType.stagingTest).runJob(JobType.productionUsEast3);
- setDeploymentMaintainedInfo(controllerTester);
+ setDeploymentMaintainedInfo();
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1", GET)
.userIdentity(USER_ID),
new File("instance-without-change-multiple-deployments.json"));
@@ -1067,7 +931,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
@Test
public void testMeteringResponses() {
- MockMeteringClient mockMeteringClient = (MockMeteringClient) controllerTester.containerTester().serviceRegistry().meteringService();
+ MockMeteringClient mockMeteringClient = tester.serviceRegistry().meteringService();
// Mock response for MeteringClient
ResourceAllocation currentSnapshot = new ResourceAllocation(1, 2, 3);
@@ -1090,7 +954,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
@Test
public void testTenantCostResponse() {
ApplicationId applicationId = createTenantAndApplication();
- MockTenantCost mockTenantCost = (MockTenantCost) controllerTester.containerTester().serviceRegistry().tenantCost();
+ MockTenantCost mockTenantCost = deploymentTester.controllerTester().serviceRegistry().tenantCost();
mockTenantCost.setMonthsWithMetering(
new TreeSet<>(Set.of(
@@ -1127,7 +991,6 @@ public class ApplicationApiTest extends ControllerContainerTest {
@Test
public void testErrorResponses() throws Exception {
- tester.computeVersionStatus();
createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID);
// PUT (update) non-existing tenant returns 403 as tenant access cannot be determined when the tenant does not exist
@@ -1216,7 +1079,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
"{\"error-code\":\"BAD_REQUEST\",\"message\":\"Could not create 'tenant1.application1.instance1': Instance already exists\"}",
400);
- ConfigServerMock configServer = serviceRegistry().configServerMock();
+ ConfigServerMock configServer = tester.serviceRegistry().configServerMock();
configServer.throwOnNextPrepare(new ConfigServerException(new URI("server-url"), "Failed to prepare application", ConfigServerException.ErrorCode.INVALID_APPLICATION_PACKAGE, null));
// GET non-existent application package
@@ -1431,87 +1294,67 @@ public class ApplicationApiTest extends ControllerContainerTest {
}
@Test
- public void deployment_fails_on_illegal_domain_in_deployment_spec() {
+ public void athenz_service_must_be_allowed_to_launch_and_be_under_tenant_domain() {
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
.upgradePolicy("default")
.athenzIdentity(com.yahoo.config.provision.AthenzDomain.from("another.domain"), com.yahoo.config.provision.AthenzService.from("service"))
.environment(Environment.prod)
.region("us-west-1")
.build();
- long screwdriverProjectId = 123;
createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID);
- configureAthenzIdentity(new com.yahoo.vespa.athenz.api.AthenzService(new AthenzDomain("another.domain"), "service"), true);
-
- 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.id());
-
- controllerTester.jobCompletion(JobType.component)
- .application(application)
- .projectId(screwdriverProjectId)
- .uploadArtifact(applicationPackage)
- .submit();
-
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/test/region/us-east-1/instance/default/", POST)
- .data(createApplicationDeployData(applicationPackage, false))
+
+ deploymentTester.controllerTester().createTenant("tenant1", ATHENZ_TENANT_DOMAIN.getName(), 1234L);
+ var application = deploymentTester.newDeploymentContext("tenant1", "application1", "default");
+ ScrewdriverId screwdriverId = new ScrewdriverId("123");
+ addScrewdriverUserToDeployRole(screwdriverId, ATHENZ_TENANT_DOMAIN, application.instanceId().application());
+
+ allowLaunchOfService(new com.yahoo.vespa.athenz.api.AthenzService(new AthenzDomain("another.domain"), "service"));
+ // Submit a package with a service under a different Athenz domain from that of the tenant
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/submit/", POST)
+ .data(createApplicationSubmissionData(applicationPackage, 123))
.screwdriverIdentity(screwdriverId),
"{\"error-code\":\"BAD_REQUEST\",\"message\":\"Athenz domain in deployment.xml: [another.domain] must match tenant domain: [domain1]\"}",
400);
- }
-
- @Test
- public void deployment_succeeds_when_correct_domain_is_used() {
- ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
+ // Set the correct domain in the application package, but do not yet allow Vespa to launch the service.
+ applicationPackage = new ApplicationPackageBuilder()
.upgradePolicy("default")
.athenzIdentity(com.yahoo.config.provision.AthenzDomain.from("domain1"), com.yahoo.config.provision.AthenzService.from("service"))
.environment(Environment.prod)
.region("us-west-1")
.build();
- long screwdriverProjectId = 123;
- ScrewdriverId screwdriverId = new ScrewdriverId(Long.toString(screwdriverProjectId));
- createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID);
- configureAthenzIdentity(new com.yahoo.vespa.athenz.api.AthenzService(ATHENZ_TENANT_DOMAIN, "service"), true);
-
- Application application = controllerTester.createApplication(ATHENZ_TENANT_DOMAIN.getName(), "tenant1", "application1", "default");
- controllerTester.authorize(ATHENZ_TENANT_DOMAIN, screwdriverId, ApplicationAction.deploy, application.id());
-
- // Allow systemtest to succeed by notifying completion of component
- controllerTester.jobCompletion(JobType.component)
- .application(application)
- .projectId(screwdriverProjectId)
- .uploadArtifact(applicationPackage)
- .submit();
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/test/region/us-east-1/instance/default/", POST)
- .data(createApplicationDeployData(applicationPackage, false))
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/submit", POST)
+ .data(createApplicationSubmissionData(applicationPackage, 123))
.screwdriverIdentity(screwdriverId),
- new File("deploy-result.json"));
+ "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Not allowed to launch Athenz service domain1.service\"}",
+ 400);
+
+ // Allow Vespa to launch the Athenz service.
+ allowLaunchOfService(new com.yahoo.vespa.athenz.api.AthenzService(ATHENZ_TENANT_DOMAIN, "service"));
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/submit/", POST)
+ .data(createApplicationSubmissionData(applicationPackage, 123))
+ .screwdriverIdentity(screwdriverId),
+ "{\"message\":\"Application package version: 1.0.1-commit1, source revision of repository 'repository1', branch 'master' with commit 'commit1', by a@b, built against 6.1 at 1970-01-01T00:00:01Z\"}");
}
@Test
- public void deployment_fails_for_personal_tenants_when_athenzdomain_specified_and_user_not_admin() {
+ public void personal_deployment_with_athenz_service_requires_user_is_admin() {
// Setup
- tester.computeVersionStatus();
UserId tenantAdmin = new UserId("tenant-admin");
UserId userId = new UserId("new-user");
createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, tenantAdmin);
- configureAthenzIdentity(new com.yahoo.vespa.athenz.api.AthenzService(ATHENZ_TENANT_DOMAIN, "service"), true);
+ allowLaunchOfService(new com.yahoo.vespa.athenz.api.AthenzService(ATHENZ_TENANT_DOMAIN, "service"));
// Create tenant
// PUT (create) the authenticated user
- byte[] data = new byte[0];
tester.assertResponse(request("/application/v4/user?user=new_user&domain=by", PUT)
- .data(data)
.userIdentity(userId), // Normalized to by-new-user by API
new File("create-user-response.json"));
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
- .upgradePolicy("default")
.athenzIdentity(com.yahoo.config.provision.AthenzDomain.from("domain1"), com.yahoo.config.provision.AthenzService.from("service"))
- .environment(Environment.dev)
- .region("us-west-1")
.build();
// POST (deploy) an application to a dev zone
@@ -1523,252 +1366,88 @@ public class ApplicationApiTest extends ControllerContainerTest {
expectedResult,
400);
- }
-
- @Test
- public void deployment_succeeds_for_personal_tenants_when_user_is_tenant_admin() {
-
- // Setup
- tester.computeVersionStatus();
- UserId tenantAdmin = new UserId("new_user");
- createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, tenantAdmin);
- configureAthenzIdentity(new com.yahoo.vespa.athenz.api.AthenzService(ATHENZ_TENANT_DOMAIN, "service"), true);
-
- // Create tenant
- // PUT (create) the authenticated user
- byte[] data = new byte[0];
- tester.assertResponse(request("/application/v4/user?user=new_user&domain=by", PUT)
- .data(data)
- .userIdentity(tenantAdmin), // Normalized to by-new-user by API
- new File("create-user-response.json"));
+ createTenantAndApplication();
+ // POST (deploy) an application to dev through a deployment job
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/new-user/deploy/dev-us-east-1", POST)
+ .data(entity)
+ .userIdentity(userId),
+ expectedResult,
+ 400);
- ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
- .upgradePolicy("default")
- .athenzIdentity(com.yahoo.config.provision.AthenzDomain.from("domain1"), com.yahoo.config.provision.AthenzService.from("service"))
- .environment(Environment.dev)
- .region("us-west-1")
- .build();
+ // Add "new-user" to the admin role, to allow service launches.
+ tester.athenzClientFactory().getSetup()
+ .domains.get(ATHENZ_TENANT_DOMAIN)
+ .admin(HostedAthenzIdentities.from(userId));
// POST (deploy) an application to a dev zone
- MultiPartStreamer entity = createApplicationDeployData(applicationPackage, true);
tester.assertResponse(request("/application/v4/tenant/by-new-user/application/application1/environment/dev/region/us-west-1/instance/default", POST)
.data(entity)
- .userIdentity(tenantAdmin),
+ .userIdentity(userId),
new File("deploy-result.json"));
- }
- @Test
- public void deployment_fails_when_athenz_service_cannot_be_launched() {
- ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
- .upgradePolicy("default")
- .athenzIdentity(com.yahoo.config.provision.AthenzDomain.from("domain1"), com.yahoo.config.provision.AthenzService.from("service"))
- .environment(Environment.prod)
- .region("us-west-1")
- .build();
- long screwdriverProjectId = 123;
- ScrewdriverId screwdriverId = new ScrewdriverId(Long.toString(screwdriverProjectId));
-
- createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID);
- configureAthenzIdentity(new com.yahoo.vespa.athenz.api.AthenzService(ATHENZ_TENANT_DOMAIN, "service"), false);
-
- Application application = controllerTester.createApplication(ATHENZ_TENANT_DOMAIN.getName(), "tenant1", "application1", "default");
- controllerTester.authorize(ATHENZ_TENANT_DOMAIN, screwdriverId, ApplicationAction.deploy, application.id());
-
- // Allow systemtest to succeed by notifying completion of system test
- controllerTester.jobCompletion(JobType.component)
- .application(application)
- .projectId(screwdriverProjectId)
- .uploadArtifact(applicationPackage)
- .submit();
-
- String expectedResult="{\"error-code\":\"BAD_REQUEST\",\"message\":\"Not allowed to launch Athenz service domain1.service\"}";
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/test/region/us-east-1/instance/default/", POST)
- .data(createApplicationDeployData(applicationPackage, false))
- .screwdriverIdentity(screwdriverId),
- expectedResult,
- 400);
-
- }
-
- @Test
- public void redeployment_succeeds_when_not_specifying_versions_or_application_package() {
- // Setup
- addUserToHostedOperatorRole(HostedAthenzIdentities.from(HOSTED_VESPA_OPERATOR));
- tester.computeVersionStatus();
-
- ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
- .upgradePolicy("default")
- .athenzIdentity(com.yahoo.config.provision.AthenzDomain.from("domain1"), com.yahoo.config.provision.AthenzService.from("service"))
- .environment(Environment.prod)
- .region("us-west-1")
- .build();
- long screwdriverProjectId = 123;
- ScrewdriverId screwdriverId = new ScrewdriverId(Long.toString(screwdriverProjectId));
-
- createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID);
- configureAthenzIdentity(new com.yahoo.vespa.athenz.api.AthenzService(ATHENZ_TENANT_DOMAIN, "service"), true);
-
- Application application = controllerTester.createApplication(ATHENZ_TENANT_DOMAIN.getName(), "tenant1", "application1", "default");
- controllerTester.authorize(ATHENZ_TENANT_DOMAIN, screwdriverId, ApplicationAction.deploy, application.id());
-
- // Allow systemtest to succeed by notifying completion of component
- controllerTester.jobCompletion(JobType.component)
- .application(application)
- .projectId(screwdriverProjectId)
- .uploadArtifact(applicationPackage)
- .submit();
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/test/region/us-east-1/instance/default/", POST)
- .data(createApplicationDeployData(applicationPackage, false))
- .screwdriverIdentity(screwdriverId),
- new File("deploy-result.json"));
-
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/test/region/us-east-1/instance/default/", POST)
- .data(createApplicationDeployData(Optional.empty(), true))
- .userIdentity(HOSTED_VESPA_OPERATOR),
- new File("deploy-result.json"));
+ // POST (deploy) an application to dev through a deployment job
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/new-user/deploy/dev-us-east-1", POST)
+ .data(entity)
+ .userIdentity(userId),
+ "{\"message\":\"Deployment started in run 1 of dev-us-east-1 for tenant1.application1.new-user. This may take about 15 minutes the first time.\",\"run\":1}");
}
-
@Test
public void testJobStatusReporting() {
addUserToHostedOperatorRole(HostedAthenzIdentities.from(HOSTED_VESPA_OPERATOR));
- tester.computeVersionStatus();
- long projectId = 1;
- Application app = controllerTester.createApplication();
- ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
- .environment(Environment.prod)
- .region("us-central-1")
- .build();
+ var app = deploymentTester.newDeploymentContext(createTenantAndApplication());
- Version vespaVersion = new Version("6.1"); // system version from mock config server client
+ Version vespaVersion = tester.configServer().initialVersion(); // system version from mock config server client
- BuildJob job = new BuildJob(report -> notifyCompletion(report, controllerTester), controllerTester.containerTester().serviceRegistry().artifactRepositoryMock())
- .application(app)
- .projectId(projectId);
- job.type(JobType.component).uploadArtifact(applicationPackage).submit();
- controllerTester.deploy(app.id().defaultInstance(), applicationPackage, TEST_ZONE);
- ((ManualClock) controllerTester.controller().clock()).advance(Duration.ofSeconds(1));
- job.type(JobType.systemTest).submit();
+ app.submit(applicationPackageInstance1);
+ String data = "{\"jobName\":\"system-test\",\"instance\":\"instance1\"}";
- // Notifying about job started not by the controller fails
var request = request("/application/v4/tenant/tenant1/application/application1/jobreport", POST)
- .data(asJson(job.type(JobType.systemTest).report()))
+ .data(data)
.userIdentity(HOSTED_VESPA_OPERATOR);
- 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().requireInstance(app.id().defaultInstance()).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);
+ // Notifying about non-running job fails
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\"}",
+ "of system-test for tenant1.application1.instance1, but that has not been triggered; last was never\"}",
400);
- // ... and assert it was recorded
- JobStatus recordedStatus =
- tester.controller().applications().getInstance(app.id().defaultInstance()).get().deploymentJobs().jobStatus().get(JobType.systemTest);
+ deploymentTester.triggerJobs();
+ // Notifying about running jobs stores success status in DeploymentTrigger
+ tester.assertResponse(request, "{\"message\":\"ok\"}");
+ JobStatus recordedStatus = app.instance().deploymentJobs().jobStatus().get(JobType.systemTest);
assertNotNull("Status was recorded", recordedStatus);
assertTrue(recordedStatus.isSuccess());
assertEquals(vespaVersion, recordedStatus.lastCompleted().get().platform());
-
- recordedStatus =
- tester.controller().applications().getInstance(app.id().defaultInstance()).get().deploymentJobs().jobStatus().get(JobType.productionApNortheast2);
- assertNull("Status of never-triggered jobs is empty", recordedStatus);
- assertTrue("All jobs have been run", tester.controller().applications().deploymentTrigger().jobsToRun().isEmpty());
- }
-
- @Test
- public void testJobStatusReportingOutOfCapacity() {
- controllerTester.containerTester().computeVersionStatus();
-
- long projectId = 1;
- Application app = controllerTester.createApplication();
- ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
- .environment(Environment.prod)
- .region("us-central-1")
- .build();
-
- // Report job failing with out of capacity
- BuildJob job = new BuildJob(report -> notifyCompletion(report, controllerTester), controllerTester.containerTester().serviceRegistry().artifactRepositoryMock())
- .application(app)
- .projectId(projectId);
- job.type(JobType.component).uploadArtifact(applicationPackage).submit();
-
- controllerTester.deploy(app.id().defaultInstance(), applicationPackage, TEST_ZONE);
- job.type(JobType.systemTest).submit();
- controllerTester.deploy(app.id().defaultInstance(), applicationPackage, STAGING_ZONE);
- job.type(JobType.stagingTest).error(DeploymentJobs.JobError.outOfCapacity).submit();
-
- // Appropriate error is recorded
- JobStatus jobStatus = tester.controller().applications().getInstance(app.id().defaultInstance()).get()
- .deploymentJobs()
- .jobStatus()
- .get(JobType.stagingTest);
- assertFalse(jobStatus.isSuccess());
- assertEquals(DeploymentJobs.JobError.outOfCapacity, jobStatus.jobError().get());
}
@Test
public void applicationWithRoutingPolicy() {
- Application app = controllerTester.createApplication();
+ var app = deploymentTester.newDeploymentContext(createTenantAndApplication());
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
.environment(Environment.prod)
+ .instances("instance1")
.region("us-west-1")
.build();
- controllerTester.deployCompletely(app, applicationPackage, 1, false);
- RoutingPolicy policy = new RoutingPolicy(app.id().defaultInstance(),
+ app.submit(applicationPackage).deploy();
+ RoutingPolicy policy = new RoutingPolicy(app.instanceId(),
ClusterSpec.Id.from("default"),
ZoneId.from(Environment.prod, RegionName.from("us-west-1")),
HostName.from("lb-0-canonical-name"),
Optional.of("dns-zone-1"), Set.of(EndpointId.of("c0")));
- tester.controller().curator().writeRoutingPolicies(app.id().defaultInstance(), Set.of(policy));
+ tester.controller().curator().writeRoutingPolicies(app.instanceId(), Set.of(policy));
// GET application
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/default", GET)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1", GET)
.userIdentity(USER_ID),
new File("instance-with-routing-policy.json"));
// GET deployment
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-west-1/instance/default", GET)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-west-1/instance/instance1", GET)
.userIdentity(USER_ID),
new File("deployment-with-routing-policy.json"));
}
- private void notifyCompletion(DeploymentJobs.JobReport report, ContainerControllerTester tester) {
- assertResponse(request("/application/v4/tenant/tenant1/application/application1/jobreport", POST)
- .userIdentity(HOSTED_VESPA_OPERATOR)
- .data(asJson(report))
- .get(),
- 200, "{\"message\":\"ok\"}");
- tester.controller().applications().deploymentTrigger().triggerReadyJobs();
- }
-
- private static byte[] asJson(DeploymentJobs.JobReport report) {
- Slime slime = new Slime();
- Cursor cursor = slime.setObject();
- cursor.setLong("projectId", report.projectId());
- cursor.setString("jobName", report.jobType().jobName());
- cursor.setLong("buildNumber", report.buildNumber());
- report.jobError().ifPresent(jobError -> cursor.setString("jobError", jobError.name()));
- report.version().flatMap(ApplicationVersion::source).ifPresent(sr -> {
- Cursor sourceRevision = cursor.setObject("sourceRevision");
- sourceRevision.setString("repository", sr.repository());
- sourceRevision.setString("branch", sr.branch());
- sourceRevision.setString("commit", sr.commit());
- });
- cursor.setString("tenant", report.applicationId().tenant().value());
- cursor.setString("application", report.applicationId().application().value());
- cursor.setString("instance", report.applicationId().instance().value());
- try {
- return SlimeUtils.toJsonBytes(slime);
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- }
-
private MultiPartStreamer createApplicationDeployData(ApplicationPackage applicationPackage, boolean deployDirectly) {
return createApplicationDeployData(Optional.of(applicationPackage), deployDirectly);
}
@@ -1785,8 +1464,9 @@ public class ApplicationApiTest extends ControllerContainerTest {
return streamer;
}
- private MultiPartStreamer createApplicationSubmissionData(ApplicationPackage applicationPackage) {
- return new MultiPartStreamer().addJson(EnvironmentResource.SUBMIT_OPTIONS, "{\"repository\":\"repo\",\"branch\":\"master\",\"commit\":\"d00d\",\"authorEmail\":\"a@b\"}")
+ private MultiPartStreamer createApplicationSubmissionData(ApplicationPackage applicationPackage, long projectId) {
+ return new MultiPartStreamer().addJson(EnvironmentResource.SUBMIT_OPTIONS, "{\"repository\":\"repository1\",\"branch\":\"master\",\"commit\":\"commit1\","
+ + "\"projectId\":" + projectId + ",\"authorEmail\":\"a@b\"}")
.addBytes(EnvironmentResource.APPLICATION_ZIP, applicationPackage.zippedContent())
.addBytes(EnvironmentResource.APPLICATION_TEST_ZIP, "content".getBytes());
}
@@ -1817,9 +1497,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
* mock setup to replicate the action.
*/
private void createAthenzDomainWithAdmin(AthenzDomain domain, UserId userId) {
- AthenzClientFactoryMock mock = (AthenzClientFactoryMock) container.components()
- .getComponent(AthenzClientFactoryMock.class.getName());
- AthenzDbMock.Domain domainMock = mock.getSetup().getOrCreateDomain(domain);
+ AthenzDbMock.Domain domainMock = tester.athenzClientFactory().getSetup().getOrCreateDomain(domain);
domainMock.markAsVespaTenant();
domainMock.admin(AthenzUser.fromUserId(userId.id()));
}
@@ -1827,11 +1505,9 @@ public class ApplicationApiTest extends ControllerContainerTest {
/**
* Mock athenz service identity configuration. Simulates that configserver is allowed to launch a service
*/
- private void configureAthenzIdentity(com.yahoo.vespa.athenz.api.AthenzService service, boolean allowLaunch) {
- AthenzClientFactoryMock mock = (AthenzClientFactoryMock) container.components()
- .getComponent(AthenzClientFactoryMock.class.getName());
- AthenzDbMock.Domain domainMock = mock.getSetup().domains.computeIfAbsent(service.getDomain(), AthenzDbMock.Domain::new);
- domainMock.services.put(service.getName(), new AthenzDbMock.Service(allowLaunch));
+ private void allowLaunchOfService(com.yahoo.vespa.athenz.api.AthenzService service) {
+ AthenzDbMock.Domain domainMock = tester.athenzClientFactory().getSetup().getOrCreateDomain(service.getDomain());
+ domainMock.services.put(service.getName(), new AthenzDbMock.Service(true));
}
@@ -1841,12 +1517,8 @@ public class ApplicationApiTest extends ControllerContainerTest {
*/
private void addScrewdriverUserToDeployRole(ScrewdriverId screwdriverId,
AthenzDomain domain,
- com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId applicationId) {
- AthenzClientFactoryMock mock = (AthenzClientFactoryMock) container.components()
- .getComponent(AthenzClientFactoryMock.class.getName());
- AthenzIdentity screwdriverIdentity = HostedAthenzIdentities.from(screwdriverId);
- AthenzDbMock.Application athenzApplication = mock.getSetup().domains.get(domain).applications.get(applicationId);
- athenzApplication.addRoleMember(ApplicationAction.deploy, screwdriverIdentity);
+ ApplicationName application) {
+ tester.authorize(domain, HostedAthenzIdentities.from(screwdriverId), ApplicationAction.deploy, application);
}
private ApplicationId createTenantAndApplication() {
@@ -1860,65 +1532,19 @@ public class ApplicationApiTest extends ControllerContainerTest {
.userIdentity(USER_ID)
.oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT),
new File("instance-reference.json"));
- addScrewdriverUserToDeployRole(SCREWDRIVER_ID, ATHENZ_TENANT_DOMAIN,
- new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId("application1"));
+ addScrewdriverUserToDeployRole(SCREWDRIVER_ID, ATHENZ_TENANT_DOMAIN, ApplicationName.from("application1"));
return ApplicationId.from("tenant1", "application1", "instance1");
}
- private void startAndTestChange(ContainerControllerTester controllerTester, ApplicationId application,
- long projectId, ApplicationPackage applicationPackage,
- MultiPartStreamer deployData, long buildNumber) {
- ContainerTester tester = controllerTester.containerTester();
-
- // Trigger application change
- controllerTester.containerTester().serviceRegistry().artifactRepositoryMock()
- .put(application, applicationPackage,"1.0." + buildNumber + "-commit1");
- controllerTester.jobCompletion(JobType.component)
- .application(application)
- .projectId(projectId)
- .buildNumber(buildNumber)
- .submit();
-
- // system-test
- 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),
- "{\"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/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),
- "{\"message\":\"Deactivated " + application + " in staging.us-east-3\"}");
- controllerTester.jobCompletion(JobType.stagingTest)
- .application(application)
- .projectId(projectId)
- .submit();
- }
-
/**
* Cluster info, utilization and application and deployment metrics are maintained async by maintainers.
*
* This sets these values as if the maintainers has been ran.
*/
- private void setDeploymentMaintainedInfo(ContainerControllerTester controllerTester) {
- for (Application application : controllerTester.controller().applications().asList()) {
- controllerTester.controller().applications().lockApplicationOrThrow(application.id(), lockedApplication -> {
+ private void setDeploymentMaintainedInfo() {
+ for (Application application : deploymentTester.applications().asList()) {
+ deploymentTester.applications().lockApplicationOrThrow(application.id(), lockedApplication -> {
lockedApplication = lockedApplication.with(new ApplicationMetrics(0.5, 0.7));
for (Instance instance : application.instances().values()) {
@@ -1938,18 +1564,14 @@ public class ApplicationApiTest extends ControllerContainerTest {
.with(deployment.zone(), metrics)
.recordActivityAt(Instant.parse("2018-06-01T10:15:30.00Z"), deployment.zone()));
}
- controllerTester.controller().applications().store(lockedApplication);
+ deploymentTester.applications().store(lockedApplication);
}
});
}
}
- private ServiceRegistryMock serviceRegistry() {
- return (ServiceRegistryMock) tester.container().components().getComponent(ServiceRegistryMock.class.getName());
- }
-
private void setZoneInRotation(String rotationName, ZoneId zone) {
- serviceRegistry().globalRoutingServiceMock().setStatus(rotationName, zone, com.yahoo.vespa.hosted.controller.api.integration.routing.RotationStatus.IN);
+ tester.serviceRegistry().globalRoutingServiceMock().setStatus(rotationName, zone, com.yahoo.vespa.hosted.controller.api.integration.routing.RotationStatus.IN);
new RotationStatusUpdater(tester.controller(), Duration.ofDays(1), new JobControl(tester.controller().curator())).run();
}
@@ -1965,12 +1587,12 @@ public class ApplicationApiTest extends ControllerContainerTest {
private void registerContact(long propertyId) {
PropertyId p = new PropertyId(String.valueOf(propertyId));
- serviceRegistry().contactRetrieverMock().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"),
+ tester.serviceRegistry().contactRetrieverMock().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()));
+ "queue", Optional.empty()));
}
private static class RequestBuilder implements Supplier<Request> {
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 daf1a4c2ea7..8e3a3b8d727 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
@@ -33,9 +33,7 @@ import static com.yahoo.vespa.hosted.controller.api.integration.deployment.JobTy
import static com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType.stagingTest;
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.DeploymentTester.instanceId;
import static com.yahoo.vespa.hosted.controller.deployment.DeploymentContext.applicationPackage;
-import static com.yahoo.vespa.hosted.controller.deployment.DeploymentTester.testerId;
import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.deploymentFailed;
import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.installationFailed;
import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.running;
@@ -51,60 +49,59 @@ public class JobControllerApiHandlerHelperTest {
@Test
public void testResponses() {
DeploymentTester tester = new DeploymentTester();
+ var app = tester.newDeploymentContext();
tester.clock().setInstant(Instant.EPOCH);
// Revision 1 gets deployed everywhere.
- ApplicationVersion revision1 = tester.newSubmission();
- tester.deployNewSubmission(revision1);
+ app.submit().deploy();
+ ApplicationVersion revision1 = app.lastSubmission().get();
assertEquals(1000, tester.application().projectId().getAsLong());
tester.clock().advance(Duration.ofMillis(1000));
// Revision 2 gets deployed everywhere except in us-east-3.
- ApplicationVersion revision2 = tester.newSubmission();
- tester.runJob(systemTest);
- tester.runJob(stagingTest);
- tester.runJob(productionUsCentral1);
+ ApplicationVersion revision2 = app.submit().lastSubmission().get();
+ app.runJob(systemTest);
+ app.runJob(stagingTest);
+ app.runJob(productionUsCentral1);
tester.triggerJobs();
// us-east-3 eats the deployment failure and fails before deployment, while us-west-1 fails after.
tester.configServer().throwOnNextPrepare(new ConfigServerException(URI.create("url"), "ERROR!", INVALID_APPLICATION_PACKAGE, null));
tester.runner().run();
- assertEquals(deploymentFailed, tester.jobs().last(instanceId, productionUsEast3).get().status());
+ assertEquals(deploymentFailed, tester.jobs().last(app.instanceId(), productionUsEast3).get().status());
ZoneId usWest1 = productionUsWest1.zone(tester.controller().system());
- tester.configServer().convergeServices(instanceId, usWest1);
- tester.configServer().convergeServices(testerId.id(), usWest1);
- tester.setEndpoints(instanceId, usWest1);
- tester.setEndpoints(testerId.id(), usWest1);
+ tester.configServer().convergeServices(app.instanceId(), usWest1);
+ tester.configServer().convergeServices(app.testerId().id(), usWest1);
+ tester.setEndpoints(app.instanceId(), usWest1);
+ tester.setEndpoints(app.testerId().id(), usWest1);
tester.runner().run();
tester.cloud().set(FAILURE);
tester.runner().run();
- assertEquals(testFailure, tester.jobs().last(instanceId, productionUsWest1).get().status());
- assertEquals(revision2, tester.instance().deployments().get(productionUsCentral1.zone(tester.controller().system())).applicationVersion());
- assertEquals(revision1, tester.instance().deployments().get(productionUsEast3.zone(tester.controller().system())).applicationVersion());
- assertEquals(revision2, tester.instance().deployments().get(productionUsWest1.zone(tester.controller().system())).applicationVersion());
+ assertEquals(testFailure, tester.jobs().last(app.instanceId(), productionUsWest1).get().status());
+ assertEquals(revision2, app.deployment(productionUsCentral1.zone(tester.controller().system())).applicationVersion());
+ assertEquals(revision1, app.deployment(productionUsEast3.zone(tester.controller().system())).applicationVersion());
+ assertEquals(revision2, app.deployment(productionUsWest1.zone(tester.controller().system())).applicationVersion());
tester.clock().advance(Duration.ofMillis(1000));
// Revision 3 starts.
- tester.newSubmission();
- tester.runJob(systemTest);
- tester.runJob(stagingTest);
- tester.triggerJobs(); // Starts a run for us-central-1.
- tester.triggerJobs(); // Starts a new staging test run.
+ app.submit()
+ .runJob(systemTest).runJob(stagingTest);
+ tester.triggerJobs(); // Starts runs for us-central-1 and a new staging test run.
tester.runner().run();
- assertEquals(running, tester.jobs().last(instanceId, productionUsCentral1).get().status());
- assertEquals(running, tester.jobs().last(instanceId, stagingTest).get().status());
+ assertEquals(running, tester.jobs().last(app.instanceId(), productionUsCentral1).get().status());
+ assertEquals(running, tester.jobs().last(app.instanceId(), stagingTest).get().status());
- // Staging is expired, and the job fails and won't be retried immediately.
- tester.controller().applications().deactivate(instanceId, stagingTest.zone(tester.controller().system()));
+ // Staging deployment expires, the job fails, and won't be retried immediately.
+ tester.controller().applications().deactivate(app.instanceId(), stagingTest.zone(tester.controller().system()));
tester.runner().run();
- assertEquals(installationFailed, tester.jobs().last(instanceId, stagingTest).get().status());
+ assertEquals(installationFailed, tester.jobs().last(app.instanceId(), stagingTest).get().status());
tester.clock().advance(Duration.ofMillis(100_000)); // More than the minute within which there are immediate retries.
tester.triggerJobs();
- assertEquals(installationFailed, tester.jobs().last(instanceId, stagingTest).get().status());
+ assertEquals(installationFailed, tester.jobs().last(app.instanceId(), stagingTest).get().status());
// System upgrades to a new version, which won't yet start.
Version platform = new Version("7.1");
@@ -117,36 +114,38 @@ public class JobControllerApiHandlerHelperTest {
// Only us-east-3 is verified, on revision1.
// staging-test has 4 runs: one success without sources on revision1, one success from revision1 to revision2,
// one success from revision2 to revision3 and one failure from revision1 to revision3.
- assertResponse(JobControllerApiHandlerHelper.runResponse(tester.jobs().runs(instanceId, stagingTest), URI.create("https://some.url:43/root")), "staging-runs.json");
- assertResponse(JobControllerApiHandlerHelper.runDetailsResponse(tester.jobs(), tester.jobs().last(instanceId, productionUsEast3).get().id(), "0"), "us-east-3-log-without-first.json");
- assertResponse(JobControllerApiHandlerHelper.jobTypeResponse(tester.controller(), instanceId, URI.create("https://some.url:43/root/")), "overview.json");
+ assertResponse(JobControllerApiHandlerHelper.runResponse(tester.jobs().runs(app.instanceId(), stagingTest), URI.create("https://some.url:43/root")), "staging-runs.json");
+ assertResponse(JobControllerApiHandlerHelper.runDetailsResponse(tester.jobs(), tester.jobs().last(app.instanceId(), productionUsEast3).get().id(), "0"), "us-east-3-log-without-first.json");
+ assertResponse(JobControllerApiHandlerHelper.jobTypeResponse(tester.controller(), app.instanceId(), URI.create("https://some.url:43/root/")), "overview.json");
- tester.runJob(instanceId, JobType.devAwsUsEast2a, applicationPackage);
- assertResponse(JobControllerApiHandlerHelper.runResponse(tester.jobs().runs(instanceId, devAwsUsEast2a), URI.create("https://some.url:43/root")), "dev-aws-us-east-2a-runs.json");
+ app.runJob(devAwsUsEast2a, applicationPackage);
+ assertResponse(JobControllerApiHandlerHelper.runResponse(tester.jobs().runs(app.instanceId(), devAwsUsEast2a), URI.create("https://some.url:43/root")), "dev-aws-us-east-2a-runs.json");
}
@Test
public void testDevResponses() {
DeploymentTester tester = new DeploymentTester();
+ var app = tester.newDeploymentContext();
tester.clock().setInstant(Instant.EPOCH);
ZoneId zone = JobType.devUsEast1.zone(tester.controller().system());
- tester.jobs().deploy(instanceId, JobType.devUsEast1, Optional.empty(), applicationPackage);
+ tester.jobs().deploy(app.instanceId(), JobType.devUsEast1, Optional.empty(), applicationPackage);
tester.configServer().setLogStream("1554970337.935104\t17491290-v6-1.ostk.bm2.prod.ne1.yahoo.com\t5480\tcontainer\tstdout\tinfo\tERROR: Bundle canary-application [71] Unable to get module class path. (java.lang.NullPointerException)\n");
- assertResponse(JobControllerApiHandlerHelper.runDetailsResponse(tester.jobs(), tester.jobs().last(instanceId, devUsEast1).get().id(), null), "dev-us-east-1-log-first-part.json");
+ assertResponse(JobControllerApiHandlerHelper.runDetailsResponse(tester.jobs(), tester.jobs().last(app.instanceId(), devUsEast1).get().id(), null), "dev-us-east-1-log-first-part.json");
tester.configServer().setLogStream("Nope, this won't be logged");
- tester.configServer().convergeServices(instanceId, zone);
- tester.setEndpoints(instanceId, zone);
+ tester.configServer().convergeServices(app.instanceId(), zone);
+ tester.setEndpoints(app.instanceId(), zone);
tester.runner().run();
- assertResponse(JobControllerApiHandlerHelper.jobTypeResponse(tester.controller(), instanceId, URI.create("https://some.url:43/root")), "dev-overview.json");
- assertResponse(JobControllerApiHandlerHelper.runDetailsResponse(tester.jobs(), tester.jobs().last(instanceId, devUsEast1).get().id(), "9"), "dev-us-east-1-log-second-part.json");
+ assertResponse(JobControllerApiHandlerHelper.jobTypeResponse(tester.controller(), app.instanceId(), URI.create("https://some.url:43/root")), "dev-overview.json");
+ assertResponse(JobControllerApiHandlerHelper.runDetailsResponse(tester.jobs(), tester.jobs().last(app.instanceId(), devUsEast1).get().id(), "9"), "dev-us-east-1-log-second-part.json");
}
@Test
public void testResponsesWithDirectDeployment() {
var tester = new DeploymentTester();
+ var app = tester.newDeploymentContext();
tester.clock().setInstant(Instant.EPOCH);
var region = "us-west-1";
var applicationPackage = new ApplicationPackageBuilder().region(region).build();
@@ -155,7 +154,7 @@ public class JobControllerApiHandlerHelperTest {
Optional.of(applicationPackage),
new DeployOptions(true, Optional.empty(),
false, false));
- assertResponse(JobControllerApiHandlerHelper.jobTypeResponse(tester.controller(), instanceId, URI.create("https://some.url:43/root/")),
+ assertResponse(JobControllerApiHandlerHelper.jobTypeResponse(tester.controller(), app.instanceId(), URI.create("https://some.url:43/root/")),
"jobs-direct-deployment.json");
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application2-with-patches.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application2-with-patches.json
index ad86fa34cde..195219c691e 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application2-with-patches.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application2-with-patches.json
@@ -3,22 +3,22 @@
"application": "application2",
"deployments": "http://localhost:8080/application/v4/tenant/tenant2/application/application2/job/",
"latestVersion": {
- "buildNumber": 42,
- "hash": "1.0.42-commit1",
+ "buildNumber": 1,
+ "hash": "1.0.1-commit1",
"source": {
"gitRepository": "repository1",
"gitBranch": "master",
"gitCommit": "commit1"
}
},
- "projectId": 456,
+ "projectId": 1000,
"deploying": {
- "version": "(ignore)"
+ "version": "7"
},
"outstandingChange": {
"revision": {
- "buildNumber": 42,
- "hash": "(ignore)",
+ "buildNumber": 1,
+ "hash": "1.0.1-commit1",
"source": {
"gitRepository": "repository1",
"gitBranch": "master",
@@ -31,6 +31,11 @@
"instances": [
{
"instance": "default",
+ "globalRotations": [],
+ "deployments": []
+ },
+ {
+ "instance": "instance1",
"deploymentJobs": [
{
"type": "system-test",
@@ -39,21 +44,43 @@
"id": -1,
"version": "7.0.0",
"revision": {
- "buildNumber": 42,
- "hash": "1.0.42-commit1",
+ "buildNumber": 1,
+ "hash": "1.0.1-commit1",
"source": {
"gitRepository": "repository1",
"gitBranch": "master",
"gitCommit": "commit1"
}
},
- "reason": "Testing last changes outside prod",
+ "reason": "Testing deployment for production-us-west-1 (platform 7, application 1.0.1-commit1)",
+ "at": "(ignore)"
+ }
+ },
+ {
+ "type": "staging-test",
+ "success": false,
+ "lastTriggered": {
+ "id": -1,
+ "version": "7.0.0",
+ "revision": {
+ "buildNumber": 1,
+ "hash": "1.0.1-commit1",
+ "source": {
+ "gitRepository": "repository1",
+ "gitBranch": "master",
+ "gitCommit": "commit1"
+ }
+ },
+ "reason": "Testing deployment for production-us-west-1 (platform 7, application 1.0.1-commit1)",
"at": "(ignore)"
}
}
],
"changeBlockers": [],
- "globalRotations": [],
+ "globalRotations": [
+ "https://instance1--application2--tenant2.global.vespa.oath.cloud:4443/"
+ ],
+ "rotationId": "rotation-id-2",
"deployments": []
}
],
@@ -66,3 +93,4 @@
},
"activity": {}
}
+
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application2.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application2.json
index 6d89fa4ee20..c29944924dc 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application2.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application2.json
@@ -3,22 +3,22 @@
"application": "application2",
"deployments": "http://localhost:8080/application/v4/tenant/tenant2/application/application2/job/",
"latestVersion": {
- "buildNumber": 42,
- "hash": "1.0.42-commit1",
+ "buildNumber": 1,
+ "hash": "1.0.1-commit1",
"source": {
"gitRepository": "repository1",
"gitBranch": "master",
"gitCommit": "commit1"
}
},
- "projectId": 456,
+ "projectId": 1000,
"deploying": {
- "version": "(ignore)"
+ "version": "7"
},
"outstandingChange": {
"revision": {
- "buildNumber": 42,
- "hash": "(ignore)",
+ "buildNumber": 1,
+ "hash": "1.0.1-commit1",
"source": {
"gitRepository": "repository1",
"gitBranch": "master",
@@ -30,6 +30,11 @@
"instances": [
{
"instance": "default",
+ "globalRotations": [],
+ "deployments": []
+ },
+ {
+ "instance": "instance1",
"deploymentJobs": [
{
"type": "system-test",
@@ -38,21 +43,43 @@
"id": -1,
"version": "7.0.0",
"revision": {
- "buildNumber": 42,
- "hash": "1.0.42-commit1",
+ "buildNumber": 1,
+ "hash": "1.0.1-commit1",
"source": {
"gitRepository": "repository1",
"gitBranch": "master",
"gitCommit": "commit1"
}
},
- "reason": "Testing last changes outside prod",
+ "reason": "Testing deployment for production-us-west-1 (platform 7, application 1.0.1-commit1)",
+ "at": "(ignore)"
+ }
+ },
+ {
+ "type": "staging-test",
+ "success": false,
+ "lastTriggered": {
+ "id": -1,
+ "version": "7.0.0",
+ "revision": {
+ "buildNumber": 1,
+ "hash": "1.0.1-commit1",
+ "source": {
+ "gitRepository": "repository1",
+ "gitBranch": "master",
+ "gitCommit": "commit1"
+ }
+ },
+ "reason": "Testing deployment for production-us-west-1 (platform 7, application 1.0.1-commit1)",
"at": "(ignore)"
}
}
],
"changeBlockers": [],
- "globalRotations": [],
+ "globalRotations": [
+ "https://instance1--application2--tenant2.global.vespa.oath.cloud:4443/"
+ ],
+ "rotationId": "rotation-id-2",
"deployments": []
}
],
@@ -63,3 +90,4 @@
},
"activity": {}
}
+
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 c9ed2ad3391..25fed881dec 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 in: dev.us-west-1, prod.us-central-1"
+ "message": "Could not delete 'application 'tenant1.application1'': It has active deployments in: dev.us-east-1, prod.us-central-1"
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment-with-routing-policy.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment-with-routing-policy.json
index 519c9b1c842..0fcbbdd925d 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment-with-routing-policy.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment-with-routing-policy.json
@@ -1,29 +1,25 @@
{
"tenant": "tenant1",
"application": "application1",
- "instance": "default",
+ "instance": "instance1",
"environment": "prod",
"region": "us-west-1",
"endpoints": [
{
"cluster": "default",
"tls": true,
- "url": "https://application1.tenant1.us-west-1.vespa.oath.cloud/"
+ "url": "https://instance1.application1.tenant1.us-west-1.vespa.oath.cloud/"
}
],
"serviceUrls": [
- "http://old-endpoint.vespa.yahooapis.com:4080",
- "http://qrs-endpoint.vespa.yahooapis.com:4080",
- "http://feeding-endpoint.vespa.yahooapis.com:4080",
- "http://global-endpoint.vespa.yahooapis.com:4080",
- "http://alias-endpoint.vespa.yahooapis.com:4080"
+ "https://instance1--application1--tenant1.us-west-1.prod.vespa:43"
],
- "nodes": "http://localhost:8080/zone/v2/prod/us-west-1/nodes/v2/node/%3F&recursive=true&application=tenant1.application1.default",
- "yamasUrl": "http://monitoring-system.test/?environment=prod&region=us-west-1&application=tenant1.application1",
+ "nodes": "http://localhost:8080/zone/v2/prod/us-west-1/nodes/v2/node/%3F&recursive=true&application=tenant1.application1.instance1",
+ "yamasUrl": "http://monitoring-system.test/?environment=prod&region=us-west-1&application=tenant1.application1.instance1",
"version": "(ignore)",
- "revision": "1.0.42-commit1",
+ "revision": "1.0.1-commit1",
"deployTimeEpochMs": "(ignore)",
- "screwdriverId": "1",
+ "screwdriverId": "1000",
"gitRepository": "repository1",
"gitBranch": "master",
"gitCommit": "commit1",
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 d62e39e42e7..4b3e4829c53 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
@@ -6,11 +6,7 @@
"region": "us-central-1",
"endpoints": [],
"serviceUrls": [
- "http://old-endpoint.vespa.yahooapis.com:4080",
- "http://qrs-endpoint.vespa.yahooapis.com:4080",
- "http://feeding-endpoint.vespa.yahooapis.com:4080",
- "http://global-endpoint.vespa.yahooapis.com:4080",
- "http://alias-endpoint.vespa.yahooapis.com:4080"
+ "https://instance1--application1--tenant1.us-central-1.prod.vespa:43"
],
"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",
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/dev-us-east-1.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/dev-us-east-1.json
index c56a269b9d4..204927f6954 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/dev-us-east-1.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/dev-us-east-1.json
@@ -3,17 +3,13 @@
"application": "application1",
"instance": "instance1",
"environment": "dev",
- "region": "us-west-1",
+ "region": "us-east-1",
"endpoints": [],
"serviceUrls": [
- "http://old-endpoint.vespa.yahooapis.com:4080",
- "http://qrs-endpoint.vespa.yahooapis.com:4080",
- "http://feeding-endpoint.vespa.yahooapis.com:4080",
- "http://global-endpoint.vespa.yahooapis.com:4080",
- "http://alias-endpoint.vespa.yahooapis.com:4080"
+ "https://instance1--application1--tenant1.us-east-1.dev.vespa:43"
],
- "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",
+ "nodes": "http://localhost:8080/zone/v2/dev/us-east-1/nodes/v2/node/%3F&recursive=true&application=tenant1.application1.instance1",
+ "yamasUrl": "http://monitoring-system.test/?environment=dev&region=us-east-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-get.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/global-rotation-get.json
index 52aabab1584..a21b1558aee 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/global-rotation-get.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/global-rotation-get.json
@@ -1 +1 @@
-{"globalrotationoverride":["upstream1",{"status":"in","reason":"","agent":"","timestamp":1497618757},"upstream1",{"status":"in","reason":"","agent":"","timestamp":1497618757}]}
+{"globalrotationoverride":["cluster1.application1.tenant1.us-west-1.prod",{"status":"in","reason":"","agent":"","timestamp":1497618757}]}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/instance-with-routing-policy.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/instance-with-routing-policy.json
index 91356bc83bf..0f759eaed55 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/instance-with-routing-policy.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/instance-with-routing-policy.json
@@ -1,15 +1,14 @@
{
"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",
"gitCommit": "commit1"
},
- "projectId": 1,
- "deployedInternally": false,
+ "projectId": 1000,
"deploymentJobs": [
{
"type": "system-test",
@@ -19,44 +18,44 @@
"version": "(ignore)",
"revision": {
"buildNumber": "(ignore)",
- "hash": "1.0.42-commit1",
+ "hash": "1.0.1-commit1",
"source": {
"gitRepository": "repository1",
"gitBranch": "master",
"gitCommit": "commit1"
}
},
- "reason": "Testing last changes outside prod",
+ "reason": "Testing deployment for production-us-west-1 (platform 6.1, application 1.0.1-commit1)",
"at": "(ignore)"
},
"lastCompleted": {
- "id": 42,
+ "id": 1,
"version": "(ignore)",
"revision": {
"buildNumber": "(ignore)",
- "hash": "1.0.42-commit1",
+ "hash": "1.0.1-commit1",
"source": {
"gitRepository": "repository1",
"gitBranch": "master",
"gitCommit": "commit1"
}
},
- "reason": "Testing last changes outside prod",
+ "reason": "Testing deployment for production-us-west-1 (platform 6.1, application 1.0.1-commit1)",
"at": "(ignore)"
},
"lastSuccess": {
- "id": 42,
+ "id": 1,
"version": "(ignore)",
"revision": {
"buildNumber": "(ignore)",
- "hash": "1.0.42-commit1",
+ "hash": "1.0.1-commit1",
"source": {
"gitRepository": "repository1",
"gitBranch": "master",
"gitCommit": "commit1"
}
},
- "reason": "Testing last changes outside prod",
+ "reason": "Testing deployment for production-us-west-1 (platform 6.1, application 1.0.1-commit1)",
"at": "(ignore)"
}
},
@@ -68,44 +67,44 @@
"version": "(ignore)",
"revision": {
"buildNumber": "(ignore)",
- "hash": "1.0.42-commit1",
+ "hash": "1.0.1-commit1",
"source": {
"gitRepository": "repository1",
"gitBranch": "master",
"gitCommit": "commit1"
}
},
- "reason": "Testing deployment for production-us-west-1 (platform (ignore), application 1.0.42-commit1)",
+ "reason": "Testing deployment for production-us-west-1 (platform 6.1, application 1.0.1-commit1)",
"at": "(ignore)"
},
"lastCompleted": {
- "id": 42,
+ "id": 1,
"version": "(ignore)",
"revision": {
"buildNumber": "(ignore)",
- "hash": "1.0.42-commit1",
+ "hash": "1.0.1-commit1",
"source": {
"gitRepository": "repository1",
"gitBranch": "master",
"gitCommit": "commit1"
}
},
- "reason": "Testing deployment for production-us-west-1 (platform (ignore), application 1.0.42-commit1)",
+ "reason": "Testing deployment for production-us-west-1 (platform 6.1, application 1.0.1-commit1)",
"at": "(ignore)"
},
"lastSuccess": {
- "id": 42,
+ "id": 1,
"version": "(ignore)",
"revision": {
"buildNumber": "(ignore)",
- "hash": "1.0.42-commit1",
+ "hash": "1.0.1-commit1",
"source": {
"gitRepository": "repository1",
"gitBranch": "master",
"gitCommit": "commit1"
}
},
- "reason": "Testing deployment for production-us-west-1 (platform (ignore), application 1.0.42-commit1)",
+ "reason": "Testing deployment for production-us-west-1 (platform 6.1, application 1.0.1-commit1)",
"at": "(ignore)"
}
},
@@ -117,7 +116,7 @@
"version": "(ignore)",
"revision": {
"buildNumber": "(ignore)",
- "hash": "1.0.42-commit1",
+ "hash": "1.0.1-commit1",
"source": {
"gitRepository": "repository1",
"gitBranch": "master",
@@ -128,11 +127,11 @@
"at": "(ignore)"
},
"lastCompleted": {
- "id": 42,
+ "id": 1,
"version": "(ignore)",
"revision": {
"buildNumber": "(ignore)",
- "hash": "1.0.42-commit1",
+ "hash": "1.0.1-commit1",
"source": {
"gitRepository": "repository1",
"gitBranch": "master",
@@ -143,11 +142,11 @@
"at": "(ignore)"
},
"lastSuccess": {
- "id": 42,
+ "id": 1,
"version": "(ignore)",
"revision": {
"buildNumber": "(ignore)",
- "hash": "1.0.42-commit1",
+ "hash": "1.0.1-commit1",
"source": {
"gitRepository": "repository1",
"gitBranch": "master",
@@ -162,14 +161,14 @@
"changeBlockers": [],
"compileVersion": "(ignore)",
"globalRotations": [
- "https://c0.application1.tenant1.global.vespa.oath.cloud/"
+ "https://c0.instance1.application1.tenant1.global.vespa.oath.cloud/"
],
"instances": [
{
"environment": "prod",
"region": "us-west-1",
- "instance": "default",
- "url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/default/environment/prod/region/us-west-1"
+ "instance": "instance1",
+ "url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/environment/prod/region/us-west-1"
}
],
"pemDeployKeys": [],
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/instance-without-change-multiple-deployments.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/instance-without-change-multiple-deployments.json
index 582753d04d6..e8adfd579d5 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/instance-without-change-multiple-deployments.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/instance-without-change-multiple-deployments.json
@@ -8,8 +8,7 @@
"gitBranch": "master",
"gitCommit": "commit1"
},
- "projectId": 1,
- "deployedInternally": false,
+ "projectId": 1000,
"deploymentJobs": [
{
"type": "system-test",
@@ -26,11 +25,11 @@
"gitCommit": "commit1"
}
},
- "reason": "Testing deployment for production-us-east-3 (platform 6.1, application 1.0.100-commit1 -> 1.0.101-commit1)",
+ "reason": "Testing deployment for production-us-west-1 (platform 6.1, application 1.0.2-commit1)",
"at": "(ignore)"
},
"lastCompleted": {
- "id": 42,
+ "id": 2,
"version": "(ignore)",
"revision": {
"buildNumber": "(ignore)",
@@ -41,11 +40,11 @@
"gitCommit": "commit1"
}
},
- "reason": "Testing deployment for production-us-east-3 (platform 6.1, application 1.0.100-commit1 -> 1.0.101-commit1)",
+ "reason": "Testing deployment for production-us-west-1 (platform 6.1, application 1.0.2-commit1)",
"at": "(ignore)"
},
"lastSuccess": {
- "id": 42,
+ "id": 2,
"version": "(ignore)",
"revision": {
"buildNumber": "(ignore)",
@@ -56,7 +55,7 @@
"gitCommit": "commit1"
}
},
- "reason": "Testing deployment for production-us-east-3 (platform 6.1, application 1.0.100-commit1 -> 1.0.101-commit1)",
+ "reason": "Testing deployment for production-us-west-1 (platform 6.1, application 1.0.2-commit1)",
"at": "(ignore)"
}
},
@@ -75,11 +74,11 @@
"gitCommit": "commit1"
}
},
- "reason": "Testing deployment for production-us-east-3 (platform 6.1, application 1.0.100-commit1 -> 1.0.101-commit1)",
+ "reason": "Testing deployment for production-us-east-3 (platform 6.1, application 1.0.1-commit1 -> 1.0.2-commit1)",
"at": "(ignore)"
},
"lastCompleted": {
- "id": 42,
+ "id": 3,
"version": "(ignore)",
"revision": {
"buildNumber": "(ignore)",
@@ -90,11 +89,11 @@
"gitCommit": "commit1"
}
},
- "reason": "Testing deployment for production-us-east-3 (platform 6.1, application 1.0.100-commit1 -> 1.0.101-commit1)",
+ "reason": "Testing deployment for production-us-east-3 (platform 6.1, application 1.0.1-commit1 -> 1.0.2-commit1)",
"at": "(ignore)"
},
"lastSuccess": {
- "id": 42,
+ "id": 3,
"version": "(ignore)",
"revision": {
"buildNumber": "(ignore)",
@@ -105,7 +104,7 @@
"gitCommit": "commit1"
}
},
- "reason": "Testing deployment for production-us-east-3 (platform 6.1, application 1.0.100-commit1 -> 1.0.101-commit1)",
+ "reason": "Testing deployment for production-us-east-3 (platform 6.1, application 1.0.1-commit1 -> 1.0.2-commit1)",
"at": "(ignore)"
}
},
@@ -128,7 +127,7 @@
"at": "(ignore)"
},
"lastCompleted": {
- "id": 42,
+ "id": 1,
"version": "(ignore)",
"revision": {
"buildNumber": "(ignore)",
@@ -143,7 +142,7 @@
"at": "(ignore)"
},
"lastSuccess": {
- "id": 42,
+ "id": 1,
"version": "(ignore)",
"revision": {
"buildNumber": "(ignore)",
@@ -177,7 +176,7 @@
"at": "(ignore)"
},
"lastCompleted": {
- "id": 42,
+ "id": 2,
"version": "(ignore)",
"revision": {
"buildNumber": "(ignore)",
@@ -192,7 +191,7 @@
"at": "(ignore)"
},
"lastSuccess": {
- "id": 42,
+ "id": 2,
"version": "(ignore)",
"revision": {
"buildNumber": "(ignore)",
@@ -211,7 +210,7 @@
"changeBlockers": [],
"compileVersion": "(ignore)",
"globalRotations": [
- "https://application1--tenant1.global.vespa.oath.cloud:4443/"
+ "https://instance1--application1--tenant1.global.vespa.oath.cloud:4443/"
],
"rotationId": "rotation-id-1",
"instances": [
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/instance.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/instance.json
index 951b42ff07a..3664af7116c 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/instance.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/instance.json
@@ -20,7 +20,6 @@
}
}
},
- "deployedInternally": false,
"deploymentJobs": [
{
"type": "system-test",
@@ -37,11 +36,11 @@
"gitCommit": "commit1"
}
},
- "reason": "Testing last changes outside prod",
+ "reason": "Testing deployment for production-us-central-1 (platform 6.1, application 1.0.1-commit1)",
"at": "(ignore)"
},
"lastCompleted": {
- "id": 42,
+ "id": 1,
"version": "(ignore)",
"revision": {
"buildNumber": "(ignore)",
@@ -52,11 +51,11 @@
"gitCommit": "commit1"
}
},
- "reason": "Testing last changes outside prod",
+ "reason": "Testing deployment for production-us-central-1 (platform 6.1, application 1.0.1-commit1)",
"at": "(ignore)"
},
"lastSuccess": {
- "id": 42,
+ "id": 1,
"version": "(ignore)",
"revision": {
"buildNumber": "(ignore)",
@@ -67,7 +66,7 @@
"gitCommit": "commit1"
}
},
- "reason": "Testing last changes outside prod",
+ "reason": "Testing deployment for production-us-central-1 (platform 6.1, application 1.0.1-commit1)",
"at": "(ignore)"
}
},
@@ -86,11 +85,11 @@
"gitCommit": "commit1"
}
},
- "reason": "Testing deployment for production-us-central-1 (platform 6.1, application 1.0.42-commit1)",
+ "reason": "Testing deployment for production-us-central-1 (platform 6.1, application 1.0.1-commit1)",
"at": "(ignore)"
},
"lastCompleted": {
- "id": 42,
+ "id": 1,
"version": "(ignore)",
"revision": {
"buildNumber": "(ignore)",
@@ -101,11 +100,11 @@
"gitCommit": "commit1"
}
},
- "reason": "Testing deployment for production-us-central-1 (platform 6.1, application 1.0.42-commit1)",
+ "reason": "Testing deployment for production-us-central-1 (platform 6.1, application 1.0.1-commit1)",
"at": "(ignore)"
},
"lastSuccess": {
- "id": 42,
+ "id": 1,
"version": "(ignore)",
"revision": {
"buildNumber": "(ignore)",
@@ -116,13 +115,13 @@
"gitCommit": "commit1"
}
},
- "reason": "Testing deployment for production-us-central-1 (platform 6.1, application 1.0.42-commit1)",
+ "reason": "Testing deployment for production-us-central-1 (platform 6.1, application 1.0.1-commit1)",
"at": "(ignore)"
}
},
{
"type": "production-us-central-1",
- "success": false,
+ "success": true,
"lastTriggered": {
"id": -1,
"version": "(ignore)",
@@ -139,7 +138,7 @@
"at": "(ignore)"
},
"lastCompleted": {
- "id": 42,
+ "id": 1,
"version": "(ignore)",
"revision": {
"buildNumber": "(ignore)",
@@ -153,8 +152,8 @@
"reason": "New change available",
"at": "(ignore)"
},
- "firstFailing": {
- "id": 42,
+ "lastSuccess": {
+ "id": 1,
"version": "(ignore)",
"revision": {
"buildNumber": "(ignore)",
@@ -168,6 +167,25 @@
"reason": "New change available",
"at": "(ignore)"
}
+ },
+ {
+ "type": "production-us-east-3",
+ "success": false,
+ "lastTriggered": {
+ "id": -1,
+ "version": "(ignore)",
+ "revision": {
+ "buildNumber": "(ignore)",
+ "hash": "(ignore)",
+ "source": {
+ "gitRepository": "repository1",
+ "gitBranch": "master",
+ "gitCommit": "commit1"
+ }
+ },
+ "reason": "Available change in production-us-central-1",
+ "at": "(ignore)"
+ }
}
],
"changeBlockers": [
@@ -197,15 +215,15 @@
],
"compileVersion": "6.0.0",
"globalRotations": [
- "https://application1--tenant1.global.vespa.oath.cloud:4443/"
+ "https://instance1--application1--tenant1.global.vespa.oath.cloud:4443/"
],
"rotationId": "rotation-id-1",
"instances": [
{
"environment": "dev",
- "region": "us-west-1",
+ "region": "us-east-1",
"instance": "instance1",
- "url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/environment/dev/region/us-west-1"
+ "url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/environment/dev/region/us-east-1"
},
{
"bcpStatus": {
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/instance1-recursive.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/instance1-recursive.json
index afa506d1e48..ddce3921518 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/instance1-recursive.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/instance1-recursive.json
@@ -20,7 +20,6 @@
}
}
},
- "deployedInternally": false,
"deploymentJobs": [
{
"type": "system-test",
@@ -37,11 +36,11 @@
"gitCommit": "commit1"
}
},
- "reason": "Testing last changes outside prod",
+ "reason": "Testing deployment for production-us-central-1 (platform 6.1, application 1.0.1-commit1)",
"at": "(ignore)"
},
"lastCompleted": {
- "id": 42,
+ "id": 1,
"version": "(ignore)",
"revision": {
"buildNumber": "(ignore)",
@@ -52,11 +51,11 @@
"gitCommit": "commit1"
}
},
- "reason": "Testing last changes outside prod",
+ "reason": "Testing deployment for production-us-central-1 (platform 6.1, application 1.0.1-commit1)",
"at": "(ignore)"
},
"lastSuccess": {
- "id": 42,
+ "id": 1,
"version": "(ignore)",
"revision": {
"buildNumber": "(ignore)",
@@ -67,7 +66,7 @@
"gitCommit": "commit1"
}
},
- "reason": "Testing last changes outside prod",
+ "reason": "Testing deployment for production-us-central-1 (platform 6.1, application 1.0.1-commit1)",
"at": "(ignore)"
}
},
@@ -86,11 +85,11 @@
"gitCommit": "commit1"
}
},
- "reason": "Testing deployment for production-us-central-1 (platform 6.1, application 1.0.42-commit1)",
+ "reason": "Testing deployment for production-us-central-1 (platform 6.1, application 1.0.1-commit1)",
"at": "(ignore)"
},
"lastCompleted": {
- "id": 42,
+ "id": 1,
"version": "(ignore)",
"revision": {
"buildNumber": "(ignore)",
@@ -101,11 +100,11 @@
"gitCommit": "commit1"
}
},
- "reason": "Testing deployment for production-us-central-1 (platform 6.1, application 1.0.42-commit1)",
+ "reason": "Testing deployment for production-us-central-1 (platform 6.1, application 1.0.1-commit1)",
"at": "(ignore)"
},
"lastSuccess": {
- "id": 42,
+ "id": 1,
"version": "(ignore)",
"revision": {
"buildNumber": "(ignore)",
@@ -116,13 +115,13 @@
"gitCommit": "commit1"
}
},
- "reason": "Testing deployment for production-us-central-1 (platform 6.1, application 1.0.42-commit1)",
+ "reason": "Testing deployment for production-us-central-1 (platform 6.1, application 1.0.1-commit1)",
"at": "(ignore)"
}
},
{
"type": "production-us-central-1",
- "success": false,
+ "success": true,
"lastTriggered": {
"id": -1,
"version": "(ignore)",
@@ -139,7 +138,7 @@
"at": "(ignore)"
},
"lastCompleted": {
- "id": 42,
+ "id": 1,
"version": "(ignore)",
"revision": {
"buildNumber": "(ignore)",
@@ -153,8 +152,8 @@
"reason": "New change available",
"at": "(ignore)"
},
- "firstFailing": {
- "id": 42,
+ "lastSuccess": {
+ "id": 1,
"version": "(ignore)",
"revision": {
"buildNumber": "(ignore)",
@@ -168,6 +167,25 @@
"reason": "New change available",
"at": "(ignore)"
}
+ },
+ {
+ "type": "production-us-east-3",
+ "success": false,
+ "lastTriggered": {
+ "id": -1,
+ "version": "(ignore)",
+ "revision": {
+ "buildNumber": "(ignore)",
+ "hash": "(ignore)",
+ "source": {
+ "gitRepository": "repository1",
+ "gitBranch": "master",
+ "gitCommit": "commit1"
+ }
+ },
+ "reason": "Available change in production-us-central-1",
+ "at": "(ignore)"
+ }
}
],
"changeBlockers": [
@@ -197,7 +215,7 @@
],
"compileVersion": "(ignore)",
"globalRotations": [
- "https://application1--tenant1.global.vespa.oath.cloud:4443/"
+ "https://instance1--application1--tenant1.global.vespa.oath.cloud:4443/"
],
"rotationId": "rotation-id-1",
"instances": [
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/jobs-direct-deployment.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/jobs-direct-deployment.json
index 5535e286dcd..85a3245c308 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/jobs-direct-deployment.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/jobs-direct-deployment.json
@@ -1,79 +1,7 @@
{
"devJobs": {},
- "deployments": [
- {
- "us-west-1": {
- "at": 0,
- "application": {
- "hash": "unknown"
- },
- "verified": false,
- "platform": "6.1"
- }
- }
- ],
+ "deployments": [],
"lastVersions": {},
"deploying": {},
- "jobs": {
- "staging-test": {
- "runs": [
- {
- "reason": "Testing for productionUsWest1",
- "wantedPlatform": "6.1",
- "currentPlatform": "6.1",
- "wantedApplication": {
- "hash": "unknown"
- },
- "currentApplication": {
- "hash": "unknown"
- },
- "tasks": {
- "capacity": "running"
- },
- "status": "pending"
- }
- ],
- "url": "https://some.url:43/root/staging-test"
- },
- "system-test": {
- "runs": [
- {
- "reason": "Testing for productionUsWest1",
- "wantedPlatform": "6.1",
- "currentPlatform": "6.1",
- "wantedApplication": {
- "hash": "unknown"
- },
- "currentApplication": {
- "hash": "unknown"
- },
- "tasks": {
- "capacity": "running"
- },
- "status": "pending"
- }
- ],
- "url": "https://some.url:43/root/system-test"
- },
- "us-west-1": {
- "runs": [
- {
- "wantedPlatform": "6.1",
- "currentPlatform": "6.1",
- "wantedApplication": {
- "hash": "unknown"
- },
- "currentApplication": {
- "hash": "unknown"
- },
- "tasks": {
- "staging-test": "pending",
- "system-test": "pending"
- },
- "status": "pending"
- }
- ],
- "url": "https://some.url:43/root/production-us-west-1"
- }
- }
+ "jobs": {}
}
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 1a312f3c99c..b73c36c804b 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
@@ -3,51 +3,52 @@
"platform": {
"platform": "6.1",
"at": "(ignore)",
- "pending": "Waiting for application change to 1.0.45-d00d to complete"
+ "pending": "Waiting for application change to 1.0.4-commit1 to complete"
},
"application": {
"application": {
- "hash": "1.0.45-d00d",
- "build": 45,
+ "hash": "1.0.4-commit1",
+ "build": 4,
"source": {
- "gitRepository": "repo",
+ "gitRepository": "repository1",
"gitBranch": "master",
- "gitCommit": "d00d"
+ "gitCommit": "commit1"
}
},
- "at": (ignore),
- "deploying": "0 of 1 complete"
+ "at": "(ignore)",
+ "deploying": "0 of 3 complete"
}
},
"deploying": {
"application": {
- "hash": "1.0.45-d00d",
- "build": 45,
+ "hash": "1.0.4-commit1",
+ "build": 4,
"source": {
- "gitRepository": "repo",
+ "gitRepository": "repository1",
"gitBranch": "master",
- "gitCommit": "d00d"
+ "gitCommit": "commit1"
}
}
},
"deployments": [
+ {},
{}
],
"jobs": {
"system-test": {
"runs": [
{
- "id": 1,
+ "id": 2,
"status": "running",
- "start": (ignore),
+ "start": "(ignore)",
"wantedPlatform": "6.1",
"wantedApplication": {
- "hash": "1.0.45-d00d",
- "build": 45,
+ "hash": "1.0.4-commit1",
+ "build": 4,
"source": {
- "gitRepository": "repo",
+ "gitRepository": "repository1",
"gitBranch": "master",
- "gitCommit": "d00d"
+ "gitCommit": "commit1"
}
},
"steps": {
@@ -63,6 +64,40 @@
"report": "unfinished"
},
"tasks": {},
+ "log": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/job/system-test/run/2"
+ },
+ {
+ "id": 1,
+ "status": "success",
+ "start": "(ignore)",
+ "end": "(ignore)",
+ "wantedPlatform": "6.1",
+ "wantedApplication": {
+ "hash": "1.0.1-commit1",
+ "build": 1,
+ "source": {
+ "gitRepository": "repository1",
+ "gitBranch": "master",
+ "gitCommit": "commit1"
+ }
+ },
+ "steps": {
+ "deployTester": "succeeded",
+ "deployReal": "succeeded",
+ "installTester": "succeeded",
+ "installReal": "succeeded",
+ "startTests": "succeeded",
+ "endTests": "succeeded",
+ "copyVespaLogs": "succeeded",
+ "deactivateReal": "succeeded",
+ "deactivateTester": "succeeded",
+ "report": "succeeded"
+ },
+ "tasks": {
+ "deploy": "succeeded",
+ "install": "succeeded",
+ "test": "succeeded"
+ },
"log": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/job/system-test/run/1"
}
],
@@ -71,17 +106,17 @@
"staging-test": {
"runs": [
{
- "id": 1,
+ "id": 2,
"status": "running",
- "start": (ignore),
+ "start": "(ignore)",
"wantedPlatform": "6.1",
"wantedApplication": {
- "hash": "1.0.45-d00d",
- "build": 45,
+ "hash": "1.0.4-commit1",
+ "build": 4,
"source": {
- "gitRepository": "repo",
+ "gitRepository": "repository1",
"gitBranch": "master",
- "gitCommit": "d00d"
+ "gitCommit": "commit1"
}
},
"steps": {
@@ -99,32 +134,164 @@
"report": "unfinished"
},
"tasks": {},
+ "log": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/job/staging-test/run/2"
+ },
+ {
+ "id": 1,
+ "status": "success",
+ "start": "(ignore)",
+ "end": "(ignore)",
+ "wantedPlatform": "6.1",
+ "wantedApplication": {
+ "hash": "1.0.1-commit1",
+ "build": 1,
+ "source": {
+ "gitRepository": "repository1",
+ "gitBranch": "master",
+ "gitCommit": "commit1"
+ }
+ },
+ "steps": {
+ "deployTester": "succeeded",
+ "deployInitialReal": "succeeded",
+ "installInitialReal": "succeeded",
+ "deployReal": "succeeded",
+ "installTester": "succeeded",
+ "installReal": "succeeded",
+ "startTests": "succeeded",
+ "endTests": "succeeded",
+ "copyVespaLogs": "succeeded",
+ "deactivateReal": "succeeded",
+ "deactivateTester": "succeeded",
+ "report": "succeeded"
+ },
+ "tasks": {
+ "deploy": "succeeded",
+ "install": "succeeded",
+ "test": "succeeded"
+ },
"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/instance1/job/staging-test"
},
- "us-west-1": {
+ "us-central-1": {
"runs": [
{
"status": "pending",
"wantedPlatform": "6.1",
"wantedApplication": {
- "hash": "1.0.45-d00d",
- "build": 45,
+ "hash": "1.0.4-commit1",
+ "build": 4,
"source": {
- "gitRepository": "repo",
+ "gitRepository": "repository1",
"gitBranch": "master",
- "gitCommit": "d00d"
+ "gitCommit": "commit1"
}
},
"tasks": {
"system-test": "running",
"staging-test": "running"
}
+ },
+ {
+ "id": 1,
+ "status": "success",
+ "start": "(ignore)",
+ "end": "(ignore)",
+ "wantedPlatform": "6.1",
+ "wantedApplication": {
+ "hash": "1.0.1-commit1",
+ "build": 1,
+ "source": {
+ "gitRepository": "repository1",
+ "gitBranch": "master",
+ "gitCommit": "commit1"
+ }
+ },
+ "steps": {
+ "deployTester": "succeeded",
+ "deployReal": "succeeded",
+ "installTester": "succeeded",
+ "installReal": "succeeded",
+ "startTests": "succeeded",
+ "endTests": "succeeded",
+ "deactivateTester": "succeeded",
+ "report": "succeeded"
+ },
+ "tasks": {
+ "deploy": "succeeded",
+ "install": "succeeded",
+ "test": "succeeded"
+ },
+ "log": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/job/production-us-central-1/run/1"
+ }
+ ],
+ "url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/job/production-us-central-1"
+ },
+ "us-west-1": {
+ "runs": [
+ {
+ "id": 1,
+ "status": "aborted",
+ "start": "(ignore)",
+ "wantedPlatform": "6.1",
+ "wantedApplication": {
+ "hash": "1.0.1-commit1",
+ "build": 1,
+ "source": {
+ "gitRepository": "repository1",
+ "gitBranch": "master",
+ "gitCommit": "commit1"
+ }
+ },
+ "steps": {
+ "deployTester": "unfinished",
+ "deployReal": "unfinished",
+ "installTester": "unfinished",
+ "installReal": "unfinished",
+ "startTests": "unfinished",
+ "endTests": "unfinished",
+ "deactivateTester": "unfinished",
+ "report": "unfinished"
+ },
+ "tasks": {},
+ "log": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/job/production-us-west-1/run/1"
}
],
"url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/job/production-us-west-1"
+ },
+ "us-east-3": {
+ "runs": [
+ {
+ "id": 1,
+ "status": "aborted",
+ "start": "(ignore)",
+ "wantedPlatform": "6.1",
+ "wantedApplication": {
+ "hash": "1.0.1-commit1",
+ "build": 1,
+ "source": {
+ "gitRepository": "repository1",
+ "gitBranch": "master",
+ "gitCommit": "commit1"
+ }
+ },
+ "steps": {
+ "deployTester": "unfinished",
+ "deployReal": "unfinished",
+ "installTester": "unfinished",
+ "installReal": "unfinished",
+ "startTests": "unfinished",
+ "endTests": "unfinished",
+ "deactivateTester": "unfinished",
+ "report": "unfinished"
+ },
+ "tasks": {},
+ "log": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/job/production-us-east-3/run/1"
+ }
+ ],
+ "url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/job/production-us-east-3"
}
},
"devJobs": {
@@ -132,19 +299,21 @@
"runs": [
{
"id": 1,
- "status": "running",
- "start": (ignore),
+ "status": "success",
+ "start": "(ignore)",
+ "end": "(ignore)",
"wantedPlatform": "6.1",
"wantedApplication": {
"hash": "unknown"
},
"steps": {
- "deployReal": "unfinished",
- "installReal": "unfinished",
- "copyVespaLogs": "unfinished"
+ "deployReal": "succeeded",
+ "installReal": "succeeded",
+ "copyVespaLogs": "succeeded"
},
"tasks": {
- "deploy": "running"
+ "deploy": "succeeded",
+ "install": "succeeded"
},
"log": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/job/dev-us-east-1/run/1"
}
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 143042e3600..a9d85dda0a9 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
@@ -18,11 +18,7 @@
"region": "us-central-1",
"endpoints": [],
"serviceUrls": [
- "http://old-endpoint.vespa.yahooapis.com:4080",
- "http://qrs-endpoint.vespa.yahooapis.com:4080",
- "http://feeding-endpoint.vespa.yahooapis.com:4080",
- "http://global-endpoint.vespa.yahooapis.com:4080",
- "http://alias-endpoint.vespa.yahooapis.com:4080"
+ "https://instance1--application1--tenant1.us-central-1.prod.vespa:43"
],
"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",
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/system-test-details.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/system-test-details.json
index 10d7f3260c1..993c53b52d8 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/system-test-details.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/system-test-details.json
@@ -1,5 +1,404 @@
{
- "active": true,
- "status": "running",
- "log": {}
+ "active": false,
+ "status": "success",
+ "log": {
+ "deployTester": [
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Deploying the tester container on platform 6.1 ..."
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "No services requiring restart."
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Deployment successful."
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "foo"
+ }
+ ],
+ "deployReal": [
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Deploying platform version 6.1 and application version 1.0.1-commit1 ..."
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "No services requiring restart."
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Deployment successful."
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "foo"
+ }
+ ],
+ "installTester": [
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Checking installation of tester container ..."
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": " host-tenant1:application1:instance1-t-test.us-east-1: unorchestrated 6.1 "
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Wanted config generation is 2"
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": " host-tenant1:application1:instance1-t-test.us-east-1: container on port 43 has config generation 1"
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Installation of tester not yet complete."
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Checking installation of tester container ..."
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": " host-tenant1:application1:instance1-t-test.us-east-1: unorchestrated 6.1 "
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Wanted config generation is 2"
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": " host-tenant1:application1:instance1-t-test.us-east-1: container on port 43 has config generation 1"
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Installation of tester not yet complete."
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Checking installation of tester container ..."
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": " host-tenant1:application1:instance1-t-test.us-east-1: unorchestrated 6.1 "
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Wanted config generation is 2"
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": " host-tenant1:application1:instance1-t-test.us-east-1: container on port 43 has config generation 1"
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Installation of tester not yet complete."
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Checking installation of tester container ..."
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": " host-tenant1:application1:instance1-t-test.us-east-1: unorchestrated 6.1 "
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Wanted config generation is 2"
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": " host-tenant1:application1:instance1-t-test.us-east-1: container on port 43 has config generation 1"
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Installation of tester not yet complete."
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Checking installation of tester container ..."
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": " host-tenant1:application1:instance1-t-test.us-east-1: unorchestrated 6.1 "
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Wanted config generation is 2"
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": " host-tenant1:application1:instance1-t-test.us-east-1: container on port 43 has config generation 1"
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Installation of tester not yet complete."
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Checking installation of tester container ..."
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": " host-tenant1:application1:instance1-t-test.us-east-1: unorchestrated 6.1 "
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Wanted config generation is 2"
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "All services on wanted config generation."
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Attempting to find deployment endpoints ..."
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Endpoints not yet ready."
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Installation of tester not yet complete."
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Checking installation of tester container ..."
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": " host-tenant1:application1:instance1-t-test.us-east-1: unorchestrated 6.1 "
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Wanted config generation is 2"
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "All services on wanted config generation."
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Attempting to find deployment endpoints ..."
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Found endpoints:"
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "- test.us-east-1"
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": " |-- https://instance1-t--application1--tenant1.us-east-1.test.vespa:43 (cluster 'default')"
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Tester container successfully installed!"
+ }
+ ],
+ "installReal": [
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Checking installation of 6.1 and 1.0.1-commit1 ..."
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": " host-tenant1:application1:instance1-test.us-east-1: unorchestrated 6.1 "
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Wanted config generation is 2"
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": " host-tenant1:application1:instance1-test.us-east-1: container on port 43 has config generation 1"
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Installation not yet complete."
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Checking installation of 6.1 and 1.0.1-commit1 ..."
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": " host-tenant1:application1:instance1-test.us-east-1: unorchestrated 6.1 "
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Wanted config generation is 2"
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": " host-tenant1:application1:instance1-test.us-east-1: container on port 43 has config generation 1"
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Installation not yet complete."
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Checking installation of 6.1 and 1.0.1-commit1 ..."
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": " host-tenant1:application1:instance1-test.us-east-1: unorchestrated 6.1 "
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Wanted config generation is 2"
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "All services on wanted config generation."
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Attempting to find deployment endpoints ..."
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Found endpoints:"
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "- test.us-east-1"
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": " |-- https://instance1--application1--tenant1.us-east-1.test.vespa:43 (cluster 'default')"
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Installation succeeded!"
+ }
+ ],
+ "startTests": [
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Attempting to find endpoints ..."
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Found endpoints:"
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "- test.us-east-1"
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": " |-- https://instance1--application1--tenant1.us-east-1.test.vespa:43 (cluster 'default')"
+ },
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Starting tests ..."
+ }
+ ],
+ "endTests": [
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Tests completed successfully."
+ }
+ ],
+ "deactivateReal": [
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Deactivating deployment of tenant1.application1.instance1 in test.us-east-1 ..."
+ }
+ ],
+ "deactivateTester": [
+ {
+ "at": "(ignore)",
+ "type": "info",
+ "message": "Deactivating tester of tenant1.application1.instance1 in test.us-east-1 ..."
+ }
+ ]
+ },
+ "lastId": 75
}
+
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 228bdb17ecb..a572fa04781 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
@@ -1,16 +1,50 @@
{
"1": {
"id": 1,
+ "status": "success",
+ "start": "(ignore)",
+ "end": "(ignore)",
+ "wantedPlatform": "6.1",
+ "wantedApplication": {
+ "hash": "1.0.1-commit1",
+ "build": 1,
+ "source": {
+ "gitRepository": "repository1",
+ "gitBranch": "master",
+ "gitCommit": "commit1"
+ }
+ },
+ "steps": {
+ "deployTester": "succeeded",
+ "deployReal": "succeeded",
+ "installTester": "succeeded",
+ "installReal": "succeeded",
+ "startTests": "succeeded",
+ "endTests": "succeeded",
+ "copyVespaLogs": "succeeded",
+ "deactivateReal": "succeeded",
+ "deactivateTester": "succeeded",
+ "report": "succeeded"
+ },
+ "tasks": {
+ "deploy": "succeeded",
+ "install": "succeeded",
+ "test": "succeeded"
+ },
+ "log": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/job/system-test/run/1"
+ },
+ "2": {
+ "id": 2,
"status": "running",
- "start": (ignore),
+ "start": "(ignore)",
"wantedPlatform": "6.1",
"wantedApplication": {
- "hash": "1.0.45-d00d",
- "build": 45,
+ "hash": "1.0.4-commit1",
+ "build": 4,
"source": {
- "gitRepository": "repo",
+ "gitRepository": "repository1",
"gitBranch": "master",
- "gitCommit": "d00d"
+ "gitCommit": "commit1"
}
},
"steps": {
@@ -25,7 +59,8 @@
"deactivateTester": "unfinished",
"report": "unfinished"
},
- "tasks": { },
- "log": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/job/system-test/run/1"
+ "tasks": {},
+ "log": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/job/system-test/run/2"
}
}
+
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/athenz/AthenzApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/athenz/AthenzApiTest.java
index e0fc403da8e..e3216a0038b 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/athenz/AthenzApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/athenz/AthenzApiTest.java
@@ -3,7 +3,6 @@ package com.yahoo.vespa.hosted.controller.restapi.athenz;
import com.yahoo.vespa.athenz.api.AthenzDomain;
import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactoryMock;
import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzDbMock;
-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 org.junit.Test;
@@ -19,7 +18,7 @@ public class AthenzApiTest extends ControllerContainerTest {
@Test
public void testAthenzApi() {
- ContainerTester tester = new ContainerControllerTester(container, responseFiles).containerTester();
+ ContainerTester tester = new ContainerTester(container, responseFiles);
((AthenzClientFactoryMock) tester.container().components().getComponent(AthenzClientFactoryMock.class.getName()))
.getSetup().addDomain(new AthenzDbMock.Domain(new AthenzDomain("domain1")));
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandlerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandlerTest.java
new file mode 100644
index 00000000000..8aea26f21e3
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandlerTest.java
@@ -0,0 +1,143 @@
+// 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.restapi.configserver;
+
+import com.yahoo.application.container.handler.Request;
+import com.yahoo.config.provision.Environment;
+import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.zone.ZoneApi;
+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.proxy.ProxyRequest;
+import com.yahoo.vespa.hosted.controller.restapi.ContainerTester;
+import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerTest;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.net.URI;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+/**
+ * @author freva
+ */
+public class ConfigServerApiHandlerTest extends ControllerContainerTest {
+
+ private static final String responseFiles = "src/test/java/com/yahoo/vespa/hosted/controller/restapi/configserver/responses/";
+ private static final List<ZoneApi> zones = List.of(
+ ZoneApiMock.fromId("prod.us-north-1"),
+ ZoneApiMock.fromId("dev.aws-us-north-2"),
+ ZoneApiMock.fromId("test.us-north-3"),
+ ZoneApiMock.fromId("staging.us-north-4"));
+
+ private ContainerTester tester;
+ private ConfigServerProxyMock proxy;
+
+ @Before
+ public void before() {
+ tester = new ContainerTester(container, responseFiles);
+ tester.serviceRegistry().zoneRegistry()
+ .setDefaultRegionForEnvironment(Environment.dev, RegionName.from("us-north-2"))
+ .setZones(zones);
+ proxy = (ConfigServerProxyMock) container.components().getComponent(ConfigServerProxyMock.class.getName());
+ }
+
+ @Test
+ public void test_requests() {
+ // GET /configserver/v1
+ tester.assertResponse(operatorRequest("http://localhost:8080/configserver/v1"),
+ new File("root.json"));
+
+ // GET /configserver/v1/nodes/v2/node/?recursive=true
+ tester.assertResponse(operatorRequest("http://localhost:8080/configserver/v1/prod/us-north-1/nodes/v2/node/?recursive=true"),
+ "ok");
+ assertLastRequest("https://cfg.prod.us-north-1.test.vip:4443/", "GET");
+
+ // POST /configserver/v1/dev/us-north-2/nodes/v2/command/restart?hostname=node1
+ tester.assertResponse(operatorRequest("http://localhost:8080/configserver/v1/dev/aws-us-north-2/nodes/v2/command/restart?hostname=node1",
+ "", Request.Method.POST),
+ "ok");
+
+ // PUT /configserver/v1/prod/us-north-1/nodes/v2/state/dirty/node1
+ tester.assertResponse(operatorRequest("http://localhost:8080/configserver/v1/prod/us-north-1/nodes/v2/state/dirty/node1",
+ "", Request.Method.PUT), "ok");
+ assertLastRequest("https://cfg.prod.us-north-1.test.vip:4443/", "PUT");
+
+ // DELETE /configserver/v1/prod/us-north-1/nodes/v2/node/node1
+ tester.assertResponse(operatorRequest("http://localhost:8080/api/configserver/v1/prod/controller/nodes/v2/node/node1",
+ "", Request.Method.DELETE), "ok");
+ assertLastRequest("https://localhost:4443/", "DELETE");
+
+ // PATCH /configserver/v1/prod/us-north-1/nodes/v2/node/node1
+ tester.assertResponse(operatorRequest("http://localhost:8080/configserver/v1/dev/aws-us-north-2/nodes/v2/node/node1",
+ "{\"currentRestartGeneration\": 1}",
+ Request.Method.PATCH), "ok");
+ assertLastRequest("https://cfg.dev.aws-us-north-2.test.vip:4443/", "PATCH");
+ assertEquals("{\"currentRestartGeneration\": 1}", proxy.lastRequestBody().get());
+
+ assertFalse("Actions are logged to audit log", tester.controller().auditLogger().readLog().entries().isEmpty());
+ }
+
+ @Test
+ public void test_allowed_apis() {
+ // GET /configserver/v1/prod/us-north-1
+ tester.assertResponse(() -> operatorRequest("http://localhost:8080/configserver/v1/prod/us-north-1"),
+ "{\"error-code\":\"FORBIDDEN\",\"message\":\"Cannot access '/' through /configserver/v1, following APIs are permitted: /flags/v1/, /nodes/v2/, /orchestrator/v1/\"}",
+ 403);
+
+ tester.assertResponse(() -> operatorRequest("http://localhost:8080/configserver/v1/prod/us-north-1/application/v2/tenant/vespa"),
+ "{\"error-code\":\"FORBIDDEN\",\"message\":\"Cannot access '/application/v2/tenant/vespa' through /configserver/v1, following APIs are permitted: /flags/v1/, /nodes/v2/, /orchestrator/v1/\"}",
+ 403);
+ }
+
+ @Test
+ public void test_invalid_requests() {
+ // POST /configserver/v1/prod/us-north-34/nodes/v2
+ tester.assertResponse(() -> operatorRequest("http://localhost:8080/configserver/v1/prod/us-north-42/nodes/v2",
+ "", Request.Method.POST),
+ "{\"error-code\":\"BAD_REQUEST\",\"message\":\"No such zone: prod.us-north-42\"}", 400);
+ assertFalse(proxy.lastReceived().isPresent());
+ }
+
+ @Test
+ public void non_operators_are_forbidden() {
+ // Read request
+ tester.assertResponse(() -> authenticatedRequest("http://localhost:8080/configserver/v1/prod/us-north-1/nodes/v2/node"),
+ "{\n" +
+ " \"code\" : 403,\n" +
+ " \"message\" : \"Access denied\"\n" +
+ "}", 403);
+
+ // Write request
+ tester.assertResponse(() -> authenticatedRequest("http://localhost:8080/configserver/v1/prod/us-north-1/nodes/v2/node", "", Request.Method.POST),
+ "{\n" +
+ " \"code\" : 403,\n" +
+ " \"message\" : \"Access denied\"\n" +
+ "}", 403);
+ }
+
+ @Test
+ public void unauthenticated_request_are_unauthorized() {
+ {
+ // Read request
+ Request request = new Request("http://localhost:8080/configserver/v1/prod/us-north-1/nodes/v2/node", "", Request.Method.GET);
+ tester.assertResponse(() -> request, "{\n \"message\" : \"Not authenticated\"\n}", 401);
+ }
+
+ {
+ // Write request
+ Request request = new Request("http://localhost:8080/configserver/v1/prod/us-north-1/nodes/v2/node", "", Request.Method.POST);
+ tester.assertResponse(() -> request, "{\n \"message\" : \"Not authenticated\"\n}", 401);
+ }
+ }
+
+
+ private void assertLastRequest(String target, String method) {
+ ProxyRequest last = proxy.lastReceived().orElseThrow();
+ assertEquals(List.of(URI.create(target)), last.getTargets());
+ assertEquals(com.yahoo.jdisc.http.HttpRequest.Method.valueOf(method), last.getMethod());
+ }
+} \ No newline at end of file
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/configserver/responses/root.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/configserver/responses/root.json
new file mode 100644
index 00000000000..5ccf75d2448
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/configserver/responses/root.json
@@ -0,0 +1,29 @@
+{
+ "zones": [
+ {
+ "environment": "prod",
+ "region": "controller",
+ "uri": "http://localhost:8080/configserver/v1/prod/controller"
+ },
+ {
+ "environment": "prod",
+ "region": "us-north-1",
+ "uri": "http://localhost:8080/configserver/v1/prod/us-north-1"
+ },
+ {
+ "environment": "dev",
+ "region": "aws-us-north-2",
+ "uri": "http://localhost:8080/configserver/v1/dev/aws-us-north-2"
+ },
+ {
+ "environment": "test",
+ "region": "us-north-3",
+ "uri": "http://localhost:8080/configserver/v1/test/us-north-3"
+ },
+ {
+ "environment": "staging",
+ "region": "us-north-4",
+ "uri": "http://localhost:8080/configserver/v1/staging/us-north-4"
+ }
+ ]
+}
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 74d637499bd..b21c588235e 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
@@ -4,10 +4,8 @@ package com.yahoo.vespa.hosted.controller.restapi.controller;
import com.yahoo.application.container.handler.Request;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.test.ManualClock;
-import com.yahoo.vespa.athenz.api.AthenzIdentity;
-import com.yahoo.vespa.athenz.api.AthenzUser;
import com.yahoo.vespa.hosted.controller.auditlog.AuditLogger;
-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 org.junit.Before;
import org.junit.Test;
@@ -27,48 +25,46 @@ import static org.junit.Assert.assertFalse;
public class ControllerApiTest extends ControllerContainerTest {
private static final String responseFiles = "src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/";
- private static final AthenzIdentity HOSTED_VESPA_OPERATOR = AthenzUser.fromUserId("johnoperator");
- private ContainerControllerTester tester;
+ private ContainerTester tester;
@Before
public void before() {
- addUserToHostedOperatorRole(HOSTED_VESPA_OPERATOR);
- tester = new ContainerControllerTester(container, responseFiles);
+ tester = new ContainerTester(container, responseFiles);
}
@Test
public void testControllerApi() {
- tester.assertResponse(authenticatedRequest("http://localhost:8080/controller/v1/", new byte[0], Request.Method.GET), new File("root.json"));
+ tester.assertResponse(authenticatedRequest("http://localhost:8080/controller/v1/", "", Request.Method.GET), new File("root.json"));
// POST deactivates a maintenance job
- tester.assertResponse(hostedOperatorRequest("http://localhost:8080/controller/v1/maintenance/inactive/DeploymentExpirer",
+ tester.assertResponse(operatorRequest("http://localhost:8080/controller/v1/maintenance/inactive/DeploymentExpirer",
"", Request.Method.POST),
"{\"message\":\"Deactivated job 'DeploymentExpirer'\"}", 200);
// GET a list of all maintenance jobs
- tester.assertResponse(authenticatedRequest("http://localhost:8080/controller/v1/maintenance/", new byte[0], Request.Method.GET),
+ tester.assertResponse(authenticatedRequest("http://localhost:8080/controller/v1/maintenance/", "", Request.Method.GET),
new File("maintenance.json"));
// DELETE activates maintenance job
- tester.assertResponse(hostedOperatorRequest("http://localhost:8080/controller/v1/maintenance/inactive/DeploymentExpirer",
+ tester.assertResponse(operatorRequest("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",
+ tester.assertResponse(operatorRequest("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",
+ tester.assertResponse(operatorRequest("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",
+ tester.assertResponse(operatorRequest("http://localhost:8080/controller/v1/maintenance/inactive/bar",
"", Request.Method.DELETE),
"{\"error-code\":\"NOT_FOUND\",\"message\":\"No job named 'bar'\"}",
404);
@@ -79,55 +75,55 @@ public class ControllerApiTest extends ControllerContainerTest {
@Test
public void testUpgraderApi() {
// Get current configuration
- tester.assertResponse(authenticatedRequest("http://localhost:8080/controller/v1/jobs/upgrader", new byte[0], Request.Method.GET),
- "{\"upgradesPerMinute\":100.0,\"confidenceOverrides\":[]}",
+ tester.assertResponse(authenticatedRequest("http://localhost:8080/controller/v1/jobs/upgrader", "", Request.Method.GET),
+ "{\"upgradesPerMinute\":0.125,\"confidenceOverrides\":[]}",
200);
// Set invalid configuration
tester.assertResponse(
- hostedOperatorRequest("http://localhost:8080/controller/v1/jobs/upgrader", "{\"upgradesPerMinute\":-1}", Request.Method.PATCH),
+ operatorRequest("http://localhost:8080/controller/v1/jobs/upgrader", "{\"upgradesPerMinute\":-1}", Request.Method.PATCH),
"{\"error-code\":\"BAD_REQUEST\",\"message\":\"Upgrades per minute must be >= 0, got -1.0\"}",
400);
// Ignores unrecognized field
tester.assertResponse(
- hostedOperatorRequest("http://localhost:8080/controller/v1/jobs/upgrader","{\"foo\":\"bar\"}", Request.Method.PATCH),
+ operatorRequest("http://localhost:8080/controller/v1/jobs/upgrader", "{\"foo\":\"bar\"}", Request.Method.PATCH),
"{\"error-code\":\"BAD_REQUEST\",\"message\":\"No such modifiable field(s)\"}",
400);
// Set upgrades per minute
tester.assertResponse(
- hostedOperatorRequest("http://localhost:8080/controller/v1/jobs/upgrader", "{\"upgradesPerMinute\":42.0}", Request.Method.PATCH),
+ operatorRequest("http://localhost:8080/controller/v1/jobs/upgrader", "{\"upgradesPerMinute\":42.0}", Request.Method.PATCH),
"{\"upgradesPerMinute\":42.0,\"confidenceOverrides\":[]}",
200);
// Set target major version
tester.assertResponse(
- hostedOperatorRequest("http://localhost:8080/controller/v1/jobs/upgrader", "{\"targetMajorVersion\":6}", Request.Method.PATCH),
+ operatorRequest("http://localhost:8080/controller/v1/jobs/upgrader", "{\"targetMajorVersion\":6}", Request.Method.PATCH),
"{\"upgradesPerMinute\":42.0,\"targetMajorVersion\":6,\"confidenceOverrides\":[]}",
200);
// Clear target major version
tester.assertResponse(
- hostedOperatorRequest("http://localhost:8080/controller/v1/jobs/upgrader", "{\"targetMajorVersion\":null}", Request.Method.PATCH),
+ operatorRequest("http://localhost:8080/controller/v1/jobs/upgrader", "{\"targetMajorVersion\":null}", Request.Method.PATCH),
"{\"upgradesPerMinute\":42.0,\"confidenceOverrides\":[]}",
200);
// Override confidence
tester.assertResponse(
- hostedOperatorRequest("http://localhost:8080/controller/v1/jobs/upgrader/confidence/6.42", "broken", Request.Method.POST),
+ operatorRequest("http://localhost:8080/controller/v1/jobs/upgrader/confidence/6.42", "broken", Request.Method.POST),
"{\"upgradesPerMinute\":42.0,\"confidenceOverrides\":[{\"6.42\":\"broken\"}]}",
200);
// Override confidence for another version
tester.assertResponse(
- hostedOperatorRequest("http://localhost:8080/controller/v1/jobs/upgrader/confidence/6.43", "broken", Request.Method.POST),
+ operatorRequest("http://localhost:8080/controller/v1/jobs/upgrader/confidence/6.43", "broken", Request.Method.POST),
"{\"upgradesPerMinute\":42.0,\"confidenceOverrides\":[{\"6.42\":\"broken\"},{\"6.43\":\"broken\"}]}",
200);
// Remove first override
tester.assertResponse(
- hostedOperatorRequest("http://localhost:8080/controller/v1/jobs/upgrader/confidence/6.42", "", Request.Method.DELETE),
+ operatorRequest("http://localhost:8080/controller/v1/jobs/upgrader/confidence/6.42", "", Request.Method.DELETE),
"{\"upgradesPerMinute\":42.0,\"confidenceOverrides\":[{\"6.43\":\"broken\"}]}",
200);
@@ -160,8 +156,4 @@ public class ControllerApiTest extends ControllerContainerTest {
tester.assertResponse(authenticatedRequest("http://localhost:8080/controller/v1/auditlog/"), new File("auditlog.json"));
}
- private static Request hostedOperatorRequest(String uri, String body, Request.Method method) {
- return addIdentityToRequest(new Request(uri, body, method), HOSTED_VESPA_OPERATOR);
- }
-
}
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 b085a87672e..f992a54a114 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
@@ -8,8 +8,7 @@ import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.vespa.athenz.api.AthenzIdentity;
import com.yahoo.vespa.athenz.api.AthenzUser;
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.ContainerTester;
import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerTest;
import org.junit.Before;
import org.junit.Test;
@@ -27,13 +26,13 @@ public class CostApiTest extends ControllerContainerTest {
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 ContainerTester tester;
@Before
public void before() {
- tester = new ContainerControllerTester(container, responses);
- zoneRegistryMock().setSystemName(SystemName.cd)
- .setZones(zone1, zone2, zone3);
+ tester = new ContainerTester(container, responses);
+ tester.serviceRegistry().zoneRegistry().setSystemName(SystemName.cd)
+ .setZones(zone1, zone2, zone3);
}
@Test
@@ -42,13 +41,9 @@ public class CostApiTest extends ControllerContainerTest {
"Date,Property,Reserved Cpu Cores,Reserved Memory GB,Reserved Disk Space GB,Usage Fraction\n", 200);
}
- private ZoneRegistryMock zoneRegistryMock() {
- return (ZoneRegistryMock) tester.containerTester().container().components()
- .getComponent(ZoneRegistryMock.class.getName());
- }
-
private void assertResponse(Request request, String body, int statusCode) {
addIdentityToRequest(request, operator);
tester.assertResponse(request, body, statusCode);
}
+
}
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 35ec5b0e37e..37cf5511c0b 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
@@ -1,12 +1,8 @@
package com.yahoo.vespa.hosted.controller.restapi.deployment;
-import com.yahoo.config.provision.AthenzService;
-import com.yahoo.config.provision.Environment;
-import com.yahoo.vespa.hosted.controller.Application;
-import com.yahoo.vespa.hosted.controller.api.integration.deployment.SourceRevision;
-import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
-import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder;
-import com.yahoo.vespa.hosted.controller.restapi.ContainerControllerTester;
+import com.yahoo.vespa.hosted.controller.ControllerTester;
+import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester;
+import com.yahoo.vespa.hosted.controller.restapi.ContainerTester;
import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerTest;
import org.junit.Test;
@@ -19,19 +15,9 @@ public class BadgeApiTest extends ControllerContainerTest {
@Test
public void testBadgeApi() {
- ContainerControllerTester tester = new ContainerControllerTester(container, responseFiles);
- 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"))
- .region("us-west-1")
- .build();
- tester.controller().jobController().submit(application.id(),
- new SourceRevision("repository", "branch", "commit"),
- "foo@bar",
- 123,
- packageWithService,
- new byte[0]);
+ ContainerTester tester = new ContainerTester(container, responseFiles);
+ var application = new DeploymentTester(new ControllerTester(tester)).newDeploymentContext("tenant", "application", "default");
+ application.submit();
tester.assertResponse(authenticatedRequest("http://localhost:8080/badge/v1/tenant/application/default"),
"", 302);
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 bb1e6b6256a..3c23053eac0 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
@@ -5,11 +5,12 @@ import com.yahoo.component.Version;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.HostName;
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.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder;
-import com.yahoo.vespa.hosted.controller.restapi.ContainerControllerTester;
+import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester;
+import com.yahoo.vespa.hosted.controller.restapi.ContainerTester;
import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerTest;
import com.yahoo.vespa.hosted.controller.versions.NodeVersion;
import com.yahoo.vespa.hosted.controller.versions.NodeVersions;
@@ -31,42 +32,42 @@ public class DeploymentApiTest extends ControllerContainerTest {
@Test
public void testDeploymentApi() {
- ContainerControllerTester tester = new ContainerControllerTester(container, responseFiles);
+ ContainerTester tester = new ContainerTester(container, responseFiles);
+ DeploymentTester deploymentTester = new DeploymentTester(new ControllerTester(tester));
Version version = Version.fromString("5.0");
- tester.containerTester().upgradeSystem(version);
+ deploymentTester.controllerTester().upgradeSystem(version);
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
.environment(Environment.prod)
.region("us-west-1")
.build();
// 3 applications deploy on current system version
- Application failingInstance = tester.createApplication("domain1", "tenant1", "application1", "default");
- Application productionInstance = tester.createApplication("domain2", "tenant2", "application2", "default");
- Application instanceWithoutDeployment = tester.createApplication("domain3", "tenant3", "application3", "default");
- tester.deployCompletely(failingInstance, applicationPackage, 1L, false);
- tester.deployCompletely(productionInstance, applicationPackage, 2L, false);
+ var failingApp = deploymentTester.newDeploymentContext("tenant1", "application1", "default");
+ var productionApp = deploymentTester.newDeploymentContext("tenant2", "application2", "default");
+ var appWithoutDeployments = deploymentTester.newDeploymentContext("tenant3", "application3", "default");
+ failingApp.submit(applicationPackage).deploy();
+ productionApp.submit(applicationPackage).deploy();
// Deploy once so that job information is stored, then remove the deployment
- tester.deployCompletely(instanceWithoutDeployment, applicationPackage, 3L, false);
- tester.controller().applications().deactivate(instanceWithoutDeployment.id().defaultInstance(), ZoneId.from("prod", "us-west-1"));
+ appWithoutDeployments.submit(applicationPackage).deploy();
+ deploymentTester.applications().deactivate(appWithoutDeployments.instanceId(), ZoneId.from("prod", "us-west-1"));
// New version released
version = Version.fromString("5.1");
- tester.containerTester().upgradeSystem(version);
+ deploymentTester.controllerTester().upgradeSystem(version);
// Applications upgrade, 1/2 succeed
- tester.upgrader().maintain();
- tester.controller().applications().deploymentTrigger().triggerReadyJobs();
- tester.controller().applications().deploymentTrigger().triggerReadyJobs();
- tester.deployCompletely(failingInstance, applicationPackage, 1L, true);
- tester.deployCompletely(productionInstance, applicationPackage, 2L, false);
+ deploymentTester.upgrader().maintain();
+ deploymentTester.triggerJobs();
+ productionApp.deployPlatform(version);
+ failingApp.runJob(JobType.systemTest).failDeployment(JobType.stagingTest);
+ deploymentTester.triggerJobs();
- tester.controller().updateVersionStatus(censorConfigServers(VersionStatus.compute(tester.controller()),
- tester.controller()));
+ tester.controller().updateVersionStatus(censorConfigServers(VersionStatus.compute(tester.controller())));
tester.assertResponse(authenticatedRequest("http://localhost:8080/deployment/v1/"), new File("root.json"));
}
- private VersionStatus censorConfigServers(VersionStatus versionStatus, Controller controller) {
+ private VersionStatus censorConfigServers(VersionStatus versionStatus) {
List<VespaVersion> censored = new ArrayList<>();
for (VespaVersion version : versionStatus.versions()) {
if (version.nodeVersions().size() > 0) {
@@ -78,7 +79,7 @@ public class DeploymentApiTest extends ControllerContainerTest {
version.isReleased(),
NodeVersions.EMPTY.with(List.of(new NodeVersion(HostName.from("config1.test"), ZoneId.defaultId(), version.versionNumber(), version.versionNumber(), Instant.EPOCH),
new NodeVersion(HostName.from("config2.test"), ZoneId.defaultId(), version.versionNumber(), version.versionNumber(), Instant.EPOCH))),
- VespaVersion.confidenceFrom(version.statistics(), controller)
+ version.confidence()
);
}
censored.add(version);
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/responses/root.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/responses/root.json
index 845fa4631b6..a3a763e9634 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/responses/root.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/responses/root.json
@@ -1,14 +1,14 @@
{
- "versions":[
+ "versions": [
{
"version": "5",
"confidence": "high",
- "commit": "(ignore)",
- "date": "(ignore)",
+ "commit": "badc0ffee",
+ "date": 0,
"controllerVersion": false,
"systemVersion": false,
- "configServers": [ ],
- "failingApplications": [ ],
+ "configServers": [],
+ "failingApplications": [],
"productionApplications": [
{
"tenant": "tenant1",
@@ -20,16 +20,16 @@
"productionSuccesses": 1
}
],
- "deployingApplications": [ ]
+ "deployingApplications": []
},
{
- "version":"5.1",
- "confidence":"normal",
- "commit":"(ignore)",
- "date": "(ignore)",
- "controllerVersion":false,
- "systemVersion":true,
- "configServers":[
+ "version": "5.1",
+ "confidence": "normal",
+ "commit": "badc0ffee",
+ "date": 0,
+ "controllerVersion": true,
+ "systemVersion": true,
+ "configServers": [
{
"hostname":"config1.test"
},
@@ -37,7 +37,7 @@
"hostname":"config2.test"
}
],
- "failingApplications":[
+ "failingApplications": [
{
"tenant": "tenant1",
"application": "application1",
@@ -47,7 +47,7 @@
"failing": "staging-test"
}
],
- "productionApplications":[
+ "productionApplications": [
{
"tenant": "tenant2",
"application": "application2",
@@ -68,18 +68,7 @@
"running": "staging-test"
}
]
- },
- {
- "version": "(ignore)",
- "confidence": "normal",
- "commit": "(ignore)",
- "date": "(ignore)",
- "controllerVersion": true,
- "systemVersion": false,
- "configServers": [ ],
- "failingApplications": [ ],
- "productionApplications": [ ],
- "deployingApplications": [ ]
}
]
}
+
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/flags/AuditedFlagsApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/flags/AuditedFlagsApiTest.java
index b4ef98cc7f6..8fdfbb14c1b 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/flags/AuditedFlagsApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/flags/AuditedFlagsApiTest.java
@@ -5,7 +5,7 @@ import com.yahoo.application.container.handler.Request;
import com.yahoo.vespa.athenz.api.AthenzIdentity;
import com.yahoo.vespa.athenz.api.AthenzUser;
import com.yahoo.vespa.hosted.controller.auditlog.AuditLog;
-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 org.junit.Before;
import org.junit.Test;
@@ -20,12 +20,12 @@ public class AuditedFlagsApiTest extends ControllerContainerTest {
private static final String responses = "src/test/java/com/yahoo/vespa/hosted/controller/restapi/flags/responses/";
private static final AthenzIdentity operator = AthenzUser.fromUserId("operatorUser");
- private ContainerControllerTester tester;
+ private ContainerTester tester;
@Before
public void before() {
addUserToHostedOperatorRole(operator);
- tester = new ContainerControllerTester(container, responses);
+ tester = new ContainerTester(container, responses);
}
@Test
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 6bc221184c1..89f11eae5ae 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
@@ -17,7 +17,7 @@ import com.yahoo.vespa.hosted.controller.integration.ZoneRegistryMock;
import com.yahoo.vespa.hosted.controller.maintenance.JobControl;
import com.yahoo.vespa.hosted.controller.maintenance.Maintainer;
import com.yahoo.vespa.hosted.controller.maintenance.OsUpgrader;
-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.versions.OsVersionStatus;
import org.intellij.lang.annotations.Language;
@@ -43,12 +43,12 @@ public class OsApiTest extends ControllerContainerTest {
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 ContainerTester tester;
private List<OsUpgrader> osUpgraders;
@Before
public void before() {
- tester = new ContainerControllerTester(container, responses);
+ tester = new ContainerTester(container, responses);
addUserToHostedOperatorRole(operator);
zoneRegistryMock().setSystemName(SystemName.cd)
.setZones(zone1, zone2, zone3)
@@ -70,8 +70,6 @@ public class OsApiTest extends ControllerContainerTest {
// All nodes are initially on empty version
upgradeAndUpdateStatus();
- assertFile(new Request("http://localhost:8080/os/v1/"), "versions-initial.json");
-
// Upgrade OS to a different version in each cloud
assertResponse(new Request("http://localhost:8080/os/v1/", "{\"version\": \"7.5.2\", \"cloud\": \"cloud1\"}", Request.Method.PATCH),
"{\"message\":\"Set target OS version for cloud 'cloud1' to 7.5.2\"}", 200);
@@ -160,12 +158,11 @@ public class OsApiTest extends ControllerContainerTest {
}
private ZoneRegistryMock zoneRegistryMock() {
- return (ZoneRegistryMock) tester.containerTester().container().components()
- .getComponent(ZoneRegistryMock.class.getName());
+ return tester.serviceRegistry().zoneRegistry();
}
private NodeRepositoryMock nodeRepository() {
- return tester.containerTester().serviceRegistry().configServerMock().nodeRepository();
+ return tester.serviceRegistry().configServerMock().nodeRepository();
}
private void assertResponse(Request request, @Language("JSON") String body, int statusCode) {
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 89d90fe6221..eaeba420bc9 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
@@ -7,7 +7,7 @@ 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.ContainerTester;
import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerCloudTest;
import org.junit.Before;
import org.junit.Test;
@@ -30,31 +30,30 @@ public class ZoneApiTest extends ControllerContainerCloudTest {
private static final Set<Role> everyone = Set.of(Role.everyone());
- private ContainerControllerTester tester;
+ private ContainerTester tester;
@Before
public void before() {
- ZoneRegistryMock zoneRegistry = (ZoneRegistryMock) container.components()
- .getComponent(ZoneRegistryMock.class.getName());
- zoneRegistry.setDefaultRegionForEnvironment(Environment.dev, RegionName.from("us-north-2"))
- .setZones(zones);
- this.tester = new ContainerControllerTester(container, responseFiles);
+ tester = new ContainerTester(container, responseFiles);
+ tester.serviceRegistry().zoneRegistry()
+ .setDefaultRegionForEnvironment(Environment.dev, RegionName.from("us-north-2"))
+ .setZones(zones);
}
@Test
public void test_requests() {
// GET /zone/v1
- tester.containerTester().assertResponse(request("/zone/v1")
+ tester.assertResponse(request("/zone/v1")
.roles(everyone),
new File("root.json"));
// GET /zone/v1/environment/prod
- tester.containerTester().assertResponse(request("/zone/v1/environment/prod")
+ tester.assertResponse(request("/zone/v1/environment/prod")
.roles(everyone),
new File("prod.json"));
// GET /zone/v1/environment/dev/default
- tester.containerTester().assertResponse(request("/api/zone/v1/environment/dev/default")
+ tester.assertResponse(request("/api/zone/v1/environment/dev/default")
.roles(everyone),
new File("default-for-region.json"));
}
@@ -62,7 +61,7 @@ public class ZoneApiTest extends ControllerContainerCloudTest {
@Test
public void test_invalid_requests() {
// GET /zone/v1/environment/prod/default: No default region
- tester.containerTester().assertResponse(request("/zone/v1/environment/prod/default")
+ tester.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 33ea538e9b6..f00363989e6 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
@@ -1,20 +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.restapi.zone.v2;
-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.config.provision.zone.ZoneId;
-import com.yahoo.text.Utf8;
-import com.yahoo.vespa.athenz.api.AthenzIdentity;
-import com.yahoo.vespa.athenz.api.AthenzUser;
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.proxy.ProxyRequest;
-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 org.junit.Before;
import org.junit.Test;
@@ -24,70 +20,69 @@ import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
/**
* @author mpolden
*/
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<ZoneApi> zones = List.of(
ZoneApiMock.fromId("prod.us-north-1"),
- ZoneApiMock.fromId("dev.us-north-2"),
+ ZoneApiMock.fromId("dev.aws-us-north-2"),
ZoneApiMock.fromId("test.us-north-3"),
ZoneApiMock.fromId("staging.us-north-4"));
- private ContainerControllerTester tester;
+ private ContainerTester tester;
private ConfigServerProxyMock proxy;
@Before
public void before() {
- ZoneRegistryMock zoneRegistry = (ZoneRegistryMock) container.components()
- .getComponent(ZoneRegistryMock.class.getName());
- zoneRegistry.setDefaultRegionForEnvironment(Environment.dev, RegionName.from("us-north-2"))
- .setZones(zones);
- this.tester = new ContainerControllerTester(container, responseFiles);
- this.proxy = (ConfigServerProxyMock) container.components().getComponent(ConfigServerProxyMock.class.getName());
- addUserToHostedOperatorRole(HOSTED_VESPA_OPERATOR);
+ tester = new ContainerTester(container, responseFiles);
+ tester.serviceRegistry().zoneRegistry()
+ .setDefaultRegionForEnvironment(Environment.dev, RegionName.from("us-north-2"))
+ .setZones(zones);
+ proxy = (ConfigServerProxyMock) container.components().getComponent(ConfigServerProxyMock.class.getName());
}
@Test
public void test_requests() {
// GET /zone/v2
- tester.containerTester().assertResponse(authenticatedRequest("http://localhost:8080/zone/v2"),
+ tester.assertResponse(authenticatedRequest("http://localhost:8080/zone/v2"),
new File("root.json"));
// GET /zone/v2/prod/us-north-1
- tester.containerTester().assertResponse(authenticatedRequest("http://localhost:8080/zone/v2/prod/us-north-1"),
+ tester.assertResponse(authenticatedRequest("http://localhost:8080/zone/v2/prod/us-north-1"),
"ok");
- assertLastRequest(ZoneId.from("prod", "us-north-1"), "GET");
+
+ assertLastRequest(ZoneId.from("prod", "us-north-1"), 2, "GET");
// GET /zone/v2/nodes/v2/node/?recursive=true
- tester.containerTester().assertResponse(authenticatedRequest("http://localhost:8080/zone/v2/prod/us-north-1/nodes/v2/node/?recursive=true"),
+ tester.assertResponse(authenticatedRequest("http://localhost:8080/zone/v2/prod/us-north-1/nodes/v2/node/?recursive=true"),
"ok");
- assertLastRequest(ZoneId.from("prod", "us-north-1"), "GET");
+ assertLastRequest(ZoneId.from("prod", "us-north-1"), 2, "GET");
// POST /zone/v2/dev/us-north-2/nodes/v2/command/restart?hostname=node1
- tester.containerTester().assertResponse(hostedOperatorRequest("http://localhost:8080/zone/v2/dev/us-north-2/nodes/v2/command/restart?hostname=node1",
- new byte[0], Method.POST),
+ tester.assertResponse(operatorRequest("http://localhost:8080/zone/v2/dev/aws-us-north-2/nodes/v2/command/restart?hostname=node1",
+ "", Method.POST),
"ok");
// PUT /zone/v2/prod/us-north-1/nodes/v2/state/dirty/node1
- tester.containerTester().assertResponse(hostedOperatorRequest("http://localhost:8080/zone/v2/prod/us-north-1/nodes/v2/state/dirty/node1",
- new byte[0], Method.PUT), "ok");
- assertLastRequest(ZoneId.from("prod", "us-north-1"), "PUT");
+ tester.assertResponse(operatorRequest("http://localhost:8080/zone/v2/prod/us-north-1/nodes/v2/state/dirty/node1",
+ "", Method.PUT), "ok");
+ assertLastRequest(ZoneId.from("prod", "us-north-1"), 2, "PUT");
// DELETE /zone/v2/prod/us-north-1/nodes/v2/node/node1
- tester.containerTester().assertResponse(hostedOperatorRequest("http://localhost:8080/zone/v2/prod/us-north-1/nodes/v2/node/node1",
- new byte[0], Method.DELETE), "ok");
- assertLastRequest(ZoneId.from("prod", "us-north-1"), "DELETE");
+ tester.assertResponse(operatorRequest("http://localhost:8080/zone/v2/prod/us-north-1/nodes/v2/node/node1",
+ "", Method.DELETE), "ok");
+ assertLastRequest(ZoneId.from("prod", "us-north-1"), 2, "DELETE");
// PATCH /zone/v2/prod/us-north-1/nodes/v2/node/node1
- tester.containerTester().assertResponse(hostedOperatorRequest("http://localhost:8080/zone/v2/prod/us-north-1/nodes/v2/node/node1",
- Utf8.toBytes("{\"currentRestartGeneration\": 1}"),
+ tester.assertResponse(operatorRequest("http://localhost:8080/zone/v2/dev/aws-us-north-2/nodes/v2/node/node1",
+ "{\"currentRestartGeneration\": 1}",
Method.PATCH), "ok");
- assertLastRequest(ZoneId.from("prod", "us-north-1"), "PATCH");
+ assertLastRequest(ZoneId.from("dev", "aws-us-north-2"), 1, "PATCH");
assertEquals("{\"currentRestartGeneration\": 1}", proxy.lastRequestBody().get());
assertFalse("Actions are logged to audit log", tester.controller().auditLogger().readLog().entries().isEmpty());
@@ -96,20 +91,17 @@ public class ZoneApiTest extends ControllerContainerTest {
@Test
public void test_invalid_requests() {
// POST /zone/v2/prod/us-north-34/nodes/v2
- tester.containerTester().assertResponse(hostedOperatorRequest("http://localhost:8080/zone/v2/prod/us-north-42/nodes/v2",
- new byte[0], Method.POST),
+ tester.assertResponse(operatorRequest("http://localhost:8080/zone/v2/prod/us-north-42/nodes/v2",
+ "", Method.POST),
new File("unknown-zone.json"), 400);
assertFalse(proxy.lastReceived().isPresent());
}
- private void assertLastRequest(ZoneId zoneId, String method) {
+ private void assertLastRequest(ZoneId zoneId, int targets, String method) {
ProxyRequest last = proxy.lastReceived().orElseThrow();
- assertEquals(zoneId, last.getZoneId());
+ assertEquals(targets, last.getTargets().size());
+ assertTrue(last.getTargets().get(0).toString().contains(zoneId.value()));
assertEquals(com.yahoo.jdisc.http.HttpRequest.Method.valueOf(method), last.getMethod());
}
- private static Request hostedOperatorRequest(String uri, byte[] body, Request.Method method) {
- return addIdentityToRequest(new Request(uri, body, method), HOSTED_VESPA_OPERATOR);
- }
-
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/responses/root.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/responses/root.json
index ab168854267..bd1bc40ba81 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/responses/root.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/responses/root.json
@@ -1,7 +1,7 @@
{
"uris": [
"http://localhost:8080/zone/v2/prod/us-north-1",
- "http://localhost:8080/zone/v2/dev/us-north-2",
+ "http://localhost:8080/zone/v2/dev/aws-us-north-2",
"http://localhost:8080/zone/v2/test/us-north-3",
"http://localhost:8080/zone/v2/staging/us-north-4"
],
@@ -12,7 +12,7 @@
},
{
"environment": "dev",
- "region": "us-north-2"
+ "region": "aws-us-north-2"
},
{
"environment": "test",
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 c76046b3f67..674f084a8b7 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
@@ -146,6 +146,44 @@ public class RotationRepositoryTest {
application2.instance().endpointsIn(SystemName.cd).main().get().url().toString());
}
+ @Test
+ public void multiple_instances_with_similar_global_service_id() {
+ ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
+ .instances("instance1,instance2")
+ .region("us-central-1")
+ .parallel("us-west-1", "us-east-3")
+ .globalServiceId("global")
+ .build();
+ var instance1 = tester.newDeploymentContext("tenant1", "application1", "instance1").submit(applicationPackage);
+ var instance2 = tester.newDeploymentContext("tenant1", "application1", "instance2");
+ assertEquals(List.of(new RotationId("foo-1")), rotationIds(instance1.instance().rotations()));
+ assertEquals(List.of(new RotationId("foo-2")), rotationIds(instance2.instance().rotations()));
+ assertEquals(URI.create("https://instance1--application1--tenant1.global.vespa.oath.cloud:4443/"),
+ instance1.instance().endpointsIn(SystemName.main).main().get().url());
+ assertEquals(URI.create("https://instance2--application1--tenant1.global.vespa.oath.cloud:4443/"),
+ instance2.instance().endpointsIn(SystemName.main).main().get().url());
+ }
+
+ @Test
+ public void multiple_instances_with_similar_endpoints() {
+ ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
+ .instances("instance1,instance2")
+ .region("us-central-1")
+ .parallel("us-west-1", "us-east-3")
+ .endpoint("default", "foo", "us-central-1", "us-west-1")
+ .build();
+ var instance1 = tester.newDeploymentContext("tenant1", "application1", "instance1").submit(applicationPackage);
+ var instance2 = tester.newDeploymentContext("tenant1", "application1", "instance2");
+
+ assertEquals(List.of(new RotationId("foo-1")), rotationIds(instance1.instance().rotations()));
+ assertEquals(List.of(new RotationId("foo-2")), rotationIds(instance2.instance().rotations()));
+
+ assertEquals(URI.create("https://instance1--application1--tenant1.global.vespa.oath.cloud:4443/"),
+ instance1.instance().endpointsIn(SystemName.main).main().get().url());
+ assertEquals(URI.create("https://instance2--application1--tenant1.global.vespa.oath.cloud:4443/"),
+ instance2.instance().endpointsIn(SystemName.main).main().get().url());
+ }
+
private void assertSingleRotation(Rotation expected, List<AssignedRotation> assignedRotations, RotationRepository repository) {
assertEquals(1, assignedRotations.size());
var rotationId = assignedRotations.get(0).rotationId();
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 5c68fd2e370..06260da833f 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
@@ -401,7 +401,7 @@ public class VersionStatusTest {
assertEquals(commitDate0, tester.controller().versionStatus().systemVersion().get().committedAt());
// Deploy app on version0 to keep computing statistics for that version
- tester.deploymentContext().submit().deploy();
+ tester.newDeploymentContext().submit().deploy();
// Commit details are updated for new version
var version1 = tester.controllerTester().nextVersion();