Skip to content

Commit 5870c05

Browse files
committed
Add toXContent and fromXContent for DiscoveryNode and DiscoveryNodes
Signed-off-by: Shivansh Arora <hishiv@amazon.com>
1 parent a228dfc commit 5870c05

File tree

5 files changed

+421
-5
lines changed

5 files changed

+421
-5
lines changed

libs/core/src/main/java/org/opensearch/core/common/transport/TransportAddress.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,4 +162,14 @@ public String toString() {
162162
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
163163
return builder.value(toString());
164164
}
165+
166+
public static TransportAddress fromString(String address) throws UnknownHostException {
167+
String[] addressSplit = address.split(":");
168+
if (addressSplit.length != 2) {
169+
throw new IllegalArgumentException("address must be of the form [hostname/ip]:[port]");
170+
}
171+
String hostname = addressSplit[0];
172+
int port = Integer.parseInt(addressSplit[1]);
173+
return new TransportAddress(InetAddress.getByName(hostname), port);
174+
}
165175
}

server/src/main/java/org/opensearch/cluster/node/DiscoveryNode.java

Lines changed: 92 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
package org.opensearch.cluster.node;
3434

3535
import org.opensearch.Version;
36+
import org.opensearch.cluster.metadata.Metadata;
3637
import org.opensearch.common.UUIDs;
3738
import org.opensearch.common.annotation.PublicApi;
3839
import org.opensearch.common.settings.Setting;
@@ -43,6 +44,7 @@
4344
import org.opensearch.core.common.transport.TransportAddress;
4445
import org.opensearch.core.xcontent.ToXContentFragment;
4546
import org.opensearch.core.xcontent.XContentBuilder;
47+
import org.opensearch.core.xcontent.XContentParser;
4648
import org.opensearch.node.Node;
4749

4850
import java.io.IOException;
@@ -60,6 +62,8 @@
6062
import java.util.stream.Collectors;
6163
import java.util.stream.Stream;
6264

65+
import static org.opensearch.cluster.metadata.Metadata.CONTEXT_MODE_API;
66+
import static org.opensearch.cluster.metadata.Metadata.CONTEXT_MODE_PARAM;
6367
import static org.opensearch.node.NodeRoleSettings.NODE_ROLES_SETTING;
6468
import static org.opensearch.node.remotestore.RemoteStoreNodeAttribute.REMOTE_STORE_NODE_ATTRIBUTE_KEY_PREFIX;
6569

