diff options
Diffstat (limited to 'src/tools/crushtool.cc')
-rw-r--r-- | src/tools/crushtool.cc | 1344 |
1 files changed, 1344 insertions, 0 deletions
diff --git a/src/tools/crushtool.cc b/src/tools/crushtool.cc new file mode 100644 index 000000000..acfc9a3c6 --- /dev/null +++ b/src/tools/crushtool.cc @@ -0,0 +1,1344 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2004-2006 Sage Weil <sage@newdream.net> + * Copyright (C) 2014 Cloudwatt <libre.licensing@cloudwatt.com> + * + * Author: Loic Dachary <loic@dachary.org> + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <errno.h> + +#include <fstream> +#include <type_traits> + +#include "common/debug.h" +#include "common/errno.h" +#include "common/config.h" +#include "common/Formatter.h" + +#include "common/ceph_argparse.h" +#include "include/stringify.h" +#include "global/global_context.h" +#include "global/global_init.h" +#include "osd/OSDMap.h" +#include "crush/CrushWrapper.h" +#include "crush/CrushCompiler.h" +#include "crush/CrushTester.h" +#include "include/ceph_assert.h" + +#define dout_context g_ceph_context +#define dout_subsys ceph_subsys_crush + +using std::cerr; +using std::cout; +using std::decay_t; +using std::ifstream; +using std::ios; +using std::is_same_v; +using std::map; +using std::ofstream; +using std::pair; +using std::set; +using std::string; +using std::vector; + +const char *infn = "stdin"; + +static int get_fd_data(int fd, bufferlist &bl) +{ + + uint64_t total = 0; + do { + ssize_t bytes = bl.read_fd(fd, 1024*1024); + if (bytes < 0) { + cerr << "read_fd error " << cpp_strerror(-bytes) << "\n"; + return -1; + } + + if (bytes == 0) + break; + + total += bytes; + } while(true); + + ceph_assert(bl.length() == total); + return 0; +} + +//////////////////////////////////////////////////////////////////////////// + +void data_analysis_usage() +{ +cout << "data output from testing routine ...\n"; +cout << " absolute_weights\n"; +cout << " the decimal weight of each OSD\n"; +cout << " data layout: ROW MAJOR\n"; +cout << " OSD id (int), weight (int)\n"; +cout << " batch_device_expected_utilization_all\n"; +cout << " the expected number of objects each OSD should receive per placement batch\n"; +cout << " which may be a decimal value\n"; +cout << " data layout: COLUMN MAJOR\n"; +cout << " round (int), objects expected on OSD 0...OSD n (float)\n"; +cout << " batch_device_utilization_all\n"; +cout << " the number of objects stored on each OSD during each placement round\n"; +cout << " data layout: COLUMN MAJOR\n"; +cout << " round (int), objects stored on OSD 0...OSD n (int)\n"; +cout << " device_utilization_all\n"; +cout << " the number of objects stored on each OSD at the end of placements\n"; +cout << " data_layout: ROW MAJOR\n"; +cout << " OSD id (int), objects stored (int), objects expected (float)\n"; +cout << " device_utilization\n"; +cout << " the number of objects stored on each OSD marked 'up' at the end of placements\n"; +cout << " data_layout: ROW MAJOR\n"; +cout << " OSD id (int), objects stored (int), objects expected (float)\n"; +cout << " placement_information\n"; +cout << " the map of input -> OSD\n"; +cout << " data_layout: ROW MAJOR\n"; +cout << " input (int), OSD's mapped (int)\n"; +cout << " proportional_weights_all\n"; +cout << " the proportional weight of each OSD specified in the CRUSH map\n"; +cout << " data_layout: ROW MAJOR\n"; +cout << " OSD id (int), proportional weight (float)\n"; +cout << " proportional_weights\n"; +cout << " the proportional weight of each 'up' OSD specified in the CRUSH map\n"; +cout << " data_layout: ROW MAJOR\n"; +cout << " OSD id (int), proportional weight (float)\n"; +} + +void usage() +{ + cout << "usage: crushtool ...\n"; + cout << "\n"; + cout << "Display, modify and test a crush map\n"; + cout << "\n"; + cout << "There are five stages, running one after the other:\n"; + cout << "\n"; + cout << " - input/build\n"; + cout << " - tunables adjustments\n"; + cout << " - modifications\n"; + cout << " - display/test\n"; + cout << " - output\n"; + cout << "\n"; + cout << "Options that are not specific to a stage.\n"; + cout << "\n"; + cout << " [--infn|-i infile]\n"; + cout << " read the crush map from infile\n"; + cout << "\n"; + cout << "Options for the input/build stage\n"; + cout << "\n"; + cout << " --decompile|-d map decompile a crush map to source\n"; + cout << " [--outfn|-o outfile]\n"; + cout << " specify output for for (de)compilation\n"; + cout << " --compile|-c map.txt compile a map from source\n"; + cout << " --enable-unsafe-tunables\n"; + cout << " compile with unsafe tunables\n"; + cout << " --build --num_osds N layer1 ...\n"; + cout << " build a new map, where each 'layer' is\n"; + cout << " 'name (uniform|straw2|straw|list|tree) size'\n"; + cout << "\n"; + cout << "Options for the tunables adjustments stage\n"; + cout << "\n"; + cout << " --set-choose-local-tries N\n"; + cout << " set choose local retries before re-descent\n"; + cout << " --set-choose-local-fallback-tries N\n"; + cout << " set choose local retries using fallback\n"; + cout << " permutation before re-descent\n"; + cout << " --set-choose-total-tries N\n"; + cout << " set choose total descent attempts\n"; + cout << " --set-chooseleaf-descend-once <0|1>\n"; + cout << " set chooseleaf to (not) retry the recursive descent\n"; + cout << " --set-chooseleaf-vary-r <0|1>\n"; + cout << " set chooseleaf to (not) vary r based on parent\n"; + cout << " --set-chooseleaf-stable <0|1>\n"; + cout << " set chooseleaf firstn to (not) return stable results\n"; + cout << "\n"; + cout << "Options for the modifications stage\n"; + cout << "\n"; + cout << " -i mapfn --add-item id weight name [--loc type name ...]\n"; + cout << " insert an item into the hierarchy at the\n"; + cout << " given location\n"; + cout << " -i mapfn --update-item id weight name [--loc type name ...]\n"; + cout << " insert or move an item into the hierarchy at the\n"; + cout << " given location\n"; + cout << " -i mapfn --remove-item name\n" + << " remove the given item\n"; + cout << " -i mapfn --reweight-item name weight\n"; + cout << " reweight a given item (and adjust ancestor\n" + << " weights as needed)\n"; + cout << " -i mapfn --add-bucket name type [--loc type name ...]\n" + << " insert a bucket into the hierarchy at the given\n" + << " location\n"; + cout << " -i mapfn --move name --loc type name ...\n" + << " move the given item to specified location\n"; + cout << " -i mapfn --reweight recalculate all bucket weights\n"; + cout << " -i mapfn --rebuild-class-roots\n"; + cout << " rebuild the per-class shadow trees (normally a no-op)\n"; + cout << " -i mapfn --create-simple-rule name root type mode\n" + << " create crush rule <name> to start from <root>,\n" + << " replicate across buckets of type <type>, using\n" + << " a choose mode of <firstn|indep>\n"; + cout << " -i mapfn --create-replicated-rule name root type\n" + << " create crush rule <name> to start from <root>,\n" + << " replicate across buckets of type <type>\n"; + cout << " --device-class <class>\n"; + cout << " use device class <class> for new rule\n"; + cout << " -i mapfn --remove-rule name\n" + << " remove the specified crush rule\n"; + cout << "\n"; + cout << "Options for the display/test stage\n"; + cout << "\n"; + cout << " -f --format the format of --dump, defaults to json-pretty\n"; + cout << " can be one of json, json-pretty, xml, xml-pretty,\n"; + cout << " table, table-kv, html, html-pretty\n"; + cout << " --dump dump the crush map\n"; + cout << " --tree print map summary as a tree\n"; + cout << " --bucket-tree print bucket map summary as a tree\n"; + cout << " --bucket-name specify bucket bucket name for bucket-tree\n"; + cout << " --check [max_id] check if any item is referencing an unknown name/type\n"; + cout << " -i mapfn --show-location id\n"; + cout << " show location for given device id\n"; + cout << " -i mapfn --test test a range of inputs on the map\n"; + cout << " [--min-x x] [--max-x x] [--x x]\n"; + cout << " [--min-rule r] [--max-rule r] [--rule r]\n"; + cout << " [--min-rep n] [--max-rep n] [--num-rep n]\n"; + cout << " [--pool-id n] specifies pool id\n"; + cout << " [--batches b] split the CRUSH mapping into b > 1 rounds\n"; + cout << " [--weight|-w devno weight]\n"; + cout << " where weight is 0 to 1.0\n"; + cout << " [--simulate] simulate placements using a random\n"; + cout << " number generator in place of the CRUSH\n"; + cout << " algorithm\n"; + cout << " --show-utilization show OSD usage\n"; + cout << " --show-utilization-all\n"; + cout << " include zero weight items\n"; + cout << " --show-statistics show chi squared statistics\n"; + cout << " --show-mappings show mappings\n"; + cout << " --show-bad-mappings show bad mappings\n"; + cout << " --show-choose-tries show choose tries histogram\n"; + cout << " --output-name name\n"; + cout << " prepend the data file(s) generated during the\n"; + cout << " testing routine with name\n"; + cout << " --output-csv\n"; + cout << " export select data generated during testing routine\n"; + cout << " to CSV files for off-line post-processing\n"; + cout << " use --help-output for more information\n"; + cout << " --reclassify transform legacy CRUSH map buckets and rules\n"; + cout << " by adding classes\n"; + cout << " --reclassify-bucket <bucket-match> <class> <default-parent>\n"; + cout << " --reclassify-root <bucket-name> <class>\n"; + cout << " --set-subtree-class <bucket-name> <class>\n"; + cout << " set class for all items beneath bucket-name\n"; + cout << " --compare <otherfile> compare two maps using --test parameters\n"; + cout << "\n"; + cout << "Options for the output stage\n"; + cout << "\n"; + cout << " [--outfn|-o outfile]\n"; + cout << " specify output for modified crush map\n"; + cout << "\n"; +} + +struct bucket_types_t { + const char *name; + int type; +} bucket_types[] = { + { "uniform", CRUSH_BUCKET_UNIFORM }, + { "list", CRUSH_BUCKET_LIST }, + { "straw", CRUSH_BUCKET_STRAW }, + { "straw2", CRUSH_BUCKET_STRAW2 }, + { "tree", CRUSH_BUCKET_TREE }, + { 0, 0 }, +}; + +struct layer_t { + const char *name; + const char *buckettype; + int size; +}; + +template<typename... Args> +bool argparse_withargs(std::vector<const char*> &args, + std::vector<const char*>::iterator& i, + std::ostream& oss, + const char* opt, + Args*... opts) +{ + if (!ceph_argparse_flag(args, i, opt, nullptr)) { + return false; + } + auto parse = [&](auto& opt) { + if (i == args.end()) { + oss << "expecting additional argument to " << opt; + return false; + } + using opt_t = std::remove_pointer_t<decay_t<decltype(opt)>>; + string err; + if constexpr (std::is_same_v<opt_t, string>) { + opt->assign(*i); + } else if constexpr (is_same_v<opt_t, int>) { + *opt = strict_strtol(*i, 10, &err); + } else if constexpr (is_same_v<opt_t, float>) { + *opt = strict_strtof(*i, &err); + } + i = args.erase(i); + if (err.empty()) + return true; + else { + oss << err; + return false; + } + }; + (... && parse(opts)); + return true; +} + +int do_add_bucket(CephContext* cct, + const char* me, + CrushWrapper& crush, + const string& add_name, + const string& add_type, + const map<string,string>& add_loc) { + int bucketno; + if (crush.name_exists(add_name)) { + cerr << me << " bucket '" << add_name << "' already exists" << std::endl; + return -EEXIST; + } + int type = crush.get_type_id(add_type); + if (type <= 0) { + cerr << me << " bad bucket type: " << add_type << std::endl; + return -EINVAL; + } + if (int r = crush.add_bucket(0, 0, CRUSH_HASH_DEFAULT, type, 0, nullptr, nullptr, &bucketno); + r < 0) { + cerr << me << " unable to add bucket: " << cpp_strerror(r) << std::endl; + return r; + } + if (int r = crush.set_item_name(bucketno, add_name); r < 0) { + cerr << me << " bad bucket name: " << add_name << std::endl; + return r; + } + if (!add_loc.empty()) { + if (!crush.check_item_loc(cct, bucketno, add_loc, (int*)nullptr)) { + if (int r = crush.move_bucket(cct, bucketno, add_loc); r < 0) { + cerr << me << " error moving bucket '" << add_name << "' to " << add_loc << std::endl; + return r; + } + } + } + return 0; +} + +// return 1 for no change, 0 for successful change, negative on error +int do_move_item(CephContext* cct, + const char *me, + CrushWrapper& crush, + const string& name, + const map<string,string>& loc) +{ + if (!crush.name_exists(name)) { + cerr << me << " item '" << name << "' does not exist" << std::endl; + return -ENOENT; + } + int id = crush.get_item_id(name); + if (loc.empty()) { + cerr << me << " expecting additional --loc argument to --move" << std::endl; + return -EINVAL; + } + if (crush.check_item_loc(cct, id, loc, (int*)nullptr)) { + // it's already there + cerr << me << " item '" << name << "' already at " << loc << std::endl; + return 1; + } + if (id >= 0) { + switch (int r = crush.create_or_move_item(cct, id, 0, name, loc)) { + case 0: + return 1; + case 1: + return 0; + default: + return r; + } + } else { + return crush.move_bucket(cct, id, loc); + } +} + +int main(int argc, const char **argv) +{ + auto args = argv_to_vec(argc, argv); + if (args.empty()) { + cerr << argv[0] << ": -h or --help for usage" << std::endl; + exit(1); + } + if (ceph_argparse_need_usage(args)) { + usage(); + exit(0); + } + + const char *me = argv[0]; + + std::string infn, srcfn, outfn, add_name, add_type, remove_name, + reweight_name, bucket_name; + std::string move_name; + bool compile = false; + bool decompile = false; + bool check = false; + int max_id = -1; + bool test = false; + bool display = false; + bool tree = false; + bool bucket_tree = false; + string dump_format = "json-pretty"; + bool dump = false; + int full_location = -1; + bool write_to_file = false; + int verbose = 0; + bool unsafe_tunables = false; + + bool rebuild_class_roots = false; + + bool reweight = false; + int add_item = -1; + bool add_bucket = false; + bool update_item = false; + bool move_item = false; + bool add_rule = false; + std::string rule_name, rule_root, rule_type, rule_mode, rule_device_class; + bool del_rule = false; + float add_weight = 0; + map<string,string> add_loc; + float reweight_weight = 0; + + bool adjust = false; + + int build = 0; + int num_osds =0; + vector<layer_t> layers; + + int choose_local_tries = -1; + int choose_local_fallback_tries = -1; + int choose_total_tries = -1; + int chooseleaf_descend_once = -1; + int chooseleaf_vary_r = -1; + int chooseleaf_stable = -1; + int straw_calc_version = -1; + int allowed_bucket_algs = -1; + + bool reclassify = false; + map<string,pair<string,string>> reclassify_bucket; // %suffix or prefix% -> class, default_root + map<string,string> reclassify_root; // bucket -> class + map<string,string> set_subtree_class; // bucket -> class + + string compare; + + CrushWrapper crush; + + CrushTester tester(crush, cout); + + // we use -c, don't confuse the generic arg parsing + // only parse arguments from CEPH_ARGS, if in the environment + vector<const char *> empty_args; + auto cct = global_init(NULL, empty_args, CEPH_ENTITY_TYPE_CLIENT, + CODE_ENVIRONMENT_UTILITY, + CINIT_FLAG_NO_DEFAULT_CONFIG_FILE); + // crushtool times out occasionally when quits. so do not + // release the g_ceph_context. + cct->get(); + common_init_finish(g_ceph_context); + + int x; + float y; + long long z; + + std::string val; + std::ostringstream err; + int tmp; + for (std::vector<const char*>::iterator i = args.begin(); i != args.end(); ) { + if (ceph_argparse_double_dash(args, i)) { + break; + } else if (ceph_argparse_witharg(args, i, &val, "-d", "--decompile", (char*)NULL)) { + infn = val; + decompile = true; + } else if (ceph_argparse_witharg(args, i, &val, "-i", "--infn", (char*)NULL)) { + infn = val; + } else if (ceph_argparse_witharg(args, i, &val, "-o", "--outfn", (char*)NULL)) { + outfn = val; + } else if (ceph_argparse_flag(args, i, "-v", "--verbose", (char*)NULL)) { + verbose += 1; + } else if (ceph_argparse_witharg(args, i, &val, "--compare", (char*)NULL)) { + compare = val; + } else if (ceph_argparse_flag(args, i, "--reclassify", (char*)NULL)) { + reclassify = true; + } else if (ceph_argparse_witharg(args, i, &val, "--reclassify-bucket", + (char*)NULL)) { + if (i == args.end()) { + cerr << "expecting additional argument" << std::endl; + return EXIT_FAILURE; + } + string c = *i; + i = args.erase(i); + if (i == args.end()) { + cerr << "expecting additional argument" << std::endl; + return EXIT_FAILURE; + } + reclassify_bucket[val] = make_pair(c, *i); + i = args.erase(i); + } else if (ceph_argparse_witharg(args, i, &val, "--reclassify-root", + (char*)NULL)) { + if (i == args.end()) { + cerr << "expecting additional argument" << std::endl; + return EXIT_FAILURE; + } + reclassify_root[val] = *i; + i = args.erase(i); + } else if (ceph_argparse_witharg(args, i, &val, "--set-subtree-class", + (char*)NULL)) { + if (i == args.end()) { + cerr << "expecting additional argument" << std::endl; + return EXIT_FAILURE; + } + set_subtree_class[val] = *i; + i = args.erase(i); + } else if (ceph_argparse_flag(args, i, "--tree", (char*)NULL)) { + tree = true; + } else if (ceph_argparse_flag(args, i, "--bucket-tree", (char*)NULL)) { + bucket_tree = true; + } else if (ceph_argparse_witharg(args, i, &val, "-b", "--bucket-name", (char*)NULL)) { + bucket_name = val; + } else if (ceph_argparse_witharg(args, i, &val, "-f", "--format", (char*)NULL)) { + dump_format = val; + } else if (ceph_argparse_flag(args, i, "--dump", (char*)NULL)) { + dump = true; + } else if (ceph_argparse_flag(args, i, "--show_utilization", (char*)NULL)) { + display = true; + tester.set_output_utilization(true); + } else if (ceph_argparse_flag(args, i, "--show_utilization_all", (char*)NULL)) { + display = true; + tester.set_output_utilization_all(true); + } else if (ceph_argparse_flag(args, i, "--show_statistics", (char*)NULL)) { + display = true; + tester.set_output_statistics(true); + } else if (ceph_argparse_flag(args, i, "--show_mappings", (char*)NULL)) { + display = true; + tester.set_output_mappings(true); + } else if (ceph_argparse_flag(args, i, "--show_bad_mappings", (char*)NULL)) { + display = true; + tester.set_output_bad_mappings(true); + } else if (ceph_argparse_flag(args, i, "--show_choose_tries", (char*)NULL)) { + display = true; + tester.set_output_choose_tries(true); + } else if (ceph_argparse_witharg(args, i, &val, "-c", "--compile", (char*)NULL)) { + srcfn = val; + compile = true; + } else if (ceph_argparse_witharg(args, i, &max_id, err, "--check", (char*)NULL)) { + check = true; + } else if (ceph_argparse_flag(args, i, "-t", "--test", (char*)NULL)) { + test = true; + } else if (ceph_argparse_witharg(args, i, &full_location, err, "--show-location", (char*)NULL)) { + } else if (ceph_argparse_flag(args, i, "-s", "--simulate", (char*)NULL)) { + tester.set_random_placement(); + } else if (ceph_argparse_flag(args, i, "--enable-unsafe-tunables", (char*)NULL)) { + unsafe_tunables = true; + } else if (ceph_argparse_witharg(args, i, &choose_local_tries, err, + "--set_choose_local_tries", (char*)NULL)) { + adjust = true; + } else if (ceph_argparse_witharg(args, i, &choose_local_fallback_tries, err, + "--set_choose_local_fallback_tries", (char*)NULL)) { + adjust = true; + } else if (ceph_argparse_witharg(args, i, &choose_total_tries, err, + "--set_choose_total_tries", (char*)NULL)) { + adjust = true; + } else if (ceph_argparse_witharg(args, i, &chooseleaf_descend_once, err, + "--set_chooseleaf_descend_once", (char*)NULL)) { + adjust = true; + } else if (ceph_argparse_witharg(args, i, &chooseleaf_vary_r, err, + "--set_chooseleaf_vary_r", (char*)NULL)) { + adjust = true; + } else if (ceph_argparse_witharg(args, i, &chooseleaf_stable, err, + "--set_chooseleaf_stable", (char*)NULL)) { + adjust = true; + } else if (ceph_argparse_witharg(args, i, &straw_calc_version, err, + "--set_straw_calc_version", (char*)NULL)) { + adjust = true; + } else if (ceph_argparse_witharg(args, i, &allowed_bucket_algs, err, + "--set_allowed_bucket_algs", (char*)NULL)) { + adjust = true; + } else if (ceph_argparse_flag(args, i, "--reweight", (char*)NULL)) { + reweight = true; + } else if (ceph_argparse_flag(args, i, "--rebuild-class-roots", (char*)NULL)) { + rebuild_class_roots = true; + } else if (ceph_argparse_witharg(args, i, &add_item, err, "--add_item", (char*)NULL)) { + if (!err.str().empty()) { + cerr << err.str() << std::endl; + return EXIT_FAILURE; + } + if (i == args.end()) { + cerr << "expecting additional argument to --add-item" << std::endl; + return EXIT_FAILURE; + } + add_weight = atof(*i); + i = args.erase(i); + if (i == args.end()) { + cerr << "expecting additional argument to --add-item" << std::endl; + return EXIT_FAILURE; + } + add_name.assign(*i); + i = args.erase(i); + } else if (ceph_argparse_witharg(args, i, &add_item, err, "--update_item", (char*)NULL)) { + update_item = true; + if (!err.str().empty()) { + cerr << err.str() << std::endl; + return EXIT_FAILURE; + } + if (i == args.end()) { + cerr << "expecting additional argument to --update-item" << std::endl; + return EXIT_FAILURE; + } + add_weight = atof(*i); + i = args.erase(i); + if (i == args.end()) { + cerr << "expecting additional argument to --update-item" << std::endl; + return EXIT_FAILURE; + } + add_name.assign(*i); + i = args.erase(i); + } else if (argparse_withargs(args, i, err, "--add-bucket", + &add_name, &add_type)) { + if (!err.str().empty()) { + cerr << err.str() << std::endl; + return EXIT_FAILURE; + } + add_bucket = true; + } else if (argparse_withargs(args, i, err, "--move", + &move_name)) { + if (!err.str().empty()) { + cerr << err.str() << std::endl; + return EXIT_FAILURE; + } + move_item = true; + } else if (ceph_argparse_witharg(args, i, &val, err, "--create-simple-rule", (char*)NULL)) { + rule_name.assign(val); + if (!err.str().empty()) { + cerr << err.str() << std::endl; + return EXIT_FAILURE; + } + if (i == args.end()) { + cerr << "expecting additional argument to --create-simple-rule" << std::endl; + return EXIT_FAILURE; + } + + rule_root.assign(*i); + i = args.erase(i); + if (i == args.end()) { + cerr << "expecting additional argument to --create-simple-rule" << std::endl; + return EXIT_FAILURE; + } + + rule_type.assign(*i); + i = args.erase(i); + if (i == args.end()) { + cerr << "expecting additional argument to --create-simple-rule" << std::endl; + return EXIT_FAILURE; + } + + rule_mode.assign(*i); + i = args.erase(i); + + cout << "--create-simple-rule:" + << " name=" << rule_name + << " root=" << rule_root + << " type=" << rule_type + << " mode=" << rule_mode + << std::endl; + add_rule = true; + } else if (ceph_argparse_witharg(args, i, &val, err, "--create-replicated-rule", (char*)NULL)) { + rule_name.assign(val); + if (!err.str().empty()) { + cerr << err.str() << std::endl; + return EXIT_FAILURE; + } + if (i == args.end()) { + cerr << "expecting additional argument to --create-replicated-rule" << std::endl; + return EXIT_FAILURE; + } + + rule_root.assign(*i); + i = args.erase(i); + if (i == args.end()) { + cerr << "expecting additional argument to --create-replicated-rule" << std::endl; + return EXIT_FAILURE; + } + + rule_type.assign(*i); + i = args.erase(i); + rule_mode = "firstn"; + + cout << "--create-replicated-rule:" + << " name=" << rule_name + << " root=" << rule_root + << " type=" << rule_type + << std::endl; + add_rule = true; + + } else if (ceph_argparse_witharg(args, i, &val, "--device-class", (char*)NULL)) { + rule_device_class.assign(val); + if (!err.str().empty()) { + cerr << err.str() << std::endl; + return EXIT_FAILURE; + } + } else if (ceph_argparse_witharg(args, i, &val, "--remove-rule", (char*)NULL)) { + rule_name.assign(val); + if (!err.str().empty()) { + cerr << err.str() << std::endl; + return EXIT_FAILURE; + } + del_rule = true; + } else if (ceph_argparse_witharg(args, i, &val, "--loc", (char*)NULL)) { + std::string type(val); + if (i == args.end()) { + cerr << "expecting additional argument to --loc" << std::endl; + return EXIT_FAILURE; + } + std::string name(*i); + i = args.erase(i); + add_loc[type] = name; + } else if (ceph_argparse_flag(args, i, "--output-csv", (char*)NULL)) { + write_to_file = true; + tester.set_output_data_file(true); + tester.set_output_csv(true); + } else if (ceph_argparse_flag(args, i, "--help-output", (char*)NULL)) { + data_analysis_usage(); + return EXIT_SUCCESS; + } else if (ceph_argparse_witharg(args, i, &val, "--output-name", (char*)NULL)) { + std::string name(val); + if (i == args.end()) { + cerr << "expecting additional argument to --output-name" << std::endl; + return EXIT_FAILURE; + } + else { + tester.set_output_data_file_name(name + "-"); + } + } else if (ceph_argparse_witharg(args, i, &val, "--remove_item", (char*)NULL)) { + remove_name = val; + } else if (ceph_argparse_witharg(args, i, &val, "--reweight_item", (char*)NULL)) { + reweight_name = val; + if (i == args.end()) { + cerr << "expecting additional argument to --reweight-item" << std::endl; + return EXIT_FAILURE; + } + reweight_weight = atof(*i); + i = args.erase(i); + } else if (ceph_argparse_flag(args, i, "--build", (char*)NULL)) { + build = true; + } else if (ceph_argparse_witharg(args, i, &num_osds, err, "--num_osds", (char*)NULL)) { + if (!err.str().empty()) { + cerr << err.str() << std::endl; + return EXIT_FAILURE; + } + } else if (ceph_argparse_witharg(args, i, &x, err, "--num_rep", (char*)NULL)) { + if (!err.str().empty()) { + cerr << err.str() << std::endl; + return EXIT_FAILURE; + } + tester.set_num_rep(x); + } else if (ceph_argparse_witharg(args, i, &x, err, "--min_rep", (char*)NULL)) { + if (!err.str().empty()) { + cerr << err.str() << std::endl; + return EXIT_FAILURE; + } + tester.set_min_rep(x); + } else if (ceph_argparse_witharg(args, i, &x, err, "--max_rep", (char*)NULL)) { + if (!err.str().empty()) { + cerr << err.str() << std::endl; + return EXIT_FAILURE; + } + tester.set_max_rep(x); + } else if (ceph_argparse_witharg(args, i, &x, err, "--max_x", (char*)NULL)) { + if (!err.str().empty()) { + cerr << err.str() << std::endl; + return EXIT_FAILURE; + } + tester.set_max_x(x); + } else if (ceph_argparse_witharg(args, i, &x, err, "--min_x", (char*)NULL)) { + if (!err.str().empty()) { + cerr << err.str() << std::endl; + return EXIT_FAILURE; + } + tester.set_min_x(x); + } else if (ceph_argparse_witharg(args, i, &z, err, "--pool_id", (char*)NULL)) { + if (!err.str().empty()) { + cerr << err.str() << std::endl; + return EXIT_FAILURE; + } + tester.set_pool_id(z); + } else if (ceph_argparse_witharg(args, i, &x, err, "--x", (char*)NULL)) { + if (!err.str().empty()) { + cerr << err.str() << std::endl; + return EXIT_FAILURE; + } + tester.set_x(x); + } else if (ceph_argparse_witharg(args, i, &x, err, "--max_rule", (char*)NULL)) { + if (!err.str().empty()) { + cerr << err.str() << std::endl; + return EXIT_FAILURE; + } + tester.set_max_rule(x); + } else if (ceph_argparse_witharg(args, i, &x, err, "--min_rule", (char*)NULL)) { + if (!err.str().empty()) { + cerr << err.str() << std::endl; + return EXIT_FAILURE; + } + tester.set_min_rule(x); + } else if (ceph_argparse_witharg(args, i, &x, err, "--rule", (char*)NULL)) { + if (!err.str().empty()) { + cerr << err.str() << std::endl; + return EXIT_FAILURE; + } + tester.set_rule(x); + } else if (ceph_argparse_witharg(args, i, &x, err, "--batches", (char*)NULL)) { + if (!err.str().empty()) { + cerr << err.str() << std::endl; + return EXIT_FAILURE; + } + tester.set_batches(x); + } else if (ceph_argparse_witharg(args, i, &y, err, "--mark-down-ratio", (char*)NULL)) { + if (!err.str().empty()) { + cerr << err.str() << std::endl; + return EXIT_FAILURE; + } + tester.set_device_down_ratio(y); + } else if (ceph_argparse_witharg(args, i, &y, err, "--mark-down-bucket-ratio", (char*)NULL)) { + if (!err.str().empty()) { + cerr << err.str() << std::endl; + return EXIT_FAILURE; + } + tester.set_bucket_down_ratio(y); + } else if (ceph_argparse_witharg(args, i, &tmp, err, "--weight", (char*)NULL)) { + if (!err.str().empty()) { + cerr << err.str() << std::endl; + return EXIT_FAILURE; + } + int dev = tmp; + if (i == args.end()) { + cerr << "expecting additional argument to --weight" << std::endl; + return EXIT_FAILURE; + } + float f = atof(*i); + i = args.erase(i); + tester.set_device_weight(dev, f); + } + else { + ++i; + } + } + + if (test && !check && !display && !write_to_file && compare.empty()) { + cerr << "WARNING: no output selected; use --output-csv or --show-X" << std::endl; + } + + if (decompile + compile + build > 1) { + cerr << "cannot specify more than one of compile, decompile, and build" << std::endl; + return EXIT_FAILURE; + } + if (!check && !compile && !decompile && !build && !test && !reweight && !adjust && !tree && !dump && + add_item < 0 && !add_bucket && !move_item && !add_rule && !del_rule && full_location < 0 && + !bucket_tree && + !reclassify && !rebuild_class_roots && + compare.empty() && + + remove_name.empty() && reweight_name.empty()) { + cerr << "no action specified; -h for help" << std::endl; + return EXIT_FAILURE; + } + if ((!build) && (!args.empty())) { + cerr << "unrecognized arguments: " << args << std::endl; + return EXIT_FAILURE; + } + else { + if ((args.size() % 3) != 0U) { + cerr << "remaining args: " << args << std::endl; + cerr << "layers must be specified with 3-tuples of (name, buckettype, size)" + << std::endl; + return EXIT_FAILURE; + } + for (size_t j = 0; j < args.size(); j += 3) { + layer_t l; + l.name = args[j]; + l.buckettype = args[j+1]; + l.size = atoi(args[j+2]); + layers.push_back(l); + } + } + + /* + if (outfn) cout << "outfn " << outfn << std::endl; + if (cinfn) cout << "cinfn " << cinfn << std::endl; + if (dinfn) cout << "dinfn " << dinfn << std::endl; + */ + + bool modified = false; + + // input ---- + + if (!infn.empty()) { + bufferlist bl; + std::string error; + + int r = 0; + if (infn == "-") { + if (isatty(STDIN_FILENO)) { + cerr << "stdin must not be from a tty" << std::endl; + return EXIT_FAILURE; + } + r = get_fd_data(STDIN_FILENO, bl); + if (r < 0) { + cerr << "error reading data from STDIN" << std::endl; + return EXIT_FAILURE; + } + } else { + r = bl.read_file(infn.c_str(), &error); + if (r < 0) { + cerr << me << ": error reading '" << infn << "': " + << error << std::endl; + return EXIT_FAILURE; + } + } + auto p = bl.cbegin(); + try { + crush.decode(p); + } catch(...) { + cerr << me << ": unable to decode " << infn << std::endl; + return EXIT_FAILURE; + } + } + + if (compile) { + crush.create(); + + // read the file + ifstream in(srcfn.c_str()); + if (!in.is_open()) { + cerr << "input file " << srcfn << " not found" << std::endl; + return -ENOENT; + } + + CrushCompiler cc(crush, cerr, verbose); + if (unsafe_tunables) + cc.enable_unsafe_tunables(); + int r = cc.compile(in, srcfn.c_str()); + if (r < 0) + return EXIT_FAILURE; + + modified = true; + } + + if (build) { + if (layers.empty()) { + cerr << me << ": must specify at least one layer" << std::endl; + return EXIT_FAILURE; + } + + crush.create(); + + vector<int> lower_items; + vector<int> lower_weights; + + crush.set_max_devices(num_osds); + for (int i=0; i<num_osds; i++) { + lower_items.push_back(i); + lower_weights.push_back(0x10000); + crush.set_item_name(i, "osd." + stringify(i)); + } + + crush.set_type_name(0, "osd"); + int type = 1; + for (vector<layer_t>::iterator p = layers.begin(); p != layers.end(); ++p, type++) { + layer_t &l = *p; + + dout(2) << "layer " << type + << " " << l.name + << " bucket type " << l.buckettype + << " " << l.size + << dendl; + + crush.set_type_name(type, l.name); + + int buckettype = -1; + for (int i = 0; bucket_types[i].name; i++) + if (l.buckettype && strcmp(l.buckettype, bucket_types[i].name) == 0) { + buckettype = bucket_types[i].type; + break; + } + if (buckettype < 0) { + cerr << "unknown bucket type '" << l.buckettype << "'" << std::endl; + return EXIT_FAILURE; + } + + // build items + vector<int> cur_items; + vector<int> cur_weights; + unsigned lower_pos = 0; // lower pos + + dout(2) << "lower_items " << lower_items << dendl; + dout(2) << "lower_weights " << lower_weights << dendl; + + int i = 0; + while (1) { + if (lower_pos == lower_items.size()) + break; + + int items[num_osds]; + int weights[num_osds]; + + int weight = 0; + int j; + for (j=0; j<l.size || l.size==0; j++) { + if (lower_pos == lower_items.size()) + break; + items[j] = lower_items[lower_pos]; + weights[j] = lower_weights[lower_pos]; + weight += weights[j]; + lower_pos++; + dout(2) << " item " << items[j] << " weight " << weights[j] << dendl; + } + + int id; + int r = crush.add_bucket(0, buckettype, CRUSH_HASH_DEFAULT, type, j, items, weights, &id); + if (r < 0) { + cerr << " Couldn't add bucket: " << cpp_strerror(r) << std::endl; + return r; + } + + char format[20]; + format[sizeof(format)-1] = '\0'; + if (l.size) + snprintf(format, sizeof(format)-1, "%s%%d", l.name); + else + strncpy(format, l.name, sizeof(format)-1); + char name[20]; + snprintf(name, sizeof(name), format, i); + crush.set_item_name(id, name); + + dout(2) << " in bucket " << id << " '" << name << "' size " << j << " weight " << weight << dendl; + + cur_items.push_back(id); + cur_weights.push_back(weight); + i++; + } + + lower_items.swap(cur_items); + lower_weights.swap(cur_weights); + } + + string root = layers.back().size == 0 ? layers.back().name : + string(layers.back().name) + "0"; + + { + set<int> roots; + crush.find_roots(&roots); + if (roots.size() > 1) { + cerr << "The crush rules will use the root " << root << "\n" + << "and ignore the others.\n" + << "There are " << roots.size() << " roots, they can be\n" + << "grouped into a single root by appending something like:\n" + << " root straw 0\n" + << std::endl; + } + } + + if (OSDMap::build_simple_crush_rules(g_ceph_context, crush, root, &cerr)) + return EXIT_FAILURE; + + modified = true; + } + + // mutate ---- + + if (choose_local_tries >= 0) { + crush.set_choose_local_tries(choose_local_tries); + modified = true; + } + if (choose_local_fallback_tries >= 0) { + crush.set_choose_local_fallback_tries(choose_local_fallback_tries); + modified = true; + } + if (choose_total_tries >= 0) { + crush.set_choose_total_tries(choose_total_tries); + modified = true; + } + if (chooseleaf_descend_once >= 0) { + crush.set_chooseleaf_descend_once(chooseleaf_descend_once); + modified = true; + } + if (chooseleaf_vary_r >= 0) { + crush.set_chooseleaf_vary_r(chooseleaf_vary_r); + modified = true; + } + if (chooseleaf_stable >= 0) { + crush.set_chooseleaf_stable(chooseleaf_stable); + modified = true; + } + if (straw_calc_version >= 0) { + crush.set_straw_calc_version(straw_calc_version); + modified = true; + } + if (allowed_bucket_algs >= 0) { + crush.set_allowed_bucket_algs(allowed_bucket_algs); + modified = true; + } + + if (!reweight_name.empty()) { + cout << me << " reweighting item " << reweight_name << " to " << reweight_weight << std::endl; + int r; + if (!crush.name_exists(reweight_name)) { + cerr << " name " << reweight_name << " dne" << std::endl; + r = -ENOENT; + } else { + int item = crush.get_item_id(reweight_name); + r = crush.adjust_item_weightf(g_ceph_context, item, reweight_weight); + } + if (r >= 0) + modified = true; + else { + cerr << me << " " << cpp_strerror(r) << std::endl; + return r; + } + } + + if (!remove_name.empty()) { + cout << me << " removing item " << remove_name << std::endl; + int r; + if (!crush.name_exists(remove_name)) { + cerr << " name " << remove_name << " dne" << std::endl; + r = -ENOENT; + } else { + int remove_item = crush.get_item_id(remove_name); + r = crush.remove_item(g_ceph_context, remove_item, false); + } + if (r == 0) + modified = true; + else { + cerr << me << " " << cpp_strerror(r) << std::endl; + return r; + } + } + + if (add_item >= 0) { + int r; + if (update_item) { + r = crush.update_item(g_ceph_context, add_item, add_weight, add_name.c_str(), add_loc); + } else { + r = crush.insert_item(g_ceph_context, add_item, add_weight, add_name.c_str(), add_loc); + } + if (r >= 0) { + modified = true; + } else { + cerr << me << " " << cpp_strerror(r) << std::endl; + return r; + } + } + + if (add_bucket) { + if (int r = do_add_bucket(cct.get(), me, crush, add_name, add_type, add_loc); !r) { + modified = true; + } else { + return r; + } + } + + if (move_item) { + if (int r = do_move_item(cct.get(), me, crush, move_name, add_loc); !r) { + modified = true; + } else { + return r; + } + } + if (add_rule) { + if (crush.rule_exists(rule_name)) { + cerr << "rule " << rule_name << " already exists" << std::endl; + return EXIT_FAILURE; + } + int r = crush.add_simple_rule(rule_name, rule_root, rule_type, + rule_device_class, + rule_mode, pg_pool_t::TYPE_REPLICATED, &err); + if (r < 0) { + cerr << err.str() << std::endl; + return EXIT_FAILURE; + } + modified = true; + } + + if (del_rule) { + if (!crush.rule_exists(rule_name)) { + cerr << "rule " << rule_name << " does not exist" << std::endl; + return 0; + } + int ruleno = crush.get_rule_id(rule_name); + ceph_assert(ruleno >= 0); + int r = crush.remove_rule(ruleno); + if (r < 0) { + cerr << "fail to remove rule " << rule_name << std::endl; + return EXIT_FAILURE; + } + modified = true; + } + + if (reweight) { + crush.reweight(g_ceph_context); + modified = true; + } + if (rebuild_class_roots) { + int r = crush.rebuild_roots_with_classes(g_ceph_context); + if (r < 0) { + cerr << "failed to rebuidl roots with classes" << std::endl; + return EXIT_FAILURE; + } + modified = true; + } + + for (auto& i : set_subtree_class) { + crush.set_subtree_class(i.first, i.second); + modified = true; + } + if (reclassify) { + int r = crush.reclassify( + g_ceph_context, + cout, + reclassify_root, + reclassify_bucket); + if (r < 0) { + cerr << "failed to reclassify map" << std::endl; + return EXIT_FAILURE; + } + modified = true; + } + + // display --- + if (full_location >= 0) { + map<string, string> loc = crush.get_full_location(full_location); + for (map<string,string>::iterator p = loc.begin(); + p != loc.end(); + ++p) { + cout << p->first << "\t" << p->second << std::endl; + } + } + + if (tree) { + crush.dump_tree(&cout, NULL, {}, true); + } + + if (bucket_tree) { + if (bucket_name.empty()) { + cerr << ": error bucket_name is empty" << std::endl; + } + else { + set<int> osd_ids; + crush.get_leaves(bucket_name.c_str(), &osd_ids); + for (auto &id : osd_ids) { + cout << "osd." << id << std::endl; + } + } + } + + if (dump) { + boost::scoped_ptr<Formatter> f(Formatter::create(dump_format, "json-pretty", "json-pretty")); + f->open_object_section("crush_map"); + crush.dump(f.get()); + f->close_section(); + f->flush(cout); + cout << "\n"; + } + + if (decompile) { + CrushCompiler cc(crush, cerr, verbose); + if (!outfn.empty()) { + ofstream o; + o.open(outfn.c_str(), ios::out | ios::binary | ios::trunc); + if (!o.is_open()) { + cerr << me << ": error writing '" << outfn << "'" << std::endl; + return EXIT_FAILURE; + } + cc.decompile(o); + o.close(); + } else { + cc.decompile(cout); + } + } + + if (check) { + if (max_id >= 0) { + if (!tester.check_name_maps(max_id)) { + return EXIT_FAILURE; + } + } + } + + if (test) { + if (tester.get_output_utilization_all() || + tester.get_output_utilization()) + tester.set_output_statistics(true); + + int r = tester.test(cct->get()); + if (r < 0) + return EXIT_FAILURE; + } + + if (compare.size()) { + CrushWrapper crush2; + bufferlist in; + string error; + int r = in.read_file(compare.c_str(), &error); + if (r < 0) { + cerr << me << ": error reading '" << compare << "': " + << error << std::endl; + return EXIT_FAILURE; + } + auto p = in.cbegin(); + try { + crush2.decode(p); + } catch(...) { + cerr << me << ": unable to decode " << compare << std::endl; + return EXIT_FAILURE; + } + r = tester.compare(crush2); + if (r < 0) + return EXIT_FAILURE; + } + + // output --- + if (modified) { + crush.finalize(); + + if (outfn.empty()) { + cout << me << " successfully built or modified map. Use '-o <file>' to write it out." << std::endl; + } else { + bufferlist bl; + crush.encode(bl, CEPH_FEATURES_SUPPORTED_DEFAULT); + int r = bl.write_file(outfn.c_str()); + if (r < 0) { + cerr << me << ": error writing '" << outfn << "': " << cpp_strerror(r) << std::endl; + return EXIT_FAILURE; + } + if (verbose) + cout << "wrote crush map to " << outfn << std::endl; + } + } + + return 0; +} +/* + * Local Variables: + * compile-command: "cd .. ; make crushtool && test/run-cli-tests" + * End: + */ |