aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjonmv <venstad@gmail.com>2022-04-09 23:39:37 +0200
committerjonmv <venstad@gmail.com>2022-04-09 23:39:37 +0200
commitdb755855b43890daa7e50bac9d7b01abda614867 (patch)
tree030d4f1d081b0257137fc5d9c8bcd7c6e8c7ec83
parentfc3a845edb740128ad693ecea064370ded758dfa (diff)
** Add RevisionId as key for ApplicationVersion, and gather them all in RevisionHistory
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/ApplicationVersion.java54
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/RevisionId.java59
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java39
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java25
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java57
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java6
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RevisionHistory.java147
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Versions.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java60
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializer.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/DeploymentQuotaCalculatorTest.java11
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java21
13 files changed, 384 insertions, 105 deletions
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/ApplicationVersion.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/ApplicationVersion.java
index bb610ef80cd..4938b76bf48 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/ApplicationVersion.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/ApplicationVersion.java
@@ -17,14 +17,10 @@ import java.util.OptionalLong;
*/
public class ApplicationVersion implements Comparable<ApplicationVersion> {
- /**
- * Used in cases where application version cannot be determined, such as manual deployments (e.g. in dev
- * environment)
- */
- public static final ApplicationVersion unknown = new ApplicationVersion(Optional.empty(), OptionalLong.empty(),
+ /** Should not be used, but may still exist in serialized data :< */
+ public static final ApplicationVersion unknown = new ApplicationVersion(Optional.empty(), OptionalLong.empty(), Optional.empty(),
Optional.empty(), Optional.empty(), Optional.empty(),
- Optional.empty(), Optional.empty(), true,
- Optional.empty());
+ Optional.empty(), true, Optional.empty(), false, true);
// This never changes and is only used to create a valid semantic version number, as required by application bundles
private static final String majorVersion = "1.0";
@@ -38,11 +34,14 @@ public class ApplicationVersion implements Comparable<ApplicationVersion> {
private final Optional<String> commit;
private final boolean deployedDirectly;
private final Optional<String> bundleHash;
+ private final boolean hasPackage;
+ private final boolean shouldSkip;
/** Public for serialisation only. */
public ApplicationVersion(Optional<SourceRevision> source, OptionalLong buildNumber, Optional<String> authorEmail,
Optional<Version> compileVersion, Optional<Instant> buildTime, Optional<String> sourceUrl,
- Optional<String> commit, boolean deployedDirectly, Optional<String> bundleHash) {
+ Optional<String> commit, boolean deployedDirectly, Optional<String> bundleHash,
+ boolean hasPackage, boolean shouldSkip) {
if (buildNumber.isEmpty() && ( source.isPresent() || authorEmail.isPresent() || compileVersion.isPresent()
|| buildTime.isPresent() || sourceUrl.isPresent() || commit.isPresent()))
throw new IllegalArgumentException("Build number must be present if any other attribute is");
@@ -68,13 +67,20 @@ public class ApplicationVersion implements Comparable<ApplicationVersion> {
this.commit = Objects.requireNonNull(commit, "commit cannot be null");
this.deployedDirectly = deployedDirectly;
this.bundleHash = bundleHash;
+ this.hasPackage = hasPackage;
+ this.shouldSkip = shouldSkip;
+ }
+
+ public RevisionId id() {
+ return isDeployedDirectly() ? RevisionId.forDevelopment(buildNumber().orElse(0))
+ : RevisionId.forProduction(buildNumber().orElseThrow());
}
/** Create an application package version from a completed build, without an author email */
public static ApplicationVersion from(SourceRevision source, long buildNumber) {
return new ApplicationVersion(Optional.of(source), OptionalLong.of(buildNumber), Optional.empty(),
Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), false,
- Optional.empty());
+ Optional.empty(), true, false);
}
/** Creates a version from a completed build, an author email, and build meta data. */
@@ -82,7 +88,7 @@ public class ApplicationVersion implements Comparable<ApplicationVersion> {
Version compileVersion, Instant buildTime) {
return new ApplicationVersion(Optional.of(source), OptionalLong.of(buildNumber), Optional.of(authorEmail),
Optional.of(compileVersion), Optional.of(buildTime), Optional.empty(), Optional.empty(), false,
- Optional.empty());
+ Optional.empty(), true, false);
}
/** Creates a version from a completed build, an author email, and build meta data. */
@@ -90,7 +96,8 @@ public class ApplicationVersion implements Comparable<ApplicationVersion> {
Optional<Version> compileVersion, Optional<Instant> buildTime,
Optional<String> sourceUrl, Optional<String> commit, boolean deployedDirectly,
Optional<String> bundleHash) {
- return new ApplicationVersion(source, OptionalLong.of(buildNumber), authorEmail, compileVersion, buildTime, sourceUrl, commit, deployedDirectly, bundleHash);
+ return new ApplicationVersion(source, OptionalLong.of(buildNumber), authorEmail, compileVersion,
+ buildTime, sourceUrl, commit, deployedDirectly, bundleHash, true, false);
}
/** Returns a unique identifier for this version or "unknown" if version is not known */
@@ -152,6 +159,31 @@ public class ApplicationVersion implements Comparable<ApplicationVersion> {
return deployedDirectly;
}
+ /** Returns a copy of this without a package stored. */
+ public ApplicationVersion withoutPackage() {
+ return new ApplicationVersion(source, buildNumber, authorEmail, compileVersion, buildTime, sourceUrl, commit, deployedDirectly, bundleHash, false, shouldSkip);
+ }
+
+ /** Whether we still have the package for this revision. */
+ public boolean hasPackage() {
+ return hasPackage;
+ }
+
+ /** Returns a copy of this which will not be rolled out to production. */
+ public ApplicationVersion skipped() {
+ return new ApplicationVersion(source, buildNumber, authorEmail, compileVersion, buildTime, sourceUrl, commit, deployedDirectly, bundleHash, hasPackage, true);
+ }
+
+ /** Whether we still have the package for this revision. */
+ public boolean shouldSkip() {
+ return shouldSkip;
+ }
+
+ /** Whether this revision should be deployed. */
+ public boolean isDeployable() {
+ return hasPackage && ! shouldSkip;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/RevisionId.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/RevisionId.java
new file mode 100644
index 00000000000..b8b24fd9c77
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/RevisionId.java
@@ -0,0 +1,59 @@
+package com.yahoo.vespa.hosted.controller.api.integration.deployment;
+
+import java.util.Objects;
+
+import static ai.vespa.validation.Validation.requireAtLeast;
+
+/**
+ * ID of a revision of an application package. This is the build number, and whether it was submitted for production deployment.
+ *
+ * @author jonmv
+ */
+public class RevisionId implements Comparable<RevisionId> {
+
+ private final long number;
+ private final boolean production;
+
+ private RevisionId(long number, boolean production) {
+ this.number = number;
+ this.production = production;
+ }
+
+ public static RevisionId forProduction(long number) {
+ return new RevisionId(requireAtLeast(number, "build number", 1L), true);
+ }
+
+ public static RevisionId forDevelopment(long number) {
+ return new RevisionId(requireAtLeast(number, "build number", 0L), false);
+ }
+
+ public long number() { return number; }
+
+ private boolean isProduction() { return production; }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ RevisionId that = (RevisionId) o;
+ return number == that.number && production == that.production;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(number, production);
+ }
+
+ /** Unknown, manual builds sort first, then known manual builds, then production builds, by increasing build number */
+ @Override
+ public int compareTo(RevisionId o) {
+ return production != o.production ? Boolean.compare(production, o.production)
+ : Long.compare(number, o.number);
+ }
+
+ @Override
+ public String toString() {
+ return (production ? "prod" : "dev") + " build " + number;
+ }
+
+}
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 67afa813bcd..b49739db012 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
@@ -14,16 +14,15 @@ import com.yahoo.vespa.hosted.controller.application.ApplicationActivity;
import com.yahoo.vespa.hosted.controller.application.Deployment;
import com.yahoo.vespa.hosted.controller.application.QuotaUsage;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
+import com.yahoo.vespa.hosted.controller.deployment.RevisionHistory;
import com.yahoo.vespa.hosted.controller.metric.ApplicationMetrics;
import com.yahoo.vespa.hosted.controller.tenant.Tenant;
import java.security.PublicKey;
import java.time.Instant;
-import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
-import java.util.Deque;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -31,9 +30,7 @@ import java.util.Optional;
import java.util.OptionalInt;
import java.util.OptionalLong;
import java.util.Set;
-import java.util.SortedSet;
import java.util.TreeMap;
-import java.util.TreeSet;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -50,7 +47,7 @@ public class Application {
private final Instant createdAt;
private final DeploymentSpec deploymentSpec;
private final ValidationOverrides validationOverrides;
- private final SortedSet<ApplicationVersion> versions;
+ private final RevisionHistory revisions;
private final OptionalLong projectId;
private final Optional<IssueId> deploymentIssueId;
private final Optional<IssueId> ownershipIssueId;
@@ -62,16 +59,16 @@ public class Application {
/** Creates an empty application. */
public Application(TenantAndApplicationId id, Instant now) {
- this(id, now, DeploymentSpec.empty, ValidationOverrides.empty,
- Optional.empty(), Optional.empty(), Optional.empty(), OptionalInt.empty(),
- new ApplicationMetrics(0, 0), Set.of(), OptionalLong.empty(), new TreeSet<>(), List.of());
+ this(id, now, DeploymentSpec.empty, ValidationOverrides.empty, Optional.empty(),
+ Optional.empty(), Optional.empty(), OptionalInt.empty(), new ApplicationMetrics(0, 0),
+ Set.of(), OptionalLong.empty(), RevisionHistory.empty(), List.of());
}
// DO NOT USE! For serialization purposes, only.
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,
- SortedSet<ApplicationVersion> versions, Collection<Instance> instances) {
+ RevisionHistory revisions, 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");
@@ -83,7 +80,7 @@ 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.versions = versions;
+ this.revisions = revisions;
this.instances = instances.stream().collect(
Collectors.collectingAndThen(Collectors.toMap(Instance::name,
Function.identity(),
@@ -108,28 +105,22 @@ public class Application {
/** Returns the project id of this application, if it has any. */
public OptionalLong projectId() { return projectId; }
+ /** Returns the known revisions for this application. */
+ public RevisionHistory revisions() { return revisions; }
+
/** Returns the last submitted version of this application. */
public Optional<ApplicationVersion> latestVersion() {
- return versions.isEmpty() ? Optional.empty() : Optional.of(versions.last());
+ return revisions().last();
}
/** Returns the currently deployed versions of the application, ordered from oldest to newest. */
- public SortedSet<ApplicationVersion> versions() {
- return versions;
+ public List<ApplicationVersion> versions() {
+ return revisions().withPackage();
}
- /** Returns the currently deployed versions of the application */
+ /** Returns the currently deployable versions of the application */
public Collection<ApplicationVersion> deployableVersions(boolean ascending) {
- Deque<ApplicationVersion> versions = new ArrayDeque<>();
- String previousHash = "";
- for (ApplicationVersion version : versions()) {
- if (version.bundleHash().isEmpty() || ! previousHash.equals(version.bundleHash().get())) {
- if (ascending) versions.addLast(version);
- else versions.addFirst(version);
- }
- previousHash = version.bundleHash().orElse("");
- }
- return versions;
+ return revisions().deployable(ascending);
}
/**
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 d66c57b7d7e..30648265ab9 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
@@ -41,6 +41,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationS
import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.ArtifactRepository;
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.api.integration.deployment.TesterId;
import com.yahoo.vespa.hosted.controller.api.integration.noderepository.RestartFilter;
import com.yahoo.vespa.hosted.controller.api.integration.secrets.TenantSecretStore;
@@ -58,6 +59,7 @@ import com.yahoo.vespa.hosted.controller.certificate.EndpointCertificates;
import com.yahoo.vespa.hosted.controller.concurrent.Once;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentTrigger;
import com.yahoo.vespa.hosted.controller.deployment.JobStatus;
+import com.yahoo.vespa.hosted.controller.deployment.RevisionHistory;
import com.yahoo.vespa.hosted.controller.deployment.Run;
import com.yahoo.vespa.hosted.controller.deployment.RunStatus;
import com.yahoo.vespa.hosted.controller.notification.Notification;
@@ -82,6 +84,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -160,12 +163,31 @@ public class ApplicationController {
for (InstanceName instance : application.get().deploymentSpec().instanceNames())
if ( ! application.get().instances().containsKey(instance))
application = withNewInstance(application, id.instance(instance));
+ // TODO jonmv: remove when data is migrated
+ // Each controller will know only about the revisions which we have packages for when they upgrade.
+ // The last controller will populate any missing, historic data after it upgrades.
+ // When all controllers are upgraded, we can start using the data, and remove this.
+ Set<ApplicationVersion> production = new HashSet<>();
+ Map<JobId, Set<ApplicationVersion>> development = new HashMap<>();
+ for (InstanceName instance : application.get().instances().keySet()) {
+ for (JobType type : JobType.allIn(controller.system())) {
+ for (Run run : controller.jobController().runs(id.instance(instance), type).values()) {
+ ApplicationVersion revision = run.versions().targetApplication();
+ if ( ! revision.isDeployedDirectly()) production.add(revision);
+ else development.computeIfAbsent(run.id().job(), __ -> new HashSet<>()).add(revision);
+ }
+ }
+ }
+ application = application.withRevisions(revisions -> {
+ production.addAll(revisions.production()); // These are already properly set, and we want ot keep their hasPackage status.
+ return RevisionHistory.ofRevisions(production, development); // All the added data is just written for now. We'll use it later.
+ });
store(application);
});
count++;
}
log.log(Level.INFO, Text.format("Wrote %d applications in %s", count,
- Duration.between(start, clock.instant())));
+ Duration.between(start, clock.instant())));
});
}
@@ -175,7 +197,6 @@ public class ApplicationController {
}
/** Returns the instance with the given id, or null if it is not present */
- // TODO jonmv: remove or inline
public Optional<Instance> getInstance(ApplicationId id) {
return getApplication(TenantAndApplicationId.from(id)).flatMap(application -> application.get(id.instance()));
}
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 fb271a586dd..551327fd2e5 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
@@ -5,10 +5,10 @@ import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.application.api.ValidationOverrides;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.vespa.curator.Lock;
-import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId;
import com.yahoo.vespa.hosted.controller.api.integration.organization.User;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
+import com.yahoo.vespa.hosted.controller.deployment.RevisionHistory;
import com.yahoo.vespa.hosted.controller.metric.ApplicationMetrics;
import java.security.PublicKey;
@@ -21,8 +21,6 @@ import java.util.Optional;
import java.util.OptionalInt;
import java.util.OptionalLong;
import java.util.Set;
-import java.util.SortedSet;
-import java.util.TreeSet;
import java.util.function.UnaryOperator;
/**
@@ -44,7 +42,7 @@ public class LockedApplication {
private final ApplicationMetrics metrics;
private final Set<PublicKey> deployKeys;
private final OptionalLong projectId;
- private final SortedSet<ApplicationVersion> versions;
+ private final RevisionHistory revisions;
private final Map<InstanceName, Instance> instances;
/**
@@ -58,15 +56,14 @@ public class LockedApplication {
application.deploymentSpec(), application.validationOverrides(),
application.deploymentIssueId(), application.ownershipIssueId(),
application.owner(), application.majorVersion(), application.metrics(), application.deployKeys(),
- application.projectId(), application.versions(), application.instances());
+ application.projectId(), application.instances(), application.revisions());
}
private LockedApplication(Lock lock, 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, SortedSet<ApplicationVersion> versions,
- Map<InstanceName, Instance> instances) {
+ OptionalLong projectId, Map<InstanceName, Instance> instances, RevisionHistory revisions) {
this.lock = lock;
this.id = id;
this.createdAt = createdAt;
@@ -79,7 +76,7 @@ public class LockedApplication {
this.metrics = metrics;
this.deployKeys = deployKeys;
this.projectId = projectId;
- this.versions = versions;
+ this.revisions = revisions;
this.instances = Map.copyOf(instances);
}
@@ -87,7 +84,7 @@ public class LockedApplication {
public Application get() {
return new Application(id, createdAt, deploymentSpec, validationOverrides,
deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, deployKeys,
- projectId, versions, instances.values());
+ projectId, revisions, instances.values());
}
LockedApplication withNewInstance(InstanceName instance) {
@@ -95,7 +92,7 @@ public class LockedApplication {
instances.put(instance, new Instance(id.instance(instance)));
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides,
deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, deployKeys,
- projectId, versions, instances);
+ projectId, instances, revisions);
}
public LockedApplication with(InstanceName instance, UnaryOperator<Instance> modification) {
@@ -103,7 +100,7 @@ public class LockedApplication {
instances.put(instance, modification.apply(instances.get(instance)));
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides,
deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, deployKeys,
- projectId, versions, instances);
+ projectId, instances, revisions);
}
public LockedApplication without(InstanceName instance) {
@@ -111,51 +108,43 @@ public class LockedApplication {
instances.remove(instance);
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides,
deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, deployKeys,
- projectId, versions, instances);
- }
-
- public LockedApplication withNewSubmission(ApplicationVersion latestVersion) {
- SortedSet<ApplicationVersion> applicationVersions = new TreeSet<>(versions);
- applicationVersions.add(latestVersion);
- return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides,
- deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, deployKeys,
- projectId, applicationVersions, instances);
+ projectId, instances, revisions);
}
public LockedApplication withProjectId(OptionalLong projectId) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides,
deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, deployKeys,
- projectId, versions, instances);
+ projectId, instances, revisions);
}
public LockedApplication withDeploymentIssueId(IssueId issueId) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides,
Optional.ofNullable(issueId), ownershipIssueId, owner, majorVersion, metrics, deployKeys,
- projectId, versions, instances);
+ projectId, instances, revisions);
}
public LockedApplication with(DeploymentSpec deploymentSpec) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides,
deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, deployKeys,
- projectId, versions, instances);
+ projectId, instances, revisions);
}
public LockedApplication with(ValidationOverrides validationOverrides) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides,
deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, deployKeys,
- projectId, versions, instances);
+ projectId, instances, revisions);
}
public LockedApplication withOwnershipIssueId(IssueId issueId) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides,
deploymentIssueId, Optional.of(issueId), owner, majorVersion, metrics, deployKeys,
- projectId, versions, instances);
+ projectId, instances, revisions);
}
public LockedApplication withOwner(User owner) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides,
deploymentIssueId, ownershipIssueId, Optional.of(owner), majorVersion, metrics, deployKeys,
- projectId, versions, instances);
+ projectId, instances, revisions);
}
/** Set a major version for this, or set to null to remove any major version override */
@@ -163,13 +152,13 @@ public class LockedApplication {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides,
deploymentIssueId, ownershipIssueId, owner,
majorVersion == null ? OptionalInt.empty() : OptionalInt.of(majorVersion),
- metrics, deployKeys, projectId, versions, instances);
+ metrics, deployKeys, projectId, instances, revisions);
}
public LockedApplication with(ApplicationMetrics metrics) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides,
deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, deployKeys,
- projectId, versions, instances);
+ projectId, instances, revisions);
}
public LockedApplication withDeployKey(PublicKey pemDeployKey) {
@@ -177,7 +166,7 @@ public class LockedApplication {
keys.add(pemDeployKey);
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides,
deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, keys,
- projectId, versions, instances);
+ projectId, instances, revisions);
}
public LockedApplication withoutDeployKey(PublicKey pemDeployKey) {
@@ -185,15 +174,13 @@ public class LockedApplication {
keys.remove(pemDeployKey);
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides,
deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, keys,
- projectId, versions, instances);
+ projectId, instances, revisions);
}
- public LockedApplication withoutVersion(ApplicationVersion version) {
- SortedSet<ApplicationVersion> applicationVersions = new TreeSet<>(versions);
- applicationVersions.remove(version);
+ public LockedApplication withRevisions(UnaryOperator<RevisionHistory> change) {
return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides,
- deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, deployKeys,
- projectId, applicationVersions, instances);
+ deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics,
+ deployKeys, projectId, instances, change.apply(revisions));
}
@Override
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 b1a9f3abdd4..7ccf9b5757a 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
@@ -422,6 +422,7 @@ public class JobController {
logs.flush(id);
metric.jobFinished(run.id().job(), finishedRun.status());
+ // TODO: update RevisionHistory, which should track all known revisions.
controller.jobController().runs(id.job()).values().stream()
.mapToLong(r -> r.versions().targetApplication().buildNumber().orElse(Integer.MAX_VALUE))
.min()
@@ -487,7 +488,7 @@ public class JobController {
applicationPackage.metaDataZip());
application = application.withProjectId(OptionalLong.of(projectId));
- application = application.withNewSubmission(version.get());
+ application = application.withRevisions(revisions -> revisions.with(version.get()));
application = withPrunedRevisions(application);
applications.storeWithUpdatedConfig(application, applicationPackage);
@@ -505,7 +506,7 @@ public class JobController {
for (ApplicationVersion version : application.get().versions())
if (version.compareTo(oldestDeployed.get()) < 0)
- application = application.withoutVersion(version);
+ application = application.withRevisions(revisions -> revisions.with(version.withoutPackage()));
}
return application;
}
@@ -570,6 +571,7 @@ public class JobController {
false,
dryRun ? JobProfile.developmentDryRun : JobProfile.development,
Optional.empty());
+ controller.applications().store(application.withRevisions(revisions -> revisions.with(version, new JobId(id, type))));
});
locked(id, type, __ -> {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RevisionHistory.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RevisionHistory.java
new file mode 100644
index 00000000000..488d86df98b
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RevisionHistory.java
@@ -0,0 +1,147 @@
+package com.yahoo.vespa.hosted.controller.deployment;
+
+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.RevisionId;
+
+import java.util.ArrayDeque;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Deque;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.Optional;
+import java.util.OptionalLong;
+import java.util.TreeMap;
+
+import static java.util.stream.Collectors.toList;
+
+/**
+ * History of application revisions for an {@link com.yahoo.vespa.hosted.controller.Application}.
+ *
+ * @author jonmv
+ */
+public class RevisionHistory {
+
+ private static final Comparator<JobId> comparator = Comparator.comparing(JobId::application).thenComparing(JobId::type);
+
+ private final NavigableMap<RevisionId, ApplicationVersion> production;
+ private final NavigableMap<JobId, NavigableMap<RevisionId, ApplicationVersion>> development;
+
+ private RevisionHistory(NavigableMap<RevisionId, ApplicationVersion> production,
+ NavigableMap<JobId, NavigableMap<RevisionId, ApplicationVersion>> development) {
+ this.production = production;
+ this.development = development;
+ }
+
+ public static RevisionHistory empty() {
+ return ofRevisions(List.of(), Map.of());
+ }
+
+ public static RevisionHistory ofRevisions(Collection<ApplicationVersion> productionRevisions,
+ Map<JobId, ? extends Collection<ApplicationVersion>> developmentRevisions) {
+ NavigableMap<RevisionId, ApplicationVersion> production = new TreeMap<>();
+ for (ApplicationVersion revision : productionRevisions)
+ production.put(revision.id(), revision);
+
+ NavigableMap<JobId, NavigableMap<RevisionId, ApplicationVersion>> development = new TreeMap<>(comparator);
+ developmentRevisions.forEach((job, jobRevisions) -> {
+ NavigableMap<RevisionId, ApplicationVersion> revisions = development.computeIfAbsent(job, __ -> new TreeMap<>());
+ for (ApplicationVersion revision : jobRevisions)
+ revisions.put(revision.id(), revision);
+ });
+
+ return new RevisionHistory(production, development);
+ }
+
+ /** Returns a copy of this with given production revision forgotten. */
+ public RevisionHistory without(RevisionId id) {
+ if ( ! production.containsKey(id)) return this;
+ TreeMap<RevisionId, ApplicationVersion> production = new TreeMap<>(this.production);
+ production.remove(id);
+ return new RevisionHistory(production, development);
+ }
+
+ /** Returns a copy of this with the given development revision forgotten. */
+ public RevisionHistory without(RevisionId id, JobId job) {
+ if ( ! development.containsKey(job) || ! development.get(job).containsKey(id)) return this;
+ NavigableMap<JobId, NavigableMap<RevisionId, ApplicationVersion>> development = new TreeMap<>(this.development);
+ development.get(job).remove(id);
+ return new RevisionHistory(production, development);
+ }
+
+ /** Returns a copy of this with the production revision added or updated */
+ public RevisionHistory with(ApplicationVersion revision) {
+ NavigableMap<RevisionId, ApplicationVersion> production = new TreeMap<>(this.production);
+ production.put(revision.id(), revision);
+ return new RevisionHistory(production, development);
+ }
+
+ /** Returns a copy of this with the new development revision added, and the previous version without a package. */
+ public RevisionHistory with(ApplicationVersion revision, JobId job) {
+ NavigableMap<JobId, NavigableMap<RevisionId, ApplicationVersion>> development = new TreeMap<>(this.development);
+ NavigableMap<RevisionId, ApplicationVersion> revisions = development.computeIfAbsent(job, __ -> new TreeMap<>());
+ if ( ! revisions.isEmpty()) revisions.compute(revisions.lastKey(), (__, last) -> last.withoutPackage());
+ revisions.put(revision.id(), revision);
+ return new RevisionHistory(production, development);
+ }
+
+ // Fallback for when an application version isn't known for the given key.
+ private static ApplicationVersion revisionOf(RevisionId id, boolean production) {
+ return new ApplicationVersion(Optional.empty(), OptionalLong.of(id.number()), Optional.empty(),
+ Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(),
+ ! production, Optional.empty(), false, false);
+ }
+
+ /** Returns the production {@link ApplicationVersion} with this revision ID. */
+ public ApplicationVersion get(RevisionId id) {
+ return production.getOrDefault(id, revisionOf(id, true));
+ }
+
+ /** Returns the development {@link ApplicationVersion} for the give job, with this revision ID. */
+ public ApplicationVersion get(RevisionId id, JobId job) {
+ return development.getOrDefault(job, Collections.emptyNavigableMap())
+ .getOrDefault(id, revisionOf(id, false));
+ }
+
+ /** Returns the last submitted production build. */
+ public Optional<ApplicationVersion> last() {
+ return Optional.ofNullable(production.lastEntry()).map(Map.Entry::getValue);
+ }
+
+ /** Returns all known production revisions we still have the package for, from oldest to newest. */
+ public List<ApplicationVersion> withPackage() {
+ return production.values().stream()
+ .filter(ApplicationVersion::hasPackage)
+ .collect(toList());
+ }
+
+ /** Returns the currently deployable revisions of the application. */
+ public Deque<ApplicationVersion> deployable(boolean ascending) {
+ Deque<ApplicationVersion> versions = new ArrayDeque<>();
+ String previousHash = "";
+ for (ApplicationVersion version : withPackage()) {
+ if (version.isDeployable() && (version.bundleHash().isEmpty() || ! previousHash.equals(version.bundleHash().get()))) {
+ if (ascending) versions.addLast(version);
+ else versions.addFirst(version);
+ }
+ previousHash = version.bundleHash().orElse("");
+ }
+ return versions;
+ }
+
+ /** All known production revisions, in ascending order. */
+ public List<ApplicationVersion> production() {
+ return List.copyOf(production.values());
+ }
+
+ /* All known development revisions, in ascending order, per job. */
+ public NavigableMap<JobId, List<ApplicationVersion>> development() {
+ NavigableMap<JobId, List<ApplicationVersion>> copy = new TreeMap<>(comparator);
+ development.forEach((job, revisions) -> copy.put(job, List.copyOf(revisions.values())));
+ return Collections.unmodifiableNavigableMap(copy);
+ }
+
+}
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 379cee22386..a762dd8d861 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
@@ -145,7 +145,7 @@ public class Versions {
private static ApplicationVersion defaultApplicationVersion(Application application) {
return application.oldestDeployedApplication()
.or(application::latestVersion)
- .orElse(ApplicationVersion.unknown);
+ .orElseThrow(() -> new IllegalStateException("no known prod revisions, but asked for one, for " + application));
}
private static <T extends Comparable<T>> Optional<T> max(Optional<T> o1, Optional<T> o2) {
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 1b5467b7eee..e036d38b875 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
@@ -18,6 +18,7 @@ import com.yahoo.slime.SlimeUtils;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Instance;
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.api.integration.deployment.SourceRevision;
import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId;
@@ -30,6 +31,7 @@ import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics;
import com.yahoo.vespa.hosted.controller.application.EndpointId;
import com.yahoo.vespa.hosted.controller.application.QuotaUsage;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
+import com.yahoo.vespa.hosted.controller.deployment.RevisionHistory;
import com.yahoo.vespa.hosted.controller.metric.ApplicationMetrics;
import com.yahoo.vespa.hosted.controller.routing.rotation.RotationId;
import com.yahoo.vespa.hosted.controller.routing.rotation.RotationState;
@@ -49,8 +51,6 @@ import java.util.Optional;
import java.util.OptionalInt;
import java.util.OptionalLong;
import java.util.Set;
-import java.util.SortedSet;
-import java.util.TreeSet;
/**
* Serializes {@link Application}s to/from slime.
@@ -77,6 +77,8 @@ public class ApplicationSerializer {
private static final String deployingField = "deployingField";
private static final String projectIdField = "projectId";
private static final String versionsField = "versions";
+ private static final String prodVersionsField = "prodVersions";
+ private static final String devVersionsField = "devVersions";
private static final String pinnedField = "pinned";
private static final String deploymentIssueField = "deploymentIssueId";
private static final String ownershipIssueIdField = "ownershipIssueId";
@@ -110,6 +112,8 @@ public class ApplicationSerializer {
private static final String commitField = "commitField";
private static final String authorEmailField = "authorEmailField";
private static final String deployedDirectlyField = "deployedDirectly";
+ private static final String hasPackageField = "hasPackage";
+ private static final String shouldSkipField = "shouldSkip";
private static final String compileVersionField = "compileVersion";
private static final String buildTimeField = "buildTime";
private static final String sourceUrlField = "sourceUrl";
@@ -165,7 +169,8 @@ public class ApplicationSerializer {
root.setDouble(queryQualityField, application.metrics().queryServiceQuality());
root.setDouble(writeQualityField, application.metrics().writeServiceQuality());
deployKeysToSlime(application.deployKeys(), root.setArray(pemDeployKeysField));
- versionsToSlime(application, root.setArray(versionsField));
+ revisionsToSlime(application.revisions().withPackage(), root.setArray(versionsField));
+ revisionsToSlime(application.revisions(), root.setArray(prodVersionsField), root.setArray(devVersionsField));
instancesToSlime(application, root.setArray(instancesField));
return slime;
}
@@ -224,8 +229,18 @@ public class ApplicationSerializer {
object.setString(regionField, zone.region().value());
}
- private void versionsToSlime(Application application, Cursor object) {
- application.versions().forEach(version -> toSlime(version, object.addObject()));
+ private void revisionsToSlime(RevisionHistory revisions, Cursor revisionsArray, Cursor devRevisionsArray) {
+ revisionsToSlime(revisions.production(), revisionsArray);
+ revisions.development().forEach((job, devRevisions) -> {
+ Cursor devRevisionsObject = devRevisionsArray.addObject();
+ devRevisionsObject.setString(instanceNameField, job.application().instance().value());
+ devRevisionsObject.setString(jobTypeField, job.type().jobName());
+ revisionsToSlime(devRevisions, devRevisionsObject.setArray(versionsField));
+ });
+ }
+
+ private void revisionsToSlime(Iterable<ApplicationVersion> revisions, Cursor revisionsArray) {
+ revisions.forEach(version -> toSlime(version, revisionsArray.addObject()));
}
private void toSlime(ApplicationVersion applicationVersion, Cursor object) {
@@ -237,6 +252,8 @@ public class ApplicationSerializer {
applicationVersion.sourceUrl().ifPresent(url -> object.setString(sourceUrlField, url));
applicationVersion.commit().ifPresent(commit -> object.setString(commitField, commit));
object.setBool(deployedDirectlyField, applicationVersion.isDeployedDirectly());
+ object.setBool(hasPackageField, applicationVersion.hasPackage());
+ object.setBool(shouldSkipField, applicationVersion.shouldSkip());
applicationVersion.bundleHash().ifPresent(bundleHash -> object.setString(bundleHashField, bundleHash));
}
@@ -317,17 +334,33 @@ public class ApplicationSerializer {
Set<PublicKey> deployKeys = deployKeysFromSlime(root.field(pemDeployKeysField));
List<Instance> instances = instancesFromSlime(id, root.field(instancesField));
OptionalLong projectId = SlimeUtils.optionalLong(root.field(projectIdField));
- SortedSet<ApplicationVersion> versions = versionsFromSlime(root.field(versionsField));
+ RevisionHistory revisions = revisionsFromSlime(root.field(versionsField), root.field(prodVersionsField), root.field(devVersionsField), id);
return new Application(id, createdAt, deploymentSpec, validationOverrides,
deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics,
- deployKeys, projectId, versions, instances);
+ deployKeys, projectId, revisions, instances);
+ }
+
+ // TODO jonmv: read only from prodVersionsArray, once data is migrated.
+ private RevisionHistory revisionsFromSlime(Inspector versionsArray, Inspector prodVersionsArray, Inspector devVersionsArray, TenantAndApplicationId id) {
+ List<ApplicationVersion> revisions = prodVersionsArray.valid() ? revisionsFromSlime(prodVersionsArray)
+ : revisionsFromSlime(versionsArray);
+ Map<JobId, List<ApplicationVersion>> devRevisions = new HashMap<>();
+ devVersionsArray.traverse((ArrayTraverser) (__, devRevisionsObject) ->
+ devRevisions.put(jobIdFromSlime(id, devRevisionsObject), revisionsFromSlime(devRevisionsObject.field(versionsField))));
+
+ return RevisionHistory.ofRevisions(revisions, devRevisions);
+ }
+
+ private JobId jobIdFromSlime(TenantAndApplicationId base, Inspector idObject) {
+ return new JobId(base.instance(idObject.field(instanceNameField).asString()),
+ JobType.fromJobName(idObject.field(jobTypeField).asString()));
}
- private SortedSet<ApplicationVersion> versionsFromSlime(Inspector versionsObject) {
- SortedSet<ApplicationVersion> versions = new TreeSet<>();
- versionsObject.traverse((ArrayTraverser) (name, object) -> versions.add(applicationVersionFromSlime(object)));
- return versions;
+ private List<ApplicationVersion> revisionsFromSlime(Inspector versionsArray) {
+ List<ApplicationVersion> revisions = new ArrayList<>();
+ versionsArray.traverse((ArrayTraverser) (__, revisionObject) -> revisions.add(applicationVersionFromSlime(revisionObject)));
+ return revisions;
}
private List<Instance> instancesFromSlime(TenantAndApplicationId id, Inspector field) {
@@ -433,9 +466,12 @@ public class ApplicationSerializer {
Optional<String> sourceUrl = SlimeUtils.optionalString(object.field(sourceUrlField));
Optional<String> commit = SlimeUtils.optionalString(object.field(commitField));
boolean deployedDirectly = object.field(deployedDirectlyField).asBool();
+ boolean hasPackage = object.field(hasPackageField).asBool();
+ boolean shouldSkip = object.field(shouldSkipField).asBool();
Optional<String> bundleHash = SlimeUtils.optionalString(object.field(bundleHashField));
- return new ApplicationVersion(sourceRevision, applicationBuildNumber, authorEmail, compileVersion, buildTime, sourceUrl, commit, deployedDirectly, bundleHash);
+ return new ApplicationVersion(sourceRevision, applicationBuildNumber, authorEmail, compileVersion, buildTime,
+ sourceUrl, commit, deployedDirectly, bundleHash, hasPackage, shouldSkip);
}
private Optional<SourceRevision> sourceRevisionFromSlime(Inspector object) {
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 c3d81b8dcd5..4d5fa0d278c 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
@@ -187,8 +187,8 @@ class RunSerializer {
boolean deployedDirectly = versionObject.field(deployedDirectlyField).asBool();
Optional<String> bundleHash = SlimeUtils.optionalString(versionObject.field(bundleHashField));
- return new ApplicationVersion(source, OptionalLong.of(buildNumber), authorEmail,
- compileVersion, buildTime, sourceUrl, commit, deployedDirectly, bundleHash);
+ return new ApplicationVersion(source, OptionalLong.of(buildNumber), authorEmail, compileVersion,
+ buildTime, sourceUrl, commit, deployedDirectly, bundleHash, false, false);
}
// Don't change this — introduce a separate array instead.
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 eb2334086f5..e78f77c74ee 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
@@ -817,7 +817,7 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
private HttpResponse applicationPackage(String tenantName, String applicationName, HttpRequest request) {
var tenantAndApplication = TenantAndApplicationId.from(tenantName, applicationName);
- SortedSet<ApplicationVersion> versions = controller.applications().requireApplication(tenantAndApplication).versions();
+ List<ApplicationVersion> versions = controller.applications().requireApplication(tenantAndApplication).versions();
if (versions.isEmpty())
throw new NotExistsException("No application package has been submitted for '" + tenantAndApplication + "'");
@@ -833,7 +833,7 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
.filter(ver -> ver.buildNumber().orElse(-1) == build)
.findFirst()
.orElseThrow(() -> new NotExistsException("No application package found for '" + tenantAndApplication + "' with build number " + build)))
- .orElseGet(versions::last);
+ .orElseGet(() -> versions.get(versions.size() - 1));
boolean tests = request.getBooleanProperty("tests");
byte[] applicationPackage = tests ?
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/DeploymentQuotaCalculatorTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/DeploymentQuotaCalculatorTest.java
index 7561592be9b..56408b099c6 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/DeploymentQuotaCalculatorTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/DeploymentQuotaCalculatorTest.java
@@ -14,6 +14,7 @@ import com.yahoo.vespa.hosted.controller.Instance;
import com.yahoo.vespa.hosted.controller.api.integration.billing.Quota;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
import com.yahoo.vespa.hosted.controller.api.integration.noderepository.ApplicationData;
+import com.yahoo.vespa.hosted.controller.deployment.RevisionHistory;
import com.yahoo.vespa.hosted.controller.metric.ApplicationMetrics;
import org.junit.Test;
@@ -61,11 +62,11 @@ public class DeploymentQuotaCalculatorTest {
@Test
public void quota_is_divided_among_prod_and_manual_instances() {
- var existing_dev_deployment = new Application(TenantAndApplicationId.from(ApplicationId.defaultId()), Instant.EPOCH, DeploymentSpec.empty, ValidationOverrides.empty,
- Optional.empty(), Optional.empty(), Optional.empty(), OptionalInt.empty(), new ApplicationMetrics(1, 1), Set.of(), OptionalLong.empty(),
- new TreeSet<>(), List.of(new Instance(ApplicationId.defaultId()).withNewDeployment(
- ZoneId.from(Environment.dev, RegionName.defaultName()), ApplicationVersion.unknown, Version.emptyVersion, Instant.EPOCH, Map.of(),
- QuotaUsage.create(0.53d))));
+ var existing_dev_deployment = new Application(TenantAndApplicationId.from(ApplicationId.defaultId()), Instant.EPOCH, DeploymentSpec.empty, ValidationOverrides.empty, Optional.empty(),
+ Optional.empty(), Optional.empty(), OptionalInt.empty(), new ApplicationMetrics(1, 1), Set.of(), OptionalLong.empty(), RevisionHistory.empty(),
+ List.of(new Instance(ApplicationId.defaultId()).withNewDeployment(
+ ZoneId.from(Environment.dev, RegionName.defaultName()), ApplicationVersion.unknown, Version.emptyVersion, Instant.EPOCH, Map.of(),
+ QuotaUsage.create(0.53d))));
Quota calculated = DeploymentQuotaCalculator.calculate(Quota.unlimited().withBudget(2), List.of(existing_dev_deployment), ApplicationId.defaultId(), ZoneId.defaultId(),
DeploymentSpec.fromXml(
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 de716a65551..4c7aa1a72f0 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
@@ -11,6 +11,7 @@ import com.yahoo.slime.SlimeUtils;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Instance;
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.api.integration.deployment.SourceRevision;
import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId;
@@ -22,6 +23,7 @@ import com.yahoo.vespa.hosted.controller.application.DeploymentActivity;
import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics;
import com.yahoo.vespa.hosted.controller.application.QuotaUsage;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
+import com.yahoo.vespa.hosted.controller.deployment.RevisionHistory;
import com.yahoo.vespa.hosted.controller.metric.ApplicationMetrics;
import com.yahoo.vespa.hosted.controller.routing.rotation.RotationId;
import com.yahoo.vespa.hosted.controller.routing.rotation.RotationState;
@@ -42,8 +44,6 @@ import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.OptionalLong;
import java.util.Set;
-import java.util.SortedSet;
-import java.util.TreeSet;
import static org.junit.Assert.assertEquals;
@@ -92,13 +92,14 @@ public class ApplicationSerializerTest {
Optional.empty(),
Optional.of("best commit"),
true,
- Optional.of("hash1"));
+ Optional.of("hash1"),
+ true,
+ false);
assertEquals("https://github/org/repo/tree/commit1", applicationVersion1.sourceUrl().get());
ApplicationVersion applicationVersion2 = ApplicationVersion
.from(new SourceRevision("repo1", "branch1", "commit1"), 32, "a@b",
Version.fromString("6.3.1"), Instant.ofEpochMilli(496));
- SortedSet<ApplicationVersion> versions = new TreeSet<>(Set.of(applicationVersion2));
Instant activityAt = Instant.parse("2018-06-01T10:15:30.00Z");
deployments.add(new Deployment(zone1, applicationVersion1, Version.fromString("1.2.3"), Instant.ofEpochMilli(3),
DeploymentMetrics.none, DeploymentActivity.none, QuotaUsage.none, OptionalDouble.empty()));
@@ -119,6 +120,8 @@ public class ApplicationSerializerTest {
ApplicationId id1 = ApplicationId.from("t1", "a1", "i1");
ApplicationId id3 = ApplicationId.from("t1", "a1", "i3");
+ RevisionHistory revisions = RevisionHistory.ofRevisions(List.of(applicationVersion2),
+ Map.of(new JobId(id3, JobType.devUsEast1), List.of(applicationVersion1)));
List<Instance> instances = List.of(new Instance(id1,
deployments,
Map.of(JobType.systemTest, Instant.ofEpochMilli(333)),
@@ -143,8 +146,8 @@ public class ApplicationSerializerTest {
new ApplicationMetrics(0.5, 0.9),
Set.of(publicKey, otherPublicKey),
projectId,
- versions,
- instances);
+ revisions, instances
+ );
Application serialized = APPLICATION_SERIALIZER.fromSlime(SlimeUtils.toJsonBytes(APPLICATION_SERIALIZER.toSlime(original)));
@@ -156,9 +159,9 @@ public class ApplicationSerializerTest {
assertEquals(original.latestVersion().get().sourceUrl(), serialized.latestVersion().get().sourceUrl());
assertEquals(original.latestVersion().get().commit(), serialized.latestVersion().get().commit());
assertEquals(original.latestVersion().get().bundleHash(), serialized.latestVersion().get().bundleHash());
- assertEquals(original.versions(), serialized.versions());
- assertEquals(original.versions(), serialized.versions());
-
+ assertEquals(original.revisions().withPackage(), serialized.revisions().withPackage());
+ assertEquals(original.revisions().production(), serialized.revisions().production());
+ assertEquals(original.revisions().development(), serialized.revisions().development());
assertEquals(original.deploymentSpec().xmlForm(), serialized.deploymentSpec().xmlForm());
assertEquals(original.validationOverrides().xmlForm(), serialized.validationOverrides().xmlForm());