From 7aa9d4bc9f078adc0efc6b0c2e131a5045e5dbc8 Mon Sep 17 00:00:00 2001 From: Andrew Raines Date: Tue, 2 Jul 2013 18:00:05 -0500 Subject: [PATCH] Start cat api with shards endpoint. --- .../indices/stats/IndicesStatsResponse.java | 18 +++ .../routing/ImmutableShardRouting.java | 23 +++ .../cluster/routing/ShardRouting.java | 5 + .../org/elasticsearch/common/table/Align.java | 32 ++++ .../org/elasticsearch/common/table/Cell.java | 89 +++++++++++ .../org/elasticsearch/common/table/Row.java | 60 +++++++ .../org/elasticsearch/common/table/Table.java | 147 ++++++++++++++++++ .../rest/action/RestActionModule.java | 5 + .../rest/action/cat/RestShardsAction.java | 147 ++++++++++++++++++ .../test/unit/common/table/TableTests.java | 45 ++++++ 10 files changed, 571 insertions(+) create mode 100644 src/main/java/org/elasticsearch/common/table/Align.java create mode 100644 src/main/java/org/elasticsearch/common/table/Cell.java create mode 100644 src/main/java/org/elasticsearch/common/table/Row.java create mode 100644 src/main/java/org/elasticsearch/common/table/Table.java create mode 100644 src/main/java/org/elasticsearch/rest/action/cat/RestShardsAction.java create mode 100644 src/test/java/org/elasticsearch/test/unit/common/table/TableTests.java diff --git a/src/main/java/org/elasticsearch/action/admin/indices/stats/IndicesStatsResponse.java b/src/main/java/org/elasticsearch/action/admin/indices/stats/IndicesStatsResponse.java index d30bcd9a645..aa5515db90f 100644 --- a/src/main/java/org/elasticsearch/action/admin/indices/stats/IndicesStatsResponse.java +++ b/src/main/java/org/elasticsearch/action/admin/indices/stats/IndicesStatsResponse.java @@ -19,6 +19,7 @@ package org.elasticsearch.action.admin.indices.stats; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; @@ -43,6 +44,8 @@ public class IndicesStatsResponse extends BroadcastOperationResponse implements private ShardStats[] shards; + private ImmutableMap shardStatsMap; + IndicesStatsResponse() { } @@ -50,6 +53,21 @@ public class IndicesStatsResponse extends BroadcastOperationResponse implements IndicesStatsResponse(ShardStats[] shards, ClusterState clusterState, int totalShards, int successfulShards, int failedShards, List shardFailures) { super(totalShards, successfulShards, failedShards, shardFailures); this.shards = shards; + this.shardStatsMap = buildShardsStatsMap(); + } + + private ImmutableMap buildShardsStatsMap() { + ImmutableMap.Builder mb = ImmutableMap.builder(); + + for (ShardStats ss : shards) { + mb.put(ss.getShardRouting().globalId(), ss.getStats()); + } + + return mb.build(); + } + + public ImmutableMap asMap() { + return this.shardStatsMap; } public ShardStats[] getShards() { diff --git a/src/main/java/org/elasticsearch/cluster/routing/ImmutableShardRouting.java b/src/main/java/org/elasticsearch/cluster/routing/ImmutableShardRouting.java index 1a20c3e697d..51cd458c27a 100644 --- a/src/main/java/org/elasticsearch/cluster/routing/ImmutableShardRouting.java +++ b/src/main/java/org/elasticsearch/cluster/routing/ImmutableShardRouting.java @@ -84,6 +84,29 @@ public class ImmutableShardRouting implements Streamable, Serializable, ShardRou this.version = version; } + @Override + public String globalId() { + String pri = "r"; + if (this.primary()) { + pri = "p"; + } + + String node = "unassigned"; + if (null != this.currentNodeId()) { + node = this.currentNodeId(); + } + + StringBuilder sb = new StringBuilder(); + sb.append(getIndex()); + sb.append("/"); + sb.append(this.shardId().id()); + sb.append("/"); + sb.append(pri); + sb.append("/"); + sb.append(node); + return sb.toString(); + } + @Override public String index() { return this.index; diff --git a/src/main/java/org/elasticsearch/cluster/routing/ShardRouting.java b/src/main/java/org/elasticsearch/cluster/routing/ShardRouting.java index 5938d5a2ecd..e960e471a31 100644 --- a/src/main/java/org/elasticsearch/cluster/routing/ShardRouting.java +++ b/src/main/java/org/elasticsearch/cluster/routing/ShardRouting.java @@ -131,6 +131,11 @@ public interface ShardRouting extends Streamable, Serializable, ToXContent { */ ShardIterator shardsIt(); + /** + * String identifier to uniquely refer to this shard routing (once it's assigned). + */ + String globalId(); + /** * Does not write index name and shard id */ diff --git a/src/main/java/org/elasticsearch/common/table/Align.java b/src/main/java/org/elasticsearch/common/table/Align.java new file mode 100644 index 00000000000..cad9f0f90d8 --- /dev/null +++ b/src/main/java/org/elasticsearch/common/table/Align.java @@ -0,0 +1,32 @@ +/* + * Licensed to ElasticSearch and Shay Banon 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.common.table; + +public enum Align { + LEFT((byte) 0), + CENTER((byte) 1), + RIGHT((byte) 2); + + private byte id; + + Align (byte id) { + this.id = id; + } +} diff --git a/src/main/java/org/elasticsearch/common/table/Cell.java b/src/main/java/org/elasticsearch/common/table/Cell.java new file mode 100644 index 00000000000..e5c26ca2106 --- /dev/null +++ b/src/main/java/org/elasticsearch/common/table/Cell.java @@ -0,0 +1,89 @@ +/* + * Licensed to ElasticSearch and Shay Banon 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.common.table; + +/** + * A String container that supports alignment. + */ +public class Cell { + private final String content; + + private final Align align; + + private final byte width; + + public Cell(String content) { + this.content = content; + this.align = Align.LEFT; + this.width = (byte) content.length(); + } + + public Cell(String content, Align align) { + this.content = content; + this.align = align; + this.width = (byte) content.length(); + } + + public Cell(String content, Align align, byte width) { + this.content = content; + this.align = align; + this.width = width; + } + + public byte width() { + return this.width; + } + + public Align align() { + return this.align; + } + + public static String pad(String orig, byte width, Align align) { + StringBuilder s = new StringBuilder(); + byte leftOver = (byte) (width - orig.length()); + if (leftOver > 0 && align == Align.LEFT) { + s.append(orig); + for (byte i = 0; i < leftOver; i++) { + s.append(" "); + } + } else if (leftOver > 0 && align == Align.RIGHT) { + for (byte i = 0; i < leftOver; i++) { + s.append(" "); + } + s.append(orig); + } else { + s.append(orig); + } + return s.toString(); + } + + public String toString(byte outWidth, Align outAlign) { + return pad(content, outWidth, outAlign); + } + + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("["); + sb.append(align()); + sb.append("]"); + sb.append(content); + return sb.toString(); + } +} diff --git a/src/main/java/org/elasticsearch/common/table/Row.java b/src/main/java/org/elasticsearch/common/table/Row.java new file mode 100644 index 00000000000..12805276dab --- /dev/null +++ b/src/main/java/org/elasticsearch/common/table/Row.java @@ -0,0 +1,60 @@ +/* + * Licensed to ElasticSearch and Shay Banon 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.common.table; + +import java.util.ArrayList; + +/** + * + */ +public class Row { + private final ArrayList cells; + + private final ArrayList widths; + + public Row () { + this.cells = new ArrayList(); + this.widths = new ArrayList(); + } + + public ArrayList cells() { + return this.cells; + } + + public int size () { + return cells.size(); + } + + public Row addCell(Cell cell) { + cells.add(cell); + widths.add(cell.width()); + return this; + } + + public Row addCell(String content) { + addCell(new Cell(content)); + return this; + } + + public Row addCell(String content, Align align) { + addCell(new Cell(content, align)); + return this; + } +} diff --git a/src/main/java/org/elasticsearch/common/table/Table.java b/src/main/java/org/elasticsearch/common/table/Table.java new file mode 100644 index 00000000000..5800a333dc1 --- /dev/null +++ b/src/main/java/org/elasticsearch/common/table/Table.java @@ -0,0 +1,147 @@ +/* + * Licensed to ElasticSearch and Shay Banon 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.common.table; + +import java.util.ArrayList; + +/** + * A generic table renderer. Can optionally print header row. + * Will justify all cells in a column to the widest one. All rows need + * to have same number of cells. + * + * Eg, new Table.addRow(new Row().addCell("foo").addCell("bar")).render() + */ +public class Table { + private ArrayList cols; + + private byte numcols; + + private byte height; + + public Table() { + this.cols = new ArrayList(); + this.numcols = 0; + this.height = 0; + } + + public Table addRow(Row row) { + addRow(row, false); + return this; + } + + public void ensureCapacity(int size) { + if (numcols < size) { + for (int i = 0; i < (size - numcols); i++) { + cols.add(new Column()); + } + } + } + + public Table addRow(Row row, boolean header) { + ensureCapacity(row.size()); + byte curCol = 0; + for (Cell cell : row.cells()) { + Column col = cols.get(curCol); + col.addCell(cell, header); + curCol += 1; + } + numcols = curCol; + height += 1; + return this; + } + + public String render() { + return render(false); + } + + public String render(boolean withHeaders) { + StringBuilder out = new StringBuilder(); + for (byte i = 0; i < height; i++) { + StringBuilder row = new StringBuilder(); + for (Column col : cols) { + Cell cell = col.getCell(i); + boolean headerRowWhenNotWantingHeaders = i == 0 && !withHeaders && col.hasHeader(); + if (! headerRowWhenNotWantingHeaders) { + row.append(cell.toString(col.width(), col.align())); + row.append(" "); + } + } + out.append(row.toString().trim()); + out.append("\n"); + } + return out.toString().trim(); + } + + private class Column { + private boolean hasHeader; + + private ArrayList cells; + + private byte width; + + private Align align; + + Column () { + cells = new ArrayList(); + width = 0; + hasHeader = false; + align = Align.LEFT; + } + + public Column addCell(Cell cell) { + addCell(cell, false); + return this; + } + + public Column addCell(Cell cell, boolean header) { + cells.add(cell); + + if (header) { + hasHeader = true; + } + + if (cell.width() > width) { + width = cell.width(); + } + + if (align != cell.align()) { + align = cell.align(); + } + + return this; + } + + public Cell getCell(int index) { + return cells.get(index); + } + + public Align align() { + return this.align; + } + + public byte width() { + return this.width; + } + + public boolean hasHeader() { + return this.hasHeader; + } + } +} diff --git a/src/main/java/org/elasticsearch/rest/action/RestActionModule.java b/src/main/java/org/elasticsearch/rest/action/RestActionModule.java index 67842f35a9a..108f2679d48 100644 --- a/src/main/java/org/elasticsearch/rest/action/RestActionModule.java +++ b/src/main/java/org/elasticsearch/rest/action/RestActionModule.java @@ -67,6 +67,7 @@ import org.elasticsearch.rest.action.admin.indices.warmer.delete.RestDeleteWarme import org.elasticsearch.rest.action.admin.indices.warmer.get.RestGetWarmerAction; import org.elasticsearch.rest.action.admin.indices.warmer.put.RestPutWarmerAction; import org.elasticsearch.rest.action.bulk.RestBulkAction; +import org.elasticsearch.rest.action.cat.RestShardsAction; import org.elasticsearch.rest.action.count.RestCountAction; import org.elasticsearch.rest.action.delete.RestDeleteAction; import org.elasticsearch.rest.action.deletebyquery.RestDeleteByQueryAction; @@ -83,7 +84,9 @@ import org.elasticsearch.rest.action.search.RestMultiSearchAction; import org.elasticsearch.rest.action.search.RestSearchAction; import org.elasticsearch.rest.action.search.RestSearchScrollAction; import org.elasticsearch.rest.action.suggest.RestSuggestAction; + import org.elasticsearch.rest.action.termvector.RestTermVectorAction; + import org.elasticsearch.rest.action.update.RestUpdateAction; import java.util.List; @@ -180,5 +183,7 @@ public class RestActionModule extends AbstractModule { bind(RestMoreLikeThisAction.class).asEagerSingleton(); bind(RestExplainAction.class).asEagerSingleton(); + + bind(RestShardsAction.class).asEagerSingleton(); } } diff --git a/src/main/java/org/elasticsearch/rest/action/cat/RestShardsAction.java b/src/main/java/org/elasticsearch/rest/action/cat/RestShardsAction.java new file mode 100644 index 00000000000..5157df9cc72 --- /dev/null +++ b/src/main/java/org/elasticsearch/rest/action/cat/RestShardsAction.java @@ -0,0 +1,147 @@ +/* + * Licensed to ElasticSearch and Shay Banon 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 org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.admin.cluster.state.ClusterStateRequest; +import org.elasticsearch.action.admin.cluster.state.ClusterStateResponse; +import org.elasticsearch.action.admin.indices.stats.IndicesStatsRequest; +import org.elasticsearch.action.admin.indices.stats.IndicesStatsResponse; +import org.elasticsearch.client.Client; +import org.elasticsearch.cluster.routing.*; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.table.Row; +import org.elasticsearch.common.table.Table; +import org.elasticsearch.common.transport.InetSocketTransportAddress; +import org.elasticsearch.rest.*; + +import java.io.IOException; + +import static org.elasticsearch.rest.RestRequest.Method.GET; + +public class RestShardsAction extends BaseRestHandler { + @Inject + public RestShardsAction(Settings settings, Client client, RestController controller) { + super(settings, client); + controller.registerHandler(GET, "/_cat/shards", this); + } + + @Override + public void handleRequest(final RestRequest request, final RestChannel channel) { + final boolean verbose = request.paramAsBoolean("verbose", false); + final StringBuilder out = new StringBuilder(); + + final ClusterStateRequest clusterStateRequest = new ClusterStateRequest(); clusterStateRequest.listenerThreaded(false); + clusterStateRequest.filterMetaData(true); + clusterStateRequest.local(false); + + client.admin().cluster().state(clusterStateRequest, new ActionListener() { + @Override + public void onResponse(final ClusterStateResponse clusterStateResponse) { + IndicesStatsRequest indicesStatsRequest = new IndicesStatsRequest(); + client.admin().indices().stats(indicesStatsRequest, new ActionListener() { + @Override + public void onResponse(IndicesStatsResponse indicesStatsResponse) { + RestStatus status = RestStatus.OK; + try { + channel.sendResponse(new StringRestResponse(status, process(clusterStateResponse, indicesStatsResponse, verbose))); + } catch (Throwable e) { + onFailure(e); + } + } + + @Override + public void onFailure(Throwable e) { + try { + channel.sendResponse(new XContentThrowableRestResponse(request, e)); + } catch (IOException e1) { + logger.error("Failed to send failure response", e1); + } + } + }); + } + + @Override + public void onFailure(Throwable e) { + try { + channel.sendResponse(new XContentThrowableRestResponse(request, e)); + } catch (IOException e1) { + logger.error("Failed to send failure response", e1); + } + } + }); + } + + private String process(ClusterStateResponse state, IndicesStatsResponse stats, boolean headers) { + Table tab = new Table(); + if (headers) { + tab.addRow(new Row() + .addCell("index") + .addCell("shard") + .addCell("replica") + .addCell("state") + .addCell("docs") + .addCell("size") + .addCell("bytes") + .addCell("host") + .addCell("node"), true); + } + + for (ShardRouting shard : state.getState().routingTable().allShards()) { + Row row = new Row(); + String pri = "r"; + String host = ""; + String docs = ""; + String size = ""; + String bytes = ""; + String nodeName = ""; + + if (shard.assignedToNode()) { + host = ((InetSocketTransportAddress) state.getState().nodes().get(shard.currentNodeId()).address()).address().getHostString(); + nodeName = state.getState().nodes().get(shard.currentNodeId()).name(); + } + + if (null != stats.asMap().get(shard.globalId())) { + size = stats.asMap().get(shard.globalId()).getStore().size().toString(); + bytes = new Long(stats.asMap().get(shard.globalId()).getStore().getSizeInBytes()).toString(); + docs = new Long(stats.asMap().get(shard.globalId()).getDocs().getCount()).toString(); + } + + if (shard.primary()) { + pri = "p"; + } + + row.addCell(shard.index()) + .addCell(new Integer(shard.shardId().id()).toString()) + .addCell(pri) + .addCell(shard.state().toString()) + .addCell(docs) + .addCell(size) + .addCell(bytes) + .addCell(host) + .addCell(nodeName); + tab.addRow(row); + } + + return tab.render(headers); + } + +} diff --git a/src/test/java/org/elasticsearch/test/unit/common/table/TableTests.java b/src/test/java/org/elasticsearch/test/unit/common/table/TableTests.java new file mode 100644 index 00000000000..8a2949f1024 --- /dev/null +++ b/src/test/java/org/elasticsearch/test/unit/common/table/TableTests.java @@ -0,0 +1,45 @@ +/* + * Licensed to ElasticSearch and Shay Banon 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.test.unit.common.table; + +import org.elasticsearch.common.table.Row; +import org.testng.annotations.Test; +import org.elasticsearch.common.table.Table; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; + +public class TableTests { + @Test + public void testTable() { + Table tab = new Table(); + tab.addRow(new Row().addCell("123").addCell("4567")); + tab.addRow(new Row().addCell("1234").addCell("567890123")); + assertThat(tab.render(), equalTo("123 4567\n1234 567890123")); + } + + @Test + public void testHeader() { + Table tab = new Table(); + tab.addRow(new Row().addCell("loooong").addCell("short"), true); + tab.addRow(new Row().addCell("012").addCell("3456789")); + assertThat(tab.render(true), equalTo("loooong short\n012 3456789")); + assertThat(tab.render(), equalTo("012 3456789")); + } +}