diff options
Diffstat (limited to 'controller-server')
5 files changed, 188 insertions, 3 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ChangeRequestSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ChangeRequestSerializer.java index 407eb5ad5ab..49da8d7a2a2 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ChangeRequestSerializer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ChangeRequestSerializer.java @@ -119,7 +119,7 @@ public class ChangeRequestSerializer { cursor.setString(STATUS_FIELD, source.getStatus().name()); } - private static ChangeRequestSource readChangeRequestSource(Inspector inspector) { + public static ChangeRequestSource readChangeRequestSource(Inspector inspector) { return new ChangeRequestSource( inspector.field(SOURCE_SYSTEM_FIELD).asString(), inspector.field(ID_FIELD).asString(), @@ -130,7 +130,7 @@ public class ChangeRequestSerializer { ); } - private static List<HostAction> readHostActionPlan(Inspector inspector) { + public static List<HostAction> readHostActionPlan(Inspector inspector) { if (!inspector.valid()) return List.of(); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/changemanagement/ChangeManagementApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/changemanagement/ChangeManagementApiHandler.java index 5973cc3fcf3..ac9612a56c5 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/changemanagement/ChangeManagementApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/changemanagement/ChangeManagementApiHandler.java @@ -18,6 +18,7 @@ import com.yahoo.slime.SlimeUtils; import com.yahoo.vespa.hosted.controller.Controller; import com.yahoo.vespa.hosted.controller.api.integration.configserver.Node; import com.yahoo.vespa.hosted.controller.api.integration.vcmr.ChangeRequest; +import com.yahoo.vespa.hosted.controller.api.integration.vcmr.VespaChangeRequest; import com.yahoo.vespa.hosted.controller.auditlog.AuditLoggingRequestHandler; import com.yahoo.vespa.hosted.controller.maintenance.ChangeManagementAssessor; import com.yahoo.vespa.hosted.controller.persistence.ChangeRequestSerializer; @@ -50,6 +51,10 @@ public class ChangeManagementApiHandler extends AuditLoggingRequestHandler { return get(request); case POST: return post(request); + case PATCH: + return patch(request); + case DELETE: + return delete(request); default: return ErrorResponse.methodNotAllowed("Method '" + request.getMethod() + "' is unsupported"); } @@ -65,6 +70,7 @@ public class ChangeManagementApiHandler extends AuditLoggingRequestHandler { Path path = new Path(request.getUri()); if (path.matches("/changemanagement/v1/assessment/{changeRequestId}")) return changeRequestAssessment(path.get("changeRequestId")); if (path.matches("/changemanagement/v1/vcmr")) return getVCMRs(); + if (path.matches("/changemanagement/v1/vcmr/{vcmrId}")) return getVCMR(path.get("vcmrId")); return ErrorResponse.notFoundError("Nothing at " + path); } @@ -74,6 +80,18 @@ public class ChangeManagementApiHandler extends AuditLoggingRequestHandler { return ErrorResponse.notFoundError("Nothing at " + path); } + private HttpResponse patch(HttpRequest request) { + Path path = new Path(request.getUri()); + if (path.matches("/changemanagement/v1/vcmr/{vcmrId}")) return patchVCMR(request, path.get("vcmrId")); + return ErrorResponse.notFoundError("Nothing at " + path); + } + + private HttpResponse delete(HttpRequest request) { + Path path = new Path(request.getUri()); + if (path.matches("/changemanagement/v1/vcmr/{vcmrId}")) return deleteVCMR(path.get("vcmrId")); + return ErrorResponse.notFoundError("Nothing at " + path); + } + private Inspector inspectorOrThrow(HttpRequest request) { try { return SlimeUtils.jsonToSlime(request.getData().readAllBytes()).get(); @@ -183,6 +201,72 @@ public class ChangeManagementApiHandler extends AuditLoggingRequestHandler { return new SlimeJsonResponse(slime); } + private HttpResponse getVCMR(String vcmrId) { + var changeRequest = controller.curator().readChangeRequest(vcmrId); + + if (changeRequest.isEmpty()) { + return ErrorResponse.notFoundError("No VCMR with id: " + vcmrId); + } + + var slime = new Slime(); + var cursor = slime.setObject(); + + ChangeRequestSerializer.writeChangeRequest(cursor, changeRequest.get()); + return new SlimeJsonResponse(slime); + } + + private HttpResponse patchVCMR(HttpRequest request, String vcmrId) { + var optionalChangeRequest = controller.curator().readChangeRequest(vcmrId); + + if (optionalChangeRequest.isEmpty()) { + return ErrorResponse.notFoundError("No VCMR with id: " + vcmrId); + } + + var changeRequest = optionalChangeRequest.get(); + var inspector = inspectorOrThrow(request); + + if (inspector.field("approval").valid()) { + var approval = ChangeRequest.Approval.valueOf(inspector.field("approval").asString()); + changeRequest = changeRequest.withApproval(approval); + } + + if (inspector.field("actionPlan").valid()) { + var actionPlan = ChangeRequestSerializer.readHostActionPlan(inspector.field("actionPlan")); + changeRequest = changeRequest.withActionPlan(actionPlan); + } + + if (inspector.field("status").valid()) { + var status = VespaChangeRequest.Status.valueOf(inspector.field("status").asString()); + changeRequest = changeRequest.withStatus(status); + } + + try (var lock = controller.curator().lockChangeRequests()) { + controller.curator().writeChangeRequest(changeRequest); + } + + var slime = new Slime(); + var cursor = slime.setObject(); + ChangeRequestSerializer.writeChangeRequest(cursor, changeRequest); + return new SlimeJsonResponse(slime); + } + + private HttpResponse deleteVCMR(String vcmrId) { + var changeRequest = controller.curator().readChangeRequest(vcmrId); + + if (changeRequest.isEmpty()) { + return ErrorResponse.notFoundError("No VCMR with id: " + vcmrId); + } + + try (var lock = controller.curator().lockChangeRequests()) { + controller.curator().deleteChangeRequest(changeRequest.get()); + } + + var slime = new Slime(); + var cursor = slime.setObject(); + ChangeRequestSerializer.writeChangeRequest(cursor, changeRequest.get()); + return new SlimeJsonResponse(slime); + } + private Optional<ZoneId> affectedZone(List<String> hosts) { var affectedHosts = hosts.stream() .map(HostName::from) diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/changemanagement/ChangeManagementApiHandlerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/changemanagement/ChangeManagementApiHandlerTest.java index c4412531f80..d87da62b8f2 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/changemanagement/ChangeManagementApiHandlerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/changemanagement/ChangeManagementApiHandlerTest.java @@ -27,11 +27,15 @@ import java.time.Instant; import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.List; +import java.util.UUID; + +import static org.junit.Assert.assertEquals; public class ChangeManagementApiHandlerTest extends ControllerContainerTest { private static final String responses = "src/test/java/com/yahoo/vespa/hosted/controller/restapi/changemanagement/responses/"; private static final AthenzIdentity operator = AthenzUser.fromUserId("operatorUser"); + private static final String changeRequestId = "id123"; private ContainerTester tester; @@ -51,6 +55,36 @@ public class ChangeManagementApiHandlerTest extends ControllerContainerTest { assertFile(new Request("http://localhost:8080/changemanagement/v1/vcmr"), "vcmrs.json"); } + @Test + public void deletes_vcmr() { + assertEquals(1, tester.controller().curator().readChangeRequests().size()); + assertFile(new Request("http://localhost:8080/changemanagement/v1/vcmr/" + changeRequestId, "", Request.Method.DELETE), "vcmr.json"); + assertEquals(0, tester.controller().curator().readChangeRequests().size()); + } + + @Test + public void get_vcmr() { + assertFile(new Request("http://localhost:8080/changemanagement/v1/vcmr/" + changeRequestId, "", Request.Method.GET), "vcmr.json"); + } + + @Test + public void patch_vcmr() { + var payload = "{" + + "\"approval\": \"REJECTED\"," + + "\"status\": \"COMPLETED\"," + + "\"actionPlan\": {" + + " \"hosts\": [{" + + " \"hostname\": \"host1\"," + + " \"state\": \"REQUIRES_OPERATOR_ACTION\"," + + " \"lastUpdated\": \"2021-05-10T14:08:15Z\"" + + "}]}" + + "}"; + assertFile(new Request("http://localhost:8080/changemanagement/v1/vcmr/" + changeRequestId, payload, Request.Method.PATCH), "patched-vcmr.json"); + var changeRequest = tester.controller().curator().readChangeRequest(changeRequestId).orElseThrow(); + assertEquals(ChangeRequest.Approval.REJECTED, changeRequest.getApproval()); + assertEquals(VespaChangeRequest.Status.COMPLETED, changeRequest.getStatus()); + } + private void assertResponse(Request request, @Language("JSON") String body, int statusCode) { addIdentityToRequest(request, operator); tester.assertResponse(request, body, statusCode); @@ -77,7 +111,7 @@ public class ChangeManagementApiHandlerTest extends ControllerContainerTest { ); return new VespaChangeRequest( - "id123", + changeRequestId, source, List.of("switch1"), List.of("host1", "host2"), diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/changemanagement/responses/patched-vcmr.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/changemanagement/responses/patched-vcmr.json new file mode 100644 index 00000000000..3db8b226b21 --- /dev/null +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/changemanagement/responses/patched-vcmr.json @@ -0,0 +1,31 @@ +{ + "id": "id123", + "status": "COMPLETED", + "impact": "VERY_HIGH", + "approval": "REJECTED", + "zoneId": "prod.default", + "source": { + "system": "aws", + "id": "id321", + "url": "url", + "plannedStartTime": "1970-01-01T00:00:09.001Z[UTC]", + "plannedEndTime": "1970-01-01T00:00:09.001Z[UTC]", + "status": "STARTED" + }, + "actionPlan": { + "hosts": [ + { + "hostname": "host1", + "state": "REQUIRES_OPERATOR_ACTION", + "lastUpdated": "2021-05-10T14:08:15Z" + } + ] + }, + "impactedHosts": [ + "host1", + "host2" + ], + "impactedSwitches": [ + "switch1" + ] +} diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/changemanagement/responses/vcmr.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/changemanagement/responses/vcmr.json new file mode 100644 index 00000000000..545fe289be8 --- /dev/null +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/changemanagement/responses/vcmr.json @@ -0,0 +1,36 @@ +{ + "id": "id123", + "status": "IN_PROGRESS", + "impact": "VERY_HIGH", + "approval": "APPROVED", + "zoneId": "prod.default", + "source": { + "system": "aws", + "id": "id321", + "url": "url", + "plannedStartTime": "1970-01-01T00:00:09.001Z[UTC]", + "plannedEndTime": "1970-01-01T00:00:09.001Z[UTC]", + "status": "STARTED" + }, + "actionPlan": { + "hosts": [ + { + "hostname": "host1", + "state": "RETIRING", + "lastUpdated": "1970-01-01T00:00:09.001Z" + }, + { + "hostname": "host2", + "state": "RETIRED", + "lastUpdated": "1970-01-01T00:00:09.001Z" + } + ] + }, + "impactedHosts": [ + "host1", + "host2" + ], + "impactedSwitches": [ + "switch1" + ] +} |