From 0f46b5a2afe92087f0314a93f2ef95a3dfcde56f Mon Sep 17 00:00:00 2001 From: Erik Merkle Date: Mon, 11 Sep 2023 13:28:10 -0500 Subject: [PATCH] Add Token Range to Endpoints endpoint (#377) --- .github/workflows/ci.yaml | 4 +- .../com/datastax/mgmtapi/NodeOpsProvider.java | 6 ++ management-api-server/doc/openapi.json | 67 ++++++++++++++ .../mgmtapi/ManagementApplication.java | 4 + .../resources/common/BaseResources.java | 23 +++++ .../resources/v2/TokenResourcesV2.java | 92 +++++++++++++++++++ .../models/TokenRangeToEndpointResponse.java | 55 +++++++++++ .../v2/models/TokenRangeToEndpoints.java | 62 +++++++++++++ .../mgmtapi/BaseDockerIntegrationTest.java | 68 +++++++++++++- .../com/datastax/mgmtapi/DSESpecificIT.java | 17 ---- .../datastax/mgmtapi/NonDestructiveOpsIT.java | 75 +++------------ .../v2/NonDestructiveOpsResourcesV2IT.java | 54 +++++++++++ 12 files changed, 444 insertions(+), 83 deletions(-) create mode 100644 management-api-server/src/main/java/com/datastax/mgmtapi/resources/v2/TokenResourcesV2.java create mode 100644 management-api-server/src/main/java/com/datastax/mgmtapi/resources/v2/models/TokenRangeToEndpointResponse.java create mode 100644 management-api-server/src/main/java/com/datastax/mgmtapi/resources/v2/models/TokenRangeToEndpoints.java create mode 100644 management-api-server/src/test/java/com/datastax/mgmtapi/resources/v2/NonDestructiveOpsResourcesV2IT.java diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 3af8d77c..8d3d9b49 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -11,7 +11,7 @@ jobs: fail-fast: false matrix: cassandra-version: ['3.11', '4.0', '4.1', '3.11_ubi', '4.0_ubi', '4.1_ubi'] - itTest : ['LifecycleIT', 'KeepAliveIT', 'NonDestructiveOpsIT', 'DestructiveOpsIT'] + itTest : ['LifecycleIT', 'KeepAliveIT', 'NonDestructiveOpsIT', 'DestructiveOpsIT', 'NonDestructiveOpsResourcesV2IT'] include: - cassandra-version: '3.11' run311tests: true @@ -94,7 +94,7 @@ jobs: fail-fast: false matrix: platform-version: ['jdk8', 'ubi'] - itTest : ['LifecycleIT', 'KeepAliveIT', 'NonDestructiveOpsIT', 'DestructiveOpsIT', 'DSESpecificIT'] + itTest : ['LifecycleIT', 'KeepAliveIT', 'NonDestructiveOpsIT', 'DestructiveOpsIT', 'DSESpecificIT', 'NonDestructiveOpsResourcesV2IT'] include: - platform-version: 'jdk8' runDSEtests: true diff --git a/management-api-agent-common/src/main/java/com/datastax/mgmtapi/NodeOpsProvider.java b/management-api-agent-common/src/main/java/com/datastax/mgmtapi/NodeOpsProvider.java index 8d6fed3c..eb74e3e9 100644 --- a/management-api-agent-common/src/main/java/com/datastax/mgmtapi/NodeOpsProvider.java +++ b/management-api-agent-common/src/main/java/com/datastax/mgmtapi/NodeOpsProvider.java @@ -883,4 +883,10 @@ public String move( return submitJob("move", moveOperation, async); } + + @Rpc(name = "getRangeToEndpointMap") + public Map, List> getRangeToEndpointMap( + @RpcParam(name = "keyspaceName") String keyspaceName) { + return ShimLoader.instance.get().getStorageService().getRangeToEndpointMap(keyspaceName); + } } diff --git a/management-api-server/doc/openapi.json b/management-api-server/doc/openapi.json index 7f2c7bee..781e8f82 100644 --- a/management-api-server/doc/openapi.json +++ b/management-api-server/doc/openapi.json @@ -1809,6 +1809,42 @@ }, "summary" : "Initiate a new repair" } + }, + "/api/v2/tokens/rangetoendpoint" : { + "get" : { + "operationId" : "getRangeToEndpointMapV2", + "parameters" : [ { + "in" : "query", + "name" : "keyspaceName", + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/TokenRangeToEndpointResponse" + } + } + }, + "description" : "Token range retrieval was successful" + }, + "404" : { + "content" : { + "text/plain" : { + "example" : "keyspace not found", + "schema" : { + "type" : "string" + } + } + }, + "description" : "Keyspace not found" + } + }, + "summary" : "Retrieve a mapping of Token ranges to endpoints" + } } }, "components" : { @@ -2316,6 +2352,37 @@ } } }, + "TokenRangeToEndpointResponse" : { + "type" : "object", + "properties" : { + "token_range_to_endpoints" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/TokenRangeToEndpoints" + } + } + }, + "required" : [ "token_range_to_endpoints" ] + }, + "TokenRangeToEndpoints" : { + "type" : "object", + "properties" : { + "endpoints" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "tokens" : { + "type" : "array", + "items" : { + "type" : "integer", + "format" : "int64" + } + } + }, + "required" : [ "endpoints", "tokens" ] + }, "Variant" : { "type" : "object", "properties" : { diff --git a/management-api-server/src/main/java/com/datastax/mgmtapi/ManagementApplication.java b/management-api-server/src/main/java/com/datastax/mgmtapi/ManagementApplication.java index ad502b8c..3d0e0125 100644 --- a/management-api-server/src/main/java/com/datastax/mgmtapi/ManagementApplication.java +++ b/management-api-server/src/main/java/com/datastax/mgmtapi/ManagementApplication.java @@ -12,6 +12,8 @@ import com.datastax.mgmtapi.resources.MetadataResources; import com.datastax.mgmtapi.resources.NodeOpsResources; import com.datastax.mgmtapi.resources.TableOpsResources; +import com.datastax.mgmtapi.resources.v2.RepairResourcesV2; +import com.datastax.mgmtapi.resources.v2.TokenResourcesV2; import com.google.common.collect.ImmutableSet; import io.swagger.v3.jaxrs2.SwaggerSerializers; import io.swagger.v3.jaxrs2.integration.resources.OpenApiResource; @@ -65,6 +67,8 @@ public ManagementApplication( new TableOpsResources(this), new com.datastax.mgmtapi.resources.v1.TableOpsResources(this), new AuthResources(this), + new RepairResourcesV2(this), + new TokenResourcesV2(this), new OpenApiResource(), new SwaggerSerializers()); } diff --git a/management-api-server/src/main/java/com/datastax/mgmtapi/resources/common/BaseResources.java b/management-api-server/src/main/java/com/datastax/mgmtapi/resources/common/BaseResources.java index ac4a4c27..625a31d3 100644 --- a/management-api-server/src/main/java/com/datastax/mgmtapi/resources/common/BaseResources.java +++ b/management-api-server/src/main/java/com/datastax/mgmtapi/resources/common/BaseResources.java @@ -8,6 +8,8 @@ import com.datastax.mgmtapi.ManagementApplication; import com.datastax.mgmtapi.resources.helpers.ResponseTools; import com.datastax.oss.driver.api.core.NoNodeAvailableException; +import com.datastax.oss.driver.api.core.cql.ResultSet; +import com.datastax.oss.driver.api.core.cql.Row; import java.util.concurrent.Callable; import javax.ws.rs.client.Entity; import javax.ws.rs.core.Response; @@ -71,4 +73,25 @@ protected Response handle(Callable action) { .build(); } } + + /** + * Returns true if the specified keyspaceName is not null and a keyspace with the name exists. + * Returns false if the keyspaceName is null or if no keyspace with the name exists. Throws a + * ConnectionClosedException if there is an issue executing the RPC call to the Cassandra agent. + * + * @param keyspaceName The name of a keyspace you are looking for. + * @return True if the keyspace is found, false otherwise. + */ + protected boolean keyspaceExists(String keyspaceName) throws ConnectionClosedException { + if (keyspaceName != null) { + ResultSet result = + app.cqlService.executePreparedStatement( + app.dbUnixSocketFile, "CALL NodeOps.getKeyspaces()"); + Row row = result.one(); + if (row != null) { + return row.getList(0, String.class).contains(keyspaceName); + } + } + return false; + } } diff --git a/management-api-server/src/main/java/com/datastax/mgmtapi/resources/v2/TokenResourcesV2.java b/management-api-server/src/main/java/com/datastax/mgmtapi/resources/v2/TokenResourcesV2.java new file mode 100644 index 00000000..8595ab25 --- /dev/null +++ b/management-api-server/src/main/java/com/datastax/mgmtapi/resources/v2/TokenResourcesV2.java @@ -0,0 +1,92 @@ +/* + * Copyright DataStax, Inc. + * + * Please see the included license file for details. + */ +package com.datastax.mgmtapi.resources.v2; + +import com.datastax.mgmtapi.ManagementApplication; +import com.datastax.mgmtapi.resources.common.BaseResources; +import com.datastax.mgmtapi.resources.helpers.ResponseTools; +import com.datastax.mgmtapi.resources.v2.models.TokenRangeToEndpointResponse; +import com.datastax.mgmtapi.resources.v2.models.TokenRangeToEndpoints; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +@Path("/api/v2/tokens") +public class TokenResourcesV2 extends BaseResources { + + public TokenResourcesV2(ManagementApplication application) { + super(application); + } + + @GET + @Path("/rangetoendpoint") + @Produces(MediaType.APPLICATION_JSON) + @Operation( + summary = "Retrieve a mapping of Token ranges to endpoints", + operationId = "getRangeToEndpointMapV2") + @ApiResponse( + responseCode = "200", + description = "Token range retrieval was successful", + content = + @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = TokenRangeToEndpointResponse.class))) + @ApiResponse( + responseCode = "404", + description = "Keyspace not found", + content = + @Content( + mediaType = MediaType.TEXT_PLAIN, + schema = @Schema(implementation = String.class), + examples = @ExampleObject(value = "keyspace not found"))) + public Response getRangeToEndpointMap(@QueryParam(value = "keyspaceName") String keyspaceName) { + return handle( + () -> { + if (keyspaceName != null && !keyspaceExists(keyspaceName)) { + return Response.status(Response.Status.NOT_FOUND).entity("keyspace not found").build(); + } + + Map, List> map = + (Map, List>) + ResponseTools.getSingleRowResponse( + app.dbUnixSocketFile, + app.cqlService, + "CALL NodeOps.getRangeToEndpointMap(?)", + keyspaceName); + return Response.ok(convert(map)).build(); + }); + } + + private TokenRangeToEndpointResponse convert(Map, List> map) { + List rangesToEndpoints = new ArrayList<>(map.size()); + map.entrySet() + .forEach( + (Map.Entry, List> e) -> { + rangesToEndpoints.add( + new TokenRangeToEndpoints(convertRanges(e.getKey()), e.getValue())); + }); + return new TokenRangeToEndpointResponse(rangesToEndpoints); + } + + private List convertRanges(List range) { + // each Range should be exactly 2 strings: start, end + assert range.size() == 2; + List tokenRange = Arrays.asList(Long.valueOf(range.get(0)), Long.parseLong(range.get(1))); + return tokenRange; + } +} diff --git a/management-api-server/src/main/java/com/datastax/mgmtapi/resources/v2/models/TokenRangeToEndpointResponse.java b/management-api-server/src/main/java/com/datastax/mgmtapi/resources/v2/models/TokenRangeToEndpointResponse.java new file mode 100644 index 00000000..819a9858 --- /dev/null +++ b/management-api-server/src/main/java/com/datastax/mgmtapi/resources/v2/models/TokenRangeToEndpointResponse.java @@ -0,0 +1,55 @@ +/* + * Copyright DataStax, Inc. + * + * Please see the included license file for details. + */ +package com.datastax.mgmtapi.resources.v2.models; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.List; +import java.util.Objects; + +public class TokenRangeToEndpointResponse { + + @JsonProperty(value = "token_range_to_endpoints", required = true) + public final List tokenRangeToEndpoints; + + @JsonCreator + public TokenRangeToEndpointResponse( + @JsonProperty(value = "token_range_to_endpoints", required = true) + List list) { + this.tokenRangeToEndpoints = list; + } + + @Override + public int hashCode() { + return 83 * Objects.hashCode(tokenRangeToEndpoints); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + TokenRangeToEndpointResponse other = (TokenRangeToEndpointResponse) obj; + return Objects.equals(this.tokenRangeToEndpoints, other.tokenRangeToEndpoints); + } + + @Override + public String toString() { + try { + return new ObjectMapper().writeValueAsString(this); + } catch (JsonProcessingException e) { + return String.format("Unable to format TokenRangeToEndpointResponse (%s)", e.getMessage()); + } + } +} diff --git a/management-api-server/src/main/java/com/datastax/mgmtapi/resources/v2/models/TokenRangeToEndpoints.java b/management-api-server/src/main/java/com/datastax/mgmtapi/resources/v2/models/TokenRangeToEndpoints.java new file mode 100644 index 00000000..ddb73242 --- /dev/null +++ b/management-api-server/src/main/java/com/datastax/mgmtapi/resources/v2/models/TokenRangeToEndpoints.java @@ -0,0 +1,62 @@ +/* + * Copyright DataStax, Inc. + * + * Please see the included license file for details. + */ +package com.datastax.mgmtapi.resources.v2.models; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.List; +import java.util.Objects; + +public class TokenRangeToEndpoints { + + @JsonProperty(value = "tokens", required = true) + public final List tokens; + + @JsonProperty(value = "endpoints", required = true) + public final List endpoints; + + @JsonCreator + public TokenRangeToEndpoints( + @JsonProperty(value = "tokens", required = true) List tokens, + @JsonProperty(value = "endpoints", required = true) List endpoints) { + this.tokens = tokens; + this.endpoints = endpoints; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + TokenRangeToEndpoints other = (TokenRangeToEndpoints) obj; + if (!Objects.equals(this.tokens, other.tokens)) { + return false; + } + return Objects.equals(this.endpoints, other.endpoints); + } + + @Override + public int hashCode() { + return 83 * Objects.hashCode(this.tokens) * Objects.hashCode(this.endpoints); + } + + @Override + public String toString() { + try { + return new ObjectMapper().writeValueAsString(this); + } catch (JsonProcessingException e) { + return String.format("Unable to format TokenRangeToEndpoints (%s)", e.getMessage()); + } + } +} diff --git a/management-api-server/src/test/java/com/datastax/mgmtapi/BaseDockerIntegrationTest.java b/management-api-server/src/test/java/com/datastax/mgmtapi/BaseDockerIntegrationTest.java index bf934ee9..70a7a9c9 100644 --- a/management-api-server/src/test/java/com/datastax/mgmtapi/BaseDockerIntegrationTest.java +++ b/management-api-server/src/test/java/com/datastax/mgmtapi/BaseDockerIntegrationTest.java @@ -5,17 +5,30 @@ */ package com.datastax.mgmtapi; +import static io.netty.util.CharsetUtil.UTF_8; +import static org.junit.Assert.assertTrue; + import com.datastax.mgmtapi.helpers.DockerHelper; import com.datastax.mgmtapi.helpers.NettyHttpClient; +import com.datastax.mgmtapi.resources.models.CreateOrAlterKeyspaceRequest; +import com.datastax.mgmtapi.resources.models.ReplicationSetting; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.Lists; +import io.netty.handler.codec.http.FullHttpResponse; import java.io.File; import java.io.IOError; import java.io.IOException; import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import javax.net.ssl.SSLException; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.http.HttpStatus; +import org.apache.http.client.utils.URIBuilder; import org.junit.AfterClass; import org.junit.AssumptionViolatedException; import org.junit.Before; @@ -26,14 +39,13 @@ import org.junit.rules.TestWatcher; import org.junit.runner.Description; import org.junit.runners.Parameterized; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; public abstract class BaseDockerIntegrationTest { - protected static final Logger logger = LoggerFactory.getLogger(BaseDockerIntegrationTest.class); protected static final String BASE_PATH = "http://localhost:8080/api/v0"; protected static final String BASE_PATH_V1 = "http://localhost:8080/api/v1"; + protected static final String BASE_PATH_V2 = "http://localhost:8080/api/v2"; protected static final URL BASE_URL; + protected static final ObjectMapper JSON_MAPPER = new ObjectMapper(); static { try { @@ -163,4 +175,54 @@ protected static File getTempDir() { protected NettyHttpClient getClient() throws SSLException { return new NettyHttpClient(BASE_URL); } + + protected void createKeyspace(NettyHttpClient client, String localDc, String keyspaceName, int rf) + throws IOException, URISyntaxException { + CreateOrAlterKeyspaceRequest request = + new CreateOrAlterKeyspaceRequest( + keyspaceName, Arrays.asList(new ReplicationSetting(localDc, rf))); + String requestAsJSON = JSON_MAPPER.writeValueAsString(request); + + URI uri = new URIBuilder(BASE_PATH + "/ops/keyspace/create").build(); + boolean requestSuccessful = + client + .post(uri.toURL(), requestAsJSON) + .thenApply(r -> r.status().code() == HttpStatus.SC_OK) + .join(); + assertTrue(requestSuccessful); + } + + protected String responseAsString(FullHttpResponse r) { + if (r.status().code() == HttpStatus.SC_OK) { + byte[] result = new byte[r.content().readableBytes()]; + r.content().readBytes(result); + + return new String(result); + } + + return null; + } + + protected Pair responseAsCodeAndBody(FullHttpResponse r) { + FullHttpResponse copy = r.copy(); + if (copy.content().readableBytes() > 0) { + return Pair.of(copy.status().code(), copy.content().toString(UTF_8)); + } + + return Pair.of(copy.status().code(), null); + } + + protected int getNumTokenRanges() { + if (this.version.startsWith("3")) { + return 256; + } + if (this.version.startsWith("dse-68")) { + return 1; + } + if (this.version.startsWith("4")) { + return 16; + } + // unsupported Cassandra/DSE version + throw new UnsupportedOperationException("Cassandra version " + this.version + " not supported"); + } } diff --git a/management-api-server/src/test/java/com/datastax/mgmtapi/DSESpecificIT.java b/management-api-server/src/test/java/com/datastax/mgmtapi/DSESpecificIT.java index a52ceacb..fc2ef7af 100644 --- a/management-api-server/src/test/java/com/datastax/mgmtapi/DSESpecificIT.java +++ b/management-api-server/src/test/java/com/datastax/mgmtapi/DSESpecificIT.java @@ -9,7 +9,6 @@ import static com.datastax.mgmtapi.BaseDockerIntegrationTest.BASE_URL; import static com.datastax.mgmtapi.NonDestructiveOpsIT.ensureStarted; import static com.datastax.oss.driver.api.core.config.DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER; -import static io.netty.util.CharsetUtil.UTF_8; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -29,7 +28,6 @@ import com.fasterxml.jackson.databind.json.JsonMapper; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import io.netty.handler.codec.http.FullHttpResponse; import java.io.IOException; import java.net.InetSocketAddress; import java.net.URI; @@ -160,19 +158,4 @@ private void createKeyspace(NettyHttpClient client, String localDc, String keysp .join(); assertTrue(requestSuccessful); } - - private String responseAsString(FullHttpResponse r) { - if (r.status().code() == HttpStatus.SC_OK) { - byte[] result = new byte[r.content().readableBytes()]; - r.content().readBytes(result); - - return new String(result); - } - - return null; - } - - private Pair responseAsCodeAndBody(FullHttpResponse r) { - return Pair.of(r.status().code(), r.content().toString(UTF_8)); - } } diff --git a/management-api-server/src/test/java/com/datastax/mgmtapi/NonDestructiveOpsIT.java b/management-api-server/src/test/java/com/datastax/mgmtapi/NonDestructiveOpsIT.java index 69b0cf0d..0d86a627 100644 --- a/management-api-server/src/test/java/com/datastax/mgmtapi/NonDestructiveOpsIT.java +++ b/management-api-server/src/test/java/com/datastax/mgmtapi/NonDestructiveOpsIT.java @@ -5,7 +5,6 @@ */ package com.datastax.mgmtapi; -import static io.netty.util.CharsetUtil.UTF_8; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.awaitility.Awaitility.await; @@ -41,7 +40,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.util.concurrent.Uninterruptibles; -import io.netty.handler.codec.http.FullHttpResponse; import io.netty.util.IllegalReferenceCountException; import java.io.IOException; import java.net.URI; @@ -52,13 +50,10 @@ import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; -import javax.ws.rs.core.MediaType; import org.apache.commons.lang3.tuple.Pair; import org.apache.http.HttpStatus; import org.apache.http.client.utils.URIBuilder; import org.assertj.core.util.Lists; -import org.jboss.resteasy.core.messagebody.ReaderUtility; -import org.jboss.resteasy.core.messagebody.WriterUtility; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -366,8 +361,7 @@ public void testCleanup() throws IOException, URISyntaxException, InterruptedExc KeyspaceRequest keyspaceRequest = new KeyspaceRequest(1, "system_traces", Collections.singletonList("events")); - String keyspaceRequestAsJSON = - WriterUtility.asString(keyspaceRequest, MediaType.APPLICATION_JSON); + String keyspaceRequestAsJSON = JSON_MAPPER.writeValueAsString(keyspaceRequest); URI uri = new URIBuilder(BASE_PATH + "/ops/keyspace/cleanup").build(); // Get job_id here.. @@ -441,7 +435,7 @@ public void testScrub() throws IOException, URISyntaxException { ScrubRequest scrubRequest = new ScrubRequest( true, true, true, true, 2, "system_traces", Collections.singletonList("events")); - String requestAsJSON = WriterUtility.asString(scrubRequest, MediaType.APPLICATION_JSON); + String requestAsJSON = JSON_MAPPER.writeValueAsString(scrubRequest); URI uri = new URIBuilder(BASE_PATH + "/ops/tables/scrub").build(); boolean requestSuccessful = client @@ -461,7 +455,7 @@ public void testCompact() throws IOException, URISyntaxException { CompactRequest compactRequest = new CompactRequest( false, false, null, null, "system_traces", null, Collections.singletonList("events")); - String requestAsJSON = WriterUtility.asString(compactRequest, MediaType.APPLICATION_JSON); + String requestAsJSON = JSON_MAPPER.writeValueAsString(compactRequest); URI uri = new URIBuilder(BASE_PATH + "/ops/tables/compact").build(); boolean requestSuccessful = client @@ -480,8 +474,7 @@ public void testGarbageCollect() throws IOException, URISyntaxException { KeyspaceRequest keyspaceRequest = new KeyspaceRequest(1, "system_traces", Collections.singletonList("events")); - String keyspaceRequestAsJSON = - WriterUtility.asString(keyspaceRequest, MediaType.APPLICATION_JSON); + String keyspaceRequestAsJSON = JSON_MAPPER.writeValueAsString(keyspaceRequest); URI uri = new URIBuilder(BASE_PATH + "/ops/tables/garbagecollect").build(); boolean requestSuccessful = client @@ -499,8 +492,7 @@ public void testFlush() throws IOException, URISyntaxException { NettyHttpClient client = new NettyHttpClient(BASE_URL); KeyspaceRequest keyspaceRequest = new KeyspaceRequest(1, null, null); - String keyspaceRequestAsJSON = - WriterUtility.asString(keyspaceRequest, MediaType.APPLICATION_JSON); + String keyspaceRequestAsJSON = JSON_MAPPER.writeValueAsString(keyspaceRequest); URI uri = new URIBuilder(BASE_PATH + "/ops/tables/flush").build(); boolean requestSuccessful = client @@ -518,8 +510,7 @@ public void testUpgradeSSTables() throws IOException, URISyntaxException { NettyHttpClient client = new NettyHttpClient(BASE_URL); KeyspaceRequest keyspaceRequest = new KeyspaceRequest(1, "", null); - String keyspaceRequestAsJSON = - WriterUtility.asString(keyspaceRequest, MediaType.APPLICATION_JSON); + String keyspaceRequestAsJSON = JSON_MAPPER.writeValueAsString(keyspaceRequest); URI uri = new URIBuilder(BASE_PATH + "/ops/tables/sstables/upgrade").build(); boolean requestSuccessful = client @@ -574,7 +565,7 @@ public void testAlterKeyspace() throws IOException, URISyntaxException { CreateOrAlterKeyspaceRequest request = new CreateOrAlterKeyspaceRequest(ks, Arrays.asList(new ReplicationSetting(localDc, 3))); - String requestAsJSON = WriterUtility.asString(request, MediaType.APPLICATION_JSON); + String requestAsJSON = JSON_MAPPER.writeValueAsString(request); boolean requestSuccessful = client @@ -624,7 +615,7 @@ public void testGetSchemaVersions() throws IOException, URISyntaxException { NettyHttpClient client = new NettyHttpClient(BASE_URL); - URIBuilder uriBuilder = new URIBuilder("http://localhost:8080/api/v1/ops/node/schema/versions"); + URIBuilder uriBuilder = new URIBuilder(BASE_PATH_V1 + "/ops/node/schema/versions"); URI uri = uriBuilder.build(); Pair response = @@ -670,7 +661,7 @@ public void testGetSnapshotDetails() null, null, null); - String requestAsJSON = WriterUtility.asString(takeSnapshotRequest, MediaType.APPLICATION_JSON); + String requestAsJSON = JSON_MAPPER.writeValueAsString(takeSnapshotRequest); boolean takeSnapshotSuccessful = client @@ -684,8 +675,7 @@ public void testGetSnapshotDetails() String getSnapshotResponse = client.get(getSnapshotsUri.toURL()).thenApply(this::responseAsString).join(); assertNotNull(getSnapshotResponse); - Object responseObject = - ReaderUtility.read(Object.class, MediaType.APPLICATION_JSON, getSnapshotResponse); + Object responseObject = JSON_MAPPER.readValue(getSnapshotResponse, Object.class); assertTrue(responseObject instanceof Map); Map responseObj = (Map) responseObject; assertTrue(responseObj.containsKey("entity")); @@ -714,8 +704,7 @@ public void testGetSnapshotDetails() getSnapshotResponse = client.get(getSnapshotsUri.toURL()).thenApply(this::responseAsString).join(); assertNotNull(getSnapshotResponse); - responseObject = - ReaderUtility.read(Object.class, MediaType.APPLICATION_JSON, getSnapshotResponse); + responseObject = JSON_MAPPER.readValue(getSnapshotResponse, Object.class); assertTrue(responseObject instanceof Map); responseObj = (Map) responseObject; assertTrue(responseObj.containsKey("entity")); @@ -746,7 +735,7 @@ public void testRepair() throws IOException, URISyntaxException, InterruptedExce // execute repair RepairRequest repairRequest = new RepairRequest(ks, null, Boolean.TRUE); - String requestAsJSON = WriterUtility.asString(repairRequest, MediaType.APPLICATION_JSON); + String requestAsJSON = JSON_MAPPER.writeValueAsString(repairRequest); boolean repairSuccessful = client @@ -772,12 +761,12 @@ public void testAsyncRepair() throws IOException, URISyntaxException, Interrupte String ks = "someTestKeyspace"; createKeyspace(client, localDc, ks, 2); - URIBuilder uriBuilder = new URIBuilder("http://localhost:8080/api/v1/ops/node/repair"); + URIBuilder uriBuilder = new URIBuilder(BASE_PATH_V1 + "/ops/node/repair"); URI repairUri = uriBuilder.build(); // execute repair RepairRequest repairRequest = new RepairRequest("someTestKeyspace", null, Boolean.TRUE); - String requestAsJSON = WriterUtility.asString(repairRequest, MediaType.APPLICATION_JSON); + String requestAsJSON = JSON_MAPPER.writeValueAsString(repairRequest); Pair repairResponse = client.post(repairUri.toURL(), requestAsJSON).thenApply(this::responseAsCodeAndBody).join(); @@ -1044,40 +1033,4 @@ public void testMoveNode() throws IOException, URISyntaxException { "status", value -> assertThat(value).isIn("COMPLETED", "ERROR")); }); } - - private void createKeyspace(NettyHttpClient client, String localDc, String keyspaceName, int rf) - throws IOException, URISyntaxException { - CreateOrAlterKeyspaceRequest request = - new CreateOrAlterKeyspaceRequest( - keyspaceName, Arrays.asList(new ReplicationSetting(localDc, rf))); - String requestAsJSON = WriterUtility.asString(request, MediaType.APPLICATION_JSON); - - URI uri = new URIBuilder(BASE_PATH + "/ops/keyspace/create").build(); - boolean requestSuccessful = - client - .post(uri.toURL(), requestAsJSON) - .thenApply(r -> r.status().code() == HttpStatus.SC_OK) - .join(); - assertTrue(requestSuccessful); - } - - private String responseAsString(FullHttpResponse r) { - if (r.status().code() == HttpStatus.SC_OK) { - byte[] result = new byte[r.content().readableBytes()]; - r.content().readBytes(result); - - return new String(result); - } - - return null; - } - - private Pair responseAsCodeAndBody(FullHttpResponse r) { - FullHttpResponse copy = r.copy(); - if (copy.content().readableBytes() > 0) { - return Pair.of(copy.status().code(), copy.content().toString(UTF_8)); - } - - return Pair.of(copy.status().code(), null); - } } diff --git a/management-api-server/src/test/java/com/datastax/mgmtapi/resources/v2/NonDestructiveOpsResourcesV2IT.java b/management-api-server/src/test/java/com/datastax/mgmtapi/resources/v2/NonDestructiveOpsResourcesV2IT.java new file mode 100644 index 00000000..f248b545 --- /dev/null +++ b/management-api-server/src/test/java/com/datastax/mgmtapi/resources/v2/NonDestructiveOpsResourcesV2IT.java @@ -0,0 +1,54 @@ +/* + * Copyright DataStax, Inc. + * + * Please see the included license file for details. + */ +package com.datastax.mgmtapi.resources.v2; + +import static com.datastax.mgmtapi.NonDestructiveOpsIT.ensureStarted; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assume.assumeTrue; + +import com.datastax.mgmtapi.BaseDockerIntegrationTest; +import com.datastax.mgmtapi.helpers.IntegrationTestUtils; +import com.datastax.mgmtapi.helpers.NettyHttpClient; +import com.datastax.mgmtapi.resources.v2.models.TokenRangeToEndpointResponse; +import java.io.IOException; +import java.net.URI; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.http.HttpStatus; +import org.apache.http.client.utils.URIBuilder; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class NonDestructiveOpsResourcesV2IT extends BaseDockerIntegrationTest { + + public NonDestructiveOpsResourcesV2IT(String version) throws IOException { + super(version); + } + + @Test + public void testGetTokenRangeToEndpointMap() throws Exception { + assumeTrue(IntegrationTestUtils.shouldRun()); + ensureStarted(); + + NettyHttpClient client = new NettyHttpClient(BASE_URL); + final URIBuilder uriBuilder = new URIBuilder(BASE_PATH_V2 + "/tokens/rangetoendpoint"); + // test keyspace not found + URI uri = uriBuilder.setParameter("keyspaceName", "notfoundkeyspace").build(); + Pair response = + client.get(uri.toURL()).thenApply(this::responseAsCodeAndBody).join(); + assertThat(response.getLeft()).isEqualTo(HttpStatus.SC_NOT_FOUND); + // test keyspace exists + uri = uriBuilder.setParameter("keyspaceName", "system_schema").build(); + response = client.get(uri.toURL()).thenApply(this::responseAsCodeAndBody).join(); + assertThat(response.getLeft()).isEqualTo(HttpStatus.SC_OK); + String mappingString = response.getRight(); + assertThat(mappingString).isNotNull().isNotEmpty(); + TokenRangeToEndpointResponse mapping = + JSON_MAPPER.readValue(mappingString, TokenRangeToEndpointResponse.class); + assertThat(mapping.tokenRangeToEndpoints).isNotNull().isNotEmpty().hasSize(getNumTokenRanges()); + } +}