@@ -72,6 +76,14 @@
7276
public class DiscoveryNode implements Writeable, ToXContentFragment {
7377

7478
static final String COORDINATING_ONLY = "coordinating_only";
79+
static final String KEY_NAME = "name";
80+
static final String KEY_EPHEMERAL_ID = "ephemeral_id";
81+
static final String KEY_HOST_NAME = "host_name";
82+
static final String KEY_HOST_ADDRESS = "host_address";
83+
static final String KEY_TRANSPORT_ADDRESS = "transport_address";
84+
static final String KEY_ATTRIBUTES = "attributes";
85+
static final String KEY_VERSION = "version";
86+
static final String KEY_ROLES = "roles";
7587

7688
public static boolean nodeRequiresLocalStorage(Settings settings) {
7789
boolean localStorageEnable = Node.NODE_LOCAL_STORAGE_SETTING.get(settings);
@@ -544,21 +556,97 @@ public String toString() {
544556

545557
@Override
546558
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
559+
Metadata.XContentContext context = Metadata.XContentContext.valueOf(params.param(CONTEXT_MODE_PARAM, CONTEXT_MODE_API));
547560
builder.startObject(getId());
548-
builder.field("name", getName());
549-
builder.field("ephemeral_id", getEphemeralId());
550-
builder.field("transport_address", getAddress().toString());
561+
builder.field(KEY_NAME, getName());
562+
builder.field(KEY_EPHEMERAL_ID, getEphemeralId());
563+
builder.field(KEY_TRANSPORT_ADDRESS, getAddress().toString());
551564

552-
builder.startObject("attributes");
565+
builder.startObject(KEY_ATTRIBUTES);
553566
for (Map.Entry<String, String> entry : attributes.entrySet()) {
554567
builder.field(entry.getKey(), entry.getValue());
555568
}
556569
builder.endObject();
570+
if (context == Metadata.XContentContext.GATEWAY) {
571+
builder.field(KEY_HOST_NAME, getHostName());
572+
builder.field(KEY_HOST_ADDRESS, getHostAddress());
573+
builder.field(KEY_VERSION, getVersion().toString());
574+
builder.startArray(KEY_ROLES);
575+
for (DiscoveryNodeRole role : roles) {
576+
builder.value(role.roleName());
577+
}
578+
builder.endArray();
579+
}
557580

558581
builder.endObject();
559582
return builder;
560583
}
561584

585+
public static DiscoveryNode fromXContent(XContentParser parser, String nodeId) throws IOException {
586+
if (parser.currentToken() == null) {
587+
parser.nextToken();
588+
}
589+
if (parser.currentToken() == XContentParser.Token.START_OBJECT) {
590+
parser.nextToken();
591+
}
592+
if (parser.currentToken() != XContentParser.Token.FIELD_NAME) {
593+
throw new IllegalArgumentException("expected field name but got a " + parser.currentToken());
594+
}
595+
String nodeName = null;
596+
String hostName = null;
597+
String hostAddress = null;
598+
String ephemeralId = null;
599+
TransportAddress transportAddress = null;
600+
Map<String, String> attributes = new HashMap<>();
601+
Set<DiscoveryNodeRole> roles = new HashSet<>();
602+
Version version = null;
603+
String currentFieldName = parser.currentName();
604+
// token should be start object at this point
605+
// XContentParser.Token token = parser.nextToken();
606+
// if (token != XContentParser.Token.START_OBJECT) {
607+
// throw new IllegalArgumentException("expected object but got a " + token);
608+
// }
609+
XContentParser.Token token;
610+
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
611+
if (token == XContentParser.Token.FIELD_NAME) {
612+
currentFieldName = parser.currentName();
613+
} else if (token.isValue()) {
614+
if (KEY_NAME.equals(currentFieldName)) {
615+
nodeName = parser.text();
616+
} else if (KEY_EPHEMERAL_ID.equals(currentFieldName)) {
617+
ephemeralId = parser.text();
618+
} else if (KEY_TRANSPORT_ADDRESS.equals(currentFieldName)) {
619+
transportAddress = TransportAddress.fromString(parser.text());
620+
} else if (KEY_HOST_NAME.equals(currentFieldName)) {
621+
hostName = parser.text();
622+
} else if (KEY_HOST_ADDRESS.equals(currentFieldName)) {
623+
hostAddress = parser.text();
624+
} else if (KEY_VERSION.equals(currentFieldName)) {
625+
version = Version.fromString(parser.text());
626+
}
627+
} else if (token == XContentParser.Token.START_OBJECT) {
628+
if (KEY_ATTRIBUTES.equals(currentFieldName)) {
629+
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
630+
if (token == XContentParser.Token.FIELD_NAME) {
631+
currentFieldName = parser.currentName();
632+
} else if (token.isValue()) {
633+
attributes.put(currentFieldName, parser.text());
634+
}
635+
}
636+
}
637+
} else if (token == XContentParser.Token.START_ARRAY) {
638+
if (KEY_ROLES.equals(currentFieldName)) {
639+
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
640+
roles.add(getRoleFromRoleName(parser.text()));
641+
}
642+
}
643+
} else {
644+
throw new IllegalArgumentException("unexpected token " + token);
645+
}
646+
}
647+
return new DiscoveryNode(nodeName, nodeId, ephemeralId, hostName, hostAddress, transportAddress, attributes, roles, version);
648+
}
649+
562650
private static Map<String, DiscoveryNodeRole> rolesToMap(final Stream<DiscoveryNodeRole> roles) {
563651
return Collections.unmodifiableMap(roles.collect(Collectors.toMap(DiscoveryNodeRole::roleName, Function.identity())));
564652
}

server/src/main/java/org/opensearch/cluster/node/DiscoveryNodes.java

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.opensearch.Version;
3636
import org.opensearch.cluster.AbstractDiffable;
3737
import org.opensearch.cluster.Diff;
38+
import org.opensearch.cluster.metadata.Metadata;
3839
import org.opensearch.common.Booleans;
3940
import org.opensearch.common.Nullable;
4041
import org.opensearch.common.annotation.PublicApi;
@@ -44,6 +45,9 @@
4445
import org.opensearch.core.common.io.stream.StreamInput;
4546
import org.opensearch.core.common.io.stream.StreamOutput;
4647
import org.opensearch.core.common.transport.TransportAddress;
48+
import org.opensearch.core.xcontent.ToXContentFragment;
49+
import org.opensearch.core.xcontent.XContentBuilder;
50+
import org.opensearch.core.xcontent.XContentParser;
4751

