From ebfc65452ac5f5a9f0aa100763dc22a80cfd7167 Mon Sep 17 00:00:00 2001 From: Jon Bratseth Date: Mon, 14 Jan 2019 13:16:06 +0100 Subject: Add application level majorVersion override in application/v4 --- .../yahoo/vespa/hosted/controller/Application.java | 21 +++- .../vespa/hosted/controller/LockedApplication.java | 52 +++++---- .../controller/application/ApplicationList.java | 4 +- .../persistence/ApplicationSerializer.java | 9 +- .../restapi/application/ApplicationApiHandler.java | 36 +++++-- .../controller/maintenance/UpgraderTest.java | 38 ++++++- .../persistence/ApplicationSerializerTest.java | 2 + .../restapi/application/ApplicationApiTest.java | 117 ++++++++++++--------- .../responses/application2-with-majorVersion.json | 87 +++++++++++++++ 9 files changed, 281 insertions(+), 85 deletions(-) create mode 100644 controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application2-with-majorVersion.json (limited to 'controller-server') 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 1baff026385..82355144b20 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 @@ -52,6 +52,7 @@ public class Application { private final Change outstandingChange; private final Optional ownershipIssueId; private final Optional owner; + private final Optional majorVersion; private final ApplicationMetrics metrics; private final Optional rotation; private final Map rotationStatus; @@ -60,23 +61,27 @@ public class Application { public Application(ApplicationId id, Instant now) { this(id, now, DeploymentSpec.empty, ValidationOverrides.empty, Collections.emptyMap(), new DeploymentJobs(OptionalLong.empty(), Collections.emptyList(), Optional.empty(), false), - Change.empty(), Change.empty(), Optional.empty(), Optional.empty(), new ApplicationMetrics(0, 0), + Change.empty(), Change.empty(), Optional.empty(), Optional.empty(), Optional.empty(), + new ApplicationMetrics(0, 0), Optional.empty(), Collections.emptyMap()); } /** Used from persistence layer: Do not use */ public Application(ApplicationId id, Instant createdAt, DeploymentSpec deploymentSpec, ValidationOverrides validationOverrides, List deployments, DeploymentJobs deploymentJobs, Change change, - Change outstandingChange, Optional ownershipIssueId, Optional owner, ApplicationMetrics metrics, + Change outstandingChange, Optional ownershipIssueId, Optional owner, + Optional majorVersion, ApplicationMetrics metrics, Optional rotation, Map rotationStatus) { this(id, createdAt, deploymentSpec, validationOverrides, deployments.stream().collect(Collectors.toMap(Deployment::zone, d -> d)), - deploymentJobs, change, outstandingChange, ownershipIssueId, owner, metrics, rotation, rotationStatus); + deploymentJobs, change, outstandingChange, ownershipIssueId, owner, majorVersion, + metrics, rotation, rotationStatus); } Application(ApplicationId id, Instant createdAt, DeploymentSpec deploymentSpec, ValidationOverrides validationOverrides, Map deployments, DeploymentJobs deploymentJobs, Change change, - Change outstandingChange, Optional ownershipIssueId, Optional owner, ApplicationMetrics metrics, + Change outstandingChange, Optional ownershipIssueId, Optional owner, + Optional majorVersion, ApplicationMetrics metrics, Optional rotation, Map rotationStatus) { this.id = Objects.requireNonNull(id, "id cannot be null"); this.createdAt = Objects.requireNonNull(createdAt, "instant of creation cannot be null"); @@ -88,6 +93,7 @@ public class Application { this.outstandingChange = Objects.requireNonNull(outstandingChange, "outstandingChange cannot be null"); this.ownershipIssueId = Objects.requireNonNull(ownershipIssueId, "ownershipIssueId cannot be null"); this.owner = Objects.requireNonNull(owner, "owner cannot be null"); + this.majorVersion = Objects.requireNonNull(majorVersion, "majorVersion cannot be null"); this.metrics = Objects.requireNonNull(metrics, "metrics cannot be null"); this.rotation = Objects.requireNonNull(rotation, "rotation cannot be null"); this.rotationStatus = ImmutableMap.copyOf(Objects.requireNonNull(rotationStatus, "rotationStatus cannot be null")); @@ -146,6 +152,13 @@ public class Application { return owner; } + /** + * Overrides the preferred major version for this application. + * This overrides the major version set in the deployment spec (if any) and the major version the system + * wants to use. + */ + public Optional majorVersion() { return majorVersion; } + /** Returns metrics for this */ public ApplicationMetrics metrics() { return metrics; 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 1e138dd5a4d..6eb321918e6 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 @@ -50,7 +50,8 @@ public class LockedApplication { private final Change change; private final Change outstandingChange; private final Optional ownershipIssueId; - private Optional owner; + private final Optional owner; + private final Optional majorVersion; private final ApplicationMetrics metrics; private final Optional rotation; private final Map rotationStatus; @@ -66,7 +67,7 @@ public class LockedApplication { application.deploymentSpec(), application.validationOverrides(), application.deployments(), application.deploymentJobs(), application.change(), application.outstandingChange(), - application.ownershipIssueId(), application.owner(), application.metrics(), + application.ownershipIssueId(), application.owner(), application.majorVersion(), application.metrics(), application.rotation(), application.rotationStatus()); } @@ -74,7 +75,8 @@ public class LockedApplication { private LockedApplication(Lock lock, ApplicationId id, Instant createdAt, DeploymentSpec deploymentSpec, ValidationOverrides validationOverrides, Map deployments, DeploymentJobs deploymentJobs, Change change, - Change outstandingChange, Optional ownershipIssueId, Optional owner, ApplicationMetrics metrics, + Change outstandingChange, Optional ownershipIssueId, Optional owner, + Optional majorVersion, ApplicationMetrics metrics, Optional rotation, Map rotationStatus) { this.lock = lock; this.id = id; @@ -87,6 +89,7 @@ public class LockedApplication { this.outstandingChange = outstandingChange; this.ownershipIssueId = ownershipIssueId; this.owner = owner; + this.majorVersion = majorVersion; this.metrics = metrics; this.rotation = rotation; this.rotationStatus = rotationStatus; @@ -95,44 +98,44 @@ public class LockedApplication { /** Returns a read-only copy of this */ public Application get() { return new Application(id, createdAt, deploymentSpec, validationOverrides, deployments, deploymentJobs, change, - outstandingChange, ownershipIssueId, owner, metrics, rotation, rotationStatus); + outstandingChange, ownershipIssueId, owner, majorVersion, metrics, rotation, rotationStatus); } public LockedApplication withBuiltInternally(boolean builtInternally) { return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments, deploymentJobs.withBuiltInternally(builtInternally), change, outstandingChange, - ownershipIssueId, owner, metrics, rotation, rotationStatus); + ownershipIssueId, owner, majorVersion, metrics, rotation, rotationStatus); } public LockedApplication withProjectId(OptionalLong projectId) { return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments, deploymentJobs.withProjectId(projectId), change, outstandingChange, - ownershipIssueId, owner, metrics, rotation, rotationStatus); + ownershipIssueId, owner, majorVersion, metrics, rotation, rotationStatus); } public LockedApplication withDeploymentIssueId(IssueId issueId) { return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments, deploymentJobs.with(issueId), change, outstandingChange, - ownershipIssueId, owner, metrics, rotation, rotationStatus); + ownershipIssueId, owner, majorVersion, metrics, rotation, rotationStatus); } public LockedApplication withJobPause(JobType jobType, OptionalLong pausedUntil) { return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments, deploymentJobs.withPause(jobType, pausedUntil), change, outstandingChange, - ownershipIssueId, owner, metrics, rotation, rotationStatus); + ownershipIssueId, owner, majorVersion, metrics, rotation, rotationStatus); } public LockedApplication withJobCompletion(long projectId, JobType jobType, JobStatus.JobRun completion, Optional jobError) { return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments, deploymentJobs.withCompletion(projectId, jobType, completion, jobError), - change, outstandingChange, ownershipIssueId, owner, metrics, rotation, rotationStatus); + change, outstandingChange, ownershipIssueId, owner, majorVersion, metrics, rotation, rotationStatus); } public LockedApplication withJobTriggering(JobType jobType, JobStatus.JobRun job) { return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments, deploymentJobs.withTriggering(jobType, job), change, outstandingChange, - ownershipIssueId, owner, metrics, rotation, rotationStatus); + ownershipIssueId, owner, majorVersion, metrics, rotation, rotationStatus); } public LockedApplication withNewDeployment(ZoneId zone, ApplicationVersion applicationVersion, Version version, @@ -182,60 +185,67 @@ public class LockedApplication { public LockedApplication withoutDeploymentJob(JobType jobType) { return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments, deploymentJobs.without(jobType), change, outstandingChange, - ownershipIssueId, owner, metrics, rotation, rotationStatus); + ownershipIssueId, owner, majorVersion, metrics, rotation, rotationStatus); } public LockedApplication with(DeploymentSpec deploymentSpec) { return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments, deploymentJobs, change, outstandingChange, - ownershipIssueId, owner, metrics, rotation, rotationStatus); + ownershipIssueId, owner, majorVersion, metrics, rotation, rotationStatus); } public LockedApplication with(ValidationOverrides validationOverrides) { return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments, deploymentJobs, change, outstandingChange, - ownershipIssueId, owner, metrics, rotation, rotationStatus); + ownershipIssueId, owner, majorVersion, metrics, rotation, rotationStatus); } public LockedApplication withChange(Change change) { return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments, deploymentJobs, change, outstandingChange, - ownershipIssueId, owner, metrics, rotation, rotationStatus); + ownershipIssueId, owner, majorVersion, metrics, rotation, rotationStatus); } public LockedApplication withOutstandingChange(Change outstandingChange) { return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments, deploymentJobs, change, outstandingChange, - ownershipIssueId, owner, metrics, rotation, rotationStatus); + ownershipIssueId, owner, majorVersion, metrics, rotation, rotationStatus); } public LockedApplication withOwnershipIssueId(IssueId issueId) { return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments, deploymentJobs, change, outstandingChange, - Optional.ofNullable(issueId), owner, metrics, rotation, rotationStatus); + Optional.ofNullable(issueId), owner, majorVersion, metrics, rotation, rotationStatus); } public LockedApplication withOwner(User owner) { return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments, deploymentJobs, change, outstandingChange, - ownershipIssueId, Optional.ofNullable(owner), metrics, rotation, rotationStatus); + ownershipIssueId, Optional.ofNullable(owner), majorVersion, metrics, rotation, rotationStatus); + } + + /** Set a major vewrsion for this, or set to null to remove any major version override */ + public LockedApplication withMajorVersion(Integer majorVersion) { + return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments, + deploymentJobs, change, outstandingChange, + ownershipIssueId, owner, Optional.ofNullable(majorVersion), metrics, rotation, rotationStatus); } public LockedApplication with(MetricsService.ApplicationMetrics metrics) { return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments, deploymentJobs, change, outstandingChange, - ownershipIssueId, owner, metrics, rotation, rotationStatus); + ownershipIssueId, owner, majorVersion, metrics, rotation, rotationStatus); } public LockedApplication with(RotationId rotation) { return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments, deploymentJobs, change, outstandingChange, - ownershipIssueId, owner, metrics, Optional.of(rotation), rotationStatus); + ownershipIssueId, owner, majorVersion, metrics, Optional.of(rotation), rotationStatus); } public LockedApplication withRotationStatus(Map rotationStatus) { return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments, deploymentJobs, change, - outstandingChange, ownershipIssueId, owner, metrics, rotation, rotationStatus); + outstandingChange, ownershipIssueId, owner, majorVersion, metrics, rotation, rotationStatus); } /** Don't expose non-leaf sub-objects. */ @@ -248,7 +258,7 @@ public class LockedApplication { private LockedApplication with(Map deployments) { return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments, deploymentJobs, change, outstandingChange, - ownershipIssueId, owner, metrics, rotation, rotationStatus); + ownershipIssueId, owner, majorVersion, metrics, rotation, rotationStatus); } @Override diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationList.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationList.java index 677b4b4ba07..d279e899df0 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationList.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationList.java @@ -160,13 +160,13 @@ public class ApplicationList { } /** - * Returns the subset of applications that hasn't pinned to an an earlier major version than the given one. + * Returns the subset of applications that hasn't pinned to another major version than the given one. * * @param targetMajorVersion the target major version which applications returned allows upgrading to * @param defaultMajorVersion the default major version to assume for applications not specifying one */ public ApplicationList allowMajorVersion(int targetMajorVersion, int defaultMajorVersion) { - return listOf(list.stream().filter(a -> a.deploymentSpec().majorVersion().orElse(defaultMajorVersion) + return listOf(list.stream().filter(a -> a.majorVersion().orElse(a.deploymentSpec().majorVersion().orElse(defaultMajorVersion)) >= targetMajorVersion)); } 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 147b2edee3e..ffb900f0fac 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 @@ -64,6 +64,7 @@ public class ApplicationSerializer { private final String outstandingChangeField = "outstandingChangeField"; private final String ownershipIssueIdField = "ownershipIssueId"; private final String ownerField = "confirmedOwner"; + private final String majorVersionField = "majorVersion"; private final String writeQualityField = "writeQuality"; private final String queryQualityField = "queryQuality"; private final String rotationField = "rotation"; @@ -153,6 +154,7 @@ public class ApplicationSerializer { toSlime(application.outstandingChange(), root, outstandingChangeField); application.ownershipIssueId().ifPresent(issueId -> root.setString(ownershipIssueIdField, issueId.value())); application.owner().ifPresent(owner -> root.setString(ownerField, owner.username())); + application.majorVersion().ifPresent(majorVersion -> root.setLong(majorVersionField, majorVersion)); root.setDouble(queryQualityField, application.metrics().queryServiceQuality()); root.setDouble(writeQualityField, application.metrics().writeServiceQuality()); application.rotation().ifPresent(rotation -> root.setString(rotationField, rotation.asString())); @@ -314,13 +316,14 @@ public class ApplicationSerializer { Change outstandingChange = changeFromSlime(root.field(outstandingChangeField)); Optional ownershipIssueId = optionalString(root.field(ownershipIssueIdField)).map(IssueId::from); Optional owner = optionalString(root.field(ownerField)).map(User::from); + Optional majorVersion = optionalInteger(root.field(majorVersionField)); ApplicationMetrics metrics = new ApplicationMetrics(root.field(queryQualityField).asDouble(), root.field(writeQualityField).asDouble()); Optional rotation = rotationFromSlime(root.field(rotationField)); Map rotationStatus = rotationStatusFromSlime(root.field(rotationStatusField)); return new Application(id, createdAt, deploymentSpec, validationOverrides, deployments, deploymentJobs, deploying, - outstandingChange, ownershipIssueId, owner, metrics, rotation, rotationStatus); + outstandingChange, ownershipIssueId, owner, majorVersion, metrics, rotation, rotationStatus); } private List deploymentsFromSlime(Inspector array) { @@ -500,6 +503,10 @@ public class ApplicationSerializer { return field.valid() ? OptionalLong.of(field.asLong()) : OptionalLong.empty(); } + private Optional optionalInteger(Inspector field) { + return field.valid() ? Optional.of((int)field.asLong()) : Optional.empty(); + } + private OptionalDouble optionalDouble(Inspector field) { return field.valid() ? OptionalDouble.of(field.asDouble()) : OptionalDouble.empty(); } 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 56e1e746d04..fcf82a79850 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 @@ -137,6 +137,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler { case GET: return handleGET(request); case PUT: return handlePUT(request); case POST: return handlePOST(request); + case PATCH: return handlePATCH(request); case DELETE: return handleDELETE(request); case OPTIONS: return handleOPTIONS(); default: return ErrorResponse.methodNotAllowed("Method '" + request.getMethod() + "' is not supported"); @@ -217,6 +218,13 @@ public class ApplicationApiHandler extends LoggingRequestHandler { return ErrorResponse.notFoundError("Nothing at " + path); } + private HttpResponse handlePATCH(HttpRequest request) { + Path path = new Path(request.getUri().getPath()); + if (path.matches("/application/v4/tenant/{tenant}/application/{application}")) + return setMajorVersion(path.get("tenant"), path.get("application"), request); + return ErrorResponse.notFoundError("Nothing at " + path); + } + private HttpResponse handleDELETE(HttpRequest request) { Path path = new Path(request.getUri().getPath()); if (path.matches("/application/v4/tenant/{tenant}")) return deleteTenant(path.get("tenant"), request); @@ -235,7 +243,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler { // We implement this to avoid redirect loops on OPTIONS requests from browsers, but do not really bother // spelling out the methods supported at each path, which we should EmptyJsonResponse response = new EmptyJsonResponse(); - response.headers().put("Allow", "GET,PUT,POST,DELETE,OPTIONS"); + response.headers().put("Allow", "GET,PUT,POST,PATCH,DELETE,OPTIONS"); return response; } @@ -344,16 +352,28 @@ public class ApplicationApiHandler extends LoggingRequestHandler { } private HttpResponse application(String tenantName, String applicationName, HttpRequest request) { - ApplicationId applicationId = ApplicationId.from(tenantName, applicationName, "default"); - Application application = - controller.applications().get(applicationId) - .orElseThrow(() -> new NotExistsException(applicationId + " not found")); - Slime slime = new Slime(); - toSlime(slime.setObject(), application, request); + toSlime(slime.setObject(), getApplication(tenantName, applicationName, request), request); return new SlimeJsonResponse(slime); } + private HttpResponse setMajorVersion(String tenantName, String applicationName, HttpRequest request) { + Application application = getApplication(tenantName, applicationName, request); + Inspector majorVersionField = toSlime(request.getData()).get().field("majorVersion"); + if ( ! majorVersionField.valid()) + throw new IllegalArgumentException("Request body must contain a majorVersion field"); + Integer majorVersion = majorVersionField.asLong() == 0 ? null : (int)majorVersionField.asLong(); + controller.applications().lockIfPresent(application.id(), + a -> controller.applications().store(a.withMajorVersion(majorVersion))); + return new MessageResponse("Set major version to " + ( majorVersion == null ? "empty" : majorVersion)); + } + + private Application getApplication(String tenantName, String applicationName, HttpRequest request) { + ApplicationId applicationId = ApplicationId.from(tenantName, applicationName, "default"); + return controller.applications().get(applicationId) + .orElseThrow(() -> new NotExistsException(applicationId + " not found")); + } + private HttpResponse logs(String tenantName, String applicationName, String instanceName, String environment, String region, String query) { ApplicationId application = ApplicationId.from(tenantName, applicationName, instanceName); ZoneId zone = ZoneId.from(environment, region); @@ -452,6 +472,8 @@ public class ApplicationApiHandler extends LoggingRequestHandler { // Compile version. The version that should be used when building an application object.setString("compileVersion", controller.applications().oldestInstalledPlatform(application.id()).toFullString()); + application.majorVersion().ifPresent(majorVersion -> object.setLong("majorVersion", majorVersion)); + // Rotation Cursor globalRotationsArray = object.setArray("globalRotations"); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java index 6755a6f9ad5..f93f9cceec7 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java @@ -6,6 +6,7 @@ import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.RegionName; import com.yahoo.test.ManualClock; import com.yahoo.vespa.hosted.controller.Application; +import com.yahoo.vespa.hosted.controller.ApplicationController; import com.yahoo.vespa.hosted.controller.ControllerTester; import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType; import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneId; @@ -895,7 +896,7 @@ public class UpgraderTest { } @Test - public void testPinningMajorVersionInApplication() { + public void testPinningMajorVersionInDeploymentXml() { Version version = Version.fromString("6.2"); tester.upgradeSystem(version); @@ -928,6 +929,41 @@ public class UpgraderTest { assertEquals(0, tester.buildService().jobs().size()); } + @Test + public void testPinningMajorVersionInApplication() { + Version version = Version.fromString("6.2"); + tester.upgradeSystem(version); + + ApplicationPackage default0ApplicationPackage = new ApplicationPackageBuilder() + .upgradePolicy("default") + .environment(Environment.prod) + .region("us-west-1") + .build(); + + // Setup applications + Application canary0 = tester.createAndDeploy("canary0", 1, "canary"); + Application default0 = tester.createAndDeploy("default0", 2, default0ApplicationPackage); + tester.applications().lockOrThrow(default0.id(), a -> tester.applications().store(a.withMajorVersion(6))); + assertEquals(Optional.of(6), tester.applications().get(default0.id()).get().majorVersion()); + + // New major version is released + version = Version.fromString("7.0"); + tester.upgradeSystem(version); + assertEquals(version, tester.controller().versionStatus().systemVersion().get().versionNumber()); + tester.triggerUntilQuiescence(); + + // ... canary upgrade to it + assertEquals(2, tester.buildService().jobs().size()); + tester.completeUpgrade(canary0, version, "canary"); + assertEquals(0, tester.buildService().jobs().size()); + tester.computeVersionStatus(); + + // The other application does not because it has pinned to major version 6 + tester.upgrader().maintain(); + tester.triggerUntilQuiescence(); + assertEquals(0, tester.buildService().jobs().size()); + } + @Test public void testPinningMajorVersionInUpgrader() { Version version = Version.fromString("6.2"); 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 0b337eb5380..d4cd7c0fe85 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 @@ -109,6 +109,7 @@ public class ApplicationSerializerTest { Change.of(ApplicationVersion.from(new SourceRevision("repo", "master", "deadcafe"), 42)), Optional.of(IssueId.from("1234")), Optional.of(User.from("by-username")), + Optional.of(7), new MetricsService.ApplicationMetrics(0.5, 0.9), Optional.of(new RotationId("my-rotation")), rotationStatus); @@ -142,6 +143,7 @@ public class ApplicationSerializerTest { assertEquals(original.ownershipIssueId(), serialized.ownershipIssueId()); assertEquals(original.owner(), serialized.owner()); + assertEquals(original.majorVersion(), serialized.majorVersion()); assertEquals(original.change(), serialized.change()); assertEquals(original.rotation().get(), serialized.rotation().get()); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java index 30795008032..95f58326076 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java @@ -82,6 +82,7 @@ import java.util.function.Supplier; import static com.yahoo.application.container.handler.Request.Method.DELETE; import static com.yahoo.application.container.handler.Request.Method.GET; +import static com.yahoo.application.container.handler.Request.Method.PATCH; import static com.yahoo.application.container.handler.Request.Method.POST; import static com.yahoo.application.container.handler.Request.Method.PUT; import static org.junit.Assert.assertEquals; @@ -313,6 +314,24 @@ public class ApplicationApiTest extends ControllerContainerTest { .userIdentity(USER_ID), new File("application2.json")); + // PATCH in a major version override + tester.assertResponse(request("/application/v4/tenant/tenant2/application/application2", PATCH) + .userIdentity(USER_ID) + .data("{\"majorVersion\":7}"), + "{\"message\":\"Set major version to 7\"}"); + // GET an application with a major version override + tester.assertResponse(request("/application/v4/tenant/tenant2/application/application2", GET) + .userIdentity(USER_ID), + new File("application2-with-majorVersion.json")); + // PATCH in removal of the application major version override removal + tester.assertResponse(request("/application/v4/tenant/tenant2/application/application2", PATCH) + .userIdentity(USER_ID) + .data("{\"majorVersion\":null}"), + "{\"message\":\"Set major version to empty\"}"); + tester.assertResponse(request("/application/v4/tenant/tenant2/application/application2", GET) + .userIdentity(USER_ID), + new File("application2.json")); + // DELETE application tester.assertResponse(request("/application/v4/tenant/tenant2/application/application2", DELETE) .userIdentity(USER_ID) @@ -1280,55 +1299,6 @@ public class ApplicationApiTest extends ControllerContainerTest { "}"; } - private static class RequestBuilder implements Supplier { - - private final String path; - private final Request.Method method; - private byte[] data = new byte[0]; - private AthenzIdentity identity; - private OktaAccessToken oktaAccessToken; - private String contentType = "application/json"; - private String recursive; - - private RequestBuilder(String path, Request.Method method) { - this.path = path; - this.method = method; - } - - private RequestBuilder data(byte[] data) { this.data = data; return this; } - private RequestBuilder data(String data) { return data(data.getBytes(StandardCharsets.UTF_8)); } - private RequestBuilder data(HttpEntity data) { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - try { - data.writeTo(out); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - return data(out.toByteArray()).contentType(data.getContentType().getValue()); - } - private RequestBuilder userIdentity(UserId userId) { this.identity = HostedAthenzIdentities.from(userId); return this; } - private RequestBuilder screwdriverIdentity(ScrewdriverId screwdriverId) { this.identity = HostedAthenzIdentities.from(screwdriverId); return this; } - private RequestBuilder oktaAccessToken(OktaAccessToken oktaAccessToken) { this.oktaAccessToken = oktaAccessToken; return this; } - private RequestBuilder contentType(String contentType) { this.contentType = contentType; return this; } - private RequestBuilder recursive(String recursive) { this.recursive = recursive; return this; } - - @Override - public Request get() { - Request request = new Request("http://localhost:8080" + path + - // user and domain parameters are translated to a Principal by MockAuthorizer as we do not run HTTP filters - (recursive == null ? "" : "?recursive=" + recursive), - data, method); - request.getHeaders().put("Content-Type", contentType); - if (identity != null) { - addIdentityToRequest(request, identity); - } - if (oktaAccessToken != null) { - addOktaAccessToken(request, oktaAccessToken); - } - return request; - } - } - /** Make a request with (athens) user domain1.mytenant */ private RequestBuilder request(String path, Request.Method method) { return new RequestBuilder(path, method); @@ -1495,4 +1465,53 @@ public class ApplicationApiTest extends ControllerContainerTest { Collections.singletonList("bob")), "queue", Optional.empty())); } + private static class RequestBuilder implements Supplier { + + private final String path; + private final Request.Method method; + private byte[] data = new byte[0]; + private AthenzIdentity identity; + private OktaAccessToken oktaAccessToken; + private String contentType = "application/json"; + private String recursive; + + private RequestBuilder(String path, Request.Method method) { + this.path = path; + this.method = method; + } + + private RequestBuilder data(byte[] data) { this.data = data; return this; } + private RequestBuilder data(String data) { return data(data.getBytes(StandardCharsets.UTF_8)); } + private RequestBuilder data(HttpEntity data) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try { + data.writeTo(out); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + return data(out.toByteArray()).contentType(data.getContentType().getValue()); + } + private RequestBuilder userIdentity(UserId userId) { this.identity = HostedAthenzIdentities.from(userId); return this; } + private RequestBuilder screwdriverIdentity(ScrewdriverId screwdriverId) { this.identity = HostedAthenzIdentities.from(screwdriverId); return this; } + private RequestBuilder oktaAccessToken(OktaAccessToken oktaAccessToken) { this.oktaAccessToken = oktaAccessToken; return this; } + private RequestBuilder contentType(String contentType) { this.contentType = contentType; return this; } + private RequestBuilder recursive(String recursive) { this.recursive = recursive; return this; } + + @Override + public Request get() { + Request request = new Request("http://localhost:8080" + path + + // user and domain parameters are translated to a Principal by MockAuthorizer as we do not run HTTP filters + (recursive == null ? "" : "?recursive=" + recursive), + data, method); + request.getHeaders().put("Content-Type", contentType); + if (identity != null) { + addIdentityToRequest(request, identity); + } + if (oktaAccessToken != null) { + addOktaAccessToken(request, oktaAccessToken); + } + return request; + } + } + } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application2-with-majorVersion.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application2-with-majorVersion.json new file mode 100644 index 00000000000..55803074ade --- /dev/null +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application2-with-majorVersion.json @@ -0,0 +1,87 @@ +{ + "application": "application2", + "instance": "default", + "deployments": "http://localhost:8080/application/v4/tenant/tenant2/application/application2/instance/default/job/", + "source": { + "gitRepository": "repository1", + "gitBranch": "master", + "gitCommit": "commit1" + }, + "projectId": 456, + "deploying": { + "version": "(ignore)" + }, + "outstandingChange": { + "revision": { + "hash": "(ignore)", + "source": { + "gitRepository": "repository1", + "gitBranch": "master", + "gitCommit": "commit1" + } + } + }, + "deployedInternally": false, + "deploymentJobs": [ + { + "type": "component", + "success": true, + "lastCompleted": { + "id": 42, + "version": "(ignore)", + "revision": { + "hash": "(ignore)", + "source": { + "gitRepository": "repository1", + "gitBranch": "master", + "gitCommit": "commit1" + } + }, + "reason": "Application commit", + "at": "(ignore)" + }, + "lastSuccess": { + "id": 42, + "version": "(ignore)", + "revision": { + "hash": "(ignore)", + "source": { + "gitRepository": "repository1", + "gitBranch": "master", + "gitCommit": "commit1" + } + }, + "reason": "Application commit", + "at": "(ignore)" + } + }, + { + "type": "system-test", + "success": false, + "lastTriggered": { + "id": -1, + "version": "7.0.0", + "revision": { + "hash": "1.0.42-commit1", + "source": { + "gitRepository": "repository1", + "gitBranch": "master", + "gitCommit": "commit1" + } + }, + "reason": "Testing last changes outside prod", + "at": "(ignore)" + } + } + ], + "changeBlockers": [], + "compileVersion": "6.1.0", + "majorVersion": 7, + "globalRotations": [], + "instances": [], + "metrics": { + "queryServiceQuality": 0.0, + "writeServiceQuality": 0.0 + }, + "activity": {} +} -- cgit v1.2.3 From 63c8d281a84012dbf18b29f069a5c6ca811113d0 Mon Sep 17 00:00:00 2001 From: Jon Bratseth Date: Mon, 14 Jan 2019 14:11:26 +0100 Subject: Fix doc typo and remove unnecessary parameter --- .../java/com/yahoo/vespa/hosted/controller/LockedApplication.java | 2 +- .../controller/restapi/application/ApplicationApiHandler.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'controller-server') 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 6eb321918e6..6ac62284661 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 @@ -224,7 +224,7 @@ public class LockedApplication { ownershipIssueId, Optional.ofNullable(owner), majorVersion, metrics, rotation, rotationStatus); } - /** Set a major vewrsion for this, or set to null to remove any major version override */ + /** Set a major version for this, or set to null to remove any major version override */ public LockedApplication withMajorVersion(Integer majorVersion) { return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, deployments, deploymentJobs, change, outstandingChange, 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 fcf82a79850..a3581334b2a 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 @@ -353,12 +353,12 @@ public class ApplicationApiHandler extends LoggingRequestHandler { private HttpResponse application(String tenantName, String applicationName, HttpRequest request) { Slime slime = new Slime(); - toSlime(slime.setObject(), getApplication(tenantName, applicationName, request), request); + toSlime(slime.setObject(), getApplication(tenantName, applicationName), request); return new SlimeJsonResponse(slime); } private HttpResponse setMajorVersion(String tenantName, String applicationName, HttpRequest request) { - Application application = getApplication(tenantName, applicationName, request); + Application application = getApplication(tenantName, applicationName); Inspector majorVersionField = toSlime(request.getData()).get().field("majorVersion"); if ( ! majorVersionField.valid()) throw new IllegalArgumentException("Request body must contain a majorVersion field"); @@ -368,7 +368,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler { return new MessageResponse("Set major version to " + ( majorVersion == null ? "empty" : majorVersion)); } - private Application getApplication(String tenantName, String applicationName, HttpRequest request) { + private Application getApplication(String tenantName, String applicationName) { ApplicationId applicationId = ApplicationId.from(tenantName, applicationName, "default"); return controller.applications().get(applicationId) .orElseThrow(() -> new NotExistsException(applicationId + " not found")); -- cgit v1.2.3