Skip to content

Commit a0598c6

Browse files
committed
Add Token Range to Endpoints endpoint (#377)
1 parent 6d4f91f commit a0598c6

File tree

12 files changed

+444
-83
lines changed

12 files changed

+444
-83
lines changed

.github/workflows/ci.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
fail-fast: false
1212
matrix:
1313
cassandra-version: ['3.11', '4.0', '4.1', '3.11_ubi', '4.0_ubi', '4.1_ubi']
14-
itTest : ['LifecycleIT', 'KeepAliveIT', 'NonDestructiveOpsIT', 'DestructiveOpsIT']
14+
itTest : ['LifecycleIT', 'KeepAliveIT', 'NonDestructiveOpsIT', 'DestructiveOpsIT', 'NonDestructiveOpsResourcesV2IT']
1515
include:
1616
- cassandra-version: '3.11'
1717
run311tests: true
@@ -94,7 +94,7 @@ jobs:
9494
fail-fast: false
9595
matrix:
9696
platform-version: ['jdk8', 'ubi']
97-
itTest : ['LifecycleIT', 'KeepAliveIT', 'NonDestructiveOpsIT', 'DestructiveOpsIT', 'DSESpecificIT']
97+
itTest : ['LifecycleIT', 'KeepAliveIT', 'NonDestructiveOpsIT', 'DestructiveOpsIT', 'DSESpecificIT', 'NonDestructiveOpsResourcesV2IT']
9898
include:
9999
- platform-version: 'jdk8'
100100
runDSEtests: true

management-api-agent-common/src/main/java/com/datastax/mgmtapi/NodeOpsProvider.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -883,4 +883,10 @@ public String move(
883883

884884
return submitJob("move", moveOperation, async);
885885
}
886+
887+
@Rpc(name = "getRangeToEndpointMap")
888+
public Map<List<String>, List<String>> getRangeToEndpointMap(
889+
@RpcParam(name = "keyspaceName") String keyspaceName) {
890+
return ShimLoader.instance.get().getStorageService().getRangeToEndpointMap(keyspaceName);
891+
}
886892
}