4852
import java.io.IOException;
4953
import java.util.ArrayList;
@@ -59,14 +63,17 @@
5963
import java.util.stream.Stream;
6064
import java.util.stream.StreamSupport;
6165

66+
import static org.opensearch.cluster.metadata.Metadata.CONTEXT_MODE_API;
67+
import static org.opensearch.cluster.metadata.Metadata.CONTEXT_MODE_PARAM;
68+
6269
/**
6370
* This class holds all {@link DiscoveryNode} in the cluster and provides convenience methods to
6471
* access, modify merge / diff discovery nodes.
6572
*
6673
* @opensearch.api
6774
*/
6875
@PublicApi(since = "1.0.0")
69-
public class DiscoveryNodes extends AbstractDiffable<DiscoveryNodes> implements Iterable<DiscoveryNode> {
76+
public class DiscoveryNodes extends AbstractDiffable<DiscoveryNodes> implements Iterable<DiscoveryNode>, ToXContentFragment {
7077

7178
public static final DiscoveryNodes EMPTY_NODES = builder().build();
7279

@@ -566,6 +573,66 @@ public String toString() {
566573
return sb.toString();
567574
}
568575

576+
@Override
577+
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
578+
builder.startObject("nodes");
579+
for (DiscoveryNode node : this) {
580+
node.toXContent(builder, params);
581+
}
582+
builder.endObject();
583+
Metadata.XContentContext context = Metadata.XContentContext.valueOf(params.param(CONTEXT_MODE_PARAM, CONTEXT_MODE_API));
584+
if (context == Metadata.XContentContext.GATEWAY && clusterManagerNodeId != null) {
585+
builder.field("cluster_manager", clusterManagerNodeId);
586+
}
587+
return builder;
588+
}
589+
590+
public static DiscoveryNodes fromXContent(XContentParser parser) throws IOException {
591+
Builder builder = new Builder();
592+
if (parser.currentToken() == null) {
593+
parser.nextToken();
594+
}
595+
if (parser.currentToken() == XContentParser.Token.START_OBJECT) {
596+
parser.nextToken();
597+
}
598+
if (parser.currentToken() != XContentParser.Token.FIELD_NAME) {
599+
throw new IllegalArgumentException("expected field name but got a " + parser.currentToken());
600+
}
601+
XContentParser.Token token;
602+
String currentFieldName = parser.currentName();
603+
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
604+
if (token == XContentParser.Token.FIELD_NAME) {
605+
currentFieldName = parser.currentName();
606+
} else if (token == XContentParser.Token.START_OBJECT) {
607+
if ("nodes".equals(currentFieldName)) {
608+
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
609+
if (token == XContentParser.Token.FIELD_NAME) {
610+
currentFieldName = parser.currentName();
611+
} else if (token == XContentParser.Token.START_OBJECT) {
612+
String nodeId = currentFieldName;
613+
DiscoveryNode node = DiscoveryNode.fromXContent(parser, nodeId);
614+
builder.add(node);
615+
}
616+
}
617+
} else {
618+
throw new IllegalArgumentException("unexpected object field " + currentFieldName);
619+
}
620+
} else if (token.isValue()) {
621+
if ("cluster_manager".equals(currentFieldName)) {
622+
String clusterManagerNodeId = parser.text();
623+
if (clusterManagerNodeId != null) {
624+
builder.clusterManagerNodeId(clusterManagerNodeId);
625+
}
626+
} else {
627+
throw new IllegalArgumentException("unexpected value field " + currentFieldName);
628+
}
629+
} else {
630+
throw new IllegalArgumentException("unexpected token " + token);
631+
}
632+
}
633+
return builder.build();
634+
}
635+
569636
/**
570637
* Delta between nodes.
571638
*

server/src/test/java/org/opensearch/cluster/node/DiscoveryNodeTests.java

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,20 @@
3333
package org.opensearch.cluster.node;
3434

3535
import org.opensearch.Version;
36+
import org.opensearch.cluster.metadata.Metadata;
3637
import org.opensearch.common.io.stream.BytesStreamOutput;
3738
import org.opensearch.common.settings.Setting;
3839
import org.opensearch.common.settings.Settings;
40+
import org.opensearch.common.xcontent.json.JsonXContent;
3941
import org.opensearch.core.common.io.stream.StreamInput;
4042
import org.opensearch.core.common.transport.TransportAddress;
43+
import org.opensearch.core.xcontent.ToXContent;
44+
import org.opensearch.core.xcontent.XContentBuilder;
4145
import org.opensearch.node.remotestore.RemoteStoreNodeAttribute;
4246
import org.opensearch.test.NodeRoles;
4347
import org.opensearch.test.OpenSearchTestCase;
4448

49+
import java.io.IOException;
4550
import java.net.InetAddress;
4651
import java.util.Collections;
4752
import java.util.HashMap;
@@ -53,6 +58,9 @@
5358

5459
import static java.util.Collections.emptyMap;
5560
import static java.util.Collections.emptySet;
61+
import static java.util.Collections.singletonMap;
62+
import static org.opensearch.cluster.metadata.Metadata.CONTEXT_MODE_API;
63+
import static org.opensearch.cluster.metadata.Metadata.CONTEXT_MODE_GATEWAY;
5664
import static org.opensearch.test.NodeRoles.nonRemoteClusterClientNode;
5765
import static org.opensearch.test.NodeRoles.nonSearchNode;
5866
import static org.opensearch.test.NodeRoles.remoteClusterClientNode;
@@ -249,4 +257,70 @@ public void testDiscoveryNodeIsSearchNode() {
249257
final DiscoveryNode node = DiscoveryNode.createLocal(settingWithSearchRole, buildNewFakeTransportAddress(), "node");
250258
assertThat(node.isSearchNode(), equalTo(true));
251259
}
260+
261+
public void testToXContentInAPIMode() throws IOException {
262+
final DiscoveryNode node = DiscoveryNode.createLocal(
263+
Settings.EMPTY,
264+
new TransportAddress(TransportAddress.META_ADDRESS, 9200),
265+
"node_1"
266+
);
267+
XContentBuilder builder = JsonXContent.contentBuilder().prettyPrint();
268+
builder.startObject();
269+
node.toXContent(builder, new ToXContent.MapParams(singletonMap(Metadata.CONTEXT_MODE_PARAM, CONTEXT_MODE_API)));
270+
builder.endObject();
271+
272+
String expectedNodeAPUXContent = "{\n"
273+
+ " \"node_1\" : {\n"
274+
+ " \"name\" : \""
275+
+ node.getName()
276+
+ "\",\n"
277+
+ " \"ephemeral_id\" : \""
278+
+ node.getEphemeralId()
279+
+ "\",\n"
280+
+ " \"transport_address\" : \"0.0.0.0:9200\",\n"
281+
+ " \"attributes\" : { }\n"
282+
+ " }\n"
283+
+ "}";
284+
285+
assertEquals(expectedNodeAPUXContent, builder.toString());
286+
}
287+
288+
public void testToXContentInGatewayMode() throws IOException {
289+
final DiscoveryNode node = DiscoveryNode.createLocal(
290+
Settings.EMPTY,
291+
new TransportAddress(TransportAddress.META_ADDRESS, 9200),
292+
"node_1"
293+
);
294+
XContentBuilder builder = JsonXContent.contentBuilder().prettyPrint();
295+
builder.startObject();
296+
node.toXContent(builder, new ToXContent.MapParams(singletonMap(Metadata.CONTEXT_MODE_PARAM, CONTEXT_MODE_GATEWAY)));
297+
builder.endObject();
298+
299+
String expectedNodeAPUXContent = "{\n"
300+
+ " \"node_1\" : {\n"
301+
+ " \"name\" : \""
302+
+ node.getName()
303+
+ "\",\n"
304+
+ " \"ephemeral_id\" : \""
305+
+ node.getEphemeralId()
306+
+ "\",\n"
307+
+ " \"transport_address\" : \"0.0.0.0:9200\",\n"
308+
+ " \"attributes\" : { },\n"
309+
+ " \"host_name\" : \"0.0.0.0\",\n"
310+
+ " \"host_address\" : \"0.0.0.0\",\n"
311+
+ " \"version\" : \""
312+
+ node.getVersion()
313+
+ "\",\n"
314+
+ " \"roles\" : [\n"
315+
+ " \"cluster_manager\",\n"
316+
+ " \"data\",\n"
317+
+ " \"ingest\",\n"
318+
+ " \"remote_cluster_client\"\n"
319+
+ " ]\n"
320+
+ " }\n"
321+
+ "}";
322+
323+
assertEquals(expectedNodeAPUXContent, builder.toString());
324+
325+
}
252326
}

0 commit comments

Comments
 (0)