/* * fdbcli.actor.cpp * * This source file is part of the FoundationDB open source project * * Copyright 2013-2018 Apple Inc. and the FoundationDB project authors * * 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. */ #include "boost/lexical_cast.hpp" #include "fdbclient/NativeAPI.actor.h" #include "fdbclient/FDBTypes.h" #include "fdbclient/IClientApi.h" #include "fdbclient/MultiVersionTransaction.h" #include "fdbclient/Status.h" #include "fdbclient/KeyBackedTypes.h" #include "fdbclient/StatusClient.h" #include "fdbclient/DatabaseContext.h" #include "fdbclient/GlobalConfig.actor.h" #include "fdbclient/IKnobCollection.h" #include "fdbclient/NativeAPI.actor.h" #include "fdbclient/ReadYourWrites.h" #include "fdbclient/ClusterInterface.h" #include "fdbclient/ManagementAPI.actor.h" #include "fdbclient/Schemas.h" #include "fdbclient/CoordinationInterface.h" #include "fdbclient/FDBOptions.g.h" #include "fdbclient/TagThrottle.actor.h" #include "fdbclient/Tuple.h" #include "fdbclient/ThreadSafeTransaction.h" #include "flow/DeterministicRandom.h" #include "flow/Platform.h" #include "flow/TLSConfig.actor.h" #include "flow/ThreadHelper.actor.h" #include "flow/SimpleOpt.h" #include "fdbcli/FlowLineNoise.h" #include "fdbcli/fdbcli.actor.h" #include #include #include #ifdef __unixish__ #include #include "fdbcli/linenoise/linenoise.h" #endif #include "fdbclient/versions.h" #include "fdbclient/BuildFlags.h" #include "flow/actorcompiler.h" // This must be the last #include. #define FDB_API_VERSION 710 /* * While we could just use the MultiVersionApi instance directly, this #define allows us to swap in any other IClientApi * instance (e.g. from ThreadSafeApi) */ #define API ((IClientApi*)MultiVersionApi::api) extern const char* getSourceVersion(); std::vector validOptions; enum { OPT_CONNFILE, OPT_DATABASE, OPT_HELP, OPT_TRACE, OPT_TRACE_DIR, OPT_TIMEOUT, OPT_EXEC, OPT_NO_STATUS, OPT_NO_HINTS, OPT_STATUS_FROM_JSON, OPT_VERSION, OPT_BUILD_FLAGS, OPT_TRACE_FORMAT, OPT_KNOB, OPT_DEBUG_TLS }; CSimpleOpt::SOption g_rgOptions[] = { { OPT_CONNFILE, "-C", SO_REQ_SEP }, { OPT_CONNFILE, "--cluster_file", SO_REQ_SEP }, { OPT_DATABASE, "-d", SO_REQ_SEP }, { OPT_TRACE, "--log", SO_NONE }, { OPT_TRACE_DIR, "--log-dir", SO_REQ_SEP }, { OPT_TIMEOUT, "--timeout", SO_REQ_SEP }, { OPT_EXEC, "--exec", SO_REQ_SEP }, { OPT_NO_STATUS, "--no-status", SO_NONE }, { OPT_NO_HINTS, "--no-hints", SO_NONE }, { OPT_HELP, "-?", SO_NONE }, { OPT_HELP, "-h", SO_NONE }, { OPT_HELP, "--help", SO_NONE }, { OPT_STATUS_FROM_JSON, "--status-from-json", SO_REQ_SEP }, { OPT_VERSION, "--version", SO_NONE }, { OPT_VERSION, "-v", SO_NONE }, { OPT_BUILD_FLAGS, "--build_flags", SO_NONE }, { OPT_TRACE_FORMAT, "--trace_format", SO_REQ_SEP }, { OPT_KNOB, "--knob_", SO_REQ_SEP }, { OPT_DEBUG_TLS, "--debug-tls", SO_NONE }, #ifndef TLS_DISABLED TLS_OPTION_FLAGS #endif SO_END_OF_OPTIONS }; void printAtCol(const char* text, int col) { const char* iter = text; const char* start = text; const char* space = nullptr; do { iter++; if (*iter == '\n' || *iter == ' ' || *iter == '\0') space = iter; if (*iter == '\n' || *iter == '\0' || (iter - start == col)) { if (!space) space = iter; printf("%.*s\n", (int)(space - start), start); start = space; if (*start == ' ' || *start == '\n') start++; space = nullptr; } } while (*iter); } std::string lineWrap(const char* text, int col) { const char* iter = text; const char* start = text; const char* space = nullptr; std::string out = ""; do { iter++; if (*iter == '\n' || *iter == ' ' || *iter == '\0') space = iter; if (*iter == '\n' || *iter == '\0' || (iter - start == col)) { if (!space) space = iter; out += format("%.*s\n", (int)(space - start), start); start = space; if (*start == ' ' /* || *start == '\n'*/) start++; space = nullptr; } } while (*iter); return out; } class FdbOptions { public: // Prints an error and throws invalid_option or invalid_option_value if the option could not be set void setOption(Reference tr, StringRef optionStr, bool enabled, Optional arg, bool intrans) { auto transactionItr = transactionOptions.legalOptions.find(optionStr.toString()); if (transactionItr != transactionOptions.legalOptions.end()) setTransactionOption(tr, transactionItr->second, enabled, arg, intrans); else { fprintf(stderr, "ERROR: invalid option '%s'. Try `help options' for a list of available options.\n", optionStr.toString().c_str()); throw invalid_option(); } } // Applies all enabled transaction options to the given transaction void apply(Reference tr) { for (const auto& [name, value] : transactionOptions.options) { tr->setOption(name, value.castTo()); } } // TODO: replace the above function after we refactor all fdbcli code void apply(Reference tr) { for (const auto& [name, value] : transactionOptions.options) { tr->setOption(name, value.castTo()); } } // Returns true if any options have been set bool hasAnyOptionsEnabled() const { return !transactionOptions.options.empty(); } // Prints a list of enabled options, along with their parameters (if any) void print() const { bool found = false; found = found || transactionOptions.print(); if (!found) printf("There are no options enabled\n"); } // Returns a vector of the names of all documented options std::vector getValidOptions() const { return transactionOptions.getValidOptions(); } // Prints the help string obtained by invoking `help options' void printHelpString() const { transactionOptions.printHelpString(); } private: // Sets a transaction option. If intrans == true, then this option is also applied to the passed in transaction. void setTransactionOption(Reference tr, FDBTransactionOptions::Option option, bool enabled, Optional arg, bool intrans) { if (enabled && arg.present() != FDBTransactionOptions::optionInfo.getMustExist(option).hasParameter) { fprintf(stderr, "ERROR: option %s a parameter\n", arg.present() ? "did not expect" : "expected"); throw invalid_option_value(); } if (intrans) tr->setOption(option, arg); transactionOptions.setOption(option, enabled, arg.castTo()); } // A group of enabled options (of type T::Option) as well as a legal options map from string to T::Option template struct OptionGroup { std::map>> options; std::map legalOptions; OptionGroup() {} OptionGroup(OptionGroup& base) : options(base.options.begin(), base.options.end()), legalOptions(base.legalOptions) {} // Enable or disable an option. Returns true if option value changed bool setOption(typename T::Option option, bool enabled, Optional arg) { auto optionItr = options.find(option); if (enabled && (optionItr == options.end() || Optional>(optionItr->second).castTo() != arg)) { options[option] = arg.castTo>(); return true; } else if (!enabled && optionItr != options.end()) { options.erase(optionItr); return true; } return false; } // Prints a list of all enabled options in this group bool print() const { bool found = false; for (auto itr = legalOptions.begin(); itr != legalOptions.end(); ++itr) { auto optionItr = options.find(itr->second); if (optionItr != options.end()) { if (optionItr->second.present()) printf("%s: `%s'\n", itr->first.c_str(), formatStringRef(optionItr->second.get()).c_str()); else printf("%s\n", itr->first.c_str()); found = true; } } return found; } // Returns true if the specified option is documented bool isDocumented(typename T::Option option) const { FDBOptionInfo info = T::optionInfo.getMustExist(option); std::string deprecatedStr = "Deprecated"; return !info.comment.empty() && info.comment.substr(0, deprecatedStr.size()) != deprecatedStr; } // Returns a vector of the names of all documented options std::vector getValidOptions() const { std::vector ret; for (auto itr = legalOptions.begin(); itr != legalOptions.end(); ++itr) if (isDocumented(itr->second)) ret.push_back(itr->first); return ret; } // Prints a help string for each option in this group. Any options with no comment // are excluded from this help string. Lines are wrapped to 80 characters. void printHelpString() const { for (auto itr = legalOptions.begin(); itr != legalOptions.end(); ++itr) { if (isDocumented(itr->second)) { FDBOptionInfo info = T::optionInfo.getMustExist(itr->second); std::string helpStr = info.name + " - " + info.comment; if (info.hasParameter) helpStr += " " + info.parameterComment; helpStr += "\n"; printAtCol(helpStr.c_str(), 80); } } } }; OptionGroup transactionOptions; public: FdbOptions() { for (auto itr = FDBTransactionOptions::optionInfo.begin(); itr != FDBTransactionOptions::optionInfo.end(); ++itr) transactionOptions.legalOptions[itr->second.name] = itr->first; } FdbOptions(FdbOptions& base) : transactionOptions(base.transactionOptions) {} }; static std::string formatStringRef(StringRef item, bool fullEscaping = false) { std::string ret; for (int i = 0; i < item.size(); i++) { if (fullEscaping && item[i] == '\\') ret += "\\\\"; else if (fullEscaping && item[i] == '"') ret += "\\\""; else if (fullEscaping && item[i] == ' ') ret += format("\\x%02x", item[i]); else if (item[i] >= 32 && item[i] < 127) ret += item[i]; else ret += format("\\x%02x", item[i]); } return ret; } static std::vector> parseLine(std::string& line, bool& err, bool& partial) { err = false; partial = false; bool quoted = false; std::vector buf; std::vector> ret; size_t i = line.find_first_not_of(' '); size_t offset = i; bool forcetoken = false; while (i <= line.length()) { switch (line[i]) { case ';': if (!quoted) { if (i > offset || (forcetoken && i == offset)) buf.push_back(StringRef((uint8_t*)(line.data() + offset), i - offset)); ret.push_back(std::move(buf)); offset = i = line.find_first_not_of(' ', i + 1); forcetoken = false; } else i++; break; case '"': quoted = !quoted; line.erase(i, 1); forcetoken = true; break; case ' ': if (!quoted) { if (i > offset || (forcetoken && i == offset)) buf.push_back(StringRef((uint8_t*)(line.data() + offset), i - offset)); offset = i = line.find_first_not_of(' ', i); forcetoken = false; } else i++; break; case '\\': if (i + 2 > line.length()) { err = true; ret.push_back(std::move(buf)); return ret; } switch (line[i + 1]) { char ent, save; case '"': case '\\': case ' ': case ';': line.erase(i, 1); break; case 'x': if (i + 4 > line.length()) { err = true; ret.push_back(std::move(buf)); return ret; } char* pEnd; save = line[i + 4]; line[i + 4] = 0; ent = char(strtoul(line.data() + i + 2, &pEnd, 16)); if (*pEnd) { err = true; ret.push_back(std::move(buf)); return ret; } line[i + 4] = save; line.replace(i, 4, 1, ent); break; default: err = true; ret.push_back(std::move(buf)); return ret; } default: i++; } } i -= 1; if (i > offset || (forcetoken && i == offset)) buf.push_back(StringRef((uint8_t*)(line.data() + offset), i - offset)); ret.push_back(std::move(buf)); if (quoted) partial = true; return ret; } static void printProgramUsage(const char* name) { printf("FoundationDB CLI " FDB_VT_PACKAGE_NAME " (v" FDB_VT_VERSION ")\n" "usage: %s [OPTIONS]\n" "\n", name); printf(" -C CONNFILE The path of a file containing the connection string for the\n" " FoundationDB cluster. The default is first the value of the\n" " FDB_CLUSTER_FILE environment variable, then `./fdb.cluster',\n" " then `%s'.\n", platform::getDefaultClusterFilePath().c_str()); printf(" --log Enables trace file logging for the CLI session.\n" " --log-dir PATH Specifes the output directory for trace files. If\n" " unspecified, defaults to the current directory. Has\n" " no effect unless --log is specified.\n" " --trace_format FORMAT\n" " Select the format of the log files. xml (the default) and json\n" " are supported. Has no effect unless --log is specified.\n" " --exec CMDS Immediately executes the semicolon separated CLI commands\n" " and then exits.\n" " --no-status Disables the initial status check done when starting\n" " the CLI.\n" #ifndef TLS_DISABLED TLS_HELP #endif " --knob_KNOBNAME KNOBVALUE\n" " Changes a knob option. KNOBNAME should be lowercase.\n" " --debug-tls Prints the TLS configuration and certificate chain, then exits.\n" " Useful in reporting and diagnosing TLS issues.\n" " --build_flags Print build information and exit.\n" " -v, --version Print FoundationDB CLI version information and exit.\n" " -h, --help Display this help and exit.\n"); } #define ESCAPINGK "\n\nFor information on escaping keys, type `help escaping'." #define ESCAPINGKV "\n\nFor information on escaping keys and values, type `help escaping'." using namespace fdb_cli; std::map& helpMap = CommandFactory::commands(); std::set& hiddenCommands = CommandFactory::hiddenCommands(); void initHelp() { helpMap["begin"] = CommandHelp("begin", "begin a new transaction", "By default, the fdbcli operates in autocommit mode. All operations are performed in their own " "transaction, and are automatically committed for you. By explicitly beginning a transaction, " "successive operations are all performed as part of a single transaction.\n\nTo commit the " "transaction, use the commit command. To discard the transaction, use the reset command."); helpMap["commit"] = CommandHelp("commit", "commit the current transaction", "Any sets or clears executed after the start of the current transaction will be " "committed to the database. On success, the committed version number is displayed. " "If commit fails, the error is displayed and the transaction must be retried."); helpMap["clear"] = CommandHelp( "clear ", "clear a key from the database", "Clear succeeds even if the specified key is not present, but may fail because of conflicts." ESCAPINGK); helpMap["clearrange"] = CommandHelp( "clearrange ", "clear a range of keys from the database", "All keys between BEGINKEY (inclusive) and ENDKEY (exclusive) are cleared from the database. This command will " "succeed even if the specified range is empty, but may fail because of conflicts." ESCAPINGK); helpMap["configure"] = CommandHelp( "configure [new|tss]" "|" "commit_proxies=|grv_proxies=|logs=|resolvers=>*|" "count=|perpetual_storage_wiggle=", "change the database configuration", "The `new' option, if present, initializes a new database with the given configuration rather than changing " "the configuration of an existing one. When used, both a redundancy mode and a storage engine must be " "specified.\n\ntss: when enabled, configures the testing storage server for the cluster instead." "When used with new to set up tss for the first time, it requires both a count and a storage engine." "To disable the testing storage server, run \"configure tss count=0\"\n\n" "Redundancy mode:\n single - one copy of the data. Not fault tolerant.\n double - two copies " "of data (survive one failure).\n triple - three copies of data (survive two failures).\n three_data_hall - " "See the Admin Guide.\n three_datacenter - See the Admin Guide.\n\nStorage engine:\n ssd - B-Tree storage " "engine optimized for solid state disks.\n memory - Durable in-memory storage engine for small " "datasets.\n\nproxies=: Sets the desired number of proxies in the cluster. The proxy role is being " "deprecated and split into GRV proxy and Commit proxy, now prefer configure 'grv_proxies' and 'commit_proxies' " "separately. Generally we should follow that 'commit_proxies' is three times of 'grv_proxies' and " "'grv_proxies' " "should be not more than 4. If 'proxies' is specified, it will be converted to 'grv_proxies' and " "'commit_proxies'. " "Must be at least 2 (1 GRV proxy, 1 Commit proxy), or set to -1 which restores the number of proxies to the " "default value.\n\ncommit_proxies=: Sets the desired number of commit proxies in the cluster. " "Must be at least 1, or set to -1 which restores the number of commit proxies to the default " "value.\n\ngrv_proxies=: Sets the desired number of GRV proxies in the cluster. Must be at least " "1, or set to -1 which restores the number of GRV proxies to the default value.\n\nlogs=: Sets the " "desired number of log servers in the cluster. Must be at least 1, or set to -1 which restores the number of " "logs to the default value.\n\nresolvers=: Sets the desired number of resolvers in the cluster. " "Must be at least 1, or set to -1 which restores the number of resolvers to the default value.\n\n" "perpetual_storage_wiggle=: Set the value speed (a.k.a., the number of processes that the Data " "Distributor should wiggle at a time). Currently, only 0 and 1 are supported. The value 0 means to disable the " "perpetual storage wiggle.\n\n" "See the FoundationDB Administration Guide for more information."); helpMap["fileconfigure"] = CommandHelp( "fileconfigure [new] ", "change the database configuration from a file", "The `new' option, if present, initializes a new database with the given configuration rather than changing " "the configuration of an existing one. Load a JSON document from the provided file, and change the database " "configuration to match the contents of the JSON document. The format should be the same as the value of the " "\"configuration\" entry in status JSON without \"excluded_servers\" or \"coordinators_count\"."); helpMap["coordinators"] = CommandHelp( "coordinators auto|
+ [description=new_cluster_description]", "change cluster coordinators or description", "If 'auto' is specified, coordinator addresses will be choosen automatically to support the configured " "redundancy level. (If the current set of coordinators are healthy and already support the redundancy level, " "nothing will be changed.)\n\nOtherwise, sets the coordinators to the list of IP:port pairs specified by " "
+. An fdbserver process must be running on each of the specified addresses.\n\ne.g. coordinators " "10.0.0.1:4000 10.0.0.2:4000 10.0.0.3:4000\n\nIf 'description=desc' is specified then the description field in " "the cluster\nfile is changed to desc, which must match [A-Za-z0-9_]+."); helpMap["exclude"] = CommandHelp( "exclude [FORCE] [failed] [no_wait] [] [locality_dcid:] " "[locality_zoneid:] [locality_machineid:] " "[locality_processid:] or any locality data", "exclude servers from the database either with IP address match or locality match", "If no addresses or locaities are specified, lists the set of excluded addresses and localities." "\n\nFor each IP address or IP:port pair in or any LocalityData attributes (like dcid, zoneid, " "machineid, processid), adds the address/locality to the set of excluded servers and localities then waits " "until all database state has been safely moved away from the specified servers. If 'no_wait' is set, the " "command returns \nimmediately without checking if the exclusions have completed successfully.\n" "If 'FORCE' is set, the command does not perform safety checks before excluding.\n" "If 'failed' is set, the transaction log queue is dropped pre-emptively before waiting\n" "for data movement to finish and the server cannot be included again."); helpMap["include"] = CommandHelp( "include all|[] [locality_dcid:] [locality_zoneid:] " "[locality_machineid:] [locality_processid:] or any locality data", "permit previously-excluded servers and localities to rejoin the database", "If `all' is specified, the excluded servers and localities list is cleared.\n\nFor each IP address or IP:port " "pair in or any LocalityData (like dcid, zoneid, machineid, processid), removes any " "matching exclusions from the excluded servers and localities list. " "(A specified IP will match all IP:* exclusion entries)"); helpMap["status"] = CommandHelp("status [minimal|details|json]", "get the status of a FoundationDB cluster", "If the cluster is down, this command will print a diagnostic which may be useful in figuring out " "what is wrong. If the cluster is running, this command will print cluster " "statistics.\n\nSpecifying `minimal' will provide a minimal description of the status of your " "database.\n\nSpecifying `details' will provide load information for individual " "workers.\n\nSpecifying `json' will provide status information in a machine readable JSON format."); helpMap["exit"] = CommandHelp("exit", "exit the CLI", ""); helpMap["quit"] = CommandHelp(); helpMap["waitconnected"] = CommandHelp(); helpMap["waitopen"] = CommandHelp(); helpMap["sleep"] = CommandHelp("sleep ", "sleep for a period of time", ""); helpMap["get"] = CommandHelp("get ", "fetch the value for a given key", "Displays the value of KEY in the database, or `not found' if KEY is not present." ESCAPINGK); helpMap["getrange"] = CommandHelp("getrange [ENDKEY] [LIMIT]", "fetch key/value pairs in a range of keys", "Displays up to LIMIT keys and values for keys between BEGINKEY (inclusive) and ENDKEY " "(exclusive). If ENDKEY is omitted, then the range will include all keys starting with BEGINKEY. " "LIMIT defaults to 25 if omitted." ESCAPINGK); helpMap["getrangekeys"] = CommandHelp( "getrangekeys [ENDKEY] [LIMIT]", "fetch keys in a range of keys", "Displays up to LIMIT keys for keys between BEGINKEY (inclusive) and ENDKEY (exclusive). If ENDKEY is omitted, " "then the range will include all keys starting with BEGINKEY. LIMIT defaults to 25 if omitted." ESCAPINGK); helpMap["getversion"] = CommandHelp("getversion", "Fetch the current read version", "Displays the current read version of the database or currently running transaction."); helpMap["reset"] = CommandHelp("reset", "reset the current transaction", "Any sets or clears executed after the start of the active transaction will be discarded."); helpMap["rollback"] = CommandHelp("rollback", "rolls back the current transaction", "The active transaction will be discarded, including any sets or clears executed " "since the transaction was started."); helpMap["set"] = CommandHelp("set ", "set a value for a given key", "If KEY is not already present in the database, it will be created." ESCAPINGKV); helpMap["option"] = CommandHelp( "option