diff --git a/core/src/main/java/org/elasticsearch/rest/action/RestActionModule.java b/core/src/main/java/org/elasticsearch/rest/action/RestActionModule.java index a13a038d6e52..0764ca2f536d 100644 --- a/core/src/main/java/org/elasticsearch/rest/action/RestActionModule.java +++ b/core/src/main/java/org/elasticsearch/rest/action/RestActionModule.java @@ -250,6 +250,7 @@ protected void configure() { catActionMultibinder.addBinding().to(RestThreadPoolAction.class).asEagerSingleton(); catActionMultibinder.addBinding().to(RestPluginsAction.class).asEagerSingleton(); catActionMultibinder.addBinding().to(RestFielddataAction.class).asEagerSingleton(); + catActionMultibinder.addBinding().to(RestNodeAttrsAction.class).asEagerSingleton(); // no abstract cat action bind(RestCatAction.class).asEagerSingleton(); } diff --git a/core/src/main/java/org/elasticsearch/rest/action/cat/RestNodeAttrsAction.java b/core/src/main/java/org/elasticsearch/rest/action/cat/RestNodeAttrsAction.java new file mode 100644 index 000000000000..48cbf8809b2a --- /dev/null +++ b/core/src/main/java/org/elasticsearch/rest/action/cat/RestNodeAttrsAction.java @@ -0,0 +1,131 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.rest.action.cat; +import com.google.common.collect.ImmutableMap; +import org.elasticsearch.action.admin.cluster.node.info.NodeInfo; +import org.elasticsearch.action.admin.cluster.node.info.NodesInfoRequest; +import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse; +import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsRequest; +import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsResponse; +import org.elasticsearch.action.admin.cluster.state.ClusterStateRequest; +import org.elasticsearch.action.admin.cluster.state.ClusterStateResponse; +import org.elasticsearch.client.Client; +import org.elasticsearch.cluster.node.DiscoveryNode; +import org.elasticsearch.cluster.node.DiscoveryNodes; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.Table; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.transport.InetSocketTransportAddress; +import org.elasticsearch.rest.*; +import org.elasticsearch.rest.action.support.RestActionListener; +import org.elasticsearch.rest.action.support.RestResponseListener; +import org.elasticsearch.rest.action.support.RestTable; + +import static org.elasticsearch.rest.RestRequest.Method.GET; + +public class RestNodeAttrsAction extends AbstractCatAction { + + @Inject + public RestNodeAttrsAction(Settings settings, RestController controller, Client client) { + super(settings, controller, client); + controller.registerHandler(GET, "/_cat/nodeattrs", this); + } + + @Override + void documentation(StringBuilder sb) { + sb.append("/_cat/nodeattrs\n"); + } + + @Override + public void doRequest(final RestRequest request, final RestChannel channel, final Client client) { + final ClusterStateRequest clusterStateRequest = new ClusterStateRequest(); + clusterStateRequest.clear().nodes(true); + clusterStateRequest.local(request.paramAsBoolean("local", clusterStateRequest.local())); + clusterStateRequest.masterNodeTimeout(request.paramAsTime("master_timeout", clusterStateRequest.masterNodeTimeout())); + + client.admin().cluster().state(clusterStateRequest, new RestActionListener(channel) { + @Override + public void processResponse(final ClusterStateResponse clusterStateResponse) { + NodesInfoRequest nodesInfoRequest = new NodesInfoRequest(); + nodesInfoRequest.clear().jvm(false).os(false).process(true); + client.admin().cluster().nodesInfo(nodesInfoRequest, new RestActionListener(channel) { + @Override + public void processResponse(final NodesInfoResponse nodesInfoResponse) { + NodesStatsRequest nodesStatsRequest = new NodesStatsRequest(); + nodesStatsRequest.clear().jvm(false).os(false).fs(false).indices(false).process(false); + client.admin().cluster().nodesStats(nodesStatsRequest, new RestResponseListener(channel) { + @Override + public RestResponse buildResponse(NodesStatsResponse nodesStatsResponse) throws Exception { + return RestTable.buildResponse(buildTable(request, clusterStateResponse, nodesInfoResponse, nodesStatsResponse), channel); + } + }); + } + }); + } + }); + } + + @Override + Table getTableWithHeader(final RestRequest request) { + Table table = new Table(); + table.startHeaders(); + table.addCell("node", "default:true;alias:name;desc:node name"); + table.addCell("id", "default:false;alias:id,nodeId;desc:unique node id"); + table.addCell("pid", "default:false;alias:p;desc:process id"); + table.addCell("host", "alias:h;desc:host name"); + table.addCell("ip", "alias:i;desc:ip address"); + table.addCell("port", "default:false;alias:po;desc:bound transport port"); + table.addCell("attr", "default:true;alias:attr.name;desc:attribute description"); + table.addCell("value","default:true;alias:attr.value;desc:attribute value"); + table.endHeaders(); + return table; + } + + private Table buildTable(RestRequest req, ClusterStateResponse state, NodesInfoResponse nodesInfo, NodesStatsResponse nodesStats) { + boolean fullId = req.paramAsBoolean("full_id", false); + + DiscoveryNodes nodes = state.getState().nodes(); + Table table = getTableWithHeader(req); + + for (DiscoveryNode node : nodes) { + NodeInfo info = nodesInfo.getNodesMap().get(node.id()); + ImmutableMap attrs = node.getAttributes(); + for(String att : attrs.keySet()) { + table.startRow(); + table.addCell(node.name()); + table.addCell(fullId ? node.id() : Strings.substring(node.getId(), 0, 4)); + table.addCell(info == null ? null : info.getProcess().getId()); + table.addCell(node.getHostName()); + table.addCell(node.getHostAddress()); + if (node.address() instanceof InetSocketTransportAddress) { + table.addCell(((InetSocketTransportAddress) node.address()).address().getPort()); + } else { + table.addCell("-"); + } + table.addCell(att); + table.addCell(attrs.containsKey(att) ? attrs.get(att) : null); + table.endRow(); + } + } + + return table; + } +} diff --git a/docs/reference/cat.asciidoc b/docs/reference/cat.asciidoc index e5f3d9674988..0e61c27618d2 100644 --- a/docs/reference/cat.asciidoc +++ b/docs/reference/cat.asciidoc @@ -112,6 +112,8 @@ include::cat/indices.asciidoc[] include::cat/master.asciidoc[] +include::cat/nodeattrs.asciidoc[] + include::cat/nodes.asciidoc[] include::cat/pending_tasks.asciidoc[] diff --git a/docs/reference/cat/nodeattrs.asciidoc b/docs/reference/cat/nodeattrs.asciidoc new file mode 100644 index 000000000000..f6494e547cc7 --- /dev/null +++ b/docs/reference/cat/nodeattrs.asciidoc @@ -0,0 +1,71 @@ +[[cat-nodeattrs]] +== cat nodeattrs + +The `nodeattrs` command shows custom node attributes. + +["source","sh",subs="attributes,callouts"] +-------------------------------------------------- +% curl 192.168.56.10:9200/_cat/nodeattrs +node host ip attr value +Black Bolt epsilon 192.168.1.8 rack rack314 +Black Bolt epsilon 192.168.1.8 azone us-east-1 +-------------------------------------------------- + +The first few columns give you basic info per node. + + +["source","sh",subs="attributes,callouts"] +-------------------------------------------------- +node host ip +Black Bolt epsilon 192.168.1.8 +Black Bolt epsilon 192.168.1.8 +-------------------------------------------------- + + +The attr and value columns can give you a picture of custom node attributes. + +[source,sh] +-------------------------------------------------- +attr value +rack rack314 +azone us-east-1 +-------------------------------------------------- + +[float] +=== Columns + +Below is an exhaustive list of the existing headers that can be +passed to `nodes?h=` to retrieve the relevant details in ordered +columns. If no headers are specified, then those marked to Appear +by Default will appear. If any header is specified, then the defaults +are not used. + +Aliases can be used in place of the full header name for brevity. +Columns appear in the order that they are listed below unless a +different order is specified (e.g., `h=attr,value` versus `h=value,attr`). + +When specifying headers, the headers are not placed in the output +by default. To have the headers appear in the output, use verbose +mode (`v`). The header name will match the supplied value (e.g., +`pid` versus `p`). For example: + +["source","sh",subs="attributes,callouts"] +-------------------------------------------------- +% curl 192.168.56.10:9200/_cat/nodeattrs?v&h=name,pid,attr,value +name pid attr value +Black Bolt 28000 rack rack314 +Black Bolt 28000 azone us-east-1 +-------------------------------------------------- + +[cols="<,<,<,<,<",options="header",subs="normal"] +|======================================================================= +|Header |Alias |Appear by Default |Description |Example +|`node`|`name`|Yes|Name of the node|Black Bolt +|`id` |`nodeId` |No |Unique node ID |k0zy +|`pid` |`p` |No |Process ID |13061 +|`host` |`h` |Yes |Host name |n1 +|`ip` |`i` |Yes |IP address |127.0.1.1 +|`port` |`po` |No |Bound transport port |9300 +|`attr` | `attr.name` | Yes | Attribute name | rack +|`value` | `attr.value` | Yes | Attribute value | rack123 +|======================================================================= diff --git a/rest-api-spec/src/main/resources/rest-api-spec/api/cat.nodeattrs.json b/rest-api-spec/src/main/resources/rest-api-spec/api/cat.nodeattrs.json new file mode 100644 index 000000000000..157a33f04088 --- /dev/null +++ b/rest-api-spec/src/main/resources/rest-api-spec/api/cat.nodeattrs.json @@ -0,0 +1,37 @@ +{ + "cat.nodeattrs": { + "documentation": "http://www.elastic.co/guide/en/elasticsearch/reference/master/cat-nodeattrs.html", + "methods": ["GET"], + "url": { + "path": "/_cat/nodeattrs", + "paths": ["/_cat/nodeattrs"], + "parts": { + }, + "params": { + "local": { + "type" : "boolean", + "description" : "Return local information, do not retrieve the state from master node (default: false)" + }, + "master_timeout": { + "type" : "time", + "description" : "Explicit operation timeout for connection to master node" + }, + "h": { + "type": "list", + "description" : "Comma-separated list of column names to display" + }, + "help": { + "type": "boolean", + "description": "Return help information", + "default": false + }, + "v": { + "type": "boolean", + "description": "Verbose mode. Display column headers", + "default": true + } + } + }, + "body": null + } +} diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/cat.nodeattrs/10_basic.yaml b/rest-api-spec/src/main/resources/rest-api-spec/test/cat.nodeattrs/10_basic.yaml new file mode 100755 index 000000000000..73d970fb27a2 --- /dev/null +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/cat.nodeattrs/10_basic.yaml @@ -0,0 +1,27 @@ +--- +"Test cat nodes attrs output": + + - do: + cat.nodeattrs: + v: false + + - match: + $body: | + /((\S+)\s+(\S+)\s+(\d{1,3}\.){3}\d{1,3}\s+(\S+)\s+(\S+)\s*)+/ + + - do: + cat.nodeattrs: + v: true + + - match: + $body: | + /((\S+)\s+(\S+)\s+(\d{1,3}\.){3}\d{1,3}\s+(\S+)\s+(\S+)\s*)+/ + + - do: + cat.nodeattrs: + h: attr,value + v: true + + - match: + $body: | + /((\S+)\s+(\S+)\s*)+/