summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJon Bratseth <bratseth@oath.com>2018-01-26 13:00:02 +0100
committerJon Bratseth <bratseth@oath.com>2018-01-26 13:00:02 +0100
commit66348be0c92c43adf5c09def6664923373e4d6be (patch)
treed4dfa114915c7b99a270c12ad8c2baed5e156051
parent8d0c9c8f584f29348f3efee6e566341e26da55e4 (diff)
Make ApplicationChange mandatory
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java33
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationVersion.java22
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Change.java36
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/JobStatus.java12
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java5
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java36
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java28
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java35
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java12
9 files changed, 121 insertions, 98 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 c054d81db35..bb1f64c121c 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
@@ -30,9 +30,9 @@ import java.util.stream.Collectors;
/**
* An instance of an application.
- *
+ *
* This is immutable.
- *
+ *
* @author bratseth
*/
public class Application {
@@ -57,11 +57,11 @@ public class Application {
}
/** Used from persistence layer: Do not use */
- public Application(ApplicationId id, DeploymentSpec deploymentSpec, ValidationOverrides validationOverrides,
+ public Application(ApplicationId id, DeploymentSpec deploymentSpec, ValidationOverrides validationOverrides,
List<Deployment> deployments, DeploymentJobs deploymentJobs, Optional<Change> deploying,
boolean outstandingChange, Optional<IssueId> ownershipIssueId, ApplicationMetrics metrics,
Optional<RotationId> rotation) {
- this(id, deploymentSpec, validationOverrides,
+ this(id, deploymentSpec, validationOverrides,
deployments.stream().collect(Collectors.toMap(Deployment::zone, d -> d)),
deploymentJobs, deploying, outstandingChange, ownershipIssueId, metrics, rotation);
}
@@ -91,24 +91,24 @@ public class Application {
}
public ApplicationId id() { return id; }
-
- /**
- * Returns the last deployed deployment spec of this application,
- * or the empty deployment spec if it has never been deployed
+
+ /**
+ * Returns the last deployed deployment spec of this application,
+ * or the empty deployment spec if it has never been deployed
*/
public DeploymentSpec deploymentSpec() { return deploymentSpec; }
/**
- * Returns the last deployed validation overrides of this application,
+ * Returns the last deployed validation overrides of this application,
* or the empty validation overrides if it has never been deployed
* (or was deployed with an empty/missing validation overrides)
*/
public ValidationOverrides validationOverrides() { return validationOverrides; }
-
+
/** Returns an immutable map of the current deployments of this */
public Map<ZoneId, Deployment> deployments() { return deployments; }
- /**
+ /**
* Returns an immutable map of the current *production* deployments of this
* (deployments also includes manually deployed environments)
*/
@@ -121,7 +121,7 @@ public class Application {
public DeploymentJobs deploymentJobs() { return deploymentJobs; }
/**
- * Returns the change that is currently in the process of being deployed on this application,
+ * Returns the change that is currently in the process of being deployed on this application,
* or empty if no change is currently being deployed.
*/
public Optional<Change> deploying() { return deploying; }
@@ -166,8 +166,13 @@ public class Application {
/** Returns the application version a deployment to this zone should use, or empty if we don't know */
public Optional<ApplicationVersion> deployApplicationVersionIn(ZoneId zone) {
- if (deploying().isPresent() && deploying().get() instanceof Change.ApplicationChange)
- return ((Change.ApplicationChange) deploying().get()).version();
+ if (deploying().isPresent() && deploying().get() instanceof Change.ApplicationChange) {
+ ApplicationVersion version = ((Change.ApplicationChange) deploying().get()).version();
+ if (version == ApplicationVersion.unknown)
+ return Optional.empty();
+ else
+ return Optional.of(version);
+ }
return applicationVersionIn(zone);
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationVersion.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationVersion.java
index aee178af275..304d82b2bec 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationVersion.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationVersion.java
@@ -13,15 +13,25 @@ import java.util.Optional;
*/
public class ApplicationVersion {
+ // TODO: Remove the need for this
+ public static final ApplicationVersion unknown = new ApplicationVersion();
+
// Never changes. Only used to create a valid version number for the bundle
private static final String majorVersion = "1.0";
// TODO: Remove after introducing new application version
private final Optional<String> applicationPackageHash;
+ // TODO: Make mandatory
private final Optional<SourceRevision> source;
private final Optional<Long> buildNumber;
+ private ApplicationVersion() {
+ this.applicationPackageHash = Optional.empty();
+ this.source = Optional.empty();
+ this.buildNumber = Optional.empty();
+ }
+
private ApplicationVersion(Optional<String> applicationPackageHash, Optional<SourceRevision> source,
Optional<Long> buildNumber) {
Objects.requireNonNull(applicationPackageHash, "applicationPackageHash cannot be null");
@@ -30,14 +40,14 @@ public class ApplicationVersion {
if (buildNumber.isPresent() && !source.isPresent()) {
throw new IllegalArgumentException("both buildNumber and source must be set if buildNumber is set");
}
- if (!buildNumber.isPresent() && !applicationPackageHash.isPresent()) {
+ if ( ! buildNumber.isPresent() && ! applicationPackageHash.isPresent()) {
throw new IllegalArgumentException("applicationPackageHash must be given if buildNumber is unset");
}
this.applicationPackageHash = applicationPackageHash;
this.source = source;
this.buildNumber = buildNumber;
}
-
+
/** Create an application package revision where there is no information about its source */
public static ApplicationVersion from(String applicationPackageHash) {
return new ApplicationVersion(Optional.of(applicationPackageHash), Optional.empty(), Optional.empty());
@@ -61,7 +71,7 @@ public class ApplicationVersion {
return String.format("%s.%d-%s", majorVersion, buildNumber.get(), abbreviateCommit(source.get().commit()));
}
- /**
+ /**
* Returns information about the source of this revision, or empty if the source is not know/defined
* (which is the case for command-line deployment from developers, but never for deployment jobs)
*/
@@ -69,17 +79,17 @@ public class ApplicationVersion {
/** Returns the build number that built this version */
public Optional<Long> buildNumber() { return buildNumber; }
-
+
@Override
public int hashCode() { return applicationPackageHash.hashCode(); }
-
+
@Override
public boolean equals(Object other) {
if (this == other) return true;
if ( ! (other instanceof ApplicationVersion)) return false;
return this.applicationPackageHash.equals(((ApplicationVersion)other).applicationPackageHash);
}
-
+
@Override
public String toString() {
if (buildNumber.isPresent()) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Change.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Change.java
index 08291373656..aeed510fa7e 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Change.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Change.java
@@ -6,31 +6,29 @@ import com.yahoo.config.application.api.DeploymentSpec;
import java.time.Instant;
import java.util.Objects;
-import java.util.Optional;
/**
* A change to an application
- *
+ *
* @author bratseth
*/
public abstract class Change {
/** Returns true if this change is blocked by the given spec at the given instant */
public abstract boolean blockedBy(DeploymentSpec deploymentSpec, Instant instant);
-
+
/** A change to the application package version of an application */
public static class ApplicationChange extends Change {
- // TODO: Make non-optional
- private final Optional<ApplicationVersion> version;
-
- private ApplicationChange(Optional<ApplicationVersion> version) {
+ private final ApplicationVersion version;
+
+ private ApplicationChange(ApplicationVersion version) {
Objects.requireNonNull(version, "version cannot be null");
this.version = version;
}
-
+
/** The application package version in this change, or empty if not known yet */
- public Optional<ApplicationVersion> version() { return version; }
+ public ApplicationVersion version() { return version; }
@Override
public boolean blockedBy(DeploymentSpec deploymentSpec, Instant instant) {
@@ -39,7 +37,7 @@ public abstract class Change {
@Override
public int hashCode() { return version.hashCode(); }
-
+
@Override
public boolean equals(Object other) {
if (this == other) return true;
@@ -47,25 +45,25 @@ public abstract class Change {
return ((ApplicationChange)other).version.equals(this.version);
}
- /**
+ /**
* Creates an application change which we don't know anything about.
* We are notified that a change has occurred by completion of the component job
* but do not get to know about what the change is until a subsequent deployment
* happens.
*/
public static ApplicationChange unknown() {
- return new ApplicationChange(Optional.empty());
+ return new ApplicationChange(ApplicationVersion.unknown);
}
-
+
public static ApplicationChange of(ApplicationVersion version) {
- return new ApplicationChange(Optional.of(version));
+ return new ApplicationChange(version);
}
@Override
- public String toString() {
- return "application change to " + version.map(ApplicationVersion::toString).orElse("an unknown version");
+ public String toString() {
+ return "application change to " + version;
}
-
+
}
/** A change to the Vespa version running an application */
@@ -97,8 +95,8 @@ public abstract class Change {
}
@Override
- public String toString() {
- return "version change to " + version;
+ public String toString() {
+ return "version change to " + version;
}
}
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 71c4a380a29..6a29d4990e7 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
@@ -55,6 +55,13 @@ public class JobStatus {
return new JobStatus(type, Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty());
}
+ public JobStatus withTriggering(Version version, ApplicationVersion applicationVersion,
+ boolean upgrade, String reason, Instant triggerTime) {
+ return withTriggering(version,
+ applicationVersion == ApplicationVersion.unknown ? Optional.empty() : Optional.of(applicationVersion),
+ upgrade, reason, triggerTime);
+ }
+
public JobStatus withTriggering(Version version, Optional<ApplicationVersion> applicationVersion,
boolean upgrade, String reason, Instant triggerTime) {
return new JobStatus(type, jobError, Optional.of(new JobRun(-1, version, applicationVersion, upgrade, reason, triggerTime)),
@@ -212,7 +219,10 @@ public class JobStatus {
public boolean lastCompletedWas(Change change) {
if (change instanceof Change.ApplicationChange) {
Change.ApplicationChange applicationChange = (Change.ApplicationChange) change;
- return applicationVersion().equals(applicationChange.version());
+ if ( ! applicationVersion().isPresent())
+ return applicationChange.version() == ApplicationVersion.unknown;
+ else
+ return applicationVersion().get().equals(applicationChange.version());
} else if (change instanceof Change.VersionChange) {
Change.VersionChange versionChange = (Change.VersionChange) change;
return version().equals(versionChange.version());
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 09768796445..6fd25f1a8c6 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
@@ -144,15 +144,14 @@ public class DeploymentTrigger {
if (change instanceof VersionChange) {
if (((VersionChange)change).version().isAfter(deployment.version())) return false; // later is ok
}
- else if (((Change.ApplicationChange)change).version().isPresent()) {
- if ( ! ((Change.ApplicationChange)change).version().get().equals(deployment.applicationVersion())) return false;
+ else if (((Change.ApplicationChange)change).version() != ApplicationVersion.unknown) {
+ if ( ! ((Change.ApplicationChange)change).version().equals(deployment.applicationVersion())) return false;
}
else {
return false; // If we don't yet know the application version we are deploying, then we are not complete
}
}
-
return true;
}
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 1514d98610a..1664e984a27 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
@@ -38,7 +38,7 @@ import java.util.Optional;
/**
* Serializes applications to/from slime.
* This class is multithread safe.
- *
+ *
* @author bratseth
*/
public class ApplicationSerializer {
@@ -67,12 +67,12 @@ public class ApplicationSerializer {
private final String repositoryField = "repositoryField";
private final String branchField = "branchField";
private final String commitField = "commitField";
-
+
// DeploymentJobs fields
private final String projectIdField = "projectId";
private final String jobStatusField = "jobStatus";
private final String issueIdField = "jiraIssueId";
-
+
// JobStatus field
private final String jobTypeField = "jobType";
private final String errorField = "jobError";
@@ -80,7 +80,7 @@ public class ApplicationSerializer {
private final String lastCompletedField = "lastCompleted";
private final String firstFailingField = "firstFailing";
private final String lastSuccessField = "lastSuccess";
-
+
// JobRun fields
private final String jobRunIdField = "id";
private final String versionField = "version";
@@ -116,7 +116,7 @@ public class ApplicationSerializer {
// ------------------ Serialization
-
+
public Slime toSlime(Application application) {
Slime slime = new Slime();
Cursor root = slime.setObject();
@@ -138,7 +138,7 @@ public class ApplicationSerializer {
for (Deployment deployment : deployments)
deploymentToSlime(deployment, array.addObject());
}
-
+
private void deploymentToSlime(Deployment deployment, Cursor object) {
zoneIdToSlime(deployment.zone(), object.setObject(zoneField));
object.setString(versionField, deployment.version().toString());
@@ -196,19 +196,19 @@ public class ApplicationSerializer {
object.setString(environmentField, zone.environment().value());
object.setString(regionField, zone.region().value());
}
-
+
private void toSlime(ApplicationVersion applicationVersion, Cursor object) {
object.setString(applicationPackageHashField, applicationVersion.id());
if (applicationVersion.source().isPresent())
toSlime(applicationVersion.source().get(), object.setObject(sourceRevisionField));
}
-
+
private void toSlime(SourceRevision sourceRevision, Cursor object) {
object.setString(repositoryField, sourceRevision.repository());
object.setString(branchField, sourceRevision.branch());
object.setString(commitField, sourceRevision.commit());
}
-
+
private void toSlime(DeploymentJobs deploymentJobs, Cursor cursor) {
deploymentJobs.projectId().ifPresent(projectId -> cursor.setLong(projectIdField, projectId));
jobStatusToSlime(deploymentJobs.jobStatus().values(), cursor.setArray(jobStatusField));
@@ -219,7 +219,7 @@ public class ApplicationSerializer {
for (JobStatus jobStatus : jobStatuses)
toSlime(jobStatus, jobStatusArray.addObject());
}
-
+
private void toSlime(JobStatus jobStatus, Cursor object) {
object.setString(jobTypeField, jobStatus.type().jobName());
if (jobStatus.jobError().isPresent())
@@ -230,7 +230,7 @@ public class ApplicationSerializer {
jobRunToSlime(jobStatus.firstFailing(), object, firstFailingField);
jobRunToSlime(jobStatus.lastSuccess(), object, lastSuccessField);
}
-
+
private void jobRunToSlime(Optional<JobStatus.JobRun> jobRun, Cursor parent, String jobRunObjectName) {
if ( ! jobRun.isPresent()) return;
Cursor object = parent.setObject(jobRunObjectName);
@@ -242,22 +242,22 @@ public class ApplicationSerializer {
object.setString(reasonField, jobRun.get().reason());
object.setLong(atField, jobRun.get().at().toEpochMilli());
}
-
+
private void toSlime(Optional<Change> deploying, Cursor parentObject) {
if ( ! deploying.isPresent()) return;
Cursor object = parentObject.setObject(deployingField);
if (deploying.get() instanceof Change.VersionChange)
object.setString(versionField, ((Change.VersionChange)deploying.get()).version().toString());
- else if (((Change.ApplicationChange)deploying.get()).version().isPresent())
- toSlime(((Change.ApplicationChange)deploying.get()).version().get(), object);
+ else if (((Change.ApplicationChange)deploying.get()).version() != ApplicationVersion.unknown)
+ toSlime(((Change.ApplicationChange)deploying.get()).version(), object);
}
// ------------------ Deserialization
public Application fromSlime(Slime slime) {
Inspector root = slime.get();
-
+
ApplicationId id = ApplicationId.fromSerializedForm(root.field(idField).asString());
DeploymentSpec deploymentSpec = DeploymentSpec.fromXml(root.field(deploymentSpecField).asString(), false);
ValidationOverrides validationOverrides = ValidationOverrides.fromXml(root.field(validationOverridesField).asString());
@@ -347,7 +347,7 @@ public class ApplicationSerializer {
return sourceRevision.isPresent() ? Optional.of(ApplicationVersion.from(applicationPackageHash, sourceRevision.get()))
: Optional.of(ApplicationVersion.from(applicationPackageHash));
}
-
+
private Optional<SourceRevision> sourceRevisionFromSlime(Inspector object) {
if ( ! object.valid()) return Optional.empty();
return Optional.of(new SourceRevision(object.field(repositoryField).asString(),
@@ -373,13 +373,13 @@ public class ApplicationSerializer {
else
return Optional.of(Change.ApplicationChange.unknown());
}
-
+
private List<JobStatus> jobStatusListFromSlime(Inspector array) {
List<JobStatus> jobStatusList = new ArrayList<>();
array.traverse((ArrayTraverser) (int i, Inspector item) -> jobStatusList.add(jobStatusFromSlime(item)));
return jobStatusList;
}
-
+
private JobStatus jobStatusFromSlime(Inspector object) {
DeploymentJobs.JobType jobType = DeploymentJobs.JobType.fromJobName(object.field(jobTypeField).asString());
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 a489e1d9f63..3cca0fb6318 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
@@ -98,7 +98,7 @@ import java.util.logging.Level;
/**
* This implements the application/v4 API which is used to deploy and manage applications
* on hosted Vespa.
- *
+ *
* @author bratseth
* @author mpolden
*/
@@ -123,7 +123,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
public Duration getTimeout() {
return Duration.ofMinutes(20); // deploys may take a long time;
}
-
+
@Override
public HttpResponse handle(HttpRequest request) {
try {
@@ -156,7 +156,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
return ErrorResponse.internalServerError(Exceptions.toMessageString(e));
}
}
-
+
private HttpResponse handleGET(HttpRequest request) {
Path path = new Path(request.getUri().getPath());
if (path.matches("/application/v4/")) return root(request);
@@ -213,7 +213,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
return setGlobalRotationOverride(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), true, request);
return ErrorResponse.notFoundError("Nothing at " + path);
}
-
+
private HttpResponse handleOPTIONS() {
// We implement this to avoid redirect loops on OPTIONS requests from browsers, but do not really bother
// spelling out the methods supported at each path, which we should
@@ -235,7 +235,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
? recursiveRoot(request)
: new ResourceResponse(request, "user", "tenant", "tenant-pipeline", "athensDomain", "property", "cookiefreshness");
}
-
+
private HttpResponse authenticatedUser(HttpRequest request) {
String userIdString = request.getProperty("userOverride");
if (userIdString == null)
@@ -243,7 +243,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
.map(UserId::id)
.orElseThrow(() -> new ForbiddenException("You must be authenticated or specify userOverride"));
UserId userId = new UserId(userIdString);
-
+
List<Tenant> tenants = controller.tenants().asList(userId);
Slime slime = new Slime();
@@ -255,7 +255,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
response.setBool("tenantExists", tenants.stream().map(Tenant::getId).anyMatch(id -> id.isTenantFor(userId)));
return new SlimeJsonResponse(slime);
}
-
+
private HttpResponse tenants(HttpRequest request) {
Slime slime = new Slime();
Cursor response = slime.setArray();
@@ -263,7 +263,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
tenantInTenantsListToSlime(tenant, request.getUri(), response.addObject());
return new SlimeJsonResponse(slime);
}
-
+
/** Lists the screwdriver project id for each application */
private HttpResponse tenantPipelines() {
Slime slime = new Slime();
@@ -281,7 +281,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
response.setArray("brokenTenantPipelines"); // not used but may need to be present
return new SlimeJsonResponse(slime);
}
-
+
private HttpResponse athenzDomains(HttpRequest request) {
Slime slime = new Slime();
Cursor response = slime.setObject();
@@ -307,7 +307,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
private HttpResponse cookieFreshness(HttpRequest request) {
Slime slime = new Slime();
String passThruHeader = request.getHeader(SetBouncerPassthruHeaderFilter.BOUNCER_PASSTHRU_HEADER_FIELD);
- slime.setObject().setBool("shouldRefreshCookie",
+ slime.setObject().setBool("shouldRefreshCookie",
! SetBouncerPassthruHeaderFilter.BOUNCER_PASSTHRU_COOKIE_OK.equals(passThruHeader));
return new SlimeJsonResponse(slime);
}
@@ -332,7 +332,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
toSlime(application, array.addObject(), request);
return new SlimeJsonResponse(slime);
}
-
+
private HttpResponse application(String tenantName, String applicationName, HttpRequest request) {
ApplicationId applicationId = ApplicationId.from(tenantName, applicationName, "default");
Application application =
@@ -352,8 +352,8 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
Cursor deployingObject = object.setObject("deploying");
if (application.deploying().get() instanceof Change.VersionChange)
deployingObject.setString("version", ((Change.VersionChange)application.deploying().get()).version().toString());
- else if (((Change.ApplicationChange)application.deploying().get()).version().isPresent())
- toSlime(((Change.ApplicationChange)application.deploying().get()).version().get(), deployingObject.setObject("revision"));
+ else if (((Change.ApplicationChange)application.deploying().get()).version() != ApplicationVersion.unknown)
+ toSlime(((Change.ApplicationChange)application.deploying().get()).version(), deployingObject.setObject("revision"));
}
// Jobs sorted according to deployment spec
@@ -594,7 +594,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
response.setResponse(result, serviceName, restPath);
return response;
}
-
+
private HttpResponse createUser(HttpRequest request) {
Optional<UserId> user = userFrom(request);
if ( ! user.isPresent() ) throw new ForbiddenException("Not authenticated.");
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 1127e739689..8c242ef1150 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
@@ -101,14 +101,15 @@ public class ControllerTest {
Version version1 = Version.fromString("6.1"); // Set in config server mock
Application app1 = tester.createApplication("app1", "tenant1", 1, 11L);
tester.notifyJobCompletion(component, app1, true);
- assertFalse("Application version is currently not known",
- ((Change.ApplicationChange)tester.controller().applications().require(app1.id()).deploying().get()).version().isPresent());
+ assertEquals("Application version is currently not known",
+ ApplicationVersion.unknown,
+ ((Change.ApplicationChange)tester.controller().applications().require(app1.id()).deploying().get()).version());
tester.deployAndNotify(app1, applicationPackage, true, systemTest);
tester.deployAndNotify(app1, applicationPackage, true, stagingTest);
assertEquals(4, applications.require(app1.id()).deploymentJobs().jobStatus().size());
- Optional<ApplicationVersion> applicationVersion = ((Change.ApplicationChange)tester.controller().applications().require(app1.id()).deploying().get()).version();
- assertTrue("Application version has been set during deployment", applicationVersion.isPresent());
+ ApplicationVersion applicationVersion = ((Change.ApplicationChange)tester.controller().applications().require(app1.id()).deploying().get()).version();
+ assertTrue("Application version has been set during deployment", applicationVersion != ApplicationVersion.unknown);
assertStatus(JobStatus.initial(stagingTest)
.withTriggering(version1, applicationVersion, false, "", tester.clock().instant().minus(Duration.ofMillis(1)))
.withCompletion(42, Optional.empty(), tester.clock().instant(), tester.controller()), app1.id(), tester.controller());
@@ -166,7 +167,7 @@ public class ControllerTest {
tester.deployAndNotify(app1, applicationPackage, true, productionUsEast3);
assertEquals(5, applications.get(app1.id()).get().deploymentJobs().jobStatus().size());
-
+
// prod zone removal is not allowed
applicationPackage = new ApplicationPackageBuilder()
.environment(Environment.prod)
@@ -178,7 +179,7 @@ public class ControllerTest {
fail("Expected exception due to unallowed production deployment removal");
}
catch (IllegalArgumentException e) {
- assertEquals("deployment-removal: application 'tenant1.app1' is deployed in corp-us-east-1, but does not include this zone in deployment.xml", e.getMessage());
+ assertEquals("deployment-removal: application 'tenant1.app1' is deployed in corp-us-east-1, but does not include this zone in deployment.xml", e.getMessage());
}
assertNotNull("Zone was not removed",
applications.require(app1.id()).deployments().get(productionCorpUsEast1.zone(SystemName.main).get()));
@@ -223,7 +224,7 @@ public class ControllerTest {
assertEquals(expectedVersionString, ((Change.ApplicationChange) tester.controller().applications()
.require(app1.id())
.deploying()
- .get()).version().get().id());
+ .get()).version().id());
// Deploy without application package
tester.deployAndNotify(app1, true, systemTest);
@@ -325,7 +326,7 @@ public class ControllerTest {
applications.require(app1.id()).deployments().get(productionCorpUsEast1.zone(SystemName.main).get()));
assertNull("Deployment job was removed", applications.require(app1.id()).deploymentJobs().jobStatus().get(productionCorpUsEast1));
}
-
+
@Test
public void testDeployVersion() {
// Setup system
@@ -415,7 +416,7 @@ public class ControllerTest {
controller.updateVersionStatus(new VersionStatus(versions));
return newSystemVersion;
}
-
+
@Test
public void testPullRequestDeployment() {
// Setup system
@@ -427,7 +428,7 @@ public class ControllerTest {
ApplicationId app1 = tester.createAndDeploy("tenant1", "domain1",
"application1", Environment.staging,
app1ProjectId).id();
-
+
// pull-request deployment - uses different instance id
ApplicationId app1pr = tester.createAndDeploy("tenant1", "domain1",
"application1", "default-pr1",
@@ -461,7 +462,7 @@ public class ControllerTest {
.filter(app -> app.id().application().equals(app2.application()))
.count());
}
-
+
@Test
public void testFailingSinceUpdates() {
// Setup system
@@ -474,13 +475,13 @@ public class ControllerTest {
Instant initialFailure = tester.clock().instant();
tester.notifyJobCompletion(component, app, true);
tester.deployAndNotify(app, applicationPackage, false, systemTest);
- assertEquals("Failure age is right at initial failure",
+ assertEquals("Failure age is right at initial failure",
initialFailure.plus(Duration.ofMillis(2)), firstFailing(app, tester).get().at());
// Failure again -- failingSince should remain the same
tester.clock().advance(Duration.ofMillis(1000));
tester.deployAndNotify(app, applicationPackage, false, systemTest);
- assertEquals("Failure age is right at second consecutive failure",
+ assertEquals("Failure age is right at second consecutive failure",
initialFailure.plus(Duration.ofMillis(2)), firstFailing(app, tester).get().at());
// Success resets failingSince
@@ -491,27 +492,27 @@ public class ControllerTest {
// Complete deployment
tester.deployAndNotify(app, applicationPackage, true, stagingTest);
tester.deployAndNotify(app, applicationPackage, true, productionCorpUsEast1);
-
+
// Two repeated failures again.
// Initial failure
tester.clock().advance(Duration.ofMillis(1000));
initialFailure = tester.clock().instant();
tester.notifyJobCompletion(component, app, true);
tester.deployAndNotify(app, applicationPackage, false, systemTest);
- assertEquals("Failure age is right at initial failure",
+ assertEquals("Failure age is right at initial failure",
initialFailure.plus(Duration.ofMillis(2)), firstFailing(app, tester).get().at());
// Failure again -- failingSince should remain the same
tester.clock().advance(Duration.ofMillis(1000));
tester.deployAndNotify(app, applicationPackage, false, systemTest);
- assertEquals("Failure age is right at second consecutive failure",
+ assertEquals("Failure age is right at second consecutive failure",
initialFailure.plus(Duration.ofMillis(2)), firstFailing(app, tester).get().at());
}
private Optional<JobStatus.JobRun> firstFailing(Application application, DeploymentTester tester) {
return tester.controller().applications().get(application.id()).get().deploymentJobs().jobStatus().get(systemTest).firstFailing();
}
-
+
@Test
public void testMigratingTenantToAthenzWillModifyAthenzDomainsCorrectly() {
ControllerTester tester = new ControllerTester();
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 f42a4c1deb3..3b099d7e7ed 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
@@ -149,16 +149,16 @@ public class ApplicationSerializerTest {
.from("hash1"))));
Application serialized2 = applicationSerializer.fromSlime(applicationSerializer.toSlime(original2));
assertEquals(original2.deploying(), serialized2.deploying());
- assertEquals(((Change.ApplicationChange)serialized2.deploying().get()).version().get().source(),
- ((Change.ApplicationChange)original2.deploying().get()).version().get().source());
+ assertEquals(((Change.ApplicationChange)serialized2.deploying().get()).version().source(),
+ ((Change.ApplicationChange)original2.deploying().get()).version().source());
Application original3 = writable(original).withDeploying(Optional.of(Change.ApplicationChange.of(ApplicationVersion
.from("hash1",
new SourceRevision("a", "b", "c")))));
Application serialized3 = applicationSerializer.fromSlime(applicationSerializer.toSlime(original3));
assertEquals(original3.deploying(), serialized2.deploying());
- assertEquals(((Change.ApplicationChange)serialized3.deploying().get()).version().get().source(),
- ((Change.ApplicationChange)original3.deploying().get()).version().get().source());
+ assertEquals(((Change.ApplicationChange)serialized3.deploying().get()).version().source(),
+ ((Change.ApplicationChange)original3.deploying().get()).version().source());
Application original4 = writable(original).withDeploying(Optional.empty());
Application serialized4 = applicationSerializer.fromSlime(applicationSerializer.toSlime(original4));
@@ -210,7 +210,7 @@ public class ApplicationSerializerTest {
Application application = applicationSerializer.fromSlime(applicationSlime(false));
assertFalse(application.deploymentJobs().jobStatus().get(DeploymentJobs.JobType.systemTest).lastCompleted().get().upgrade());
}
-
+
@Test
public void testCompleteApplicationDeserialization() {
applicationSerializer.fromSlime(SlimeUtils.jsonToSlime(longApplicationJson.getBytes(StandardCharsets.UTF_8)));
@@ -253,6 +253,6 @@ public class ApplicationSerializerTest {
" }\n" +
"}\n";
}
-
+
private final String longApplicationJson = "{\"id\":\"tripod:service-aggregation-vespa:default\",\"deploymentSpecField\":\"<deployment version='1.0'>\\n <test />\\n <!--<staging />-->\\n <prod global-service-id=\\\"tripod\\\">\\n <region active=\\\"true\\\">us-east-3</region>\\n <region active=\\\"true\\\">us-west-1</region>\\n </prod>\\n</deployment>\\n\",\"validationOverrides\":\"<validation-overrides>\\n <allow until=\\\"2016-04-28\\\" comment=\\\"Renaming content cluster\\\">content-cluster-removal</allow>\\n <allow until=\\\"2016-08-22\\\" comment=\\\"Migrating us-east-3 to C-2E\\\">cluster-size-reduction</allow>\\n <allow until=\\\"2017-06-30\\\" comment=\\\"Test Vespa upgrade tests\\\">force-automatic-tenant-upgrade-test</allow>\\n</validation-overrides>\\n\",\"deployments\":[{\"zone\":{\"environment\":\"prod\",\"region\":\"us-west-1\"},\"version\":\"6.173.62\",\"deployTime\":1510837817704,\"applicationPackageRevision\":{\"applicationPackageHash\":\"9db423e1021d7b452d37ec6372bc757d9c1bda87\",\"sourceRevision\":{\"repositoryField\":\"git@git.ouroath.com:Tripod/service-aggregation-vespa.git\",\"branchField\":\"origin/master\",\"commitField\":\"49cd7bbb1ed9f4b922083cb042590b0885ffe22b\"}},\"clusterInfo\":{\"tripod\":{\"flavor\":\"d-3-16-100\",\"cost\":9,\"flavorCpu\":0,\"flavorMem\":0,\"flavorDisk\":0,\"clusterType\":\"container\",\"hostnames\":[\"oxy-oxygen-2001-4998-c-2942--10d1.gq1.yahoo.com\",\"oxy-oxygen-2001-4998-c-2942--10e2.gq1.yahoo.com\"]},\"tripodaggregation\":{\"flavor\":\"d-12-64-400\",\"cost\":38,\"flavorCpu\":0,\"flavorMem\":0,\"flavorDisk\":0,\"clusterType\":\"content\",\"hostnames\":[\"oxy-oxygen-2001-4998-c-2941--106a.gq1.yahoo.com\",\"zt74700-v6-23.ostk.bm2.prod.gq1.yahoo.com\",\"zt74714-v6-28.ostk.bm2.prod.gq1.yahoo.com\",\"zt74730-v6-13.ostk.bm2.prod.gq1.yahoo.com\",\"zt74717-v6-7.ostk.bm2.prod.gq1.yahoo.com\",\"2080260-v6-12.ostk.bm2.prod.gq1.yahoo.com\",\"zt74719-v6-23.ostk.bm2.prod.gq1.yahoo.com\",\"zt74722-v6-26.ostk.bm2.prod.gq1.yahoo.com\",\"zt74704-v6-9.ostk.bm2.prod.gq1.yahoo.com\",\"oxy-oxygen-2001-4998-c-2942--107d.gq1.yahoo.com\"]},\"tripodaggregationstream\":{\"flavor\":\"d-12-64-400\",\"cost\":38,\"flavorCpu\":0,\"flavorMem\":0,\"flavorDisk\":0,\"clusterType\":\"content\",\"hostnames\":[\"zt74727-v6-21.ostk.bm2.prod.gq1.yahoo.com\",\"zt74773-v6-8.ostk.bm2.prod.gq1.yahoo.com\",\"zt74699-v6-25.ostk.bm2.prod.gq1.yahoo.com\",\"zt74766-v6-27.ostk.bm2.prod.gq1.yahoo.com\"]}},\"clusterUtils\":{\"tripod\":{\"cpu\":0.1720353499228221,\"mem\":0.4986146831512451,\"disk\":0.0617671330041831,\"diskbusy\":0},\"tripodaggregation\":{\"cpu\":0.07505730001866318,\"mem\":0.7936344432830811,\"disk\":0.2260549694485994,\"diskbusy\":0},\"tripodaggregationstream\":{\"cpu\":0.01712671480989384,\"mem\":0.0225852754983035,\"disk\":0.006084436856721915,\"diskbusy\":0}},\"metrics\":{\"queriesPerSecond\":1.25,\"writesPerSecond\":43.83199977874756,\"documentCount\":525880277.9999999,\"queryLatencyMillis\":5.607503938674927,\"writeLatencyMillis\":20.57866265104621}},{\"zone\":{\"environment\":\"test\",\"region\":\"us-east-1\"},\"version\":\"6.173.62\",\"deployTime\":1511256872316,\"applicationPackageRevision\":{\"applicationPackageHash\":\"ec548fa61cbfab7a270a51d46b1263ec1be5d9a8\",\"sourceRevision\":{\"repositoryField\":\"git@git.ouroath.com:Tripod/service-aggregation-vespa.git\",\"branchField\":\"origin/master\",\"commitField\":\"234f3e4e77049d0b9538c9e1b356d17eb1dedb6a\"}},\"clusterInfo\":{},\"clusterUtils\":{},\"metrics\":{\"queriesPerSecond\":0,\"writesPerSecond\":0,\"documentCount\":0,\"queryLatencyMillis\":0,\"writeLatencyMillis\":0}},{\"zone\":{\"environment\":\"dev\",\"region\":\"us-east-1\"},\"version\":\"6.173.62\",\"deployTime\":1510597489464,\"applicationPackageRevision\":{\"applicationPackageHash\":\"59b883f263c2a3c23dfab249730097d7e0e1ed32\"},\"clusterInfo\":{\"tripod\":{\"flavor\":\"d-2-8-50\",\"cost\":5,\"flavorCpu\":0,\"flavorMem\":0,\"flavorDisk\":0,\"clusterType\":\"container\",\"hostnames\":[\"zt40807-v6-29.ostk.bm2.prod.bf1.yahoo.com\"]},\"tripodaggregation\":{\"flavor\":\"d-2-8-50\",\"cost\":5,\"flavorCpu\":0,\"flavorMem\":0,\"flavorDisk\":0,\"clusterType\":\"content\",\"hostnames\":[\"zt40807-v6-24.ostk.bm2.prod.bf1.yahoo.com\"]},\"tripodaggregationstream\":{\"flavor\":\"d-2-8-50\",\"cost\":5,\"flavorCpu\":0,\"flavorMem\":0,\"flavorDisk\":0,\"clusterType\":\"content\",\"hostnames\":[\"zt40694-v6-21.ostk.bm2.prod.bf1.yahoo.com\"]}},\"clusterUtils\":{\"tripod\":{\"cpu\":0.191833330678661,\"mem\":0.4625738318415235,\"disk\":0.05582004563850269,\"diskbusy\":0},\"tripodaggregation\":{\"cpu\":0.2227037978608054,\"mem\":0.2051752598416401,\"disk\":0.05471533698695047,\"diskbusy\":0},\"tripodaggregationstream\":{\"cpu\":0.1869410834020498,\"mem\":0.1691722576000564,\"disk\":0.04977374774258153,\"diskbusy\":0}},\"metrics\":{\"queriesPerSecond\":0,\"writesPerSecond\":0,\"documentCount\":30916,\"queryLatencyMillis\":0,\"writeLatencyMillis\":0}},{\"zone\":{\"environment\":\"prod\",\"region\":\"us-east-3\"},\"version\":\"6.173.62\",\"deployTime\":1510817190016,\"applicationPackageRevision\":{\"applicationPackageHash\":\"9db423e1021d7b452d37ec6372bc757d9c1bda87\",\"sourceRevision\":{\"repositoryField\":\"git@git.ouroath.com:Tripod/service-aggregation-vespa.git\",\"branchField\":\"origin/master\",\"commitField\":\"49cd7bbb1ed9f4b922083cb042590b0885ffe22b\"}},\"clusterInfo\":{\"tripod\":{\"flavor\":\"d-3-16-100\",\"cost\":9,\"flavorCpu\":0,\"flavorMem\":0,\"flavorDisk\":0,\"clusterType\":\"container\",\"hostnames\":[\"zt40738-v6-13.ostk.bm2.prod.bf1.yahoo.com\",\"zt40783-v6-31.ostk.bm2.prod.bf1.yahoo.com\"]},\"tripodaggregation\":{\"flavor\":\"d-12-64-400\",\"cost\":38,\"flavorCpu\":0,\"flavorMem\":0,\"flavorDisk\":0,\"clusterType\":\"content\",\"hostnames\":[\"zt40819-v6-7.ostk.bm2.prod.bf1.yahoo.com\",\"zt40661-v6-3.ostk.bm2.prod.bf1.yahoo.com\",\"zt40805-v6-30.ostk.bm2.prod.bf1.yahoo.com\",\"zt40702-v6-32.ostk.bm2.prod.bf1.yahoo.com\",\"zt40706-v6-3.ostk.bm2.prod.bf1.yahoo.com\",\"zt40691-v6-27.ostk.bm2.prod.bf1.yahoo.com\",\"zt40676-v6-15.ostk.bm2.prod.bf1.yahoo.com\",\"zt40788-v6-23.ostk.bm2.prod.bf1.yahoo.com\",\"zt40782-v6-30.ostk.bm2.prod.bf1.yahoo.com\",\"zt40802-v6-32.ostk.bm2.prod.bf1.yahoo.com\"]},\"tripodaggregationstream\":{\"flavor\":\"d-12-64-400\",\"cost\":38,\"flavorCpu\":0,\"flavorMem\":0,\"flavorDisk\":0,\"clusterType\":\"content\",\"hostnames\":[\"zt40779-v6-27.ostk.bm2.prod.bf1.yahoo.com\",\"zt40791-v6-15.ostk.bm2.prod.bf1.yahoo.com\",\"zt40733-v6-31.ostk.bm2.prod.bf1.yahoo.com\",\"zt40724-v6-30.ostk.bm2.prod.bf1.yahoo.com\"]}},\"clusterUtils\":{\"tripod\":{\"cpu\":0.2295038983007097,\"mem\":0.4627357390237263,\"disk\":0.05559941525894966,\"diskbusy\":0},\"tripodaggregation\":{\"cpu\":0.05340429087579549,\"mem\":0.8107630891552372,\"disk\":0.226444914138854,\"diskbusy\":0},\"tripodaggregationstream\":{\"cpu\":0.02148227413975218,\"mem\":0.02162174219104161,\"disk\":0.006057760545243265,\"diskbusy\":0}},\"metrics\":{\"queriesPerSecond\":1.734000012278557,\"writesPerSecond\":44.59999895095825,\"documentCount\":525868193.9999999,\"queryLatencyMillis\":5.65284947195106,\"writeLatencyMillis\":17.34593812832452}}],\"deploymentJobs\":{\"projectId\":102889,\"jobStatus\":[{\"jobType\":\"staging-test\",\"lastTriggered\":{\"id\":-1,\"version\":\"6.173.62\",\"revision\":{\"applicationPackageHash\":\"9db423e1021d7b452d37ec6372bc757d9c1bda87\",\"sourceRevision\":{\"repositoryField\":\"git@git.ouroath.com:Tripod/service-aggregation-vespa.git\",\"branchField\":\"origin/master\",\"commitField\":\"49cd7bbb1ed9f4b922083cb042590b0885ffe22b\"}},\"upgrade\":true,\"reason\":\"system-test completed\",\"at\":1510830134259},\"lastCompleted\":{\"id\":1184,\"version\":\"6.173.62\",\"revision\":{\"applicationPackageHash\":\"9db423e1021d7b452d37ec6372bc757d9c1bda87\",\"sourceRevision\":{\"repositoryField\":\"git@git.ouroath.com:Tripod/service-aggregation-vespa.git\",\"branchField\":\"origin/master\",\"commitField\":\"49cd7bbb1ed9f4b922083cb042590b0885ffe22b\"}},\"upgrade\":true,\"reason\":\"system-test completed\",\"at\":1510830684960},\"lastSuccess\":{\"id\":1184,\"version\":\"6.173.62\",\"revision\":{\"applicationPackageHash\":\"9db423e1021d7b452d37ec6372bc757d9c1bda87\",\"sourceRevision\":{\"repositoryField\":\"git@git.ouroath.com:Tripod/service-aggregation-vespa.git\",\"branchField\":\"origin/master\",\"commitField\":\"49cd7bbb1ed9f4b922083cb042590b0885ffe22b\"}},\"upgrade\":true,\"reason\":\"system-test completed\",\"at\":1510830684960}},{\"jobType\":\"component\",\"lastCompleted\":{\"id\":849,\"version\":\"6.174.156\",\"upgrade\":false,\"reason\":\"Application commit\",\"at\":1511217733555},\"lastSuccess\":{\"id\":849,\"version\":\"6.174.156\",\"upgrade\":false,\"reason\":\"Application commit\",\"at\":1511217733555}},{\"jobType\":\"production-us-east-3\",\"lastTriggered\":{\"id\":-1,\"version\":\"6.173.62\",\"revision\":{\"applicationPackageHash\":\"9db423e1021d7b452d37ec6372bc757d9c1bda87\",\"sourceRevision\":{\"repositoryField\":\"git@git.ouroath.com:Tripod/service-aggregation-vespa.git\",\"branchField\":\"origin/master\",\"commitField\":\"49cd7bbb1ed9f4b922083cb042590b0885ffe22b\"}},\"upgrade\":true,\"reason\":\"staging-test completed\",\"at\":1510830685127},\"lastCompleted\":{\"id\":923,\"version\":\"6.173.62\",\"revision\":{\"applicationPackageHash\":\"9db423e1021d7b452d37ec6372bc757d9c1bda87\",\"sourceRevision\":{\"repositoryField\":\"git@git.ouroath.com:Tripod/service-aggregation-vespa.git\",\"branchField\":\"origin/master\",\"commitField\":\"49cd7bbb1ed9f4b922083cb042590b0885ffe22b\"}},\"upgrade\":true,\"reason\":\"staging-test completed\",\"at\":1510837650046},\"lastSuccess\":{\"id\":923,\"version\":\"6.173.62\",\"revision\":{\"applicationPackageHash\":\"9db423e1021d7b452d37ec6372bc757d9c1bda87\",\"sourceRevision\":{\"repositoryField\":\"git@git.ouroath.com:Tripod/service-aggregation-vespa.git\",\"branchField\":\"origin/master\",\"commitField\":\"49cd7bbb1ed9f4b922083cb042590b0885ffe22b\"}},\"upgrade\":true,\"reason\":\"staging-test completed\",\"at\":1510837650046}},{\"jobType\":\"production-us-west-1\",\"lastTriggered\":{\"id\":-1,\"version\":\"6.173.62\",\"revision\":{\"applicationPackageHash\":\"9db423e1021d7b452d37ec6372bc757d9c1bda87\",\"sourceRevision\":{\"repositoryField\":\"git@git.ouroath.com:Tripod/service-aggregation-vespa.git\",\"branchField\":\"origin/master\",\"commitField\":\"49cd7bbb1ed9f4b922083cb042590b0885ffe22b\"}},\"upgrade\":true,\"reason\":\"production-us-east-3 completed\",\"at\":1510837650139},\"lastCompleted\":{\"id\":646,\"version\":\"6.173.62\",\"revision\":{\"applicationPackageHash\":\"9db423e1021d7b452d37ec6372bc757d9c1bda87\",\"sourceRevision\":{\"repositoryField\":\"git@git.ouroath.com:Tripod/service-aggregation-vespa.git\",\"branchField\":\"origin/master\",\"commitField\":\"49cd7bbb1ed9f4b922083cb042590b0885ffe22b\"}},\"upgrade\":true,\"reason\":\"production-us-east-3 completed\",\"at\":1510843559162},\"lastSuccess\":{\"id\":646,\"version\":\"6.173.62\",\"revision\":{\"applicationPackageHash\":\"9db423e1021d7b452d37ec6372bc757d9c1bda87\",\"sourceRevision\":{\"repositoryField\":\"git@git.ouroath.com:Tripod/service-aggregation-vespa.git\",\"branchField\":\"origin/master\",\"commitField\":\"49cd7bbb1ed9f4b922083cb042590b0885ffe22b\"}},\"upgrade\":true,\"reason\":\"production-us-east-3 completed\",\"at\":1510843559162}},{\"jobType\":\"system-test\",\"jobError\":\"unknown\",\"lastTriggered\":{\"id\":-1,\"version\":\"6.173.62\",\"revision\":{\"applicationPackageHash\":\"ec548fa61cbfab7a270a51d46b1263ec1be5d9a8\",\"sourceRevision\":{\"repositoryField\":\"git@git.ouroath.com:Tripod/service-aggregation-vespa.git\",\"branchField\":\"origin/master\",\"commitField\":\"234f3e4e77049d0b9538c9e1b356d17eb1dedb6a\"}},\"upgrade\":false,\"reason\":\"Available change in component\",\"at\":1511256608649},\"lastCompleted\":{\"id\":1686,\"version\":\"6.173.62\",\"revision\":{\"applicationPackageHash\":\"ec548fa61cbfab7a270a51d46b1263ec1be5d9a8\",\"sourceRevision\":{\"repositoryField\":\"git@git.ouroath.com:Tripod/service-aggregation-vespa.git\",\"branchField\":\"origin/master\",\"commitField\":\"234f3e4e77049d0b9538c9e1b356d17eb1dedb6a\"}},\"upgrade\":false,\"reason\":\"Available change in component\",\"at\":1511256603353},\"firstFailing\":{\"id\":1659,\"version\":\"6.173.62\",\"revision\":{\"applicationPackageHash\":\"ec548fa61cbfab7a270a51d46b1263ec1be5d9a8\",\"sourceRevision\":{\"repositoryField\":\"git@git.ouroath.com:Tripod/service-aggregation-vespa.git\",\"branchField\":\"origin/master\",\"commitField\":\"234f3e4e77049d0b9538c9e1b356d17eb1dedb6a\"}},\"upgrade\":false,\"reason\":\"component completed\",\"at\":1511219070725},\"lastSuccess\":{\"id\":1658,\"version\":\"6.173.62\",\"revision\":{\"applicationPackageHash\":\"9db423e1021d7b452d37ec6372bc757d9c1bda87\",\"sourceRevision\":{\"repositoryField\":\"git@git.ouroath.com:Tripod/service-aggregation-vespa.git\",\"branchField\":\"origin/master\",\"commitField\":\"49cd7bbb1ed9f4b922083cb042590b0885ffe22b\"}},\"upgrade\":true,\"reason\":\"Upgrading to 6.173.62\",\"at\":1511175754163}}]},\"deployingField\":{\"applicationPackageHash\":\"ec548fa61cbfab7a270a51d46b1263ec1be5d9a8\",\"sourceRevision\":{\"repositoryField\":\"git@git.ouroath.com:Tripod/service-aggregation-vespa.git\",\"branchField\":\"origin/master\",\"commitField\":\"234f3e4e77049d0b9538c9e1b356d17eb1dedb6a\"}},\"outstandingChangeField\":false,\"queryQuality\":100,\"writeQuality\":99.99894341115082}";
}