Skip to content

Commit

Permalink
Add Token Range to Endpoints endpoint (#377)
Browse files Browse the repository at this point in the history
  • Loading branch information
emerkle826 authored Sep 11, 2023
1 parent b7b6cf4 commit 3727fab
Show file tree
Hide file tree
Showing 12 changed files with 444 additions and 83 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -883,4 +883,10 @@ public String move(

return submitJob("move", moveOperation, async);
}

@Rpc(name = "getRangeToEndpointMap")
public Map<List<String>, List<String>> getRangeToEndpointMap(
@RpcParam(name = "keyspaceName") String keyspaceName) {
return ShimLoader.instance.get().getStorageService().getRangeToEndpointMap(keyspaceName);
}
}
67 changes: 67 additions & 0 deletions management-api-server/doc/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -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" : {
Expand Down Expand Up @@ -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" : {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -71,4 +73,25 @@ protected Response handle(Callable<Response> 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;
}
}
Original file line number Diff line number Diff line change
@@ -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<String>, List<String>> map =
(Map<List<String>, List<String>>)
ResponseTools.getSingleRowResponse(
app.dbUnixSocketFile,
app.cqlService,
"CALL NodeOps.getRangeToEndpointMap(?)",
keyspaceName);
return Response.ok(convert(map)).build();
});
}

private TokenRangeToEndpointResponse convert(Map<List<String>, List<String>> map) {
List<TokenRangeToEndpoints> rangesToEndpoints = new ArrayList<>(map.size());
map.entrySet()
.forEach(
(Map.Entry<List<String>, List<String>> e) -> {
rangesToEndpoints.add(
new TokenRangeToEndpoints(convertRanges(e.getKey()), e.getValue()));
});
return new TokenRangeToEndpointResponse(rangesToEndpoints);
}

private List<Long> convertRanges(List<String> range) {
// each Range should be exactly 2 strings: start, end
assert range.size() == 2;
List<Long> tokenRange = Arrays.asList(Long.valueOf(range.get(0)), Long.parseLong(range.get(1)));
return tokenRange;
}
}
Original file line number Diff line number Diff line change
@@ -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> tokenRangeToEndpoints;

@JsonCreator
public TokenRangeToEndpointResponse(
@JsonProperty(value = "token_range_to_endpoints", required = true)
List<TokenRangeToEndpoints> 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());
}
}
}
Original file line number Diff line number Diff line change
@@ -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<Long> tokens;

@JsonProperty(value = "endpoints", required = true)
public final List<String> endpoints;

@JsonCreator
public TokenRangeToEndpoints(
@JsonProperty(value = "tokens", required = true) List<Long> tokens,
@JsonProperty(value = "endpoints", required = true) List<String> 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());
}
}
}
Loading

0 comments on commit 3727fab

Please sign in to comment.