diff options
Diffstat (limited to 'controller-server/src/main')
8 files changed, 69 insertions, 50 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 e3a0ad7cb84..66e62ff7b95 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java @@ -65,7 +65,7 @@ public class Application { Set.of(), OptionalLong.empty(), RevisionHistory.empty(), List.of()); } - // DO NOT USE! For serialization purposes, only. + // Do not use directly - edit through LockedApplication. public Application(TenantAndApplicationId id, Instant createdAt, DeploymentSpec deploymentSpec, ValidationOverrides validationOverrides, Optional<IssueId> deploymentIssueId, Optional<IssueId> ownershipIssueId, Optional<User> owner, OptionalInt majorVersion, ApplicationMetrics metrics, Set<PublicKey> deployKeys, OptionalLong projectId, @@ -230,11 +230,8 @@ public class Application { @Override public boolean equals(Object o) { if (this == o) return true; - if (! (o instanceof Application)) return false; - - Application that = (Application) o; - - return id.equals(that.id); + if (! (o instanceof Application other)) return false; + return id.equals(other.id); } @Override diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java index 19775ef420d..d8234e4f269 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java @@ -12,6 +12,7 @@ import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.CloudAccount; import com.yahoo.config.provision.DockerImage; import com.yahoo.config.provision.InstanceName; +import com.yahoo.config.provision.Tags; import com.yahoo.config.provision.TenantName; import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.log.LogLevel; @@ -166,10 +167,11 @@ public class ApplicationController { int count = 0; for (TenantAndApplicationId id : curator.readApplicationIds()) { lockApplicationIfPresent(id, application -> { - for (InstanceName instance : application.get().deploymentSpec().instanceNames()) - if ( ! application.get().instances().containsKey(instance)) - application = withNewInstance(application, id.instance(instance)); - + for (var declaredInstance : application.get().deploymentSpec().instances()) + if ( ! application.get().instances().containsKey(declaredInstance.name())) + application = withNewInstance(application, + id.instance(declaredInstance.name()), + declaredInstance.tags()); store(application); }); count++; @@ -451,14 +453,14 @@ public class ApplicationController { * * @throws IllegalArgumentException if the instance already exists, or has an invalid instance name. */ - public void createInstance(ApplicationId id) { + public void createInstance(ApplicationId id, Tags tags) { lockApplicationOrThrow(TenantAndApplicationId.from(id), application -> { - store(withNewInstance(application, id)); + store(withNewInstance(application, id, tags)); }); } /** Returns given application with a new instance */ - public LockedApplication withNewInstance(LockedApplication application, ApplicationId instance) { + public LockedApplication withNewInstance(LockedApplication application, ApplicationId instance, Tags tags) { if (instance.instance().isTester()) throw new IllegalArgumentException("'" + instance + "' is a tester application!"); InstanceId.validate(instance.instance().value()); @@ -469,7 +471,7 @@ public class ApplicationController { throw new IllegalArgumentException("Could not create '" + instance + "': Instance " + dashToUnderscore(instance) + " already exists"); log.info("Created " + instance); - return application.withNewInstance(instance.instance()); + return application.withNewInstance(instance.instance(), tags); } /** Deploys an application package for an existing application instance. */ @@ -496,10 +498,11 @@ public class ApplicationController { ApplicationPackage applicationPackage = new ApplicationPackage(applicationStore.get(deployment, revision)); AtomicReference<RevisionId> lastRevision = new AtomicReference<>(); + Instance instance; try (Mutex lock = lock(applicationId)) { LockedApplication application = new LockedApplication(requireApplication(applicationId), lock); application.get().revisions().last().map(ApplicationVersion::id).ifPresent(lastRevision::set); - Instance instance = application.get().require(job.application().instance()); + instance = application.get().require(job.application().instance()); if ( ! applicationPackage.trustedCertificates().isEmpty() && run.testerCertificate().isPresent()) @@ -512,7 +515,7 @@ public class ApplicationController { } // Release application lock while doing the deployment, which is a lengthy task. // Carry out deployment without holding the application lock. - ActivateResult result = deploy(job.application(), applicationPackage, zone, platform, containerEndpoints, + ActivateResult result = deploy(job.application(), instance.tags(), applicationPackage, zone, platform, containerEndpoints, endpointCertificateMetadata, run.isDryRun()); endpointCertificateMetadata.ifPresent(e -> deployLogger.accept("Using CA signed certificate version %s".formatted(e.version()))); @@ -543,9 +546,9 @@ public class ApplicationController { lockApplicationOrThrow(applicationId, application -> store(application.with(job.application().instance(), - instance -> instance.withNewDeployment(zone, revision, platform, - clock.instant(), warningsFrom(result), - quotaUsage)))); + i -> i.withNewDeployment(zone, revision, platform, + clock.instant(), warningsFrom(result), + quotaUsage)))); return result; } } @@ -557,16 +560,19 @@ public class ApplicationController { application = application.with(applicationPackage.deploymentSpec()); application = application.with(applicationPackage.validationOverrides()); - var existingInstances = application.get().instances().keySet(); - var declaredInstances = applicationPackage.deploymentSpec().instanceNames(); - for (var name : declaredInstances) - if ( ! existingInstances.contains(name)) - application = withNewInstance(application, application.get().id().instance(name)); + var existingInstances = application.get().instances(); + var declaredInstances = applicationPackage.deploymentSpec().instances(); + for (var declaredInstance : declaredInstances) { + if ( ! existingInstances.containsKey(declaredInstance.name())) + application = withNewInstance(application, application.get().id().instance(declaredInstance.name()), declaredInstance.tags()); + else if ( ! existingInstances.get(declaredInstance.name()).tags().equals(declaredInstance.tags())) + application = application.with(declaredInstance.name(), instance -> instance.with(declaredInstance.tags())); + } // Delete zones not listed in DeploymentSpec, if allowed // We do this at deployment time for externally built applications, and at submission time // for internally built ones, to be able to return a validation failure message when necessary - for (InstanceName name : existingInstances) { + for (InstanceName name : existingInstances.keySet()) { application = withoutDeletedDeployments(application, name); } @@ -599,7 +605,7 @@ public class ApplicationController { ApplicationPackage applicationPackage = new ApplicationPackage( artifactRepository.getSystemApplicationPackage(application.id(), zone, version) ); - return deploy(application.id(), applicationPackage, zone, version, Set.of(), /* No application cert */ Optional.empty(), false); + return deploy(application.id(), Tags.empty(), applicationPackage, zone, version, Set.of(), /* No application cert */ Optional.empty(), false); } else { throw new RuntimeException("This system application does not have an application package: " + application.id().toShortString()); } @@ -607,10 +613,10 @@ public class ApplicationController { /** Deploys the given tester application to the given zone. */ public ActivateResult deployTester(TesterId tester, ApplicationPackage applicationPackage, ZoneId zone, Version platform) { - return deploy(tester.id(), applicationPackage, zone, platform, Set.of(), /* No application cert for tester*/ Optional.empty(), false); + return deploy(tester.id(), Tags.empty(), applicationPackage, zone, platform, Set.of(), /* No application cert for tester*/ Optional.empty(), false); } - private ActivateResult deploy(ApplicationId application, ApplicationPackage applicationPackage, + private ActivateResult deploy(ApplicationId application, Tags tags, ApplicationPackage applicationPackage, ZoneId zone, Version platform, Set<ContainerEndpoint> endpoints, Optional<EndpointCertificateMetadata> endpointCertificateMetadata, boolean dryRun) { @@ -646,7 +652,7 @@ public class ApplicationController { .collect(toList()); Optional<CloudAccount> cloudAccount = decideCloudAccountOf(deployment, applicationPackage.deploymentSpec()); ConfigServer.PreparedApplication preparedApplication = - configServer.deploy(new DeploymentData(application, zone, applicationPackage.zippedContent(), platform, + configServer.deploy(new DeploymentData(application, tags, zone, applicationPackage.zippedContent(), platform, endpoints, endpointCertificateMetadata, dockerImageRepo, domain, deploymentQuota, tenantSecretStores, operatorCertificates, cloudAccount, dryRun)); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Instance.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Instance.java index d66d1491f73..430bafe5c44 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Instance.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Instance.java @@ -5,6 +5,7 @@ import com.yahoo.component.Version; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.InstanceName; +import com.yahoo.config.provision.Tags; import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType; import com.yahoo.vespa.hosted.controller.api.integration.deployment.RevisionId; @@ -40,6 +41,7 @@ import java.util.stream.Collectors; public class Instance { private final ApplicationId id; + private final Tags tags; private final Map<ZoneId, Deployment> deployments; private final List<AssignedRotation> rotations; private final RotationStatus rotationStatus; @@ -47,14 +49,15 @@ public class Instance { private final Change change; /** Creates an empty instance */ - public Instance(ApplicationId id) { - this(id, Set.of(), Map.of(), List.of(), RotationStatus.EMPTY, Change.empty()); + public Instance(ApplicationId id, Tags tags) { + this(id, tags, Set.of(), Map.of(), List.of(), RotationStatus.EMPTY, Change.empty()); } /** Creates an empty instance*/ - public Instance(ApplicationId id, Collection<Deployment> deployments, Map<JobType, Instant> jobPauses, + public Instance(ApplicationId id, Tags tags, Collection<Deployment> deployments, Map<JobType, Instant> jobPauses, List<AssignedRotation> rotations, RotationStatus rotationStatus, Change change) { this.id = Objects.requireNonNull(id, "id cannot be null"); + this.tags = Objects.requireNonNull(tags, "tags cannot be null"); this.deployments = Objects.requireNonNull(deployments, "deployments cannot be null").stream() .collect(Collectors.toUnmodifiableMap(Deployment::zone, Function.identity())); this.jobPauses = Map.copyOf(Objects.requireNonNull(jobPauses, "deploymentJobs cannot be null")); @@ -63,6 +66,10 @@ public class Instance { this.change = Objects.requireNonNull(change, "change cannot be null"); } + public Instance with(Tags tags) { + return new Instance(id, tags, deployments.values(), jobPauses, rotations, rotationStatus, change); + } + public Instance withNewDeployment(ZoneId zone, RevisionId revision, Version version, Instant instant, Map<DeploymentMetrics.Warning, Integer> warnings, QuotaUsage quotaUsage) { // Use info from previous deployment if available, otherwise create a new one. @@ -87,7 +94,7 @@ public class Instance { else jobPauses.remove(jobType); - return new Instance(id, deployments.values(), jobPauses, rotations, rotationStatus, change); + return new Instance(id, tags, deployments.values(), jobPauses, rotations, rotationStatus, change); } public Instance recordActivityAt(Instant instant, ZoneId zone) { @@ -118,15 +125,15 @@ public class Instance { } public Instance with(List<AssignedRotation> assignedRotations) { - return new Instance(id, deployments.values(), jobPauses, assignedRotations, rotationStatus, change); + return new Instance(id, tags, deployments.values(), jobPauses, assignedRotations, rotationStatus, change); } public Instance with(RotationStatus rotationStatus) { - return new Instance(id, deployments.values(), jobPauses, rotations, rotationStatus, change); + return new Instance(id, tags, deployments.values(), jobPauses, rotations, rotationStatus, change); } public Instance withChange(Change change) { - return new Instance(id, deployments.values(), jobPauses, rotations, rotationStatus, change); + return new Instance(id, tags, deployments.values(), jobPauses, rotations, rotationStatus, change); } private Instance with(Deployment deployment) { @@ -136,13 +143,15 @@ public class Instance { } private Instance with(Map<ZoneId, Deployment> deployments) { - return new Instance(id, deployments.values(), jobPauses, rotations, rotationStatus, change); + return new Instance(id, tags, deployments.values(), jobPauses, rotations, rotationStatus, change); } public ApplicationId id() { return id; } public InstanceName name() { return id.instance(); } + public Tags tags() { return tags; } + /** Returns an immutable map of the current deployments of this */ public Map<ZoneId, Deployment> deployments() { return deployments; } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java index 3e822415e96..fa702a166d2 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java @@ -4,6 +4,7 @@ package com.yahoo.vespa.hosted.controller; import com.yahoo.config.application.api.DeploymentSpec; import com.yahoo.config.application.api.ValidationOverrides; import com.yahoo.config.provision.InstanceName; +import com.yahoo.config.provision.Tags; import com.yahoo.transaction.Mutex; import com.yahoo.vespa.curator.Lock; import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId; @@ -88,9 +89,9 @@ public class LockedApplication { projectId, revisions, instances.values()); } - LockedApplication withNewInstance(InstanceName instance) { + LockedApplication withNewInstance(InstanceName instance, Tags tags) { var instances = new HashMap<>(this.instances); - instances.put(instance, new Instance(id.instance(instance))); + instances.put(instance, new Instance(id.instance(instance), tags)); return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, deployKeys, projectId, instances, revisions); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackage.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackage.java index 4bc9aeb00e4..c2c95a0c4bf 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackage.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackage.java @@ -16,6 +16,7 @@ import com.yahoo.config.application.api.ValidationOverrides; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.InstanceName; import com.yahoo.config.provision.RegionName; +import com.yahoo.config.provision.Tags; import com.yahoo.security.X509CertificateUtils; import com.yahoo.slime.Inspector; import com.yahoo.slime.Slime; @@ -202,7 +203,8 @@ public class ApplicationPackage { new InputStreamReader(new ByteArrayInputStream(servicesXml.content()), UTF_8), InstanceName.defaultName(), Environment.prod, - RegionName.defaultName()) + RegionName.defaultName(), + Tags.empty()) .run(); // Populates the zip archive cache with files that would be included. } catch (IllegalArgumentException e) { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java index 36036d6d36d..cca5310ccb9 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java @@ -8,6 +8,7 @@ import com.yahoo.concurrent.UncheckedTimeoutException; import com.yahoo.config.application.api.DeploymentSpec.UpgradePolicy; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.SystemName; +import com.yahoo.config.provision.Tags; import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.transaction.Mutex; import com.yahoo.vespa.hosted.controller.Application; @@ -321,15 +322,13 @@ public class JobController { public List<ApplicationId> instances() { return controller.applications().readable().stream() .flatMap(application -> application.instances().values().stream()) - .map(Instance::id) - .collect(toUnmodifiableList()); + .map(Instance::id).toList(); } /** Returns all job types which have been run for the given application. */ private List<JobType> jobs(ApplicationId id) { return JobType.allIn(controller.zoneRegistry()).stream() - .filter(type -> last(id, type).isPresent()) - .collect(toUnmodifiableList()); + .filter(type -> last(id, type).isPresent()).toList(); } /** Returns an immutable map of all known runs for the given application and job type. */ @@ -340,9 +339,8 @@ public class JobController { /** Lists the start time of non-redeployment runs of the given job, in order of increasing age. */ public List<Instant> jobStarts(JobId id) { return runs(id).descendingMap().values().stream() - .filter(run -> ! run.isRedeployment()) - .map(Run::start) - .collect(toUnmodifiableList()); + .filter(run -> !run.isRedeployment()) + .map(Run::start).toList(); } /** Returns when given deployment last started deploying, falling back to time of deployment if it cannot be determined from job runs */ @@ -698,7 +696,7 @@ public class JobController { controller.applications().lockApplicationOrThrow(TenantAndApplicationId.from(id), application -> { if ( ! application.get().instances().containsKey(id.instance())) - application = controller.applications().withNewInstance(application, id); + application = controller.applications().withNewInstance(application, id, Tags.empty()); // TODO(mpolden): Enable for public CD once all tests have been updated if (controller.system() != SystemName.PublicCd) { controller.applications().validatePackage(applicationPackage, application.get()); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java index 5aa847f648a..37c45f38e36 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java @@ -9,6 +9,7 @@ import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.InstanceName; import com.yahoo.config.provision.RegionName; import com.yahoo.config.provision.SystemName; +import com.yahoo.config.provision.Tags; import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.security.KeyUtils; import com.yahoo.slime.ArrayTraverser; @@ -97,6 +98,7 @@ public class ApplicationSerializer { // Instance fields private static final String instanceNameField = "instanceName"; + private static final String tagsField = "tags"; private static final String deploymentsField = "deployments"; private static final String deploymentJobsField = "deploymentJobs"; // TODO jonmv: clean up serialisation format private static final String assignedRotationsField = "assignedRotations"; @@ -184,6 +186,7 @@ public class ApplicationSerializer { for (Instance instance : application.instances().values()) { Cursor instanceObject = array.addObject(); instanceObject.setString(instanceNameField, instance.name().value()); + instanceObject.setString(tagsField, instance.tags().asString()); deploymentsToSlime(instance.deployments().values(), instanceObject.setArray(deploymentsField)); toSlime(instance.jobPauses(), instanceObject.setObject(deploymentJobsField)); assignedRotationsToSlime(instance.rotations(), instanceObject); @@ -380,12 +383,14 @@ public class ApplicationSerializer { List<Instance> instances = new ArrayList<>(); field.traverse((ArrayTraverser) (name, object) -> { InstanceName instanceName = InstanceName.from(object.field(instanceNameField).asString()); - List<Deployment> deployments = deploymentsFromSlime(object.field(deploymentsField), id.instance(instanceName)); + Tags tags = Tags.fromString(object.field(tagsField).asString()); + List < Deployment > deployments = deploymentsFromSlime(object.field(deploymentsField), id.instance(instanceName)); Map<JobType, Instant> jobPauses = jobPausesFromSlime(object.field(deploymentJobsField)); List<AssignedRotation> assignedRotations = assignedRotationsFromSlime(object); RotationStatus rotationStatus = rotationStatusFromSlime(object); Change change = changeFromSlime(object.field(deployingField)); instances.add(new Instance(id.instance(instanceName), + tags, deployments, jobPauses, assignedRotations, diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java index e8fe7b7d5f0..81fb72e19fd 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java @@ -22,6 +22,7 @@ import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.HostName; import com.yahoo.config.provision.InstanceName; import com.yahoo.config.provision.NodeResources; +import com.yahoo.config.provision.Tags; import com.yahoo.config.provision.TenantName; import com.yahoo.config.provision.zone.RoutingMethod; import com.yahoo.config.provision.zone.ZoneId; @@ -2035,7 +2036,7 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler { if (controller.applications().getApplication(applicationId).isEmpty()) createApplication(tenantName, applicationName, request); - controller.applications().createInstance(applicationId.instance(instanceName)); + controller.applications().createInstance(applicationId.instance(instanceName), Tags.empty()); Slime slime = new Slime(); toSlime(applicationId.instance(instanceName), slime.setObject(), request); |