/* * Copyright 2008-2014 LinkedIn, Inc * * Licensed 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 voldemort.tools.admin.command; import java.io.File; import java.io.IOException; import java.io.PrintStream; import java.io.StringReader; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.Set; import java.util.TreeMap; import joptsimple.OptionParser; import joptsimple.OptionSet; import org.apache.commons.io.FileUtils; import voldemort.VoldemortException; import voldemort.client.protocol.admin.AdminClient; import voldemort.cluster.Cluster; import voldemort.cluster.Node; import voldemort.routing.RoutingStrategy; import voldemort.routing.RoutingStrategyFactory; import voldemort.server.rebalance.RebalancerState; import voldemort.store.StoreDefinition; import voldemort.store.metadata.MetadataStore; import voldemort.store.metadata.MetadataStore.VoldemortState; import voldemort.store.quota.QuotaType; import voldemort.store.readonly.ReadOnlyFileEntry; import voldemort.store.readonly.ReadOnlyStorageConfiguration; import voldemort.store.system.SystemStoreConstants; import voldemort.tools.admin.AdminParserUtils; import voldemort.tools.admin.AdminToolUtils; import voldemort.utils.ByteArray; import voldemort.utils.MetadataVersionStoreUtils; import voldemort.utils.StoreDefinitionUtils; import voldemort.utils.Utils; import voldemort.versioning.VectorClock; import voldemort.versioning.Version; import voldemort.versioning.Versioned; import voldemort.xml.ClusterMapper; import voldemort.xml.StoreDefinitionsMapper; import com.google.common.base.Joiner; import com.google.common.collect.Lists; /** * Implements all meta commands. */ public class AdminCommandMeta extends AbstractAdminCommand { private static final String METAKEY_ALL = "all"; /** * Parses command-line and directs to sub-commands. * * @param args Command-line input * @throws Exception */ public static void executeCommand(String[] args) throws Exception { String subCmd = (args.length > 0) ? args[0] : ""; args = AdminToolUtils.copyArrayCutFirst(args); if(subCmd.equals("check")) { SubCommandMetaCheck.executeCommand(args); } else if(subCmd.equals("clear-rebalance")) { SubCommandMetaClearRebalance.executeCommand(args); } else if(subCmd.equals("get")) { SubCommandMetaGet.executeCommand(args); } else if(subCmd.equals("get-ro")) { SubCommandMetaGetRO.executeCommand(args); } else if(subCmd.equals("set")) { SubCommandMetaSet.executeCommand(args); } else if(subCmd.equals("sync-version")) { SubCommandMetaSyncVersion.executeCommand(args); } else if(subCmd.equals("check-version")) { SubCommandMetaCheckVersion.executeCommand(args); } else { printHelp(System.out); } } /** * Prints command-line help menu. */ public static void printHelp(PrintStream stream) { stream.println(); stream.println("Voldemort Admin Tool Meta Commands"); stream.println("----------------------------------"); stream.println("check Check if metadata is consistent across all nodes."); stream.println("clear-rebalance Remove metadata related to rebalancing."); stream.println("get Get metadata from nodes."); stream.println("get-ro Get read-only metadata from nodes and stores."); stream.println("set Set metadata on nodes."); stream.println("sync-version Synchronize metadata versions across all nodes."); stream.println("check-version Verify metadata versions on all the cluster nodes."); stream.println(); stream.println("To get more information on each command,"); stream.println("please try \'help meta <command-name>\'."); stream.println(); } /** * Parses command-line input and prints help menu. * * @throws Exception */ public static void executeHelp(String[] args, PrintStream stream) throws Exception { String subCmd = (args.length > 0) ? args[0] : ""; if(subCmd.equals("check")) { SubCommandMetaCheck.printHelp(stream); } else if(subCmd.equals("clear-rebalance")) { SubCommandMetaClearRebalance.printHelp(stream); } else if(subCmd.equals("get")) { SubCommandMetaGet.printHelp(stream); } else if(subCmd.equals("get-ro")) { SubCommandMetaGetRO.printHelp(stream); } else if(subCmd.equals("set")) { SubCommandMetaSet.printHelp(stream); } else if(subCmd.equals("sync-version")) { SubCommandMetaSyncVersion.printHelp(stream); } else if(subCmd.equals("check-version")) { SubCommandMetaCheckVersion.printHelp(stream); } else { printHelp(stream); } } /** * meta check command */ public static class SubCommandMetaCheck extends AbstractAdminCommand { private static final String OPT_HEAD_META_CHECK = "meta-check"; /** * Initializes parser * * @return OptionParser object with all available options */ protected static OptionParser getParser() { OptionParser parser = new OptionParser(); // help options AdminParserUtils.acceptsHelp(parser); // required options parser.accepts(OPT_HEAD_META_CHECK, "metadata keys to be checked") .withOptionalArg() .describedAs("meta-key-list") .withValuesSeparatedBy(',') .ofType(String.class); AdminParserUtils.acceptsUrl(parser); return parser; } /** * Prints help menu for command. * * @param stream PrintStream object for output * @throws IOException */ public static void printHelp(PrintStream stream) throws IOException { stream.println(); stream.println("NAME"); stream.println(" meta check - Check if metadata is consistent across all nodes"); stream.println(); stream.println("SYNOPSIS"); stream.println(" meta check (<meta-key-list> | all) -u <url>"); stream.println(); stream.println("COMMENTS"); stream.println(" Valid meta keys are:"); stream.println(" " + MetadataStore.CLUSTER_KEY); stream.println(" " + MetadataStore.STORES_KEY); stream.println(" " + MetadataStore.SERVER_STATE_KEY); stream.println(); getParser().printHelpOn(stream); stream.println(); } /** * Parses command-line and checks if metadata is consistent across all * nodes. * * @param args Command-line input * @param printHelp Tells whether to print help only or execute command * actually * @throws IOException * */ @SuppressWarnings("unchecked") public static void executeCommand(String[] args) throws IOException { OptionParser parser = getParser(); // declare parameters List<String> metaKeys = null; String url = null; // parse command-line input args = AdminToolUtils.copyArrayAddFirst(args, "--" + OPT_HEAD_META_CHECK); OptionSet options = parser.parse(args); if(options.has(AdminParserUtils.OPT_HELP)) { printHelp(System.out); return; } // check required options and/or conflicting options AdminParserUtils.checkRequired(options, OPT_HEAD_META_CHECK); AdminParserUtils.checkRequired(options, AdminParserUtils.OPT_URL); // load parameters metaKeys = (List<String>) options.valuesOf(OPT_HEAD_META_CHECK); url = (String) options.valueOf(AdminParserUtils.OPT_URL); // execute command if(metaKeys.size() == 0 || (metaKeys.size() == 1 && metaKeys.get(0).equals(METAKEY_ALL))) { metaKeys = Lists.newArrayList(); metaKeys.add(MetadataStore.CLUSTER_KEY); metaKeys.add(MetadataStore.STORES_KEY); metaKeys.add(MetadataStore.SERVER_STATE_KEY); } AdminClient adminClient = AdminToolUtils.getAdminClient(url); doMetaCheck(adminClient, metaKeys); } private static void addMetadataValue(Map<Object, List<String>> allValues, Object metadataValue, String node) { if(allValues.containsKey(metadataValue) == false) { allValues.put(metadataValue, new ArrayList<String>()); } allValues.get(metadataValue).add(node); } private static Boolean checkDiagnostics(String keyName, Map<Object, List<String>> metadataValues, Collection<String> allNodeNames) { Collection<String> nodesInResult = new ArrayList<String>(); Boolean checkResult = true; if(metadataValues.size() == 1) { Map.Entry<Object, List<String>> entry = metadataValues.entrySet().iterator().next(); nodesInResult.addAll(entry.getValue()); } else { // Some nodes have different set of data than the others. checkResult = false; int groupCount = 0; for(Map.Entry<Object, List<String>> entry: metadataValues.entrySet()) { groupCount++; String keyToPrint = entry.getKey().toString() ; if(keyToPrint.length() > 50) { keyToPrint = keyToPrint.substring(0, 50) + "...truncated"; } System.err.println("Nodes with same value " + keyToPrint +" for " + keyName + ". Id :" + groupCount); nodesInResult.addAll(entry.getValue()); for(String nodeName: entry.getValue()) { System.err.println("Node " + nodeName); } System.out.println(); } } // Some times when a store could be missing from one of the nodes // In that case the map will have only one value, but total number // of nodes will be lesser. The following code handles that. // removeAll modifies the list that is being called on. so create a // copy Collection<String> nodesDiff = new ArrayList<String>(allNodeNames.size()); nodesDiff.addAll(allNodeNames); nodesDiff.removeAll(nodesInResult); if(nodesDiff.size() > 0) { checkResult = false; for(String nodeName: nodesDiff) { System.err.println(keyName + " is missing in the Node " + nodeName); } } return checkResult; } private static byte[] toBytes(int i) { byte[] result = new byte[4]; result[0] = (byte) (i >> 24); result[1] = (byte) (i >> 16); result[2] = (byte) (i >> 8); result[3] = (byte) (i /*>> 0*/); return result; } private static ByteArray generateByteArrayKey(AdminClient adminClient, StoreDefinition storeDef, int nodeId) { Cluster cluster = adminClient.getAdminClientCluster(); RoutingStrategy routingStrategy = new RoutingStrategyFactory().updateRoutingStrategy(storeDef, cluster); final int MAX_NUM_TO_SEARCH = 10000000; for(int i = 0; i < MAX_NUM_TO_SEARCH ; i ++) { byte[] serializedKey = toBytes(i); List<Node> nodesServingKey = routingStrategy.routeRequest(serializedKey); for(Node node: nodesServingKey) { if(node.getId() == nodeId) { return new ByteArray(serializedKey); } } } throw new VoldemortException("Could not find a key that maps to the node in the range 0.." + MAX_NUM_TO_SEARCH); } private static boolean checkGetStore(AdminClient adminClient, StoreDefinition storeDef, Node node) { if (node.getPartitionIds().size() == 0) { // No Partitions are assigned to this node. return true; } String storeName = storeDef.getName(); try { ByteArray randomKey = generateByteArrayKey(adminClient, storeDef, node.getId()); adminClient.storeOps.getNodeKey(storeName, node.getId(), randomKey); } catch(Exception e) { System.out.println(" Error doing sample key get from Store " + storeName + node.briefToString() + " Error " + e.getMessage()); e.printStackTrace(); return false; } return true; } private static void validateROFiles(AdminClient adminClient, Collection<Node> allNodes, Map<String, Integer> roStoreToReplicationFactorMap) { if(roStoreToReplicationFactorMap.size() == 0) return; boolean checkResult = true; boolean isSizePresent = false; for(Map.Entry<String, Integer> storeAndReplicationFactor: roStoreToReplicationFactorMap.entrySet()) { String storeName = storeAndReplicationFactor.getKey(); Integer replicationFactor = storeAndReplicationFactor.getValue(); if(replicationFactor == 1) { System.out.println(" Store " + storeName + " has replication factor of 1, skipping consistency check across nodes"); continue; } Map<ReadOnlyFileEntry, List<String>> fileToNodesMap = new TreeMap<ReadOnlyFileEntry, List<String>>(); for(Node node: allNodes) { List<ReadOnlyFileEntry> fileEntries = null; try { fileEntries = adminClient.readonlyOps.getROStorageFileMetadata(node.getId(), storeName); } catch(Exception e) { System.out.println(" Error retrieving files for ReadOnly Store " + storeName + node.briefToString() + " Error " + e.getMessage()); e.printStackTrace(); checkResult = false; // Once one node throws an exception, it is going to // spew lots of missing files break; } for(ReadOnlyFileEntry entry: fileEntries) { if(entry.getSize() > 0) { isSizePresent = true; } if(fileToNodesMap.containsKey(entry) == false) { fileToNodesMap.put(entry, new ArrayList<String>()); } fileToNodesMap.get(entry).add(node.briefToString()); } } for(Map.Entry<ReadOnlyFileEntry, List<String>> fileToNodes: fileToNodesMap.entrySet()) { ReadOnlyFileEntry entry = fileToNodes.getKey(); List<String> nodes = fileToNodes.getValue(); if(nodes.size() == replicationFactor) { // The file has the required replication factor. continue; } else if(isSizePresent && entry.getSize() == 0){ // 0 - sized files are created on the fly, when data and index is empty. // Old protocol does not send size, but if sizes are present, then // do not report the empty files continue; } else { System.out.println(" Store " + storeName + " File " + entry + " is expected in " + replicationFactor + " nodes, but present only in " + Arrays.toString(nodes.toArray())); checkResult = false; } } } System.out.println("RO File Validation check : " + (checkResult ? "PASSED" : "FAILED")); } /** * Checks if metadata is consistent across all nodes. * * @param adminClient An instance of AdminClient points to given cluster * @param metaKeys List of metakeys to check * */ public static void doMetaCheck(AdminClient adminClient, List<String> metaKeys) { Map<String, Integer> roStoreToReplicationFactorMap = new HashMap<String, Integer>(); Collection<Node> allNodes = adminClient.getAdminClientCluster().getNodes(); for(String key: metaKeys) { Map<String, Map<Object, List<String>>> storeDefToNodeMap = new HashMap<String, Map<Object, List<String>>>(); Map<Object, List<String>> metadataNodeValueMap = new HashMap<Object, List<String>>(); Collection<String> allNodeNames = new ArrayList<String>(); Boolean checkResult = true; for(Node node: allNodes) { String nodeName = "Host '" + node.getHost() + "' : ID " + node.getId(); allNodeNames.add(nodeName); Versioned<String> versioned = adminClient.metadataMgmtOps.getRemoteMetadata(node.getId(), key); if(versioned == null || versioned.getValue() == null) { throw new VoldemortException("Value returned from node " + node.getId() + " was null"); } else if(key.compareTo(MetadataStore.STORES_KEY) == 0) { List<StoreDefinition> storeDefinitions = new StoreDefinitionsMapper().readStoreList(new StringReader(versioned.getValue())); for(StoreDefinition storeDef: storeDefinitions) { String storeName = storeDef.getName(); if(storeDef.getType().compareTo(ReadOnlyStorageConfiguration.TYPE_NAME) == 0) { roStoreToReplicationFactorMap.put(storeName, storeDef.getReplicationFactor()); } if(storeDefToNodeMap.containsKey(storeName) == false) { storeDefToNodeMap.put(storeName, new HashMap<Object, List<String>>()); } Map<Object, List<String>> storeDefMap = storeDefToNodeMap.get(storeName); addMetadataValue(storeDefMap, storeDef, nodeName); // Try a random Get against one key for store/Node checkGetStore(adminClient, storeDef, node); } } else { if(key.compareTo(MetadataStore.CLUSTER_KEY) == 0 || key.compareTo(MetadataStore.REBALANCING_SOURCE_CLUSTER_XML) == 0) { Cluster cluster = new ClusterMapper().readCluster(new StringReader(versioned.getValue())); addMetadataValue(metadataNodeValueMap, cluster, nodeName); } else if(key.compareTo(MetadataStore.SERVER_STATE_KEY) == 0) { VoldemortState voldemortStateValue = VoldemortState.valueOf(versioned.getValue()); addMetadataValue(metadataNodeValueMap, voldemortStateValue, nodeName); } else { throw new VoldemortException("Incorrect metadata key"); } } } if(metadataNodeValueMap.size() > 0) { checkResult &= checkDiagnostics("Key " + key, metadataNodeValueMap, allNodeNames); } if(storeDefToNodeMap.size() > 0) { for(Map.Entry<String, Map<Object, List<String>>> storeNodeValueEntry: storeDefToNodeMap.entrySet()) { String storeName = storeNodeValueEntry.getKey(); Map<Object, List<String>> storeDefMap = storeNodeValueEntry.getValue(); checkResult &= checkDiagnostics("Store " + storeName, storeDefMap, allNodeNames); for(QuotaType type : QuotaType.values()) { Map<Object, List<String>> quotaNodeValues = new HashMap<Object, List<String>>(); String keyName = "Store:" + storeName + "_QuotaType_"+ type; for(Node node: allNodes) { String nodeName = "Host '" + node.getHost() + "' : ID " + node.getId(); Versioned<String> quotaValue = adminClient.quotaMgmtOps.getQuotaForNode(storeName, type, node.getId()); if(quotaValue == null || quotaValue.getValue() == null) { continue; } addMetadataValue( quotaNodeValues , quotaValue.getValue() , nodeName); } if(quotaNodeValues.size() > 0) { boolean result = checkDiagnostics(keyName, quotaNodeValues, allNodeNames); checkResult &= result; } } } } System.out.println(key + " metadata check : " + (checkResult ? "PASSED" : "FAILED")); } validateROFiles(adminClient, allNodes, roStoreToReplicationFactorMap); } } /** * meta clear-rebalance command */ public static class SubCommandMetaClearRebalance extends AbstractAdminCommand { /** * Initializes parser * * @return OptionParser object with all available options */ protected static OptionParser getParser() { OptionParser parser = new OptionParser(); // help options AdminParserUtils.acceptsHelp(parser); // required options AdminParserUtils.acceptsUrl(parser); // optional options AdminParserUtils.acceptsNodeMultiple(parser); // either // --node or // --all-nodes AdminParserUtils.acceptsAllNodes(parser); // either --node or // --all-nodes AdminParserUtils.acceptsConfirm(parser); return parser; } /** * Prints help menu for command. * * @param stream PrintStream object for output * @throws IOException */ public static void printHelp(PrintStream stream) throws IOException { stream.println(); stream.println("NAME"); stream.println(" meta clear-rebalance - Remove metadata related to rebalancing"); stream.println(); stream.println("SYNOPSIS"); stream.println(" meta clear-rebalance -u <url> [-n <node-id-list> | --all-nodes] [--confirm]"); stream.println(); getParser().printHelpOn(stream); stream.println(); } /** * Parses command-line and removes metadata related to rebalancing. * * @param args Command-line input * @param printHelp Tells whether to print help only or execute command * actually * @throws IOException * */ @SuppressWarnings("unchecked") public static void executeCommand(String[] args) throws IOException { OptionParser parser = getParser(); // declare parameters String url = null; List<Integer> nodeIds = null; Boolean allNodes = true; Boolean confirm = false; // parse command-line input OptionSet options = parser.parse(args); if(options.has(AdminParserUtils.OPT_HELP)) { printHelp(System.out); return; } // check required options and/or conflicting options AdminParserUtils.checkRequired(options, AdminParserUtils.OPT_URL); AdminParserUtils.checkOptional(options, AdminParserUtils.OPT_NODE, AdminParserUtils.OPT_ALL_NODES); // load parameters url = (String) options.valueOf(AdminParserUtils.OPT_URL); if(options.has(AdminParserUtils.OPT_NODE)) { nodeIds = (List<Integer>) options.valuesOf(AdminParserUtils.OPT_NODE); allNodes = false; } if(options.has(AdminParserUtils.OPT_CONFIRM)) { confirm = true; } // print summary System.out.println("Remove metadata related to rebalancing"); System.out.println("Location:"); System.out.println(" bootstrap url = " + url); if(allNodes) { System.out.println(" node = all nodes"); } else { System.out.println(" node = " + Joiner.on(", ").join(nodeIds)); } // execute command if(!AdminToolUtils.askConfirm(confirm, "remove metadata related to rebalancing")) { return; } AdminClient adminClient = AdminToolUtils.getAdminClient(url); if(allNodes) { nodeIds = AdminToolUtils.getAllNodeIds(adminClient); } AdminToolUtils.assertServerNotInRebalancingState(adminClient, nodeIds); doMetaClearRebalance(adminClient, nodeIds); } /** * Removes metadata related to rebalancing. * * @param adminClient An instance of AdminClient points to given cluster * @param nodeIds Node ids to clear metadata after rebalancing * */ public static void doMetaClearRebalance(AdminClient adminClient, List<Integer> nodeIds) { AdminToolUtils.assertServerNotInOfflineState(adminClient, nodeIds); System.out.println("Setting " + MetadataStore.SERVER_STATE_KEY + " to " + MetadataStore.VoldemortState.NORMAL_SERVER); doMetaSet(adminClient, nodeIds, MetadataStore.SERVER_STATE_KEY, MetadataStore.VoldemortState.NORMAL_SERVER.toString()); RebalancerState state = RebalancerState.create("[]"); System.out.println("Cleaning up " + MetadataStore.REBALANCING_STEAL_INFO + " to " + state.toJsonString()); doMetaSet(adminClient, nodeIds, MetadataStore.REBALANCING_STEAL_INFO, state.toJsonString()); System.out.println("Cleaning up " + MetadataStore.REBALANCING_SOURCE_CLUSTER_XML + " to empty string"); doMetaSet(adminClient, nodeIds, MetadataStore.REBALANCING_SOURCE_CLUSTER_XML, ""); } } /** * meta get command */ public static class SubCommandMetaGet extends AbstractAdminCommand { public static final String OPT_HEAD_META_GET = "meta-get"; public static final String OPT_VERBOSE = "verbose"; /** * Initializes parser * * @return OptionParser object with all available options */ protected static OptionParser getParser() { OptionParser parser = new OptionParser(); // help options AdminParserUtils.acceptsHelp(parser); // required options parser.accepts(OPT_HEAD_META_GET, "metadata keys to fetch") .withOptionalArg() .describedAs("meta-key-list") .withValuesSeparatedBy(',') .ofType(String.class); AdminParserUtils.acceptsUrl(parser); // optional options AdminParserUtils.acceptsDir(parser); AdminParserUtils.acceptsNodeMultiple(parser); // either // --node or // --all-nodes AdminParserUtils.acceptsAllNodes(parser); // either --node or // --all-nodes parser.accepts(OPT_VERBOSE, "print all metadata"); return parser; } /** * Prints help menu for command. * * @param stream PrintStream object for output * @throws IOException */ public static void printHelp(PrintStream stream) throws IOException { stream.println(); stream.println("NAME"); stream.println(" meta get - Get metadata from nodes"); stream.println(); stream.println("SYNOPSIS"); stream.println(" meta get (<meta-key-list> | all) -u <url> [-d <output-dir>]"); stream.println(" [-n <node-id-list> | --all-nodes] [--verbose]"); stream.println(); stream.println("COMMENTS"); stream.println(" Valid meta keys are:"); for(Object key: MetadataStore.METADATA_KEYS) { stream.println(" " + (String) key); } stream.println(); getParser().printHelpOn(stream); stream.println(); } /** * Parses command-line and gets metadata. * * @param args Command-line input * @param printHelp Tells whether to print help only or execute command * actually * @throws IOException */ @SuppressWarnings("unchecked") public static void executeCommand(String[] args) throws IOException { OptionParser parser = getParser(); // declare parameters List<String> metaKeys = null; String url = null; String dir = null; List<Integer> nodeIds = null; Boolean allNodes = true; Boolean verbose = false; // parse command-line input args = AdminToolUtils.copyArrayAddFirst(args, "--" + OPT_HEAD_META_GET); OptionSet options = parser.parse(args); if(options.has(AdminParserUtils.OPT_HELP)) { printHelp(System.out); return; } // check required options and/or conflicting options AdminParserUtils.checkRequired(options, OPT_HEAD_META_GET); AdminParserUtils.checkRequired(options, AdminParserUtils.OPT_URL); AdminParserUtils.checkOptional(options, AdminParserUtils.OPT_NODE, AdminParserUtils.OPT_ALL_NODES); // load parameters metaKeys = (List<String>) options.valuesOf(OPT_HEAD_META_GET); url = (String) options.valueOf(AdminParserUtils.OPT_URL); if(options.has(AdminParserUtils.OPT_DIR)) { dir = (String) options.valueOf(AdminParserUtils.OPT_DIR); } if(options.has(AdminParserUtils.OPT_NODE)) { nodeIds = (List<Integer>) options.valuesOf(AdminParserUtils.OPT_NODE); allNodes = false; } if(options.has(OPT_VERBOSE)) { verbose = true; } // execute command File directory = AdminToolUtils.createDir(dir); AdminClient adminClient = AdminToolUtils.getAdminClient(url); if(allNodes) { nodeIds = AdminToolUtils.getAllNodeIds(adminClient); } if(metaKeys.size() == 1 && metaKeys.get(0).equals(METAKEY_ALL)) { metaKeys = Lists.newArrayList(); for(Object key: MetadataStore.METADATA_KEYS) { metaKeys.add((String) key); } } doMetaGet(adminClient, nodeIds, metaKeys, directory, verbose); } /** * Gets metadata. * * @param adminClient An instance of AdminClient points to given cluster * @param nodeIds Node ids to fetch metadata from * @param metaKeys List of metadata to fetch * @param directory Directory to output to * @param verbose Tells whether to print metadata verbosely * @throws IOException */ @SuppressWarnings({ "unchecked", "cast", "rawtypes" }) public static void doMetaGet(AdminClient adminClient, Collection<Integer> nodeIds, List<String> metaKeys, File directory, Boolean verbose) throws IOException { Map<String, List<Node>> nodeMap = new HashMap<String, List<Node>>(); Map<Node, Version> versionMap = new HashMap<Node, Version>(); for(String key: metaKeys) { nodeMap.clear(); versionMap.clear(); System.out.println("Metadata: " + key); for(Integer nodeId: nodeIds) { Versioned<String> versioned = null; try { versioned = adminClient.metadataMgmtOps.getRemoteMetadata(nodeId, key); } catch(Exception e) { System.out.println("Error in retrieving " + e.getMessage()); System.out.println(); continue; } if(directory != null) { FileUtils.writeStringToFile(new File(directory, key + "_" + nodeId), ((versioned == null) ? "" : versioned.getValue())); } else { Node node = adminClient.getAdminClientCluster().getNodeById(nodeId); if(verbose) { System.out.println(node.getHost() + ":" + nodeId); if(versioned == null) { System.out.println("null"); } else { System.out.println(versioned.getVersion()); System.out.print(": "); System.out.println(versioned.getValue()); System.out.println(); } } else { if(!nodeMap.containsKey(versioned.getValue())) { nodeMap.put(versioned.getValue(), new ArrayList<Node>()); } nodeMap.get(versioned.getValue()).add(node); if(!versionMap.containsKey(node)) { versionMap.put(node, versioned.getVersion()); } } } } if(!verbose && !nodeMap.isEmpty()) { Iterator<Entry<String, List<Node>>> iter = nodeMap.entrySet().iterator(); while(iter.hasNext()) { Map.Entry entry = (Map.Entry) iter.next(); String metaValue = (String) entry.getKey(); List<Node> nodeList = (List<Node>) entry.getValue(); for(Node node: nodeList) { System.out.println(node.getHost() + ":" + node.getId() + " " + versionMap.get(node)); } System.out.println(metaValue); System.out.println(); } } } } } /** * meta get-ro command */ public static class SubCommandMetaGetRO extends AbstractAdminCommand { public static final String OPT_HEAD_META_GET_RO = "meta-get-ro"; public static final String KEY_MAX_VERSION = "max-version"; public static final String KEY_CURRENT_VERSION = "current-version"; public static final String KEY_STORAGE_FORMAT = "storage-format"; /** * Initializes parser * * @return OptionParser object with all available options */ protected static OptionParser getParser() { OptionParser parser = new OptionParser(); // help options AdminParserUtils.acceptsHelp(parser); // required options parser.accepts(OPT_HEAD_META_GET_RO, "read-only metadata keys to fetch") .withOptionalArg() .describedAs("ro-meta-key-list") .withValuesSeparatedBy(',') .ofType(String.class); AdminParserUtils.acceptsUrl(parser); AdminParserUtils.acceptsStoreMultiple(parser); // optional options AdminParserUtils.acceptsNodeMultiple(parser); // either --node or // --all-nodes AdminParserUtils.acceptsAllNodes(parser); // either --node or // --all-nodes return parser; } /** * Prints help menu for command. * * @param stream PrintStream object for output * @throws IOException */ public static void printHelp(PrintStream stream) throws IOException { stream.println(); stream.println("NAME"); stream.println(" meta get-ro - Get read-only metadata from nodes and stores"); stream.println(); stream.println("SYNOPSIS"); stream.println(" meta get-ro (<ro-meta-key-list> | all) -s <store-name-list> -u <url>"); stream.println(" [-n <node-id-list> | --all-nodes]"); stream.println(); stream.println("COMMENTS"); stream.println(" Valid read-only meta keys are:"); stream.println(" " + KEY_MAX_VERSION); stream.println(" " + KEY_CURRENT_VERSION); stream.println(" " + KEY_STORAGE_FORMAT); stream.println(); getParser().printHelpOn(stream); stream.println(); } /** * Parses command-line and gets read-only metadata. * * @param args Command-line input * @param printHelp Tells whether to print help only or execute command * actually * @throws IOException */ @SuppressWarnings("unchecked") public static void executeCommand(String[] args) throws IOException { OptionParser parser = getParser(); // declare parameters List<String> metaKeys = null; String url = null; List<Integer> nodeIds = null; Boolean allNodes = true; List<String> storeNames = null; // parse command-line input args = AdminToolUtils.copyArrayAddFirst(args, "--" + OPT_HEAD_META_GET_RO); OptionSet options = parser.parse(args); if(options.has(AdminParserUtils.OPT_HELP)) { printHelp(System.out); return; } // check required options and/or conflicting options AdminParserUtils.checkRequired(options, OPT_HEAD_META_GET_RO); AdminParserUtils.checkRequired(options, AdminParserUtils.OPT_URL); AdminParserUtils.checkOptional(options, AdminParserUtils.OPT_NODE, AdminParserUtils.OPT_ALL_NODES); AdminParserUtils.checkRequired(options, AdminParserUtils.OPT_STORE); // load parameters metaKeys = (List<String>) options.valuesOf(OPT_HEAD_META_GET_RO); url = (String) options.valueOf(AdminParserUtils.OPT_URL); if(options.has(AdminParserUtils.OPT_NODE)) { nodeIds = (List<Integer>) options.valuesOf(AdminParserUtils.OPT_NODE); allNodes = false; } storeNames = (List<String>) options.valuesOf(AdminParserUtils.OPT_STORE); // execute command AdminClient adminClient = AdminToolUtils.getAdminClient(url); if(allNodes) { nodeIds = AdminToolUtils.getAllNodeIds(adminClient); } if(metaKeys.size() == 1 && metaKeys.get(0).equals(METAKEY_ALL)) { metaKeys = Lists.newArrayList(); metaKeys.add(KEY_MAX_VERSION); metaKeys.add(KEY_CURRENT_VERSION); metaKeys.add(KEY_STORAGE_FORMAT); } doMetaGetRO(adminClient, nodeIds, storeNames, metaKeys); } /** * Gets read-only metadata. * * @param adminClient An instance of AdminClient points to given cluster * @param nodeIds Node ids to fetch read-only metadata from * @param storeNames Stores names to fetch read-only metadata from * @param metaKeys List of read-only metadata to fetch * @throws IOException */ public static void doMetaGetRO(AdminClient adminClient, Collection<Integer> nodeIds, List<String> storeNames, List<String> metaKeys) throws IOException { for(String key: metaKeys) { System.out.println("Metadata: " + key); if(!key.equals(KEY_MAX_VERSION) && !key.equals(KEY_CURRENT_VERSION) && !key.equals(KEY_STORAGE_FORMAT)) { System.out.println(" Invalid read-only metadata key: " + key); } else { for(Integer nodeId: nodeIds) { String hostName = adminClient.getAdminClientCluster() .getNodeById(nodeId) .getHost(); System.out.println(" Node: " + hostName + ":" + nodeId); if(key.equals(KEY_MAX_VERSION)) { Map<String, Long> mapStoreToROVersion = adminClient.readonlyOps.getROMaxVersion(nodeId, storeNames); for(String storeName: mapStoreToROVersion.keySet()) { System.out.println(" " + storeName + ":" + mapStoreToROVersion.get(storeName)); } } else if(key.equals(KEY_CURRENT_VERSION)) { Map<String, Long> mapStoreToROVersion = adminClient.readonlyOps.getROCurrentVersion(nodeId, storeNames); for(String storeName: mapStoreToROVersion.keySet()) { System.out.println(" " + storeName + ":" + mapStoreToROVersion.get(storeName)); } } else if(key.equals(KEY_STORAGE_FORMAT)) { Map<String, String> mapStoreToROFormat = adminClient.readonlyOps.getROStorageFormat(nodeId, storeNames); for(String storeName: mapStoreToROFormat.keySet()) { System.out.println(" " + storeName + ":" + mapStoreToROFormat.get(storeName)); } } } } System.out.println(); } } } /** * meta set command */ public static class SubCommandMetaSet extends AbstractAdminCommand { public static final String OPT_HEAD_META_SET = "meta-set"; public static final String KEY_OFFLINE = "offline"; /** * Initializes parser * * @return OptionParser object with all available options */ protected static OptionParser getParser() { OptionParser parser = new OptionParser(); // help options AdminParserUtils.acceptsHelp(parser); // required options parser.accepts(OPT_HEAD_META_SET, "metadata key-file pairs") .withOptionalArg() .describedAs("meta-key>=<meta-file") .withValuesSeparatedBy(',') .ofType(String.class); AdminParserUtils.acceptsUrl(parser); // optional options AdminParserUtils.acceptsNodeMultiple(parser); // either // --node or // --all-nodes AdminParserUtils.acceptsAllNodes(parser); // either --node or // --all-nodes AdminParserUtils.acceptsConfirm(parser); return parser; } /** * Prints help menu for command. * * @param stream PrintStream object for output * @throws IOException */ public static void printHelp(PrintStream stream) throws IOException { stream.println(); stream.println("NAME"); stream.println(" meta set - Set metadata on nodes"); stream.println(); stream.println("SYNOPSIS"); stream.println(" meta set <meta-key>=<meta-value>[,<meta-key2>=<meta-value2>] -u <url>"); stream.println(" [-n <node-id-list> | --all-nodes] [--confirm]"); stream.println(); stream.println("COMMENTS"); stream.println(" To set one metadata, please specify one of the following:"); stream.println(" " + MetadataStore.CLUSTER_KEY + " (meta-value is cluster.xml file path)"); stream.println(" " + MetadataStore.STORES_KEY + " (meta-value is stores.xml file path)"); stream.println(" " + MetadataStore.SLOP_STREAMING_ENABLED_KEY); stream.println(" " + MetadataStore.PARTITION_STREAMING_ENABLED_KEY); stream.println(" " + MetadataStore.READONLY_FETCH_ENABLED_KEY); stream.println(" " + MetadataStore.QUOTA_ENFORCEMENT_ENABLED_KEY); stream.println(" " + MetadataStore.REBALANCING_SOURCE_CLUSTER_XML); stream.println(" " + MetadataStore.REBALANCING_STEAL_INFO); stream.println(" " + KEY_OFFLINE); stream.println(" To set a pair of metadata values, valid meta keys are:"); stream.println(" " + MetadataStore.CLUSTER_KEY + " (meta-value is cluster.xml file path)"); stream.println(" " + MetadataStore.STORES_KEY + " (meta-value is stores.xml file path)"); stream.println(); getParser().printHelpOn(stream); stream.println(); } private static void printMessage(List<String> storeNames, String message, PrintStream stream) { if(storeNames.size() > 0) { stream.println(message + Arrays.toString(storeNames.toArray())); } } private static void printChangeStoreSummary(List<StoreDefinition> oldStoreDefs, List<StoreDefinition> newStoreDefs, PrintStream stream) { Set<String> storeNamesUnion = new HashSet<String>(); Map<String, StoreDefinition> oldStoreDefinitionMap = new HashMap<String, StoreDefinition>(); Map<String, StoreDefinition> newStoreDefinitionMap = new HashMap<String, StoreDefinition>(); List<String> newStores = new ArrayList<String>(); List<String> deletedStores = new ArrayList<String>(); List<String> schemaChangeStores = new ArrayList<String>(); List<String> replicationFactorChangeStores = new ArrayList<String>(); List<String> allOtherChanges = new ArrayList<String>(); List<String> noChanges = new ArrayList<String>(); for(StoreDefinition storeDef: oldStoreDefs) { String storeName = storeDef.getName(); storeNamesUnion.add(storeName); oldStoreDefinitionMap.put(storeName, storeDef); } for(StoreDefinition storeDef: newStoreDefs) { String storeName = storeDef.getName(); storeNamesUnion.add(storeName); newStoreDefinitionMap.put(storeName, storeDef); } for(String storeName: storeNamesUnion) { StoreDefinition oldStoreDef = oldStoreDefinitionMap.get(storeName); StoreDefinition newStoreDef = newStoreDefinitionMap.get(storeName); if(oldStoreDef == null && newStoreDef != null) { newStores.add(newStoreDef.getName()); } else if(oldStoreDef != null && newStoreDef == null) { deletedStores.add(oldStoreDef.getName()); } else if(oldStoreDef.equals(newStoreDef)) { noChanges.add(oldStoreDef.getName()); } else { boolean isRecognizedChange = false; if(!oldStoreDef.getKeySerializer().equals(newStoreDef.getKeySerializer()) || !oldStoreDef.getValueSerializer() .equals(newStoreDef.getValueSerializer())) { schemaChangeStores.add(newStoreDef.getName()); isRecognizedChange = true; } boolean isZoneReplicationChanged = false; Map<Integer,Integer> oldZoneReplication = oldStoreDef.getZoneReplicationFactor(); Map<Integer,Integer> newZoneReplication = newStoreDef.getZoneReplicationFactor(); if((oldZoneReplication == null && newZoneReplication != null) || (oldZoneReplication != null && newZoneReplication == null)) { isZoneReplicationChanged = true; } else if(oldZoneReplication != null && newZoneReplication != null && !oldZoneReplication.equals(newZoneReplication)) { isZoneReplicationChanged = true; } if(oldStoreDef.getReplicationFactor() != newStoreDef.getReplicationFactor() || oldStoreDef.getRequiredReads() != newStoreDef.getRequiredReads() || oldStoreDef.getRequiredWrites() != newStoreDef.getRequiredWrites() || oldStoreDef.getZoneCountReads() != newStoreDef.getZoneCountReads() || oldStoreDef.getZoneCountWrites() != newStoreDef.getZoneCountWrites() || isZoneReplicationChanged) { replicationFactorChangeStores.add(newStoreDef.getName()); isRecognizedChange = true; } if(isRecognizedChange == false) { allOtherChanges.add(newStoreDef.getName()); } } } printMessage(deletedStores, "***WARNING!!!! Use delete store command, this will leave the cluster in an inconsistent state. Deleted Stores ", stream); printMessage(newStores, "***WARNING!!!! Use add store command, this will leave the cluster in an inconsistent state. Added Stores ", stream); printMessage(noChanges, "Unchanged Stores ", stream); printMessage(schemaChangeStores, "Schema modified stores ", stream); printMessage(replicationFactorChangeStores, "Replication Factor Changed Stores ", stream); printMessage(allOtherChanges, "All Other Changes in Stores ", stream); } /** * Parses command-line and sets metadata. * * @param args Command-line input * @param printHelp Tells whether to print help only or execute command * actually * @throws Exception * */ @SuppressWarnings("unchecked") public static void executeCommand(String[] args) throws Exception { OptionParser parser = getParser(); // declare parameters List<String> meta = null; String url = null; List<Integer> nodeIds = null; Boolean allNodes = true; Boolean confirm = false; // parse command-line input args = AdminToolUtils.copyArrayAddFirst(args, "--" + OPT_HEAD_META_SET); OptionSet options = parser.parse(args); if(options.has(AdminParserUtils.OPT_HELP)) { printHelp(System.out); return; } // check required options and/or conflicting options AdminParserUtils.checkRequired(options, OPT_HEAD_META_SET); AdminParserUtils.checkRequired(options, AdminParserUtils.OPT_URL); AdminParserUtils.checkOptional(options, AdminParserUtils.OPT_NODE, AdminParserUtils.OPT_ALL_NODES); // load parameters meta = AdminToolUtils.getValueList((List<String>) options.valuesOf(OPT_HEAD_META_SET), "="); if(meta.size() != 2 && meta.size() != 4) { throw new VoldemortException("Invalid metakey-metafile pairs."); } url = (String) options.valueOf(AdminParserUtils.OPT_URL); if(options.has(AdminParserUtils.OPT_NODE)) { nodeIds = (List<Integer>) options.valuesOf(AdminParserUtils.OPT_NODE); allNodes = false; } if(options.has(AdminParserUtils.OPT_CONFIRM)) { confirm = true; } // print summary System.out.println("Set metadata"); System.out.println("Metadata:"); for(Integer i = 0; i < meta.size(); i += 2) { System.out.println(" set \'" + meta.get(i) + "\' from file \'" + meta.get(i + 1) + "\'"); } System.out.println("Location:"); System.out.println(" bootstrap url = " + url); if(allNodes) { System.out.println(" node = all nodes"); } else { System.out.println(" node = " + Joiner.on(", ").join(nodeIds)); } AdminClient adminClient = AdminToolUtils.getAdminClient(url); if(allNodes) { nodeIds = AdminToolUtils.getAllNodeIds(adminClient); } AdminToolUtils.assertServerNotInRebalancingState(adminClient, nodeIds); if(meta.size() == 2) { String metaKey = meta.get(0), metaValue = meta.get(1); String metaFile = metaValue.replace("~", System.getProperty("user.home")); if(metaKey.equals(MetadataStore.STORES_KEY)) { if(!Utils.isReadableFile(metaFile)) { throw new VoldemortException("Stores definition xml file path incorrect"); } StoreDefinitionsMapper mapper = new StoreDefinitionsMapper(); List<StoreDefinition> newStoreDefs = mapper.readStoreList(new File(metaFile)); StoreDefinitionUtils.validateSchemasAsNeeded(newStoreDefs); // original metadata Integer nodeIdToGetStoreXMLFrom = nodeIds.iterator().next(); Versioned<String> storesXML = adminClient.metadataMgmtOps.getRemoteMetadata(nodeIdToGetStoreXMLFrom, MetadataStore.STORES_KEY); List<StoreDefinition> oldStoreDefs = mapper.readStoreList(new StringReader(storesXML.getValue())); printChangeStoreSummary(oldStoreDefs, newStoreDefs, System.err); if(!AdminToolUtils.askConfirm(confirm, "set metadata")) { return; } // execute command doMetaSet(adminClient, nodeIds, metaKey, mapper.writeStoreList(newStoreDefs)); if(!allNodes) { System.err.println("WARNING: Metadata version update of stores goes to all servers, " + "although this set-metadata oprations only goes to node: "); for(Integer nodeId: nodeIds) { System.err.println(nodeId); } } doMetaUpdateVersionsOnStores(adminClient, oldStoreDefs, newStoreDefs); } else { // execute command if(!AdminToolUtils.askConfirm(confirm, "set metadata")) { return; } if(metaKey.equals(MetadataStore.CLUSTER_KEY) || metaKey.equals(MetadataStore.REBALANCING_SOURCE_CLUSTER_XML)) { if(!Utils.isReadableFile(metaFile)) { throw new VoldemortException("Cluster xml file path incorrect"); } ClusterMapper mapper = new ClusterMapper(); Cluster newCluster = mapper.readCluster(new File(metaFile)); doMetaSet(adminClient, nodeIds, metaKey, mapper.writeCluster(newCluster)); } else if(metaKey.equals(MetadataStore.SLOP_STREAMING_ENABLED_KEY) || metaKey.equals(MetadataStore.PARTITION_STREAMING_ENABLED_KEY) || metaKey.equals(MetadataStore.READONLY_FETCH_ENABLED_KEY) || metaKey.equals(MetadataStore.QUOTA_ENFORCEMENT_ENABLED_KEY)) { doMetaSet(adminClient, nodeIds, metaKey, metaValue); } else if(metaKey.equals(KEY_OFFLINE)) { boolean setOffline = Boolean.parseBoolean(metaValue); if(setOffline && nodeIds.size() > 1) { throw new VoldemortException("Setting more than one node to offline is not allowed."); } for(Integer nodeId: nodeIds) { adminClient.metadataMgmtOps.setRemoteOfflineState(nodeId, setOffline); } } else if(metaKey.equals(MetadataStore.REBALANCING_STEAL_INFO)) { if(!Utils.isReadableFile(metaFile)) { throw new VoldemortException("Rebalancing steal info file path incorrect"); } String rebalancingStealInfoJsonString = FileUtils.readFileToString(new File(metaFile)); RebalancerState state = RebalancerState.create(rebalancingStealInfoJsonString); doMetaSet(adminClient, nodeIds, metaKey, state.toJsonString()); } else { throw new VoldemortException("Incorrect metadata key"); } } } else if(meta.size() == 4) { // set metadata pair cluster.xml, stores.xml String clusterFile, storesFile; if(meta.get(0).equals(MetadataStore.CLUSTER_KEY) && meta.get(2).equals(MetadataStore.STORES_KEY)) { clusterFile = meta.get(1); storesFile = meta.get(3); } else if(meta.get(0).equals(MetadataStore.STORES_KEY) && meta.get(2).equals(MetadataStore.CLUSTER_KEY)) { storesFile = meta.get(1); clusterFile = meta.get(3); } else { throw new VoldemortException("meta set-pair keys must be <cluster.xml, stores.xml>"); } clusterFile = clusterFile.replace("~", System.getProperty("user.home")); storesFile = storesFile.replace("~", System.getProperty("user.home")); ClusterMapper clusterMapper = new ClusterMapper(); StoreDefinitionsMapper storeDefsMapper = new StoreDefinitionsMapper(); // original metadata Integer nodeIdToGetStoreXMLFrom = nodeIds.iterator().next(); Versioned<String> storesXML = adminClient.metadataMgmtOps.getRemoteMetadata(nodeIdToGetStoreXMLFrom, MetadataStore.STORES_KEY); List<StoreDefinition> oldStoreDefs = storeDefsMapper.readStoreList(new StringReader(storesXML.getValue())); if(!Utils.isReadableFile(clusterFile)) { throw new VoldemortException("Cluster xml file path incorrect"); } Cluster cluster = clusterMapper.readCluster(new File(clusterFile)); if(!Utils.isReadableFile(storesFile)) { throw new VoldemortException("Stores definition xml file path incorrect"); } List<StoreDefinition> newStoreDefs = storeDefsMapper.readStoreList(new File(storesFile)); StoreDefinitionUtils.validateSchemasAsNeeded(newStoreDefs); printChangeStoreSummary(oldStoreDefs, newStoreDefs, System.err); if(!AdminToolUtils.askConfirm(confirm, "set metadata")) { return; } // execute command doMetaSetPair(adminClient, nodeIds, clusterMapper.writeCluster(cluster), storeDefsMapper.writeStoreList(newStoreDefs)); if(!allNodes) { System.err.println("WARNING: Metadata version update of stores goes to all servers, " + "although this set-metadata oprations only goes to node: "); for(Integer nodeId: nodeIds) { System.err.println(nodeId); } } doMetaUpdateVersionsOnStores(adminClient, oldStoreDefs, newStoreDefs); } } /** * Sets <cluster.xml,stores.xml> metadata pair atomically. * * @param adminClient An instance of AdminClient points to given cluster * @param nodeIds Node ids to set metadata * @param clusterValue Cluster value to set * @param storesValue Stores value to set */ public static void doMetaSetPair(AdminClient adminClient, List<Integer> nodeIds, Object clusterValue, Object storesValue) { VectorClock updatedClusterVersion = null; VectorClock updatedStoresVersion = null; for(Integer nodeId: nodeIds) { if(updatedClusterVersion == null && updatedStoresVersion == null) { updatedClusterVersion = (VectorClock) adminClient.metadataMgmtOps.getRemoteMetadata(nodeId, MetadataStore.CLUSTER_KEY) .getVersion(); updatedStoresVersion = (VectorClock) adminClient.metadataMgmtOps.getRemoteMetadata(nodeId, MetadataStore.STORES_KEY) .getVersion(); } else { updatedClusterVersion = updatedClusterVersion.merge((VectorClock) adminClient.metadataMgmtOps.getRemoteMetadata(nodeId, MetadataStore.CLUSTER_KEY) .getVersion()); updatedStoresVersion = updatedStoresVersion.merge((VectorClock) adminClient.metadataMgmtOps.getRemoteMetadata(nodeId, MetadataStore.STORES_KEY) .getVersion()); } // TODO: This will work for now but we should take a step back // and // think about a uniform clock for the metadata values. updatedClusterVersion = updatedClusterVersion.incremented(nodeIds.iterator().next(), System.currentTimeMillis()); updatedStoresVersion = updatedStoresVersion.incremented(nodeIds.iterator().next(), System.currentTimeMillis()); } adminClient.metadataMgmtOps.updateRemoteMetadataPair(nodeIds, MetadataStore.CLUSTER_KEY, Versioned.value(clusterValue.toString(), updatedClusterVersion), MetadataStore.STORES_KEY, Versioned.value(storesValue.toString(), updatedStoresVersion)); } /** * Updates metadata versions on stores. * * @param adminClient An instance of AdminClient points to given cluster * @param oldStoreDefs List of old store definitions * @param newStoreDefs List of new store definitions */ public static void doMetaUpdateVersionsOnStores(AdminClient adminClient, List<StoreDefinition> oldStoreDefs, List<StoreDefinition> newStoreDefs) { Set<String> storeNamesUnion = new HashSet<String>(); Map<String, StoreDefinition> oldStoreDefinitionMap = new HashMap<String, StoreDefinition>(); Map<String, StoreDefinition> newStoreDefinitionMap = new HashMap<String, StoreDefinition>(); List<String> storesChanged = new ArrayList<String>(); for(StoreDefinition storeDef: oldStoreDefs) { String storeName = storeDef.getName(); storeNamesUnion.add(storeName); oldStoreDefinitionMap.put(storeName, storeDef); } for(StoreDefinition storeDef: newStoreDefs) { String storeName = storeDef.getName(); storeNamesUnion.add(storeName); newStoreDefinitionMap.put(storeName, storeDef); } for(String storeName: storeNamesUnion) { StoreDefinition oldStoreDef = oldStoreDefinitionMap.get(storeName); StoreDefinition newStoreDef = newStoreDefinitionMap.get(storeName); if(oldStoreDef == null && newStoreDef != null || oldStoreDef != null && newStoreDef == null || oldStoreDef != null && newStoreDef != null && !oldStoreDef.equals(newStoreDef)) { storesChanged.add(storeName); } } System.out.println("Updating metadata version for the following stores: " + storesChanged); try { adminClient.metadataMgmtOps.updateMetadataversion(adminClient.getAdminClientCluster() .getNodeIds(), storesChanged); } catch(Exception e) { System.err.println("Error while updating metadata version for the specified store."); } } } /** * meta sync-version command */ public static class SubCommandMetaSyncVersion extends AbstractAdminCommand { /** * Initializes parser * * @return OptionParser object with all available options */ protected static OptionParser getParser() { OptionParser parser = new OptionParser(); // help options AdminParserUtils.acceptsHelp(parser); AdminParserUtils.acceptsUrl(parser); // optional options AdminParserUtils.acceptsConfirm(parser); return parser; } /** * Prints help menu for command. * * @param stream PrintStream object for output * @throws IOException */ public static void printHelp(PrintStream stream) throws IOException { stream.println(); stream.println("NAME"); stream.println(" meta sync-version - Synchronize metadata versions across all nodes"); stream.println(); stream.println("SYNOPSIS"); stream.println(" meta sync-version -u <url> [--confirm]"); stream.println(); getParser().printHelpOn(stream); stream.println(); } /** * Parses command-line and synchronizes metadata versions across all * nodes. * * @param args Command-line input * @param printHelp Tells whether to print help only or execute command * actually * @throws IOException * */ public static void executeCommand(String[] args) throws IOException { OptionParser parser = getParser(); String url = null; Boolean confirm = false; // parse command-line input OptionSet options = parser.parse(args); if(options.has(AdminParserUtils.OPT_HELP)) { printHelp(System.out); return; } AdminParserUtils.checkRequired(options, AdminParserUtils.OPT_URL); url = (String) options.valueOf(AdminParserUtils.OPT_URL); if(options.has(AdminParserUtils.OPT_CONFIRM)) { confirm = true; } // print summary System.out.println("Synchronize metadata versions across all nodes"); System.out.println("Location:"); System.out.println(" bootstrap url = " + url); System.out.println(" node = all nodes"); AdminClient adminClient = AdminToolUtils.getAdminClient(url); AdminToolUtils.assertServerNotInRebalancingState(adminClient); Versioned<Properties> versionedProps = mergeAllVersions(adminClient); printVersions(versionedProps); // execute command if(!AdminToolUtils.askConfirm(confirm, "do you want to synchronize metadata versions to all node")) return; adminClient.metadataMgmtOps.setMetadataVersion(versionedProps); } private static void printVersions(Versioned<Properties> versionedProps) { Version version = versionedProps.getVersion(); Properties props = versionedProps.getValue(); System.out.println("Version value " + version); for(String propName: props.stringPropertyNames()) { SubCommandMetaCheckVersion.printProperty(propName, props.getProperty(propName)); } } private static Versioned<Properties> mergeAllVersions(AdminClient adminClient) { Properties props = new Properties(); VectorClock version = new VectorClock(); for(Integer nodeId: adminClient.getAdminClientCluster().getNodeIds()) { Versioned<Properties> versionedProp = doMetaGetVersionsForNode_ExitOnError(adminClient, nodeId); Properties newProps = versionedProp.getValue(); VectorClock newVersion = (VectorClock) versionedProp.getVersion(); version = version.merge(newVersion); props = MetadataVersionStoreUtils.mergeVersions(props, newProps); } return new Versioned<Properties>(props, version); } } /** * meta check-version command */ public static class SubCommandMetaCheckVersion extends AbstractAdminCommand { /** * Initializes parser * * @return OptionParser object with all available options */ protected static OptionParser getParser() { OptionParser parser = new OptionParser(); // help options AdminParserUtils.acceptsHelp(parser); // required options AdminParserUtils.acceptsUrl(parser); return parser; } /** * Prints help menu for command. * * @param stream PrintStream object for output * @throws IOException */ public static void printHelp(PrintStream stream) throws IOException { stream.println(); stream.println("NAME"); stream.println(" meta check-version - Verify metadata versions on all the cluster nodes"); stream.println(); stream.println("SYNOPSIS"); stream.println(" meta check-version -u <url>"); stream.println(); getParser().printHelpOn(stream); stream.println(); } /** * Parses command-line and verifies metadata versions on all the cluster * nodes * * @param args Command-line input * @param printHelp Tells whether to print help only or execute command * actually * @throws IOException * */ public static void executeCommand(String[] args) throws IOException { OptionParser parser = getParser(); // declare parameters String url = null; // parse command-line input OptionSet options = parser.parse(args); if(options.has(AdminParserUtils.OPT_HELP)) { printHelp(System.out); return; } // check required options and/or conflicting options AdminParserUtils.checkRequired(options, AdminParserUtils.OPT_URL); // load parameters url = (String) options.valueOf(AdminParserUtils.OPT_URL); // execute command AdminClient adminClient = AdminToolUtils.getAdminClient(url); doMetaCheckVersion(adminClient); } public static Long tryParse(String s) { try { return Long.parseLong(s); } catch(NumberFormatException e) { return -1L; } } private static void printProperty(String propName, String propValue) { System.out.print(propName + "=" + propValue); Long lValue = tryParse(propValue); if(lValue > 0) { Date date = new Date(lValue); System.out.print(" [ " + date.toString() + " ] "); } System.out.println(); } private static void printProperty(String propName, String propValue, List<Integer> nodes) { System.out.println("**************************** Node(s): " + Arrays.toString(nodes.toArray()) + " ****************************"); printProperty(propName, propValue); } private static void printProperties(Properties props) { for(String propName: props.stringPropertyNames()) { String propValue = props.getProperty(propName); printProperty(propName, propValue); } System.out.println(); } /** * Verifies metadata versions for all the cluster nodes * * @param adminClient An instance of AdminClient points to given cluster * */ public static void doMetaCheckVersion(AdminClient adminClient) { Map<Properties, List<Integer>> versionsNodeMap = new HashMap<Properties, List<Integer>>(); for(Integer nodeId: adminClient.getAdminClientCluster().getNodeIds()) { Versioned<Properties> versionedProp = doMetaGetVersionsForNode_ExitOnError(adminClient, nodeId); Properties props = versionedProp.getValue(); if(versionsNodeMap.containsKey(props) == false) { versionsNodeMap.put(props, new ArrayList<Integer>()); } versionsNodeMap.get(props).add(nodeId); } if(versionsNodeMap.keySet().size() <= 0) { System.err.println("No Versions found in the system store... something seriously wrong"); } else if(versionsNodeMap.keySet().size() == 1) { System.err.println("All the nodes have the same metadata versions."); printProperties(versionsNodeMap.keySet().iterator().next()); } else { System.err.println("Mismatching versions detected !!! . All are supposed to be written by the same client " + "" + " and hence they should exactly match but something different, let us analyze deeper "); Map<String, Map<String, List<Integer>>> propertyValueMap = new HashMap<String, Map<String, List<Integer>>>(); for(Entry<Properties, List<Integer>> entry: versionsNodeMap.entrySet()) { System.out.println("**************************** Node(s): " + Arrays.toString(entry.getValue().toArray()) + " ****************************"); Properties props = entry.getKey(); printProperties(props); for(String propName: props.stringPropertyNames()) { String propValue = props.getProperty(propName); if(propertyValueMap.containsKey(propName) == false) { propertyValueMap.put(propName, new HashMap<String, List<Integer>>()); } Map<String, List<Integer>> valuetoNodeMap = propertyValueMap.get(propName); if(valuetoNodeMap.containsKey(propValue) == false) { valuetoNodeMap.put(propValue, new ArrayList<Integer>()); } valuetoNodeMap.get(propValue).addAll(entry.getValue()); } } System.out.println("########## Properties discrepancy report ########"); for(Entry<String, Map<String, List<Integer>>> entry: propertyValueMap.entrySet()) { Map<String, List<Integer>> valueToNodeMap = entry.getValue(); String propName = entry.getKey(); List<Integer> allNodeIds = new ArrayList<Integer>(); allNodeIds.addAll(adminClient.getAdminClientCluster().getNodeIds()); List<Integer> nodesWithValues = new ArrayList<Integer>(); if(valueToNodeMap.size() != 1) { System.out.println("Properties with multiple values"); for(Entry<String, List<Integer>> valueToNodeEntry: valueToNodeMap .entrySet()) { String propValue = valueToNodeEntry.getKey(); nodesWithValues.addAll(valueToNodeEntry.getValue()); printProperty(propName, propValue, valueToNodeEntry.getValue()); } } else { Map.Entry<String, List<Integer>> valueToNodeEntry = valueToNodeMap.entrySet() .iterator() .next(); nodesWithValues.addAll(valueToNodeEntry.getValue()); if(nodesWithValues.size() < allNodeIds.size()) { String propValue = valueToNodeEntry.getKey(); printProperty(propName, propValue, valueToNodeEntry.getValue()); } } allNodeIds.removeAll(nodesWithValues); if(allNodeIds.size() > 0) { System.out.println("The Property " + propName + " is present in the nodes " + Arrays.toString(nodesWithValues.toArray()) + " but missing from the nodes " + Arrays.toString(allNodeIds.toArray())); } } } } } /** * Sets metadata. * * @param adminClient An instance of AdminClient points to given cluster * @param nodeIds Node ids to set metadata * @param metaKey Metadata key to set * @param metaValue Metadata value to set */ public static void doMetaSet(AdminClient adminClient, List<Integer> nodeIds, String metaKey, String metaValue) { adminClient.metadataMgmtOps.updateRemoteMetadata(nodeIds, metaKey, metaValue); } private static Versioned<Properties> doMetaGetVersionsForNode_ExitOnError(AdminClient adminClient, Integer nodeId) { try { return doMetaGetVersionsForNode(adminClient, nodeId); } catch(IOException e) { System.err.println("Error while retrieving Metadata versions from node : " + nodeId + ". Exception = " + e.getMessage() + " \n"); e.printStackTrace(); System.exit(-1); return null; } } /** * Gets metadata versions for a given node. * * @param adminClient An instance of AdminClient points to given cluster * @param nodeId Node id to get metadata version from */ private static Versioned<Properties> doMetaGetVersionsForNode(AdminClient adminClient, Integer nodeId) throws IOException { ByteArray keyArray = new ByteArray(SystemStoreConstants.VERSIONS_METADATA_KEY.getBytes("UTF8")); List<Versioned<byte[]>> valueObj = adminClient.storeOps.getNodeKey(SystemStoreConstants.SystemStoreName.voldsys$_metadata_version_persistence.name(), nodeId, keyArray); if(valueObj.size() != 1) { throw new IOException("Expected one value for the key " + SystemStoreConstants.VERSIONS_METADATA_KEY + " on node id " + nodeId + " but found " + valueObj.size()); } System.out.println(" Node : " + nodeId + " Version : " + valueObj.get(0).getVersion()); return MetadataVersionStoreUtils.parseProperties(valueObj); } }