summaryrefslogtreecommitdiffstats
path: root/clustercontroller-reindexer
diff options
context:
space:
mode:
authorJon Marius Venstad <venstad@gmail.com>2020-11-23 09:50:10 +0100
committerJon Marius Venstad <venstad@gmail.com>2020-11-23 09:50:10 +0100
commitc404cb9beebdf9cdb6eba563eef220cfe4f318a1 (patch)
tree333de2813c6377e6bc064d74041c91d25348d216 /clustercontroller-reindexer
parentea0c57ca65864ee3261e00c795fcc5cef91c24b2 (diff)
Revert "Revert "Jonmv/reindexing rest api""
This reverts commit 278967c98483da6b1308d34bd7b2b77e2f690288.
Diffstat (limited to 'clustercontroller-reindexer')
-rw-r--r--clustercontroller-reindexer/src/main/java/ai/vespa/reindexing/ReindexingCurator.java16
-rw-r--r--clustercontroller-reindexer/src/main/java/ai/vespa/reindexing/http/ReindexingV1ApiHandler.java98
-rw-r--r--clustercontroller-reindexer/src/test/java/ai/vespa/reindexing/http/ReindexingV1ApiTest.java80
3 files changed, 186 insertions, 8 deletions
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());
+
+ }
+
+}