]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
crushtool: implement --reclassify
authorSage Weil <sage@redhat.com>
Fri, 5 Oct 2018 17:47:37 +0000 (12:47 -0500)
committerSage Weil <sage@redhat.com>
Tue, 16 Oct 2018 13:50:23 +0000 (08:50 -0500)
Add two modes of reclassification of existing hierarchies:

--classify-root <rootname> <class> will rewrite a hierarchy from an
existing root so that all of its devices are a different class.  Rules
that reference that root will be implicitly adjusted to 'take <rootname>
class <class>'. Ids will be preserved.

--classify-bucket <match> <class> <default-parent> will match a pattern
in the bucket name, where % at the beginning or end of the string is used
as a wildcard (e.g., "%-ssd" will match an "-ssd" suffix, "foo-%" will
match a "foo-" prefix).  Each such bucket is mapped to a "base" bucket
(with the suffix or prefix), items are labeled with the appropriate
class and the moved to that base bucket, and rules adjusted.  The
<default-parent> is used as the parent if the base bucket doesn't exist
and has to be created.

Similarly,
--classify-bucket <bucket> <class> <base-bucket> does the same but
a single existing bucket is mapped to an existing base bucket.  For
example, there is often an 'ssd' bucket that is the counterpart for
the 'default' root; '--classify-bucket ssd ssd default' will map it over.

Signed-off-by: Sage Weil <sage@redhat.com>
12 files changed:
src/crush/CrushWrapper.cc
src/crush/CrushWrapper.h
src/test/cli/crushtool/crush-classes/Eric.Smith@ccur.com [new file with mode: 0644]
src/test/cli/crushtool/crush-classes/dan@vanderster.com-beesly.crush [new file with mode: 0644]
src/test/cli/crushtool/crush-classes/dan@vanderster.com-flax.crush [new file with mode: 0644]
src/test/cli/crushtool/crush-classes/dan@vanderster.com-gabe.crush [new file with mode: 0644]
src/test/cli/crushtool/crush-classes/iversons@rushville.k12.in.us [new file with mode: 0644]
src/test/cli/crushtool/crush-classes/jwillem@stads.net [new file with mode: 0644]
src/test/cli/crushtool/crush-classes/michael.nieporte@uk-essen.de [new file with mode: 0644]
src/test/cli/crushtool/help.t [changed mode: 0755->0644]
src/test/cli/crushtool/reclassify.t [new file with mode: 0644]
src/tools/crushtool.cc

index d4585ce0ea0071960945cf6eaf4227aa22f6ac47..2b735d93f8cf7d103c5d4d3842af66b5ff36eb49 100644 (file)
@@ -1645,6 +1645,324 @@ int32_t CrushWrapper::_alloc_class_id() const {
   ceph_abort_msg("no available class id");
 }
 
