From c404cb9beebdf9cdb6eba563eef220cfe4f318a1 Mon Sep 17 00:00:00 2001 From: Jon Marius Venstad Date: Mon, 23 Nov 2020 09:50:10 +0100 Subject: Revert "Revert "Jonmv/reindexing rest api"" This reverts commit 278967c98483da6b1308d34bd7b2b77e2f690288. --- .../ai/vespa/reindexing/ReindexingCurator.java | 16 ++-- .../reindexing/http/ReindexingV1ApiHandler.java | 98 ++++++++++++++++++++++ .../vespa/reindexing/http/ReindexingV1ApiTest.java | 80 ++++++++++++++++++ 3 files changed, 186 insertions(+), 8 deletions(-) create mode 100644 clustercontroller-reindexer/src/main/java/ai/vespa/reindexing/http/ReindexingV1ApiHandler.java create mode 100644 clustercontroller-reindexer/src/test/java/ai/vespa/reindexing/http/ReindexingV1ApiTest.java (limited to 'clustercontroller-reindexer') diff --git a/clustercontroller-reindexer/src/main/java/ai/vespa/reindexing/ReindexingCurator.java b/clustercontroller-reindexer/src/main/java/ai/vespa/reindexing/ReindexingCurator.java index 2044e6869f6..202bd92d86e 100644 --- a/clustercontroller-reindexer/src/main/java/ai/vespa/reindexing/ReindexingCurator.java +++ b/clustercontroller-reindexer/src/main/java/ai/vespa/reindexing/ReindexingCurator.java @@ -28,14 +28,6 @@ import static java.util.stream.Collectors.toUnmodifiableMap; */ public class ReindexingCurator { - private static final String STATUS = "status"; - private static final String TYPE = "type"; - private static final String STARTED_MILLIS = "startedMillis"; - private static final String ENDED_MILLIS = "endedMillis"; - private static final String PROGRESS = "progress"; - private static final String STATE = "state"; - private static final String MESSAGE = "message"; - private final Curator curator; private final String clusterName; private final ReindexingSerializer serializer; @@ -78,6 +70,14 @@ public class ReindexingCurator { private static class ReindexingSerializer { + private static final String STATUS = "status"; + private static final String TYPE = "type"; + private static final String STARTED_MILLIS = "startedMillis"; + private static final String ENDED_MILLIS = "endedMillis"; + private static final String PROGRESS = "progress"; + private static final String STATE = "state"; + private static final String MESSAGE = "message"; + private final DocumentTypeManager types; public ReindexingSerializer(DocumentTypeManager types) { diff --git a/clustercontroller-reindexer/src/main/java/ai/vespa/reindexing/http/ReindexingV1ApiHandler.java b/clustercontroller-reindexer/src/main/java/ai/vespa/reindexing/http/ReindexingV1ApiHandler.java new file mode 100644 index 00000000000..fca08f7743c --- /dev/null +++ b/clustercontroller-reindexer/src/main/java/ai/vespa/reindexing/http/ReindexingV1ApiHandler.java @@ -0,0 +1,98 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.reindexing.http; + +import ai.vespa.reindexing.Reindexing; +import ai.vespa.reindexing.ReindexingCurator; +import com.google.inject.Inject; +import com.yahoo.cloud.config.ClusterListConfig; +import com.yahoo.cloud.config.ZookeepersConfig; +import com.yahoo.container.jdisc.HttpRequest; +import com.yahoo.container.jdisc.HttpResponse; +import com.yahoo.container.jdisc.ThreadedHttpRequestHandler; +import com.yahoo.document.DocumentTypeManager; +import com.yahoo.document.config.DocumentmanagerConfig; +import com.yahoo.jdisc.Metric; +import com.yahoo.restapi.ErrorResponse; +import com.yahoo.restapi.Path; +import com.yahoo.restapi.SlimeJsonResponse; +import com.yahoo.slime.Cursor; +import com.yahoo.slime.Slime; +import com.yahoo.vespa.config.content.AllClustersBucketSpacesConfig; +import com.yahoo.vespa.config.content.reindexing.ReindexingConfig; +import com.yahoo.vespa.curator.Curator; +import com.yahoo.vespa.zookeeper.VespaZooKeeperServer; + +import java.util.concurrent.Executor; + +import static com.yahoo.jdisc.http.HttpRequest.Method.GET; + +/** + * Allows inspecting reindexing status over HTTP. + * + * @author jonmv + */ +public class ReindexingV1ApiHandler extends ThreadedHttpRequestHandler { + + private final ReindexingCurator database; + + @Inject + public ReindexingV1ApiHandler(Executor executor, Metric metric, + @SuppressWarnings("unused") VespaZooKeeperServer ensureZkHasStarted, ZookeepersConfig zookeepersConfig, + ReindexingConfig reindexingConfig, DocumentmanagerConfig documentmanagerConfig) { + this(executor, + metric, + new ReindexingCurator(Curator.create(zookeepersConfig.zookeeperserverlist()), + reindexingConfig.clusterName(), + new DocumentTypeManager(documentmanagerConfig))); + } + + ReindexingV1ApiHandler(Executor executor, Metric metric, ReindexingCurator database) { + super(executor, metric); + this.database = database; + } + + @Override + public HttpResponse handle(HttpRequest request) { + Path path = new Path(request.getUri()); + if (request.getMethod() != GET) + return ErrorResponse.methodNotAllowed("Only GET is supported under /reindexing/v1/"); + + if (path.matches("/reindexing/v1")) return getRoot(); + if (path.matches("/reindexing/v1/status")) return getStatus(); + + return ErrorResponse.notFoundError("Nothing at " + request.getUri().getRawPath()); + } + + HttpResponse getRoot() { + Slime slime = new Slime(); + slime.setObject().setArray("resources").addObject().setString("url", "/reindexing/v1/status"); + return new SlimeJsonResponse(slime); + } + + HttpResponse getStatus() { + Slime slime = new Slime(); + Cursor statusArray = slime.setObject().setArray("status"); + database.readReindexing().status().forEach((type, status) -> { + Cursor statusObject = statusArray.addObject(); + statusObject.setString("type", type.getName()); + statusObject.setLong("startedMillis", status.startedAt().toEpochMilli()); + status.endedAt().ifPresent(endedAt -> statusObject.setLong("endedMillis", endedAt.toEpochMilli())); + status.progress().ifPresent(progress -> statusObject.setString("progress", progress.serializeToString())); + statusObject.setString("state", toString(status.state())); + status.message().ifPresent(message -> statusObject.setString("message", message)); + }); + return new SlimeJsonResponse(slime); + } + + + private static String toString(Reindexing.State state) { + switch (state) { + case READY: return "pending"; + case RUNNING: return "running"; + case SUCCESSFUL: return "successful"; + case FAILED: return "failed"; + default: throw new IllegalArgumentException("Unexpected state '" + state + "'"); + } + } + +} diff --git a/clustercontroller-reindexer/src/test/java/ai/vespa/reindexing/http/ReindexingV1ApiTest.java b/clustercontroller-reindexer/src/test/java/ai/vespa/reindexing/http/ReindexingV1ApiTest.java new file mode 100644 index 00000000000..1b6379d21e5 --- /dev/null +++ b/clustercontroller-reindexer/src/test/java/ai/vespa/reindexing/http/ReindexingV1ApiTest.java @@ -0,0 +1,80 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.reindexing.http; + +import ai.vespa.reindexing.Reindexing; +import ai.vespa.reindexing.Reindexing.Status; +import ai.vespa.reindexing.ReindexingCurator; +import com.yahoo.container.jdisc.RequestHandlerTestDriver; +import com.yahoo.document.DocumentType; +import com.yahoo.document.DocumentTypeManager; +import com.yahoo.document.config.DocumentmanagerConfig; +import com.yahoo.documentapi.ProgressToken; +import com.yahoo.jdisc.test.MockMetric; +import com.yahoo.searchdefinition.derived.Deriver; +import com.yahoo.vespa.curator.mock.MockCurator; +import org.junit.jupiter.api.Test; + +import java.time.Instant; +import java.util.concurrent.Executors; + +import static com.yahoo.jdisc.http.HttpRequest.Method.POST; +import static org.junit.Assert.assertEquals; + +/** + * @author jonmv + */ +class ReindexingV1ApiTest { + + DocumentmanagerConfig musicConfig = Deriver.getDocumentManagerConfig("src/test/resources/schemas/music.sd").build(); + DocumentTypeManager manager = new DocumentTypeManager(musicConfig); + DocumentType musicType = manager.getDocumentType("music"); + ReindexingCurator database = new ReindexingCurator(new MockCurator(), "cluster", manager); + ReindexingV1ApiHandler handler = new ReindexingV1ApiHandler(Executors.newSingleThreadExecutor(), new MockMetric(), database); + + @Test + void testResponses() { + RequestHandlerTestDriver driver = new RequestHandlerTestDriver(handler); + + // GET at root + var response = driver.sendRequest("http://localhost/reindexing/v1/"); + assertEquals("{\"resources\":[{\"url\":\"/reindexing/v1/status\"}]}", response.readAll()); + assertEquals("application/json; charset=UTF-8", response.getResponse().headers().getFirst("Content-Type")); + assertEquals(200, response.getStatus()); + + // GET at status with empty database + response = driver.sendRequest("http://localhost/reindexing/v1/status"); + assertEquals("{\"status\":[]}", response.readAll()); + assertEquals(200, response.getStatus()); + + // GET at status with a failed status + database.writeReindexing(Reindexing.empty().with(musicType, Status.ready(Instant.EPOCH) + .running() + .progressed(new ProgressToken()) + .failed(Instant.ofEpochMilli(123), "ヽ(。_°)ノ"))); + response = driver.sendRequest("http://localhost/reindexing/v1/status"); + assertEquals("{\"status\":[{" + + "\"type\":\"music\"," + + "\"startedMillis\":0," + + "\"endedMillis\":123," + + "\"progress\":\"" + new ProgressToken().serializeToString() + "\"," + + "\"state\":\"failed\"," + + "\"message\":\"ヽ(。_°)ノ\"}" + + "]}", + response.readAll()); + assertEquals(200, response.getStatus()); + + // POST at root + response = driver.sendRequest("http://localhost/reindexing/v1/status", POST); + assertEquals("{\"error-code\":\"METHOD_NOT_ALLOWED\",\"message\":\"Only GET is supported under /reindexing/v1/\"}", + response.readAll()); + assertEquals(405, response.getStatus()); + + // GET at non-existent path + response = driver.sendRequest("http://localhost/reindexing/v1/moo"); + assertEquals("{\"error-code\":\"NOT_FOUND\",\"message\":\"Nothing at /reindexing/v1/moo\"}", + response.readAll()); + assertEquals(404, response.getStatus()); + + } + +} -- cgit v1.2.3