management-api-server/doc/openapi.json

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1809,6 +1809,42 @@
18091809
},
18101810
"summary" : "Initiate a new repair"
18111811
}
1812+
},
1813+
"/api/v2/tokens/rangetoendpoint" : {
1814+
"get" : {
1815+
"operationId" : "getRangeToEndpointMapV2",
1816+
"parameters" : [ {
1817+
"in" : "query",
1818+
"name" : "keyspaceName",
1819+
"schema" : {
1820+
"type" : "string"
1821+
}
1822+
} ],
1823+
"responses" : {
1824+
"200" : {
1825+
"content" : {
1826+
"application/json" : {
1827+
"schema" : {
1828+
"$ref" : "#/components/schemas/TokenRangeToEndpointResponse"
1829+
}
1830+
}
1831+
},
1832+
"description" : "Token range retrieval was successful"
1833+
},
1834+
"404" : {
1835+
"content" : {
1836+
"text/plain" : {
1837+
"example" : "keyspace not found",
1838+
"schema" : {
1839+
"type" : "string"
1840+
}
1841+
}
1842+
},
1843+
"description" : "Keyspace not found"
1844+
}
1845+
},
1846+
"summary" : "Retrieve a mapping of Token ranges to endpoints"
1847+
}
18121848
}
18131849
},
18141850
"components" : {
@@ -2316,6 +2352,37 @@
23162352
}
23172353
}
23182354
},
2355+
"TokenRangeToEndpointResponse" : {
2356+
"type" : "object",
2357+
"properties" : {
2358+
"token_range_to_endpoints" : {
2359+
"type" : "array",
2360+
"items" : {
2361+
"$ref" : "#/components/schemas/TokenRangeToEndpoints"
2362+
}
2363+
}
2364+
},
2365+
"required" : [ "token_range_to_endpoints" ]
2366+
},
2367+
"TokenRangeToEndpoints" : {
2368+
"type" : "object",
2369+
"properties" : {
2370+
"endpoints" : {
2371+
"type" : "array",
2372+
"items" : {
2373+
"type" : "string"
2374+
}
2375+
},
2376+
"tokens" : {
2377+
"type" : "array",
2378+
"items" : {
2379+
"type" : "integer",
2380+
"format" : "int64"
2381+
}
2382+
}
2383+
},
2384+
"required" : [ "endpoints", "tokens" ]
2385+
},
23192386
"Variant" : {
23202387
"type" : "object",
23212388
"properties" : {

management-api-server/src/main/java/com/datastax/mgmtapi/ManagementApplication.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import com.datastax.mgmtapi.resources.MetadataResources;
1313
import com.datastax.mgmtapi.resources.NodeOpsResources;
1414
import com.datastax.mgmtapi.resources.TableOpsResources;
15+
import com.datastax.mgmtapi.resources.v2.RepairResourcesV2;
16+
import com.datastax.mgmtapi.resources.v2.TokenResourcesV2;
1517
import com.google.common.collect.ImmutableSet;
1618
import io.swagger.v3.jaxrs2.SwaggerSerializers;
1719
import io.swagger.v3.jaxrs2.integration.resources.OpenApiResource;
@@ -65,6 +67,8 @@ public ManagementApplication(
6567
new TableOpsResources(this),
6668
new com.datastax.mgmtapi.resources.v1.TableOpsResources(this),
6769
new AuthResources(this),
70+
new RepairResourcesV2(this),
71+
new TokenResourcesV2(this),
6872
new OpenApiResource(),
6973
new SwaggerSerializers());
7074
}

management-api-server/src/main/java/com/datastax/mgmtapi/resources/common/BaseResources.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import com.datastax.mgmtapi.ManagementApplication;
99
import com.datastax.mgmtapi.resources.helpers.ResponseTools;
1010
import com.datastax.oss.driver.api.core.NoNodeAvailableException;
11+
import com.datastax.oss.driver.api.core.cql.ResultSet;
12+
import com.datastax.oss.driver.api.core.cql.Row;
1113
import java.util.concurrent.Callable;
1214
import javax.ws.rs.client.Entity;
1315
import javax.ws.rs.core.Response;
@@ -71,4 +73,25 @@ protected Response handle(Callable<Response> action) {
7173
.build();
7274
}
7375
}
76+
77+
/**
78+
* Returns true if the specified keyspaceName is not null and a keyspace with the name exists.
79+
* Returns false if the keyspaceName is null or if no keyspace with the name exists. Throws a
80+
* ConnectionClosedException if there is an issue executing the RPC call to the Cassandra agent.
81+
*
82+
* @param keyspaceName The name of a keyspace you are looking for.
83+
* @return True if the keyspace is found, false otherwise.
84+
*/
85+
protected boolean keyspaceExists(String keyspaceName) throws ConnectionClosedException {
86+
if (keyspaceName != null) {
87+
ResultSet result =
88+
app.cqlService.executePreparedStatement(
89+
app.dbUnixSocketFile, "CALL NodeOps.getKeyspaces()");
90+
Row row = result.one();
91+
if (row != null) {
92+
return row.getList(0, String.class).contains(keyspaceName);
93+
}
94+
}
95+
return false;
96+
}
7497
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Copyright DataStax, Inc.
3+
*
4+
* Please see the included license file for details.
5+
*/
6+
package com.datastax.mgmtapi.resources.v2;
7+
8+
import com.datastax.mgmtapi.ManagementApplication;
9+
import com.datastax.mgmtapi.resources.common.BaseResources;
10+
import com.datastax.mgmtapi.resources.helpers.ResponseTools;
11+
import com.datastax.mgmtapi.resources.v2.models.TokenRangeToEndpointResponse;
12+
import com.datastax.mgmtapi.resources.v2.models.TokenRangeToEndpoints;
13+
import io.swagger.v3.oas.annotations.Operation;
14+
import io.swagger.v3.oas.annotations.media.Content;
15+
import io.swagger.v3.oas.annotations.media.ExampleObject;
16+
import io.swagger.v3.oas.annotations.media.Schema;
17+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
18+
import java.util.ArrayList;
19+
import java.util.Arrays;
20+
import java.util.List;
21+
import java.util.Map;
22+
import javax.ws.rs.GET;
23+
import javax.ws.rs.Path;
24+
import javax.ws.rs.Produces;
25+
import javax.ws.rs.QueryParam;
26+
import javax.ws.rs.core.MediaType;
27+
import javax.ws.rs.core.Response;
28+
29+
@Path("/api/v2/tokens")
30+
public class TokenResourcesV2 extends BaseResources {
31+
32+
public TokenResourcesV2(ManagementApplication application) {
33+
super(application);
34+
}
35+
36+
@GET
37+
@Path("/rangetoendpoint")
38+
@Produces(MediaType.APPLICATION_JSON)
39+
@Operation(
40+
summary = "Retrieve a mapping of Token ranges to endpoints",
41+
operationId = "getRangeToEndpointMapV2")
42+
@ApiResponse(
43+
responseCode = "200",
44+
description = "Token range retrieval was successful",
45+
content =
46+
@Content(
47+
mediaType = MediaType.APPLICATION_JSON,
48+
schema = @Schema(implementation = TokenRangeToEndpointResponse.class)))
49+
@ApiResponse(
50+
responseCode = "404",
51+
description = "Keyspace not found",
52+
content =
53+
@Content(
54+
mediaType = MediaType.TEXT_PLAIN,
55+
schema = @Schema(implementation = String.class),
56+
examples = @ExampleObject(value = "keyspace not found")))
57+
public Response getRangeToEndpointMap(@QueryParam(value = "keyspaceName") String keyspaceName) {
58+
return handle(
59+
() -> {
60+
if (keyspaceName != null && !keyspaceExists(keyspaceName)) {
61+
return Response.status(Response.Status.NOT_FOUND).entity("keyspace not found").build();
62+
}
63+
64+
Map<List<String>, List<String>> map =
65+
(Map<List<String>, List<String>>)
66+
ResponseTools.getSingleRowResponse(
67+
app.dbUnixSocketFile,
68+
app.cqlService,
69+
"CALL NodeOps.getRangeToEndpointMap(?)",
70+
keyspaceName);
71+
return Response.ok(convert(map)).build();
72+
});
73+
}
74+
75+
private TokenRangeToEndpointResponse convert(Map<List<String>, List<String>> map) {
76+
List<TokenRangeToEndpoints> rangesToEndpoints = new ArrayList<>(map.size());
77+
map.entrySet()
78+
.forEach(
79+
(Map.Entry<List<String>, List<String>> e) -> {
80+
rangesToEndpoints.add(
81+
new TokenRangeToEndpoints(convertRanges(e.getKey()), e.getValue()));
82+
});
83+
return new TokenRangeToEndpointResponse(rangesToEndpoints);
84+
}
85+
86+
private List<Long> convertRanges(List<String> range) {
87+
// each Range should be exactly 2 strings: start, end
88+
assert range.size() == 2;
89+
List<Long> tokenRange = Arrays.asList(Long.valueOf(range.get(0)), Long.parseLong(range.get(1)));
90+
return tokenRange;
91+
}
92+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright DataStax, Inc.
3+
*
4+
* Please see the included license file for details.
5+
*/
6+
package com.datastax.mgmtapi.resources.v2.models;
7+
8+
import com.fasterxml.jackson.annotation.JsonCreator;
9+
import com.fasterxml.jackson.annotation.JsonProperty;
10+
import com.fasterxml.jackson.core.JsonProcessingException;
11+
import com.fasterxml.jackson.databind.ObjectMapper;
12+
import java.util.List;
13+
import java.util.Objects;
14+
15+
public class TokenRangeToEndpointResponse {
16+
17+
@JsonProperty(value = "token_range_to_endpoints", required = true)
18+
public final List<TokenRangeToEndpoints> tokenRangeToEndpoints;
19+
20+
@JsonCreator
21+
public TokenRangeToEndpointResponse(
22+
@JsonProperty(value = "token_range_to_endpoints", required = true)
23+
List<TokenRangeToEndpoints> list) {
24+
this.tokenRangeToEndpoints = list;
25+
}
26+
27+
@Override
28+
public int hashCode() {
29+
return 83 * Objects.hashCode(tokenRangeToEndpoints);
30+
}
31+
32+
@Override
33+
public boolean equals(Object obj) {
34+
if (this == obj) {
35+
return true;
36+
}
37+
if (obj == null) {
38+
return false;
39+
}
40+
if (getClass() != obj.getClass()) {
41+
return false;
42+
}
43+
TokenRangeToEndpointResponse other = (TokenRangeToEndpointResponse) obj;
44+
return Objects.equals(this.tokenRangeToEndpoints, other.tokenRangeToEndpoints);
45+
}
46+
47+
@Override
48+
public String toString() {
49+
try {
50+
return new ObjectMapper().writeValueAsString(this);
51+
} catch (JsonProcessingException e) {
52+
return String.format("Unable to format TokenRangeToEndpointResponse (%s)", e.getMessage());
53+
}
54+
}
55+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright DataStax, Inc.
3+
*
4+
* Please see the included license file for details.
5+
*/
6+
package com.datastax.mgmtapi.resources.v2.models;
7+
8+
import com.fasterxml.jackson.annotation.JsonCreator;
9+
import com.fasterxml.jackson.annotation.JsonProperty;
10+
import com.fasterxml.jackson.core.JsonProcessingException;
11+
import com.fasterxml.jackson.databind.ObjectMapper;
12+
import java.util.List;
13+
import java.util.Objects;
14+
15+
public class TokenRangeToEndpoints {
16+
17+
@JsonProperty(value = "tokens", required = true)
18+
public final List<Long> tokens;
19+
20+
@JsonProperty(value = "endpoints", required = true)
21+
public final List<String> endpoints;
22+
23+
@JsonCreator
24+
public TokenRangeToEndpoints(
25+
@JsonProperty(value = "tokens", required = true) List<Long> tokens,
26+
@JsonProperty(value = "endpoints", required = true) List<String> endpoints) {
27+
this.tokens = tokens;
28+
this.endpoints = endpoints;
29+
}
30+
31+
@Override
32+
public boolean equals(Object obj) {
33+
if (this == obj) {
34+
return true;
35+
}
36+
if (obj == null) {
37+
return false;
38+
}
39+
if (getClass() != obj.getClass()) {
40+
return false;
41+
}
42+
TokenRangeToEndpoints other = (TokenRangeToEndpoints) obj;
43+
if (!Objects.equals(this.tokens, other.tokens)) {
44+
return false;
45+
}
46+
return Objects.equals(this.endpoints, other.endpoints);
47+
}
48+
49+
@Override
50+
public int hashCode() {
51+
return 83 * Objects.hashCode(this.tokens) * Objects.hashCode(this.endpoints);
52+
}
53+
54+
@Override
55+
public String toString() {
56+
try {
57+
return new ObjectMapper().writeValueAsString(this);
58+
} catch (JsonProcessingException e) {
59+
return String.format("Unable to format TokenRangeToEndpoints (%s)", e.getMessage());
60+
}
61+
}
62+
}

0 commit comments

Comments
 (0)