+int CrushWrapper::reclassify(
+  CephContext *cct,
+  ostream& out,
+  const map<string,string>& classify_root,
+  const map<string,pair<string,string>>& classify_bucket
+  )
+{
+  map<int,string> reclassified_bucket; // orig_id -> class
+
+  // classify_root
+  for (auto& i : classify_root) {
+    string root = i.first;
+    if (!name_exists(root)) {
+      out << "root " << root << " does not exist" << std::endl;
+      return -EINVAL;
+    }
+    int root_id = get_item_id(root);
+    string new_class = i.second;
+    int new_class_id = -1;
+    out << "classify_root " << root << " (" << root_id
+       << ") as " << new_class << std::endl;
+    if (class_exists(new_class)) {
+      new_class_id = get_class_id(new_class);
+      out << "  new class " << new_class << " exists as " << new_class_id
+         << std::endl;
+    } else {
+      for (new_class_id = 1; class_name.count(new_class_id); ++new_class_id) ;
+      class_name[new_class_id] = new_class;
+      class_rname[new_class] = new_class_id;
+      out << "  created new class " << new_class << " as " << new_class_id
+         << std::endl;
+    }
+
+    // validate rules
+    for (unsigned j = 0; j < crush->max_rules; j++) {
+      if (crush->rules[j]) {
+       auto rule = crush->rules[j];
+       for (unsigned k = 0; k < rule->len; ++k) {
+         if (rule->steps[k].op == CRUSH_RULE_TAKE) {
+           int step_item = get_rule_arg1(j, k);
+           int original_item;
+           int c;
+           int res = split_id_class(step_item, &original_item, &c);
+           if (res < 0)
+             return res;
+           if (c >= 0) {
+             if (original_item == root_id) {
+               out << "  rule " << j << " includes take on root "
+                   << root << " class " << c << std::endl;
+               return -EINVAL;
+             }
+           }
+         }
+       }
+      }
+    }
+
+    // rebuild new buckets for root
+    //cout << "before class_bucket: " << class_bucket << std::endl;
+    map<int,int> renumber;
+    list<int> q;
+    q.push_back(root_id);
+    while (!q.empty()) {
+      int id = q.front();
+      q.pop_front();
+      crush_bucket *bucket = get_bucket(id);
+      if (IS_ERR(bucket)) {
+       out << "cannot find bucket " << id
+           << ": " << cpp_strerror(PTR_ERR(bucket)) << std::endl;
+       return PTR_ERR(bucket);
+      }
+
+      // move bucket
+      int new_id = get_new_bucket_id();
+      out << "  renumbering bucket " << id << " -> " << new_id << std::endl;
+      renumber[id] = new_id;
+      crush->buckets[-1-new_id] = bucket;
+      bucket->id = new_id;
+      crush->buckets[-1-id] = crush_make_bucket(crush,
+                                               bucket->alg,
+                                               bucket->hash,
+                                               bucket->type,
+                                               0, NULL, NULL);
+      crush->buckets[-1-id]->id = id;
+      for (auto& i : choose_args) {
+       i.second.args[-1-new_id] = i.second.args[-1-id];
+       memset(&i.second.args[-1-id], 0, sizeof(i.second.args[0]));
+      }
+      class_bucket.erase(id);
+      class_bucket[new_id][new_class_id] = id;
+      name_map[new_id] = string(get_item_name(id));
+      name_map[id] = string(get_item_name(id)) + "~" + new_class;
+
+      for (unsigned j = 0; j < bucket->size; ++j) {
+       if (bucket->items[j] < 0) {
+         q.push_front(bucket->items[j]);
+       } else {
+         // reclassify device
+         class_map[bucket->items[j]] = new_class_id;
+       }
+      }
+    }
+    //cout << "mid class_bucket: " << class_bucket << std::endl;
+
+    for (int i = 0; i < crush->max_buckets; ++i) {
+      crush_bucket *b = crush->buckets[i];
+      if (!b) {
+       continue;
+      }
+      for (unsigned j = 0; j < b->size; ++j) {
+       if (renumber.count(b->items[j])) {
+         b->items[j] = renumber[b->items[j]];
+       }
+      }
+    }
+
+    int r = rebuild_roots_with_classes();
+    if (r < 0) {
+      out << "failed to rebuild_roots_with_classes: " << cpp_strerror(r)
+         << std::endl;
+      return r;
+    }
+    //cout << "final class_bucket: " << class_bucket << std::endl;
+  }
+
+  // classify_bucket
+  map<int,int> send_to;  // source bucket -> dest bucket
+  map<int,map<int,int>> new_class_bucket;
+  map<int,string> new_bucket_names;
+  map<int,map<string,string>> new_buckets;
+  for (auto& i : classify_bucket) {
+    const string& match = i.first;  // prefix% or %suffix
+    const string& new_class = i.second.first;
+    const string& default_parent = i.second.second;
+    if (!name_exists(default_parent)) {
+      out << "default parent " << default_parent << " does not exist"
+         << std::endl;
+      return -EINVAL;
+    }
+    int default_parent_id = get_item_id(default_parent);
+    crush_bucket *default_parent_bucket = get_bucket(default_parent_id);
+    assert(default_parent_bucket);
+    string default_parent_type_name = get_type_name(default_parent_bucket->type);
+
+    out << "classify_bucket " << match << " as " << new_class
+       << " default bucket " << default_parent
+       << " (" << default_parent_type_name << ")" << std::endl;
+
+    int new_class_id = -1;
+    if (class_exists(new_class)) {
+      new_class_id = get_class_id(new_class);
+      out << "  new class " << new_class << " exists as " << new_class_id
+         << std::endl;
+    } else {
+      for (new_class_id = 1; class_name.count(new_class_id); ++new_class_id) {
+      }
+      class_name[new_class_id] = new_class;
+      class_rname[new_class] = new_class_id;
+      out << "  created new class " << new_class << " as " << new_class_id
+         << std::endl;
+    }
+
+    for (int j = 0; j < crush->max_buckets; ++j) {
+      crush_bucket *b = crush->buckets[j];
+      if (!b || is_shadow_item(b->id)) {
+       continue;
+      }
+      string name = get_item_name(b->id);
+      if (name.length() < match.length()) {
+       continue;
+      }
+      string basename;
+      if (match[0] == '%') {
+       if (match.substr(1) != name.substr(name.size() - match.size() + 1)) {
+         continue;
+       }
+       basename = name.substr(0, name.size() - match.size() + 1);
+      } else if (match[match.size() - 1] == '%') {
+       if (match.substr(0, match.size() - 1) !=
+           name.substr(0, match.size() - 1)) {
+         continue;
+       }
+       basename = name.substr(match.size() - 1);
+      } else if (match == name) {
+       basename = default_parent;
+      } else {
+       continue;
+      }
+      cout << "match " << match << " to " << name << " basename " << basename
+          << std::endl;
+      // look up or create basename bucket
+      int base_id;
+      if (name_exists(basename)) {
+       base_id = get_item_id(basename);
+       cout << "  have base " << base_id << std::endl;
+      } else {
+       base_id = get_new_bucket_id();
+       crush->buckets[-1-base_id] = crush_make_bucket(crush,
+                                                      b->alg,
+                                                      b->hash,
+                                                      b->type,
+                                                      0, NULL, NULL);
+       crush->buckets[-1-base_id]->id = base_id;
+       name_map[base_id] = basename;
+       cout << "  created base " << base_id << std::endl;
+
+       new_buckets[base_id][default_parent_type_name] = default_parent;
+      }
+      send_to[b->id] = base_id;
+      new_class_bucket[base_id][new_class_id] = b->id;
+      new_bucket_names[b->id] = basename + "~" + get_class_name(new_class_id);
+
+      // make sure devices are classified
+      for (unsigned i = 0; i < b->size; ++i) {
+       int item = b->items[i];
+       if (item >= 0) {
+         class_map[item] = new_class_id;
+       }
+      }
+    }
+  }
+
+  // no name_exists() works below,
+  have_rmaps = false;
+
+  // copy items around
+  //cout << "send_to " << send_to << std::endl;
+  set<int> roots;
+  find_roots(&roots);
+  for (auto& i : send_to) {
+    crush_bucket *from = get_bucket(i.first);
+    crush_bucket *to = get_bucket(i.second);
+    cout << "moving items from " << from->id << " (" << get_item_name(from->id)
+        << ") to " << to->id << " (" << get_item_name(to->id) << ")"
+        << std::endl;
+    for (unsigned j = 0; j < from->size; ++j) {
+      int item = from->items[j];
+      int r;
+      map<string,string> to_loc;
+      to_loc[get_type_name(to->type)] = get_item_name(to->id);
+      if (item >= 0) {
+       if (subtree_contains(to->id, item)) {
+         continue;
+       }
+       map<string,string> from_loc;
+       from_loc[get_type_name(from->type)] = get_item_name(from->id);
+       auto w = get_item_weightf_in_loc(item, from_loc);
+       r = insert_item(cct, item,
+                       w,
+                       get_item_name(item),
+                       to_loc);
+      } else {
+       if (!send_to.count(item)) {
+         lderr(cct) << "item " << item << " in bucket " << from->id
+              << " is not also a reclassified bucket" << dendl;
+         return -EINVAL;
+       }
+       int newitem = send_to[item];
+       if (subtree_contains(to->id, newitem)) {
+         continue;
+       }
+       r = link_bucket(cct, newitem, to_loc);
+      }
+      if (r != 0) {
+       cout << __func__ << " err from insert_item: " << cpp_strerror(r)
+            << std::endl;
+       return r;
+      }
+    }
+  }
+
+  // set class mappings
+  //cout << "pre class_bucket: " << class_bucket << std::endl;
+  for (auto& i : new_class_bucket) {
+    for (auto& j : i.second) {
+      class_bucket[i.first][j.first] = j.second;
+    }
+
+  }
+  //cout << "post class_bucket: " << class_bucket << std::endl;
+  for (auto& i : new_bucket_names) {
+    name_map[i.first] = i.second;
+  }
+
+  int r = rebuild_roots_with_classes();
+  if (r < 0) {
+    out << "failed to rebuild_roots_with_classes: " << cpp_strerror(r)
+       << std::endl;
+    return r;
+  }
+  //cout << "final class_bucket: " << class_bucket << std::endl;
+
+  return 0;
+}
+
+int CrushWrapper::get_new_bucket_id()
+{
+  int id = -1;
+  while (crush->buckets[-1-id] &&
+        -1-id < crush->max_buckets) {
+    id--;
+  }
+  if (-1-id == crush->max_buckets) {
+    ++crush->max_buckets;
+    crush->buckets = (struct crush_bucket**)realloc(
+      crush->buckets,
+      sizeof(crush->buckets[0]) * crush->max_buckets);
+    for (auto& i : choose_args) {
+      assert(i.second.size == crush->max_buckets - 1);
+      ++i.second.size;
+      i.second.args = (struct crush_choose_arg*)realloc(
+       i.second.args,
+       sizeof(i.second.args[0]) * i.second.size);
+    }
+  }
+  return id;
+}
+
 void CrushWrapper::reweight(CephContext *cct)
 {
   set<int> roots;
index cbe487ce5376195a11ef7f576599f020b3751e48..08c5759e85b4288de45d32bb5fcf01e8dd15b4ed 100644 (file)
@@ -1201,6 +1201,8 @@ private:
    **/
   int detach_bucket(CephContext *cct, int item);
 
+  int get_new_bucket_id();
+
 public:
   int get_max_buckets() const {
     if (!crush) return -EINVAL;
@@ -1301,6 +1303,13 @@ public:
   /* remove unused roots generated for class devices */
   int trim_roots_with_class();
 
+  int reclassify(
+    CephContext *cct,
+    ostream& out,
+    const map<string,string>& classify_root,
+    const map<string,pair<string,string>>& classify_bucket
+    );
+
   void start_choose_profile() {
     free(crush->choose_tries);
     /*
diff --git a/src/test/cli/crushtool/crush-classes/Eric.Smith@ccur.com b/src/test/cli/crushtool/crush-classes/Eric.Smith@ccur.com
new file mode 100644 (file)
index 0000000..e431636
Binary files /dev/null and b/src/test/cli/crushtool/crush-classes/Eric.Smith@ccur.com differ
diff --git a/src/test/cli/crushtool/crush-classes/dan@vanderster.com-beesly.crush b/src/test/cli/crushtool/crush-classes/dan@vanderster.com-beesly.crush
new file mode 100644 (file)
index 0000000..0048b14
Binary files /dev/null and b/src/test/cli/crushtool/crush-classes/dan@vanderster.com-beesly.crush differ
diff --git a/src/test/cli/crushtool/crush-classes/dan@vanderster.com-flax.crush b/src/test/cli/crushtool/crush-classes/dan@vanderster.com-flax.crush
new file mode 100644 (file)
index 0000000..4f579dd
Binary files /dev/null and b/src/test/cli/crushtool/crush-classes/dan@vanderster.com-flax.crush differ
diff --git a/src/test/cli/crushtool/crush-classes/dan@vanderster.com-gabe.crush b/src/test/cli/crushtool/crush-classes/dan@vanderster.com-gabe.crush
new file mode 100644 (file)
index 0000000..90e8ed7
Binary files /dev/null and b/src/test/cli/crushtool/crush-classes/dan@vanderster.com-gabe.crush differ
diff --git a/src/test/cli/crushtool/crush-classes/iversons@rushville.k12.in.us b/src/test/cli/crushtool/crush-classes/iversons@rushville.k12.in.us
new file mode 100644 (file)
index 0000000..a1720c7
Binary files /dev/null and b/src/test/cli/crushtool/crush-classes/iversons@rushville.k12.in.us differ
diff --git a/src/test/cli/crushtool/crush-classes/jwillem@stads.net b/src/test/cli/crushtool/crush-classes/jwillem@stads.net
new file mode 100644 (file)
index 0000000..e709cb9
Binary files /dev/null and b/src/test/cli/crushtool/crush-classes/jwillem@stads.net differ
diff --git a/src/test/cli/crushtool/crush-classes/michael.nieporte@uk-essen.de b/src/test/cli/crushtool/crush-classes/michael.nieporte@uk-essen.de
new file mode 100644 (file)
index 0000000..68c900f
Binary files /dev/null and b/src/test/cli/crushtool/crush-classes/michael.nieporte@uk-essen.de differ
old mode 100755 (executable)
new mode 100644 (file)
index 3db3c25..de42cd5
                            export select data generated during testing routine
                            to CSV files for off-line post-processing
                            use --help-output for more information
+     --reclassify          transform legacy CRUSH map buckets and rules
+                           by adding classes
+        --reclassify-bucket <bucket-match> <class> <default-parent>
+        --reclassify-root <bucket-name> <class>
   
   Options for the output stage
   
diff --git a/src/test/cli/crushtool/reclassify.t b/src/test/cli/crushtool/reclassify.t
new file mode 100644 (file)
index 0000000..fc5397b
--- /dev/null
@@ -0,0 +1,385 @@
+  $ crushtool -i $TESTDIR/crush-classes/Eric.Smith@ccur.com --reclassify --reclassify-bucket %-ssd ssd default --reclassify-bucket ssd ssd default --reclassify-root default hdd -o foo
+  classify_root default (-1) as hdd
+    created new class hdd as 1
+    renumbering bucket -1 -> -13
+    renumbering bucket -6 -> -14
+    renumbering bucket -5 -> -15
+    renumbering bucket -4 -> -16
+    renumbering bucket -3 -> -17
+    renumbering bucket -2 -> -18
+  classify_bucket %-ssd as ssd default bucket default (root)
+    created new class ssd as 2
+  match %-ssd to node-20-ssd basename node-20
+    have base -18
+  match %-ssd to node-21-ssd basename node-21
+    created base -25
+  match %-ssd to node-22-ssd basename node-22
+    created base -26
+  match %-ssd to node-23-ssd basename node-23
+    created base -27
+  match %-ssd to node-27-ssd basename node-27
+    created base -28
+  classify_bucket ssd as ssd default bucket default (root)
+    new class ssd exists as 2
+  match ssd to ssd basename default
+    have base -13
+  moving items from -12 (node-27-ssd) to -28 (node-27)
+  moving items from -11 (node-23-ssd) to -27 (node-23)
+  moving items from -10 (node-22-ssd) to -26 (node-22)
+  moving items from -9 (node-21-ssd) to -25 (node-21)
+  moving items from -8 (node-20-ssd) to -18 (node-20)
+  moving items from -7 (ssd) to -13 (default)
+  $ crushtool -i $TESTDIR/crush-classes/Eric.Smith@ccur.com --compare foo
+  rule 0 had 0/10240 mismatched mappings (0)
+  rule 1 had 0/10240 mismatched mappings (0)
+  maps appear equivalent
+
+  $ crushtool -i $TESTDIR/crush-classes/Eric.Smith@ccur.com --reclassify --reclassify-bucket %-ssd ssd default --reclassify-bucket ssd ssd default --reclassify-root default hdd -o foo
+  classify_root default (-1) as hdd
+    created new class hdd as 1
+    renumbering bucket -1 -> -13
+    renumbering bucket -6 -> -14
+    renumbering bucket -5 -> -15
+    renumbering bucket -4 -> -16
+    renumbering bucket -3 -> -17
+    renumbering bucket -2 -> -18
+  classify_bucket %-ssd as ssd default bucket default (root)
+    created new class ssd as 2
+  match %-ssd to node-20-ssd basename node-20
+    have base -18
+  match %-ssd to node-21-ssd basename node-21
+    created base -25
+  match %-ssd to node-22-ssd basename node-22
+    created base -26
+  match %-ssd to node-23-ssd basename node-23
+    created base -27
+  match %-ssd to node-27-ssd basename node-27
+    created base -28
+  classify_bucket ssd as ssd default bucket default (root)
+    new class ssd exists as 2
+  match ssd to ssd basename default
+    have base -13
+  moving items from -12 (node-27-ssd) to -28 (node-27)
+  moving items from -11 (node-23-ssd) to -27 (node-23)
+  moving items from -10 (node-22-ssd) to -26 (node-22)
+  moving items from -9 (node-21-ssd) to -25 (node-21)
+  moving items from -8 (node-20-ssd) to -18 (node-20)
+  moving items from -7 (ssd) to -13 (default)
+  $ crushtool -i $TESTDIR/crush-classes/Eric.Smith@ccur.com --compare foo
+  rule 0 had 0/10240 mismatched mappings (0)
+  rule 1 had 0/10240 mismatched mappings (0)
+  maps appear equivalent
+
+  $ crushtool -i $TESTDIR/crush-classes/iversons@rushville.k12.in.us --reclassify --reclassify-bucket ceph-osd-ssd-% ssd default --reclassify-bucket ssd-root ssd default --reclassify-root default hdd -o foo
+  classify_root default (-1) as hdd
+    new class hdd exists as 1
+    renumbering bucket -1 -> -55
+    renumbering bucket -34 -> -56
+    renumbering bucket -20 -> -57
+    renumbering bucket -14 -> -58
+    renumbering bucket -15 -> -59
+    renumbering bucket -16 -> -60
+    renumbering bucket -52 -> -61
+    renumbering bucket -46 -> -62
+    renumbering bucket -40 -> -63
+  classify_bucket ceph-osd-ssd-% as ssd default bucket default (root)
+    new class ssd exists as 0
+  match ceph-osd-ssd-% to ceph-osd-ssd-node4 basename node4
+    have base -57
+  match ceph-osd-ssd-% to ceph-osd-ssd-node3 basename node3
+    have base -58
+  match ceph-osd-ssd-% to ceph-osd-ssd-node1 basename node1
+    have base -60
+  match ceph-osd-ssd-% to ceph-osd-ssd-node2 basename node2
+    have base -59
+  match ceph-osd-ssd-% to ceph-osd-ssd-node5 basename node5
+    have base -56
+  match ceph-osd-ssd-% to ceph-osd-ssd-node6 basename node6
+    have base -63
+  match ceph-osd-ssd-% to ceph-osd-ssd-node7 basename node7
+    have base -62
+  match ceph-osd-ssd-% to ceph-osd-ssd-node8 basename node8
+    have base -61
+  classify_bucket ssd-root as ssd default bucket default (root)
+    new class ssd exists as 0
+  match ssd-root to ssd-root basename default
+    have base -55
+  moving items from -49 (ceph-osd-ssd-node8) to -61 (node8)
+  moving items from -43 (ceph-osd-ssd-node7) to -62 (node7)
+  moving items from -37 (ceph-osd-ssd-node6) to -63 (node6)
+  moving items from -31 (ceph-osd-ssd-node5) to -56 (node5)
+  moving items from -18 (ssd-root) to -55 (default)
+  moving items from -9 (ceph-osd-ssd-node2) to -59 (node2)
+  moving items from -7 (ceph-osd-ssd-node1) to -60 (node1)
+  moving items from -5 (ceph-osd-ssd-node3) to -58 (node3)
+  moving items from -3 (ceph-osd-ssd-node4) to -57 (node4)
+
+this one has weird node weights, so *lots* of mappings change...
+
+  $ crushtool -i $TESTDIR/crush-classes/iversons@rushville.k12.in.us --compare foo
+  rule 0 had 6540/10240 mismatched mappings (0.638672)
+  rule 1 had 8417/10240 mismatched mappings (0.821973)
+  warning: maps are NOT equivalent
+  [1]
+
+  $ crushtool -i $TESTDIR/crush-classes/jwillem@stads.net --reclassify --reclassify-bucket %-SSD ssd default --reclassify-bucket ssd ssd default --reclassify-root default hdd -o foo
+  classify_root default (-1) as hdd
+    new class hdd exists as 0
+    renumbering bucket -1 -> -55
+    renumbering bucket -9 -> -56
+    renumbering bucket -8 -> -57
+    renumbering bucket -7 -> -58
+    renumbering bucket -6 -> -59
+    renumbering bucket -5 -> -60
+    renumbering bucket -4 -> -61
+    renumbering bucket -3 -> -62
+    renumbering bucket -2 -> -63
+  classify_bucket %-SSD as ssd default bucket default (root)
+    created new class ssd as 2
+  match %-SSD to Ceph-Stor1-SSD basename Ceph-Stor1
+    have base -63
+  match %-SSD to Ceph-Stor2-SSD basename Ceph-Stor2
+    have base -62
+  match %-SSD to Ceph-Stor3-SSD basename Ceph-Stor3
+    have base -61
+  match %-SSD to Ceph-Stor4-SSD basename Ceph-Stor4
+    have base -60
+  match %-SSD to Ceph-Stor5-SSD basename Ceph-Stor5
+    have base -59
+  match %-SSD to Ceph-Stor6-SSD basename Ceph-Stor6
+    have base -58
+  match %-SSD to Ceph-Stor7-SSD basename Ceph-Stor7
+    have base -57
+  match %-SSD to Ceph-Stor8-SSD basename Ceph-Stor8
+    have base -56
+  classify_bucket ssd as ssd default bucket default (root)
+    new class ssd exists as 2
+  match ssd to ssd basename default
+    have base -55
+  moving items from -18 (ssd) to -55 (default)
+  moving items from -17 (Ceph-Stor8-SSD) to -56 (Ceph-Stor8)
+  moving items from -16 (Ceph-Stor7-SSD) to -57 (Ceph-Stor7)
+  moving items from -15 (Ceph-Stor6-SSD) to -58 (Ceph-Stor6)
+  moving items from -14 (Ceph-Stor5-SSD) to -59 (Ceph-Stor5)
+  moving items from -13 (Ceph-Stor4-SSD) to -60 (Ceph-Stor4)
+  moving items from -12 (Ceph-Stor3-SSD) to -61 (Ceph-Stor3)
+  moving items from -11 (Ceph-Stor2-SSD) to -62 (Ceph-Stor2)
+  moving items from -10 (Ceph-Stor1-SSD) to -63 (Ceph-Stor1)
+
+wonky crush weights on Ceph-Stor1, so a small number of mappings change
+because the new map has a strictly summing hierarchy.
+
+  $ crushtool -i $TESTDIR/crush-classes/jwillem@stads.net --compare foo
+  rule 0 had 158/10240 mismatched mappings (0.0154297)
+  rule 1 had 62/5120 mismatched mappings (0.0121094)
+  rule 2 had 0/10240 mismatched mappings (0)
+  warning: maps are NOT equivalent
+  [1]
+
+  $ crushtool -i $TESTDIR/crush-classes/dan@vanderster.com-beesly.crush --reclassify --reclassify-root 0513-R-0050 hdd --reclassify-root 0513-R-0060 hdd -o foo
+  classify_root 0513-R-0050 (-2) as hdd
+    new class hdd exists as 0
+    renumbering bucket -2 -> -131
+    renumbering bucket -14 -> -132
+    renumbering bucket -34 -> -133
+    renumbering bucket -33 -> -134
+    renumbering bucket -30 -> -135
+    renumbering bucket -26 -> -136
+    renumbering bucket -22 -> -137
+    renumbering bucket -18 -> -138
+    renumbering bucket -13 -> -139
+    renumbering bucket -9 -> -140
+    renumbering bucket -12 -> -141
+    renumbering bucket -11 -> -142
+    renumbering bucket -32 -> -143
+    renumbering bucket -31 -> -144
+    renumbering bucket -10 -> -145
+    renumbering bucket -8 -> -146
+    renumbering bucket -6 -> -147
+    renumbering bucket -28 -> -148
+    renumbering bucket -27 -> -149
+    renumbering bucket -21 -> -150
+    renumbering bucket -20 -> -151
+    renumbering bucket -19 -> -152
+    renumbering bucket -7 -> -153
+    renumbering bucket -5 -> -154
+    renumbering bucket -4 -> -155
+    renumbering bucket -25 -> -156
+    renumbering bucket -24 -> -157
+    renumbering bucket -23 -> -158
+    renumbering bucket -17 -> -159
+    renumbering bucket -16 -> -160
+    renumbering bucket -15 -> -161
+    renumbering bucket -3 -> -162
+    renumbering bucket -72 -> -163
+    renumbering bucket -98 -> -164
+    renumbering bucket -97 -> -165
+    renumbering bucket -96 -> -166
+    renumbering bucket -95 -> -167
+    renumbering bucket -94 -> -168
+    renumbering bucket -93 -> -169
+    renumbering bucket -68 -> -170
+  classify_root 0513-R-0060 (-65) as hdd
+    new class hdd exists as 0
+    renumbering bucket -65 -> -35
+    renumbering bucket -76 -> -36
+    renumbering bucket -78 -> -37
+    renumbering bucket -87 -> -38
+    renumbering bucket -82 -> -39
+    renumbering bucket -81 -> -40
+    renumbering bucket -77 -> -41
+    renumbering bucket -75 -> -42
+    renumbering bucket -89 -> -43
+    renumbering bucket -85 -> -44
+    renumbering bucket -84 -> -45
+    renumbering bucket -74 -> -46
+    renumbering bucket -71 -> -47
+    renumbering bucket -80 -> -48
+    renumbering bucket -91 -> -49
+    renumbering bucket -90 -> -50
+    renumbering bucket -88 -> -51
+    renumbering bucket -79 -> -52
+    renumbering bucket -70 -> -53
+    renumbering bucket -86 -> -54
+    renumbering bucket -83 -> -55
+    renumbering bucket -73 -> -56
+    renumbering bucket -69 -> -57
+  $ crushtool -i $TESTDIR/crush-classes/dan@vanderster.com-beesly.crush --compare foo
+  rule 0 had 0/10240 mismatched mappings (0)
+  rule 1 had 0/10240 mismatched mappings (0)
+  rule 2 had 0/10240 mismatched mappings (0)
+  rule 4 had 0/10240 mismatched mappings (0)
+  maps appear equivalent
+
+  $ crushtool -i $TESTDIR/crush-classes/dan@vanderster.com-flax.crush --reclassify --reclassify-root default hdd -o foo
+  classify_root default (-1) as hdd
+    new class hdd exists as 0
+    renumbering bucket -1 -> -5
+    renumbering bucket -12 -> -7
+    renumbering bucket -9 -> -8
+    renumbering bucket -6 -> -10
+    renumbering bucket -4 -> -11
+    renumbering bucket -3 -> -13
+    renumbering bucket -2 -> -14
+  $ crushtool -i $TESTDIR/crush-classes/dan@vanderster.com-flax.crush --compare foo
+  rule 0 had 0/10240 mismatched mappings (0)
+  maps appear equivalent
+
+  $ crushtool -i $TESTDIR/crush-classes/dan@vanderster.com-gabe.crush --reclassify --reclassify-root default hdd -o foo
+  classify_root default (-1) as hdd
+    new class hdd exists as 0
+    rule 3 includes take on root default class 0
+  failed to reclassify map
+  [1]
+
+above fails because of ec-rack-by-2-hdd also has take default class hdd
+
+
+  $ crushtool -i $TESTDIR/crush-classes/michael.nieporte@uk-essen.de --reclassify --reclassify-bucket %-hdd hdd default --reclassify-bucket %-ssd ssd default --reclassify-bucket ssd ssd default --reclassify-bucket hdd hdd default -o foo
+  classify_bucket %-hdd as hdd default bucket default (root)
+    new class hdd exists as 0
+  match %-hdd to berta-hdd basename berta
+    have base -37
+  match %-hdd to oelgard-hdd basename oelgard
+    have base -36
+  match %-hdd to leonhard-hdd basename leonhard
+    have base -33
+  match %-hdd to gottlieb-hdd basename gottlieb
+    have base -30
+  match %-hdd to hieronymus-hdd basename hieronymus
+    have base -31
+  match %-hdd to uhu-hdd basename uhu
+    have base -34
+  match %-hdd to euphrosyne-hdd basename euphrosyne
+    have base -35
+  match %-hdd to frauenhaus-hdd basename frauenhaus
+    created base -145
+  match %-hdd to herrenhaus-hdd basename herrenhaus
+    created base -146
+  match %-hdd to zoo-hdd basename zoo
+    created base -147
+  match %-hdd to borkenkaefer-hdd basename borkenkaefer
+    have base -4
+  match %-hdd to hirsch-hdd basename hirsch
+    have base -41
+  match %-hdd to cassowary-hdd basename cassowary
+    created base -148
+  match %-hdd to fuchs-hdd basename fuchs
+    created base -149
+  match %-hdd to analia-hdd basename analia
+    created base -150
+  match %-hdd to gundula-hdd basename gundula
+    created base -151
+  match %-hdd to achim-hdd basename achim
+    created base -152
+  match %-hdd to hugo-hdd basename hugo
+    created base -153
+  match %-hdd to carl-hdd basename carl
+    have base -32
+  classify_bucket %-ssd as ssd default bucket default (root)
+    new class ssd exists as 1
+  match %-ssd to frauenhaus-ssd basename frauenhaus
+    created base -154
+  match %-ssd to herrenhaus-ssd basename herrenhaus
+    created base -155
+  match %-ssd to zoo-ssd basename zoo
+    created base -156
+  match %-ssd to berta-ssd basename berta
+    have base -37
+  match %-ssd to euphrosyne-ssd basename euphrosyne
+    have base -35
+  match %-ssd to oelgard-ssd basename oelgard
+    have base -36
+  match %-ssd to leonhard-ssd basename leonhard
+    have base -33
+  match %-ssd to hieronymus-ssd basename hieronymus
+    have base -31
+  match %-ssd to gottlieb-ssd basename gottlieb
+    have base -30
+  match %-ssd to uhu-ssd basename uhu
+    have base -34
+  match %-ssd to borkenkaefer-ssd basename borkenkaefer
+    have base -4
+  match %-ssd to hirsch-ssd basename hirsch
+    have base -41
+  match %-ssd to phaidon-ssd basename phaidon
+    created base -157
+  match %-ssd to glykera-ssd basename glykera
+    created base -158
+  match %-ssd to bonobo-ssd basename bonobo
+    created base -159
+  classify_bucket hdd as hdd default bucket default (root)
+    new class hdd exists as 0
+  match hdd to hdd basename default
+    have base -1
+  classify_bucket ssd as ssd default bucket default (root)
+    new class ssd exists as 1
+  match ssd to ssd basename default
+    have base -1
+  moving items from -124 (bonobo-ssd) to -159 (bonobo)
+  moving items from -123 (glykera-ssd) to -158 (glykera)
+  moving items from -122 (phaidon-ssd) to -157 (phaidon)
+  moving items from -121 (carl-hdd) to -32 (carl)
+  moving items from -120 (hugo-hdd) to -153 (hugo)
+  moving items from -119 (achim-hdd) to -152 (achim)
+  moving items from -118 (gundula-hdd) to -151 (gundula)
+  moving items from -117 (analia-hdd) to -150 (analia)
+  moving items from -116 (fuchs-hdd) to -149 (fuchs)
+  moving items from -115 (cassowary-hdd) to -148 (cassowary)
+  moving items from -39 (hirsch-ssd) to -41 (hirsch)
+  moving items from -38 (hirsch-hdd) to -41 (hirsch)
+  moving items from -29 (borkenkaefer-ssd) to -4 (borkenkaefer)
+  moving items from -28 (hdd) to -1 (default)
+  moving items from -27 (ssd) to -1 (default)
+  reclassify err from insert_item: (17) File exists
+  failed to reclassify map
+  [1]
+
+this makes changes, but it doesn't really clean up the map, which is
+a mess!
+
+  $ crushtool -i $TESTDIR/crush-classes/michael.nieporte@uk-essen.de --compare foo
+  rule 0 had 3060/3072 mismatched mappings (0.996094)
+  rule 1 had 4096/4096 mismatched mappings (1)
+  warning: maps are NOT equivalent
+  [1]
index 7006bbf9efa0e7948041d6c8e0d326cd39fa0fb1..3d153e6b76584fac349f5a116b37faece8607487 100644 (file)
@@ -218,6 +218,10 @@ void usage()
   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 << "\n";
   cout << "Options for the output stage\n";
   cout << "\n";
@@ -408,6 +412,10 @@ int main(int argc, const char **argv)
   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
+
   CrushWrapper crush;
 
   CrushTester tester(crush, cout);
@@ -442,6 +450,30 @@ int main(int argc, const char **argv)
       outfn = val;
     } else if (ceph_argparse_flag(args, i, "-v", "--verbose", (char*)NULL)) {
       verbose += 1;
+    } 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_flag(args, i, "--tree", (char*)NULL)) {
       tree = true;
     } else if (ceph_argparse_witharg(args, i, &val, "-f", "--format", (char*)NULL)) {
@@ -773,6 +805,7 @@ int main(int argc, const char **argv)
   }
   if (!check && !compile && !decompile && !build && !test && !reweight && !adjust && !tree && !dump &&
       add_item < 0 && !add_bucket && !move_item && !add_rule && !del_rule && full_location < 0 &&
+      !reclassify &&
       remove_name.empty() && reweight_name.empty()) {
     cerr << "no action specified; -h for help" << std::endl;
     return EXIT_FAILURE;
@@ -1115,6 +1148,18 @@ int main(int argc, const char **argv)
     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) {
@@ -1127,7 +1172,7 @@ int main(int argc, const char **argv)
   }
 
   if (tree) {
-    crush.dump_tree(&cout, NULL);
+    crush.dump_tree(&cout, NULL, {}, true);
   }
 
   if (dump) {