PageRenderTime 47ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/solr/core/src/java/org/apache/solr/handler/admin/ClusterStatus.java

https://github.com/apache/solr
Java | 331 lines | 250 code | 31 blank | 50 comment | 64 complexity | d71df3ee083b70f3ae0444be886e4878 MD5 | raw file
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. package org.apache.solr.handler.admin;
  18. import java.util.ArrayList;
  19. import java.util.Arrays;
  20. import java.util.Collection;
  21. import java.util.Collections;
  22. import java.util.HashMap;
  23. import java.util.HashSet;
  24. import java.util.List;
  25. import java.util.Map;
  26. import java.util.Set;
  27. import java.util.stream.Collectors;
  28. import org.apache.solr.common.SolrException;
  29. import org.apache.solr.common.cloud.Aliases;
  30. import org.apache.solr.common.cloud.ClusterState;
  31. import org.apache.solr.common.cloud.DocCollection;
  32. import org.apache.solr.common.cloud.DocRouter;
  33. import org.apache.solr.common.cloud.Replica;
  34. import org.apache.solr.common.cloud.Slice;
  35. import org.apache.solr.common.cloud.ZkNodeProps;
  36. import org.apache.solr.common.cloud.ZkStateReader;
  37. import org.apache.solr.common.params.ShardParams;
  38. import org.apache.solr.common.util.NamedList;
  39. import org.apache.solr.common.util.SimpleOrderedMap;
  40. import org.apache.solr.common.util.Utils;
  41. import org.apache.zookeeper.KeeperException;
  42. public class ClusterStatus {
  43. private final ZkStateReader zkStateReader;
  44. private final ZkNodeProps message;
  45. private final String collection; // maybe null
  46. /** Shard / collection health state. */
  47. public enum Health {
  48. /** All replicas up, leader exists. */
  49. GREEN,
  50. /** Some replicas down, leader exists. */
  51. YELLOW,
  52. /** Most replicas down, leader exists. */
  53. ORANGE,
  54. /** No leader or all replicas down. */
  55. RED;
  56. public static final float ORANGE_LEVEL = 0.5f;
  57. public static final float RED_LEVEL = 0.0f;
  58. public static Health calcShardHealth(float fractionReplicasUp, boolean hasLeader) {
  59. if (hasLeader) {
  60. if (fractionReplicasUp == 1.0f) {
  61. return GREEN;
  62. } else if (fractionReplicasUp > ORANGE_LEVEL) {
  63. return YELLOW;
  64. } else if (fractionReplicasUp > RED_LEVEL) {
  65. return ORANGE;
  66. } else {
  67. return RED;
  68. }
  69. } else {
  70. return RED;
  71. }
  72. }
  73. /** Combine multiple states into one. Always reports as the worst state. */
  74. public static Health combine(Collection<Health> states) {
  75. Health res = GREEN;
  76. for (Health state : states) {
  77. if (state.ordinal() > res.ordinal()) {
  78. res = state;
  79. }
  80. }
  81. return res;
  82. }
  83. }
  84. public ClusterStatus(ZkStateReader zkStateReader, ZkNodeProps props) {
  85. this.zkStateReader = zkStateReader;
  86. this.message = props;
  87. collection = props.getStr(ZkStateReader.COLLECTION_PROP);
  88. }
  89. public void getClusterStatus(NamedList<Object> results)
  90. throws KeeperException, InterruptedException {
  91. // read aliases
  92. Aliases aliases = zkStateReader.getAliases();
  93. Map<String, List<String>> collectionVsAliases = new HashMap<>();
  94. Map<String, List<String>> aliasVsCollections = aliases.getCollectionAliasListMap();
  95. for (Map.Entry<String, List<String>> entry : aliasVsCollections.entrySet()) {
  96. String alias = entry.getKey();
  97. List<String> colls = entry.getValue();
  98. for (String coll : colls) {
  99. if (collection == null || collection.equals(coll)) {
  100. List<String> list = collectionVsAliases.computeIfAbsent(coll, k -> new ArrayList<>());
  101. list.add(alias);
  102. }
  103. }
  104. }
  105. Map<?, ?> roles = null;
  106. if (zkStateReader.getZkClient().exists(ZkStateReader.ROLES, true)) {
  107. roles =
  108. (Map<?, ?>)
  109. Utils.fromJSON(
  110. zkStateReader.getZkClient().getData(ZkStateReader.ROLES, null, null, true));
  111. }
  112. ClusterState clusterState = zkStateReader.getClusterState();
  113. String routeKey = message.getStr(ShardParams._ROUTE_);
  114. String shard = message.getStr(ZkStateReader.SHARD_ID_PROP);
  115. Map<String, DocCollection> collectionsMap = null;
  116. if (collection == null) {
  117. collectionsMap = clusterState.getCollectionsMap();
  118. } else {
  119. collectionsMap =
  120. Collections.singletonMap(collection, clusterState.getCollectionOrNull(collection));
  121. }
  122. boolean isAlias = aliasVsCollections.containsKey(collection);
  123. boolean didNotFindCollection = collectionsMap.get(collection) == null;
  124. if (didNotFindCollection && isAlias) {
  125. // In this case this.collection is an alias name not a collection
  126. // get all collections and filter out collections not in the alias
  127. collectionsMap =
  128. clusterState.getCollectionsMap().entrySet().stream()
  129. .filter((entry) -> aliasVsCollections.get(collection).contains(entry.getKey()))
  130. .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
  131. }
  132. NamedList<Object> collectionProps = new SimpleOrderedMap<>();
  133. for (Map.Entry<String, DocCollection> entry : collectionsMap.entrySet()) {
  134. Map<String, Object> collectionStatus;
  135. String name = entry.getKey();
  136. DocCollection clusterStateCollection = entry.getValue();
  137. if (clusterStateCollection == null) {
  138. if (collection != null) {
  139. SolrException solrException =
  140. new SolrException(
  141. SolrException.ErrorCode.BAD_REQUEST, "Collection: " + name + " not found");
  142. solrException.setMetadata("CLUSTERSTATUS", "NOT_FOUND");
  143. throw solrException;
  144. } else {
  145. // collection might have got deleted at the same time
  146. continue;
  147. }
  148. }
  149. Set<String> requestedShards = new HashSet<>();
  150. if (routeKey != null) {
  151. DocRouter router = clusterStateCollection.getRouter();
  152. Collection<Slice> slices = router.getSearchSlices(routeKey, null, clusterStateCollection);
  153. for (Slice slice : slices) {
  154. requestedShards.add(slice.getName());
  155. }
  156. }
  157. if (shard != null) {
  158. String[] paramShards = shard.split(",");
  159. requestedShards.addAll(Arrays.asList(paramShards));
  160. }
  161. byte[] bytes = Utils.toJSON(clusterStateCollection);
  162. @SuppressWarnings("unchecked")
  163. Map<String, Object> docCollection = (Map<String, Object>) Utils.fromJSON(bytes);
  164. collectionStatus = getCollectionStatus(docCollection, name, requestedShards);
  165. collectionStatus.put("znodeVersion", clusterStateCollection.getZNodeVersion());
  166. if (collectionVsAliases.containsKey(name) && !collectionVsAliases.get(name).isEmpty()) {
  167. collectionStatus.put("aliases", collectionVsAliases.get(name));
  168. }
  169. String configName = clusterStateCollection.getConfigName();
  170. collectionStatus.put("configName", configName);
  171. collectionProps.add(name, collectionStatus);
  172. }
  173. List<String> liveNodes =
  174. zkStateReader.getZkClient().getChildren(ZkStateReader.LIVE_NODES_ZKNODE, null, true);
  175. // now we need to walk the collectionProps tree to cross-check replica state with live nodes
  176. crossCheckReplicaStateWithLiveNodes(liveNodes, collectionProps);
  177. NamedList<Object> clusterStatus = new SimpleOrderedMap<>();
  178. clusterStatus.add("collections", collectionProps);
  179. // read cluster properties
  180. Map<String, Object> clusterProps = zkStateReader.getClusterProperties();
  181. if (clusterProps != null && !clusterProps.isEmpty()) {
  182. clusterStatus.add("properties", clusterProps);
  183. }
  184. // add the alias map too
  185. Map<String, String> collectionAliasMap = aliases.getCollectionAliasMap(); // comma delim
  186. if (!collectionAliasMap.isEmpty()) {
  187. clusterStatus.add("aliases", collectionAliasMap);
  188. }
  189. // add the roles map
  190. if (roles != null) {
  191. clusterStatus.add("roles", roles);
  192. }
  193. // add live_nodes
  194. clusterStatus.add("live_nodes", liveNodes);
  195. results.add("cluster", clusterStatus);
  196. }
  197. /**
  198. * Get collection status from cluster state. Can return collection status by given shard name.
  199. *
  200. * @param collection collection map parsed from JSON-serialized {@link ClusterState}
  201. * @param name collection name
  202. * @param requestedShards a set of shards to be returned in the status. An empty or null values
  203. * indicates <b>all</b> shards.
  204. * @return map of collection properties
  205. */
  206. @SuppressWarnings("unchecked")
  207. private Map<String, Object> getCollectionStatus(
  208. Map<String, Object> collection, String name, Set<String> requestedShards) {
  209. if (collection == null) {
  210. throw new SolrException(
  211. SolrException.ErrorCode.BAD_REQUEST, "Collection: " + name + " not found");
  212. }
  213. if (requestedShards == null || requestedShards.isEmpty()) {
  214. return postProcessCollectionJSON(collection);
  215. } else {
  216. Map<String, Object> shards = (Map<String, Object>) collection.get("shards");
  217. Map<String, Object> selected = new HashMap<>();
  218. for (String selectedShard : requestedShards) {
  219. if (!shards.containsKey(selectedShard)) {
  220. throw new SolrException(
  221. SolrException.ErrorCode.BAD_REQUEST,
  222. "Collection: " + name + " shard: " + selectedShard + " not found");
  223. }
  224. selected.put(selectedShard, shards.get(selectedShard));
  225. collection.put("shards", selected);
  226. }
  227. return postProcessCollectionJSON(collection);
  228. }
  229. }
  230. /**
  231. * Walks the tree of collection status to verify that any replicas not reporting a "down" status
  232. * is on a live node, if any replicas reporting their status as "active" but the node is not live
  233. * is marked as "down"; used by CLUSTERSTATUS.
  234. *
  235. * @param liveNodes List of currently live node names.
  236. * @param collectionProps Map of collection status information pulled directly from ZooKeeper.
  237. */
  238. @SuppressWarnings("unchecked")
  239. protected void crossCheckReplicaStateWithLiveNodes(
  240. List<String> liveNodes, NamedList<Object> collectionProps) {
  241. for (Map.Entry<String, Object> next : collectionProps) {
  242. Map<String, Object> collMap = (Map<String, Object>) next.getValue();
  243. Map<String, Object> shards = (Map<String, Object>) collMap.get("shards");
  244. for (Object nextShard : shards.values()) {
  245. Map<String, Object> shardMap = (Map<String, Object>) nextShard;
  246. Map<String, Object> replicas = (Map<String, Object>) shardMap.get("replicas");
  247. for (Object nextReplica : replicas.values()) {
  248. Map<String, Object> replicaMap = (Map<String, Object>) nextReplica;
  249. if (Replica.State.getState((String) replicaMap.get(ZkStateReader.STATE_PROP))
  250. != Replica.State.DOWN) {
  251. // not down, so verify the node is live
  252. String node_name = (String) replicaMap.get(ZkStateReader.NODE_NAME_PROP);
  253. if (!liveNodes.contains(node_name)) {
  254. // node is not live, so this replica is actually down
  255. replicaMap.put(ZkStateReader.STATE_PROP, Replica.State.DOWN.toString());
  256. }
  257. }
  258. }
  259. }
  260. }
  261. }
  262. @SuppressWarnings("unchecked")
  263. public static Map<String, Object> postProcessCollectionJSON(Map<String, Object> collection) {
  264. final Map<String, Map<String, Object>> shards =
  265. collection != null
  266. ? (Map<String, Map<String, Object>>)
  267. collection.getOrDefault("shards", Collections.emptyMap())
  268. : Collections.emptyMap();
  269. final List<Health> healthStates = new ArrayList<>(shards.size());
  270. shards.forEach(
  271. (shardName, s) -> {
  272. final Map<String, Map<String, Object>> replicas =
  273. (Map<String, Map<String, Object>>) s.getOrDefault("replicas", Collections.emptyMap());
  274. int[] totalVsActive = new int[2];
  275. boolean hasLeader = false;
  276. for (Map<String, Object> r : replicas.values()) {
  277. totalVsActive[0]++;
  278. boolean active = false;
  279. if (Replica.State.ACTIVE.toString().equals(r.get("state"))) {
  280. totalVsActive[1]++;
  281. active = true;
  282. }
  283. if ("true".equals(r.get("leader")) && active) {
  284. hasLeader = true;
  285. }
  286. }
  287. float ratioActive;
  288. if (totalVsActive[0] == 0) {
  289. ratioActive = 0.0f;
  290. } else {
  291. ratioActive = (float) totalVsActive[1] / totalVsActive[0];
  292. }
  293. Health health = Health.calcShardHealth(ratioActive, hasLeader);
  294. s.put("health", health.toString());
  295. healthStates.add(health);
  296. });
  297. collection.put("health", Health.combine(healthStates).toString());
  298. return collection;
  299. }
  300. }