]> git.proxmox.com Git - ceph.git/blame - ceph/src/crush/CrushCompiler.cc
bump version to 18.2.2-pve1
[ceph.git] / ceph / src / crush / CrushCompiler.cc
CommitLineData
9f95a23c
TL
1// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2// vim: ts=8 sw=2 smarttab
7c673cae
FG
3
4#include "CrushCompiler.h"
5
6#if defined(_AIX)
7#define EBADE ECORRUPT
8#endif
9
10#ifndef EBADE
11#define EBADE EFTYPE
12#endif
7c673cae 13#include <string>
7c673cae
FG
14#include "common/errno.h"
15#include <boost/algorithm/string.hpp>
16
9f95a23c
TL
17using std::cout;
18using std::istream;
19using std::map;
20using std::ostream;
21using std::set;
22using std::string;
23using std::vector;
24
7c673cae
FG
25// -------------
26
27static void print_type_name(ostream& out, int t, CrushWrapper &crush)
28{
29 const char *name = crush.get_type_name(t);
30 if (name)
31 out << name;
32 else if (t == 0)
33 out << "device";
34 else
35 out << "type" << t;
36}
37
38static void print_item_name(ostream& out, int t, CrushWrapper &crush)
39{
40 const char *name = crush.get_item_name(t);
41 if (name)
42 out << name;
43 else if (t >= 0)
44 out << "device" << t;
45 else
46 out << "bucket" << (-1-t);
47}
48
49static void print_bucket_class_ids(ostream& out, int t, CrushWrapper &crush)
50{
51 if (crush.class_bucket.count(t) == 0)
52 return;
53 auto &class_to_id = crush.class_bucket[t];
54 for (auto &i : class_to_id) {
55 int c = i.first;
56 int cid = i.second;
57 const char* class_name = crush.get_class_name(c);
11fdf7f2 58 ceph_assert(class_name);
7c673cae
FG
59 out << "\tid " << cid << " class " << class_name << "\t\t# do not change unnecessarily\n";
60 }
61}
62
63static void print_item_class(ostream& out, int t, CrushWrapper &crush)
64{
65 const char *c = crush.get_item_class(t);
66 if (c)
67 out << " class " << c;
68}
69
70static void print_class(ostream& out, int t, CrushWrapper &crush)
71{
72 const char *c = crush.get_class_name(t);
73 if (c)
74 out << " class " << c;
75 else
76 out << " # unexpected class " << t;
77}
78
79static void print_rule_name(ostream& out, int t, CrushWrapper &crush)
80{
81 const char *name = crush.get_rule_name(t);
82 if (name)
83 out << name;
84 else
85 out << "rule" << t;
86}
87
88static void print_fixedpoint(ostream& out, int i)
89{
90 char s[20];
20effc67 91 snprintf(s, sizeof(s), "%.5f", (float)i / (float)0x10000);
7c673cae
FG
92 out << s;
93}
94
95int CrushCompiler::decompile_bucket_impl(int i, ostream &out)
96{
97 const char *name = crush.get_item_name(i);
98 if (name && !crush.is_valid_crush_name(name))
99 return 0;
100 int type = crush.get_bucket_type(i);
101 print_type_name(out, type, crush);
102 out << " ";
103 print_item_name(out, i, crush);
104 out << " {\n";
105 out << "\tid " << i << "\t\t# do not change unnecessarily\n";
106 print_bucket_class_ids(out, i, crush);
107
108 out << "\t# weight ";
109 print_fixedpoint(out, crush.get_bucket_weight(i));
110 out << "\n";
111
112 int n = crush.get_bucket_size(i);
113
114 int alg = crush.get_bucket_alg(i);
115 out << "\talg " << crush_bucket_alg_name(alg);
116
117 // notate based on alg type
118 bool dopos = false;
119 switch (alg) {
120 case CRUSH_BUCKET_UNIFORM:
121 out << "\t# do not change bucket size (" << n << ") unnecessarily";
122 dopos = true;
123 break;
124 case CRUSH_BUCKET_LIST:
125 out << "\t# add new items at the end; do not change order unnecessarily";
126 break;
127 case CRUSH_BUCKET_TREE:
128 out << "\t# do not change pos for existing items unnecessarily";
129 dopos = true;
130 break;
131 }
132 out << "\n";
133
134 int hash = crush.get_bucket_hash(i);
135 out << "\thash " << hash << "\t# " << crush_hash_name(hash) << "\n";
136
137 for (int j=0; j<n; j++) {
138 int item = crush.get_bucket_item(i, j);
139 int w = crush.get_bucket_item_weight(i, j);
140 out << "\titem ";
141 print_item_name(out, item, crush);
142 out << " weight ";
143 print_fixedpoint(out, w);
144 if (dopos)
145 out << " pos " << j;
146
147 out << "\n";
148 }
149 out << "}\n";
150 return 0;
151}
152
153/* Basically, we just descend recursively into all of the buckets,
154 * executing a depth-first traversal of the graph. Since the buckets form a
155 * directed acyclic graph, this should work just fine. The graph isn't
156 * necessarily a tree, so we have to keep track of what buckets we already
157 * outputted. We don't want to output anything twice. We also keep track of
158 * what buckets are in progress so that we can detect cycles. These can
159 * arise through user error.
160 */
161int CrushCompiler::decompile_bucket(int cur,
162 std::map<int, dcb_state_t>& dcb_states,
163 ostream &out)
164{
165 if ((cur == 0) || (!crush.bucket_exists(cur)))
166 return 0;
167
168 std::map<int, dcb_state_t>::iterator c = dcb_states.find(cur);
169 if (c == dcb_states.end()) {
170 // Mark this bucket as "in progress."
171 std::map<int, dcb_state_t>::value_type val(cur, DCB_STATE_IN_PROGRESS);
172 std::pair <std::map<int, dcb_state_t>::iterator, bool> rval
173 (dcb_states.insert(val));
11fdf7f2 174 ceph_assert(rval.second);
7c673cae
FG
175 c = rval.first;
176 }
177 else if (c->second == DCB_STATE_DONE) {
178 // We already did this bucket.
179 return 0;
180 }
181 else if (c->second == DCB_STATE_IN_PROGRESS) {
182 err << "decompile_crush_bucket: logic error: tried to decompile "
183 "a bucket that is already being decompiled" << std::endl;
184 return -EBADE;
185 }
186 else {
187 err << "decompile_crush_bucket: logic error: illegal bucket state! "
188 << c->second << std::endl;
189 return -EBADE;
190 }
191
192 int bsize = crush.get_bucket_size(cur);
193 for (int i = 0; i < bsize; ++i) {
194 int item = crush.get_bucket_item(cur, i);
195 std::map<int, dcb_state_t>::iterator d = dcb_states.find(item);
196 if (d == dcb_states.end()) {
197 int ret = decompile_bucket(item, dcb_states, out);
198 if (ret)
199 return ret;
200 }
201 else if (d->second == DCB_STATE_IN_PROGRESS) {
202 err << "decompile_crush_bucket: error: while trying to output bucket "
203 << cur << ", we found out that it contains one of the buckets that "
204 << "contain it. This is not allowed. The buckets must form a "
205 << "directed acyclic graph." << std::endl;
206 return -EINVAL;
207 }
208 else if (d->second != DCB_STATE_DONE) {
209 err << "decompile_crush_bucket: logic error: illegal bucket state "
210 << d->second << std::endl;
211 return -EBADE;
212 }
213 }
214 decompile_bucket_impl(cur, out);
215 c->second = DCB_STATE_DONE;
216 return 0;
217}
218
219int CrushCompiler::decompile_weight_set_weights(crush_weight_set weight_set,
220 ostream &out)
221{
222 out << " [ ";
223 for (__u32 i = 0; i < weight_set.size; i++) {
224 print_fixedpoint(out, weight_set.weights[i]);
225 out << " ";
226 }
227 out << "]\n";
228 return 0;
229}
230
231int CrushCompiler::decompile_weight_set(crush_weight_set *weight_set,
232 __u32 size,
233 ostream &out)
234{
235 out << " weight_set [\n";
236 for (__u32 i = 0; i < size; i++) {
237 int r = decompile_weight_set_weights(weight_set[i], out);
238 if (r < 0)
239 return r;
240 }
241 out << " ]\n";
242 return 0;
243}
244
224ce89b 245int CrushCompiler::decompile_ids(__s32 *ids,
7c673cae
FG
246 __u32 size,
247 ostream &out)
248{
249 out << " ids [ ";
250 for (__u32 i = 0; i < size; i++)
251 out << ids[i] << " ";
252 out << "]\n";
253 return 0;
254}
255
256int CrushCompiler::decompile_choose_arg(crush_choose_arg *arg,
257 int bucket_id,
258 ostream &out)
259{
260 int r;
261 out << " {\n";
262 out << " bucket_id " << bucket_id << "\n";
28e407b8
AA
263 if (arg->weight_set_positions > 0) {
264 r = decompile_weight_set(arg->weight_set, arg->weight_set_positions, out);
7c673cae
FG
265 if (r < 0)
266 return r;
267 }
268 if (arg->ids_size > 0) {
269 r = decompile_ids(arg->ids, arg->ids_size, out);
270 if (r < 0)
271 return r;
272 }
273 out << " }\n";
274 return 0;
275}
276
277int CrushCompiler::decompile_choose_arg_map(crush_choose_arg_map arg_map,
278 ostream &out)
279{
280 for (__u32 i = 0; i < arg_map.size; i++) {
281 if ((arg_map.args[i].ids_size == 0) &&
28e407b8 282 (arg_map.args[i].weight_set_positions == 0))
7c673cae
FG
283 continue;
284 int r = decompile_choose_arg(&arg_map.args[i], -1-i, out);
285 if (r < 0)
286 return r;
287 }
288 return 0;
289}
290
291int CrushCompiler::decompile_choose_args(const std::pair<const long unsigned int, crush_choose_arg_map> &i,
292 ostream &out)
293{
294 out << "choose_args " << i.first << " {\n";
295 int r = decompile_choose_arg_map(i.second, out);
296 if (r < 0)
297 return r;
298 out << "}\n";
299 return 0;
300}
301
302int CrushCompiler::decompile(ostream &out)
303{
7c673cae
FG
304 out << "# begin crush map\n";
305
306 // only dump tunables if they differ from the defaults
307 if (crush.get_choose_local_tries() != 2)
308 out << "tunable choose_local_tries " << crush.get_choose_local_tries() << "\n";
309 if (crush.get_choose_local_fallback_tries() != 5)
310 out << "tunable choose_local_fallback_tries " << crush.get_choose_local_fallback_tries() << "\n";
311 if (crush.get_choose_total_tries() != 19)
312 out << "tunable choose_total_tries " << crush.get_choose_total_tries() << "\n";
313 if (crush.get_chooseleaf_descend_once() != 0)
314 out << "tunable chooseleaf_descend_once " << crush.get_chooseleaf_descend_once() << "\n";
315 if (crush.get_chooseleaf_vary_r() != 0)
316 out << "tunable chooseleaf_vary_r " << crush.get_chooseleaf_vary_r() << "\n";
317 if (crush.get_chooseleaf_stable() != 0)
318 out << "tunable chooseleaf_stable " << crush.get_chooseleaf_stable() << "\n";
319 if (crush.get_straw_calc_version() != 0)
320 out << "tunable straw_calc_version " << crush.get_straw_calc_version() << "\n";
321 if (crush.get_allowed_bucket_algs() != CRUSH_LEGACY_ALLOWED_BUCKET_ALGS)
322 out << "tunable allowed_bucket_algs " << crush.get_allowed_bucket_algs()
323 << "\n";
324
325 out << "\n# devices\n";
326 for (int i=0; i<crush.get_max_devices(); i++) {
b32b8144
FG
327 const char *name = crush.get_item_name(i);
328 if (name) {
329 out << "device " << i << " " << name;
330 print_item_class(out, i, crush);
331 out << "\n";
332 }
7c673cae
FG
333 }
334
335 out << "\n# types\n";
336 int n = crush.get_num_type_names();
337 for (int i=0; n; i++) {
338 const char *name = crush.get_type_name(i);
339 if (!name) {
340 if (i == 0) out << "type 0 osd\n";
341 continue;
342 }
343 n--;
344 out << "type " << i << " " << name << "\n";
345 }
346
347 out << "\n# buckets\n";
348 std::map<int, dcb_state_t> dcb_states;
349 for (int bucket = -1; bucket > -1-crush.get_max_buckets(); --bucket) {
350 int ret = decompile_bucket(bucket, dcb_states, out);
351 if (ret)
352 return ret;
353 }
354
355 out << "\n# rules\n";
356 for (int i=0; i<crush.get_max_rules(); i++) {
357 if (!crush.rule_exists(i))
358 continue;
359 out << "rule ";
360 if (crush.get_rule_name(i))
361 print_rule_name(out, i, crush);
362 out << " {\n";
c07f9fc5 363 out << "\tid " << i << "\n";
7c673cae 364
20effc67 365 switch (crush.get_rule_type(i)) {
7c673cae
FG
366 case CEPH_PG_TYPE_REPLICATED:
367 out << "\ttype replicated\n";
368 break;
369 case CEPH_PG_TYPE_ERASURE:
370 out << "\ttype erasure\n";
371 break;
372 default:
20effc67 373 out << "\ttype " << crush.get_rule_type(i) << "\n";
7c673cae
FG
374 }
375
7c673cae
FG
376 for (int j=0; j<crush.get_rule_len(i); j++) {
377 switch (crush.get_rule_op(i, j)) {
378 case CRUSH_RULE_NOOP:
379 out << "\tstep noop\n";
380 break;
381 case CRUSH_RULE_TAKE:
382 out << "\tstep take ";
383 {
384 int step_item = crush.get_rule_arg1(i, j);
385 int original_item;
386 int c;
387 int res = crush.split_id_class(step_item, &original_item, &c);
388 if (res < 0)
389 return res;
390 if (c >= 0)
391 step_item = original_item;
392 print_item_name(out, step_item, crush);
393 if (c >= 0)
394 print_class(out, c, crush);
395 }
396 out << "\n";
397 break;
398 case CRUSH_RULE_EMIT:
399 out << "\tstep emit\n";
400 break;
401 case CRUSH_RULE_SET_CHOOSE_TRIES:
402 out << "\tstep set_choose_tries " << crush.get_rule_arg1(i, j)
403 << "\n";
404 break;
405 case CRUSH_RULE_SET_CHOOSE_LOCAL_TRIES:
406 out << "\tstep set_choose_local_tries " << crush.get_rule_arg1(i, j)
407 << "\n";
408 break;
409 case CRUSH_RULE_SET_CHOOSE_LOCAL_FALLBACK_TRIES:
410 out << "\tstep set_choose_local_fallback_tries " << crush.get_rule_arg1(i, j)
411 << "\n";
412 break;
413 case CRUSH_RULE_SET_CHOOSELEAF_TRIES:
414 out << "\tstep set_chooseleaf_tries " << crush.get_rule_arg1(i, j)
415 << "\n";
416 break;
417 case CRUSH_RULE_SET_CHOOSELEAF_VARY_R:
418 out << "\tstep set_chooseleaf_vary_r " << crush.get_rule_arg1(i, j)
419 << "\n";
420 break;
421 case CRUSH_RULE_SET_CHOOSELEAF_STABLE:
422 out << "\tstep set_chooseleaf_stable " << crush.get_rule_arg1(i, j)
423 << "\n";
424 break;
425 case CRUSH_RULE_CHOOSE_FIRSTN:
426 out << "\tstep choose firstn "
427 << crush.get_rule_arg1(i, j)
428 << " type ";
429 print_type_name(out, crush.get_rule_arg2(i, j), crush);
430 out << "\n";
431 break;
432 case CRUSH_RULE_CHOOSE_INDEP:
433 out << "\tstep choose indep "
434 << crush.get_rule_arg1(i, j)
435 << " type ";
436 print_type_name(out, crush.get_rule_arg2(i, j), crush);
437 out << "\n";
438 break;
439 case CRUSH_RULE_CHOOSELEAF_FIRSTN:
440 out << "\tstep chooseleaf firstn "
441 << crush.get_rule_arg1(i, j)
442 << " type ";
443 print_type_name(out, crush.get_rule_arg2(i, j), crush);
444 out << "\n";
445 break;
446 case CRUSH_RULE_CHOOSELEAF_INDEP:
447 out << "\tstep chooseleaf indep "
448 << crush.get_rule_arg1(i, j)
449 << " type ";
450 print_type_name(out, crush.get_rule_arg2(i, j), crush);
451 out << "\n";
452 break;
453 }
454 }
455 out << "}\n";
456 }
457 if (crush.choose_args.size() > 0) {
458 out << "\n# choose_args\n";
459 for (auto i : crush.choose_args) {
460 int ret = decompile_choose_args(i, out);
461 if (ret)
462 return ret;
463 }
464 }
465 out << "\n# end crush map" << std::endl;
466 return 0;
467}
468
469
470// ================================================================
471
472string CrushCompiler::string_node(node_t &node)
473{
474 return boost::trim_copy(string(node.value.begin(), node.value.end()));
475}
476
477int CrushCompiler::int_node(node_t &node)
478{
479 string str = string_node(node);
480 return strtol(str.c_str(), 0, 10);
481}
482
483float CrushCompiler::float_node(node_t &node)
484{
485 string s = string_node(node);
486 return strtof(s.c_str(), 0);
487}
488
489int CrushCompiler::parse_device(iter_t const& i)
490{
491 int id = int_node(i->children[1]);
492
493 string name = string_node(i->children[2]);
494 crush.set_item_name(id, name.c_str());
495 if (item_id.count(name)) {
496 err << "item " << name << " defined twice" << std::endl;
497 return -1;
498 }
499 item_id[name] = id;
500 id_item[id] = name;
501
502 if (verbose) err << "device " << id << " '" << name << "'";
503
504 if (i->children.size() > 3) {
505 string c = string_node(i->children[4]);
506 crush.set_item_class(id, c);
507 if (verbose) err << " class" << " '" << c << "'" << std::endl;
508 } else {
509 if (verbose) err << std::endl;
510 }
511 return 0;
512}
513
514int CrushCompiler::parse_tunable(iter_t const& i)
515{
516 string name = string_node(i->children[1]);
517 int val = int_node(i->children[2]);
518
519 if (name == "choose_local_tries")
520 crush.set_choose_local_tries(val);
521 else if (name == "choose_local_fallback_tries")
522 crush.set_choose_local_fallback_tries(val);
523 else if (name == "choose_total_tries")
524 crush.set_choose_total_tries(val);
525 else if (name == "chooseleaf_descend_once")
526 crush.set_chooseleaf_descend_once(val);
527 else if (name == "chooseleaf_vary_r")
528 crush.set_chooseleaf_vary_r(val);
529 else if (name == "chooseleaf_stable")
530 crush.set_chooseleaf_stable(val);
531 else if (name == "straw_calc_version")
532 crush.set_straw_calc_version(val);
533 else if (name == "allowed_bucket_algs")
534 crush.set_allowed_bucket_algs(val);
535 else {
536 err << "tunable " << name << " not recognized" << std::endl;
537 return -1;
538 }
539
540 /*
541
542 current crop of tunables are all now "safe". re-enable this when we
543 add new ones that are ... new.
544
545 if (!unsafe_tunables) {
546 err << "tunables are NOT FULLY IMPLEMENTED; enable with --enable-unsafe-tunables to enable this feature" << std::endl;
547 return -1;
548 }
549 */
550
551 if (verbose) err << "tunable " << name << " " << val << std::endl;
552 return 0;
553}
554
555int CrushCompiler::parse_bucket_type(iter_t const& i)
556{
557 int id = int_node(i->children[1]);
558 string name = string_node(i->children[2]);
559 if (verbose) err << "type " << id << " '" << name << "'" << std::endl;
560 type_id[name] = id;
561 crush.set_type_name(id, name.c_str());
562 return 0;
563}
564
565int CrushCompiler::parse_bucket(iter_t const& i)
566{
567 string tname = string_node(i->children[0]);
568 if (!type_id.count(tname)) {
569 err << "bucket type '" << tname << "' is not defined" << std::endl;
570 return -1;
571 }
572 int type = type_id[tname];
573
574 string name = string_node(i->children[1]);
575 if (item_id.count(name)) {
576 err << "bucket or device '" << name << "' is already defined" << std::endl;
577 return -1;
578 }
579
580 int id = 0; // none, yet!
581 int alg = -1;
582 int hash = 0;
583 set<int> used_items;
584 int size = 0;
585 map<int32_t, int32_t> class_id;
586
587 for (unsigned p=3; p<i->children.size()-1; p++) {
588 iter_t sub = i->children.begin() + p;
589 string tag = string_node(sub->children[0]);
590 //err << "tag " << tag << std::endl;
591 if (tag == "id") {
592 int maybe_id = int_node(sub->children[1]);
593 if (verbose) err << "bucket " << name << " id " << maybe_id;
594 if (sub->children.size() > 2) {
595 string class_name = string_node(sub->children[3]);
b5b8bbf5
FG
596 // note that we do not verify class existence here,
597 // as this bucket might come from an empty shadow tree
598 // which currently has no OSDs but is still referenced by a rule!
599 int cid = crush.get_or_create_class_id(class_name);
7c673cae
FG
600 if (class_id.count(cid) != 0) {
601 err << "duplicate device class " << class_name << " for bucket " << name << std::endl;
602 return -ERANGE;
603 }
604 class_id[cid] = maybe_id;
605 if (verbose) err << " class" << " '" << class_name << "'" << std::endl;
606 } else {
607 id = maybe_id;
608 if (verbose) err << std::endl;
609 }
610 } else if (tag == "alg") {
611 string a = string_node(sub->children[1]);
612 if (a == "uniform")
613 alg = CRUSH_BUCKET_UNIFORM;
614 else if (a == "list")
615 alg = CRUSH_BUCKET_LIST;
616 else if (a == "tree")
617 alg = CRUSH_BUCKET_TREE;
618 else if (a == "straw")
619 alg = CRUSH_BUCKET_STRAW;
620 else if (a == "straw2")
621 alg = CRUSH_BUCKET_STRAW2;
622 else {
623 err << "unknown bucket alg '" << a << "'" << std::endl << std::endl;
624 return -EINVAL;
625 }
626 }
627 else if (tag == "hash") {
628 string a = string_node(sub->children[1]);
629 if (a == "rjenkins1")
630 hash = CRUSH_HASH_RJENKINS1;
631 else
632 hash = atoi(a.c_str());
633 }
634 else if (tag == "item") {
635 // first, just determine which item pos's are already used
636 size++;
637 for (unsigned q = 2; q < sub->children.size(); q++) {
638 string tag = string_node(sub->children[q++]);
639 if (tag == "pos") {
640 int pos = int_node(sub->children[q]);
641 if (used_items.count(pos)) {
642 err << "item '" << string_node(sub->children[1]) << "' in bucket '" << name << "' has explicit pos " << pos << ", which is occupied" << std::endl;
643 return -1;
644 }
645 used_items.insert(pos);
646 }
647 }
648 }
649 else ceph_abort();
650 }
651
652 // now do the items.
653 if (!used_items.empty())
11fdf7f2 654 size = std::max(size, *used_items.rbegin());
7c673cae
FG
655 vector<int> items(size);
656 vector<int> weights(size);
657
658 int curpos = 0;
659 unsigned bucketweight = 0;
660 bool have_uniform_weight = false;
661 unsigned uniform_weight = 0;
662 for (unsigned p=3; p<i->children.size()-1; p++) {
663 iter_t sub = i->children.begin() + p;
664 string tag = string_node(sub->children[0]);
665 if (tag == "item") {
666
667 string iname = string_node(sub->children[1]);
668 if (!item_id.count(iname)) {
669 err << "item '" << iname << "' in bucket '" << name << "' is not defined" << std::endl;
670 return -1;
671 }
672 int itemid = item_id[iname];
673
674 unsigned weight = 0x10000;
675 if (item_weight.count(itemid))
676 weight = item_weight[itemid];
677
678 int pos = -1;
679 for (unsigned q = 2; q < sub->children.size(); q++) {
680 string tag = string_node(sub->children[q++]);
681 if (tag == "weight") {
682 weight = float_node(sub->children[q]) * (float)0x10000;
683 if (weight > CRUSH_MAX_DEVICE_WEIGHT && itemid >= 0) {
684 err << "device weight limited to " << CRUSH_MAX_DEVICE_WEIGHT / 0x10000 << std::endl;
685 return -ERANGE;
686 }
687 else if (weight > CRUSH_MAX_BUCKET_WEIGHT && itemid < 0) {
688 err << "bucket weight limited to " << CRUSH_MAX_BUCKET_WEIGHT / 0x10000
689 << " to prevent overflow" << std::endl;
690 return -ERANGE;
691 }
692 }
693 else if (tag == "pos")
694 pos = int_node(sub->children[q]);
695 else
696 ceph_abort();
697
698 }
699 if (alg == CRUSH_BUCKET_UNIFORM) {
700 if (!have_uniform_weight) {
701 have_uniform_weight = true;
702 uniform_weight = weight;
703 } else {
704 if (uniform_weight != weight) {
705 err << "item '" << iname << "' in uniform bucket '" << name << "' has weight " << weight
706 << " but previous item(s) have weight " << (float)uniform_weight/(float)0x10000
707 << "; uniform bucket items must all have identical weights." << std::endl;
708 return -1;
709 }
710 }
711 }
712
713 if (pos >= size) {
714 err << "item '" << iname << "' in bucket '" << name << "' has pos " << pos << " >= size " << size << std::endl;
715 return -1;
716 }
717 if (pos < 0) {
718 while (used_items.count(curpos)) curpos++;
719 pos = curpos++;
720 }
721 //err << " item " << iname << " (" << itemid << ") pos " << pos << " weight " << weight << std::endl;
722 items[pos] = itemid;
723 weights[pos] = weight;
724
725 if (crush_addition_is_unsafe(bucketweight, weight)) {
726 err << "oh no! our bucket weights are overflowing all over the place, better lower the item weights" << std::endl;
727 return -ERANGE;
728 }
729
730 bucketweight += weight;
731 }
732 }
733
734 if (id == 0) {
735 for (id=-1; id_item.count(id); id--) ;
736 //err << "assigned id " << id << std::endl;
737 }
738
739 for (auto &i : class_id)
d2e6a577 740 class_bucket[id][i.first] = i.second;
7c673cae
FG
741
742 if (verbose) err << "bucket " << name << " (" << id << ") " << size << " items and weight "
743 << (float)bucketweight / (float)0x10000 << std::endl;
744 id_item[id] = name;
745 item_id[name] = id;
746 item_weight[id] = bucketweight;
747
11fdf7f2 748 ceph_assert(id != 0);
b5b8bbf5
FG
749 int idout;
750 int r = crush.add_bucket(id, alg, hash, type, size,
11fdf7f2 751 items.data(), weights.data(), &idout);
7c673cae
FG
752 if (r < 0) {
753 if (r == -EEXIST)
754 err << "Duplicate bucket id " << id << std::endl;
755 else
756 err << "add_bucket failed " << cpp_strerror(r) << std::endl;
757 return r;
758 }
759 r = crush.set_item_name(id, name.c_str());
760 return r;
761}
762
763int CrushCompiler::parse_rule(iter_t const& i)
764{
7c673cae
FG
765 int start; // rule name is optional!
766
767 string rname = string_node(i->children[1]);
768 if (rname != "{") {
769 if (rule_id.count(rname)) {
770 err << "rule name '" << rname << "' already defined\n" << std::endl;
771 return -1;
772 }
773 start = 4;
774 } else {
775 rname = string();
776 start = 3;
777 }
778
c07f9fc5 779 int ruleno = int_node(i->children[start]);
7c673cae
FG
780
781 string tname = string_node(i->children[start+2]);
782 int type;
783 if (tname == "replicated")
784 type = CEPH_PG_TYPE_REPLICATED;
785 else if (tname == "erasure")
786 type = CEPH_PG_TYPE_ERASURE;
787 else
788 ceph_abort();
789
20effc67
TL
790 // ignore min_size+max_size and find first step
791 int step_start = 0;
792 int steps = 0;
793 for (unsigned p = start + 3; p < i->children.size()-1; ++p) {
794 string tag = string_node(i->children[p]);
795 if (tag == "min_size" || tag == "max_size") {
796 std::cerr << "WARNING: " << tag << " is no longer supported, ignoring" << std::endl;
797 ++p;
798 continue;
799 }
800 // has to be a step--grammer doesn't recognized anything else
801 assert(i->children[p].value.id().to_long() == crush_grammar::_step);
802 step_start = p;
803 steps = i->children.size() - p - 1;
804 break;
805 }
806 //err << "num steps " << steps << " start " << step_start << std::endl;
c07f9fc5
FG
807
808 if (crush.rule_exists(ruleno)) {
809 err << "rule " << ruleno << " already exists" << std::endl;
810 return -1;
811 }
20effc67 812 int r = crush.add_rule(ruleno, steps, type);
c07f9fc5
FG
813 if (r != ruleno) {
814 err << "unable to add rule id " << ruleno << " for rule '" << rname
815 << "'" << std::endl;
816 return -1;
817 }
7c673cae
FG
818 if (rname.length()) {
819 crush.set_rule_name(ruleno, rname.c_str());
820 rule_id[rname] = ruleno;
821 }
822
823 int step = 0;
20effc67 824 for (iter_t p = i->children.begin() + step_start; step < steps; p++) {
7c673cae
FG
825 iter_t s = p->children.begin() + 1;
826 int stepid = s->value.id().to_long();
827 switch (stepid) {
828 case crush_grammar::_step_take:
829 {
830 string item = string_node(s->children[1]);
831 if (!item_id.count(item)) {
832 err << "in rule '" << rname << "' item '" << item << "' not defined" << std::endl;
833 return -1;
834 }
835 int id = item_id[item];
836 int c = -1;
837 string class_name;
838 if (s->children.size() > 2) {
839 class_name = string_node(s->children[3]);
840 c = crush.get_class_id(class_name);
841 if (c < 0)
842 return c;
843 if (crush.class_bucket.count(id) == 0) {
844 err << "in rule '" << rname << "' step take " << item
845 << " has no class information" << std::endl;
846 return -EINVAL;
847 }
848 if (crush.class_bucket[id].count(c) == 0) {
849 err << "in rule '" << rname << "' step take " << item
850 << " no matching bucket for class " << class_name << std::endl;
851 return -EINVAL;
852 }
853 id = crush.class_bucket[id][c];
854 }
855 if (verbose) {
856 err << "rule " << rname << " take " << item;
857 if (c < 0)
858 err << std::endl;
859 else
860 err << " remapped to " << crush.get_item_name(id) << std::endl;
861 }
862
863 crush.set_rule_step_take(ruleno, step++, id);
864 }
865 break;
866
867 case crush_grammar::_step_set_choose_tries:
868 {
869 int val = int_node(s->children[1]);
870 crush.set_rule_step_set_choose_tries(ruleno, step++, val);
871 }
872 break;
873
874 case crush_grammar::_step_set_choose_local_tries:
875 {
876 int val = int_node(s->children[1]);
877 crush.set_rule_step_set_choose_local_tries(ruleno, step++, val);
878 }
879 break;
880
881 case crush_grammar::_step_set_choose_local_fallback_tries:
882 {
883 int val = int_node(s->children[1]);
884 crush.set_rule_step_set_choose_local_fallback_tries(ruleno, step++, val);
885 }
886 break;
887
888 case crush_grammar::_step_set_chooseleaf_tries:
889 {
890 int val = int_node(s->children[1]);
891 crush.set_rule_step_set_chooseleaf_tries(ruleno, step++, val);
892 }
893 break;
894
895 case crush_grammar::_step_set_chooseleaf_vary_r:
896 {
897 int val = int_node(s->children[1]);
898 crush.set_rule_step_set_chooseleaf_vary_r(ruleno, step++, val);
899 }
900 break;
901
902 case crush_grammar::_step_set_chooseleaf_stable:
903 {
904 int val = int_node(s->children[1]);
905 crush.set_rule_step_set_chooseleaf_stable(ruleno, step++, val);
906 }
907 break;
908
909 case crush_grammar::_step_choose:
910 case crush_grammar::_step_chooseleaf:
911 {
912 string type = string_node(s->children[4]);
913 if (!type_id.count(type)) {
914 err << "in rule '" << rname << "' type '" << type << "' not defined" << std::endl;
915 return -1;
916 }
917 string choose = string_node(s->children[0]);
918 string mode = string_node(s->children[1]);
919 if (choose == "choose") {
920 if (mode == "firstn")
921 crush.set_rule_step_choose_firstn(ruleno, step++, int_node(s->children[2]), type_id[type]);
922 else if (mode == "indep")
923 crush.set_rule_step_choose_indep(ruleno, step++, int_node(s->children[2]), type_id[type]);
924 else ceph_abort();
925 } else if (choose == "chooseleaf") {
926 if (mode == "firstn")
927 crush.set_rule_step_choose_leaf_firstn(ruleno, step++, int_node(s->children[2]), type_id[type]);
928 else if (mode == "indep")
929 crush.set_rule_step_choose_leaf_indep(ruleno, step++, int_node(s->children[2]), type_id[type]);
930 else ceph_abort();
931 } else ceph_abort();
932 }
933 break;
934
935 case crush_grammar::_step_emit:
936 crush.set_rule_step_emit(ruleno, step++);
937 break;
938
939 default:
940 err << "bad crush step " << stepid << std::endl;
941 return -1;
942 }
943 }
11fdf7f2 944 ceph_assert(step == steps);
7c673cae
FG
945 return 0;
946}
947
948int CrushCompiler::parse_weight_set_weights(iter_t const& i, int bucket_id, crush_weight_set *weight_set)
949{
950 // -2 for the enclosing [ ]
951 __u32 size = i->children.size() - 2;
952 __u32 bucket_size = crush.get_bucket_size(bucket_id);
953 if (size != bucket_size) {
954 err << bucket_id << " needs exactly " << bucket_size
955 << " weights but got " << size << std::endl;
956 return -1;
957 }
958 weight_set->size = size;
959 weight_set->weights = (__u32 *)calloc(weight_set->size, sizeof(__u32));
960 __u32 pos = 0;
961 for (iter_t p = i->children.begin() + 1; p != i->children.end(); p++, pos++)
962 if (pos < size)
963 weight_set->weights[pos] = float_node(*p) * (float)0x10000;
964 return 0;
965}
966
967int CrushCompiler::parse_weight_set(iter_t const& i, int bucket_id, crush_choose_arg *arg)
968{
969 // -3 stands for the leading "weight_set" keyword and the enclosing [ ]
28e407b8
AA
970 arg->weight_set_positions = i->children.size() - 3;
971 arg->weight_set = (crush_weight_set *)calloc(arg->weight_set_positions, sizeof(crush_weight_set));
7c673cae
FG
972 __u32 pos = 0;
973 for (iter_t p = i->children.begin(); p != i->children.end(); p++) {
974 int r = 0;
975 switch((int)p->value.id().to_long()) {
976 case crush_grammar::_weight_set_weights:
28e407b8 977 if (pos < arg->weight_set_positions) {
7c673cae
FG
978 r = parse_weight_set_weights(p, bucket_id, &arg->weight_set[pos]);
979 pos++;
980 } else {
981 err << "invalid weight_set syntax" << std::endl;
982 r = -1;
983 }
984 }
985 if (r < 0)
986 return r;
987 }
988 return 0;
989}
990
991int CrushCompiler::parse_choose_arg_ids(iter_t const& i, int bucket_id, crush_choose_arg *arg)
992{
993 // -3 for the leading "ids" keyword and the enclosing [ ]
994 __u32 size = i->children.size() - 3;
995 __u32 bucket_size = crush.get_bucket_size(bucket_id);
996 if (size != bucket_size) {
997 err << bucket_id << " needs exactly " << bucket_size
998 << " ids but got " << size << std::endl;
999 return -1;
1000 }
1001 arg->ids_size = size;
224ce89b 1002 arg->ids = (__s32 *)calloc(arg->ids_size, sizeof(__s32));
7c673cae
FG
1003 __u32 pos = 0;
1004 for (iter_t p = i->children.begin() + 2; pos < size; p++, pos++)
1005 arg->ids[pos] = int_node(*p);
1006 return 0;
1007}
1008
1009int CrushCompiler::parse_choose_arg(iter_t const& i, crush_choose_arg *args)
1010{
1011 int bucket_id = int_node(i->children[2]);
1012 if (-1-bucket_id < 0 || -1-bucket_id >= crush.get_max_buckets()) {
1013 err << bucket_id << " is out of range" << std::endl;
1014 return -1;
1015 }
1016 if (!crush.bucket_exists(bucket_id)) {
1017 err << bucket_id << " does not exist" << std::endl;
1018 return -1;
1019 }
1020 crush_choose_arg *arg = &args[-1-bucket_id];
1021 for (iter_t p = i->children.begin(); p != i->children.end(); p++) {
1022 int r = 0;
1023 switch((int)p->value.id().to_long()) {
1024 case crush_grammar::_weight_set:
1025 r = parse_weight_set(p, bucket_id, arg);
1026 break;
1027 case crush_grammar::_choose_arg_ids:
1028 r = parse_choose_arg_ids(p, bucket_id, arg);
1029 break;
1030 }
1031 if (r < 0)
1032 return r;
1033 }
1034 return 0;
1035}
1036
1037int CrushCompiler::parse_choose_args(iter_t const& i)
1038{
1039 int choose_arg_index = int_node(i->children[1]);
1040 if (crush.choose_args.find(choose_arg_index) != crush.choose_args.end()) {
1041 err << choose_arg_index << " duplicated" << std::endl;
1042 return -1;
1043 }
11fdf7f2
TL
1044 const auto max_buckets = crush.get_max_buckets();
1045 if (max_buckets < 0) {
1046 err << "get_max_buckets() returned error" << std::endl;
1047 return -1;
1048 }
7c673cae 1049 crush_choose_arg_map arg_map;
11fdf7f2 1050 arg_map.size = max_buckets;
7c673cae
FG
1051 arg_map.args = (crush_choose_arg *)calloc(arg_map.size, sizeof(crush_choose_arg));
1052 for (iter_t p = i->children.begin() + 2; p != i->children.end(); p++) {
1053 int r = 0;
1054 switch((int)p->value.id().to_long()) {
1055 case crush_grammar::_choose_arg:
1056 r = parse_choose_arg(p, arg_map.args);
1057 break;
1058 }
1059 if (r < 0) {
1060 crush.destroy_choose_args(arg_map);
1061 return r;
1062 }
1063 }
1064 crush.choose_args[choose_arg_index] = arg_map;
1065 return 0;
1066}
1067
1068void CrushCompiler::find_used_bucket_ids(iter_t const& i)
1069{
1070 for (iter_t p = i->children.begin(); p != i->children.end(); p++) {
1071 if ((int)p->value.id().to_long() == crush_grammar::_bucket) {
f64942e4
AA
1072 for (iter_t firstline = p->children.begin() + 3;
1073 firstline != p->children.end();
1074 ++firstline) {
1075 string tag = string_node(firstline->children[0]);
1076 if (tag != "id") {
1077 break;
1078 }
7c673cae
FG
1079 int id = int_node(firstline->children[1]);
1080 //err << "saw bucket id " << id << std::endl;
1081 id_item[id] = string();
1082 }
1083 }
1084 }
1085}
1086
1087int CrushCompiler::parse_crush(iter_t const& i)
1088{
1089 find_used_bucket_ids(i);
c07f9fc5 1090 bool saw_rule = false;
7c673cae
FG
1091 for (iter_t p = i->children.begin(); p != i->children.end(); p++) {
1092 int r = 0;
1093 switch (p->value.id().to_long()) {
1094 case crush_grammar::_tunable:
1095 r = parse_tunable(p);
1096 break;
1097 case crush_grammar::_device:
1098 r = parse_device(p);
1099 break;
1100 case crush_grammar::_bucket_type:
1101 r = parse_bucket_type(p);
1102 break;
c07f9fc5
FG
1103 case crush_grammar::_bucket:
1104 if (saw_rule) {
1105 err << "buckets must be defined before rules" << std::endl;
1106 return -1;
1107 }
7c673cae
FG
1108 r = parse_bucket(p);
1109 break;
c07f9fc5
FG
1110 case crush_grammar::_crushrule:
1111 if (!saw_rule) {
1112 saw_rule = true;
d2e6a577 1113 crush.populate_classes(class_bucket);
c07f9fc5 1114 }
7c673cae
FG
1115 r = parse_rule(p);
1116 break;
1117 case crush_grammar::_choose_args:
1118 r = parse_choose_args(p);
1119 break;
1120 default:
1121 ceph_abort();
1122 }
1123 if (r < 0) {
1124 return r;
1125 }
1126 }
1127
1128 //err << "max_devices " << crush.get_max_devices() << std::endl;
7c673cae
FG
1129 crush.finalize();
1130
1131 return 0;
1132}
1133
1134// squash runs of whitespace to one space, excepting newlines
1135string CrushCompiler::consolidate_whitespace(string in)
1136{
1137 string out;
1138
1139 bool white = false;
1140 for (unsigned p=0; p<in.length(); p++) {
1141 if (isspace(in[p]) && in[p] != '\n') {
1142 if (white)
1143 continue;
1144 white = true;
1145 } else {
1146 if (white) {
1147 if (out.length()) out += " ";
1148 white = false;
1149 }
1150 out += in[p];
1151 }
1152 }
1153 if (verbose > 3)
1154 err << " \"" << in << "\" -> \"" << out << "\"" << std::endl;
1155 return out;
1156}
1157
1158void CrushCompiler::dump(iter_t const& i, int ind)
1159{
1160 err << "dump";
1161 for (int j=0; j<ind; j++)
1162 cout << "\t";
1163 long id = i->value.id().to_long();
1164 err << id << "\t";
1165 err << "'" << string(i->value.begin(), i->value.end())
1166 << "' " << i->children.size() << " children" << std::endl;
1167 for (unsigned int j = 0; j < i->children.size(); j++)
1168 dump(i->children.begin() + j, ind+1);
1169}
1170
1171/**
1172* This function fix the problem like below
1173* rack using_foo { item foo }
1174* host foo { ... }
1175*
1176* if an item being used by a bucket is defined after that bucket.
1177* CRUSH compiler will create a map by which we can
1178* not identify that item when selecting in that bucket.
1179**/
1180int CrushCompiler::adjust_bucket_item_place(iter_t const &i)
1181{
1182 map<string,set<string> > bucket_items;
1183 map<string,iter_t> bucket_itrer;
1184 vector<string> buckets;
1185 for (iter_t p = i->children.begin(); p != i->children.end(); ++p) {
1186 if ((int)p->value.id().to_long() == crush_grammar::_bucket) {
1187 string name = string_node(p->children[1]);
1188 buckets.push_back(name);
1189 bucket_itrer[name] = p;
1190 //skip non-bucket-item children in the bucket's parse tree
1191 for (unsigned q=3; q < p->children.size()-1; ++q) {
1192 iter_t sub = p->children.begin() + q;
1193 if ((int)sub->value.id().to_long()
1194 == crush_grammar::_bucket_item) {
1195 string iname = string_node(sub->children[1]);
1196 bucket_items[name].insert(iname);
1197 }
1198 }
1199 }
1200 }
1201
1202 //adjust the bucket
1203 for (unsigned i=0; i < buckets.size(); ++i) {
1204 for (unsigned j=i+1; j < buckets.size(); ++j) {
1205 if (bucket_items[buckets[i]].count(buckets[j])) {
1206 if (bucket_items[buckets[j]].count(buckets[i])) {
1207 err << "bucket '" << buckets[i] << "' and bucket '"
1208 << buckets[j] << "' are included each other" << std::endl;
1209 return -1;
1210 } else {
1211 std::iter_swap(bucket_itrer[buckets[i]], bucket_itrer[buckets[j]]);
1212 }
1213 }
1214 }
1215 }
1216
1217 return 0;
1218}
1219
1220int CrushCompiler::compile(istream& in, const char *infn)
1221{
1222 if (!infn)
1223 infn = "<input>";
1224
1225 // always start with legacy tunables, so that the compiled result of
1226 // a given crush file is fixed for all time.
1227 crush.set_tunables_legacy();
1228
1229 string big;
1230 string str;
1231 int line = 1;
1232 map<int,int> line_pos; // pos -> line
1233 map<int,string> line_val;
1234 while (getline(in, str)) {
1235 // remove newline
1236 int l = str.length();
1237 if (l && str[l - 1] == '\n')
1238 str.erase(l-1, 1);
1239
1240 line_val[line] = str;
1241
1242 // strip comment
1243 int n = str.find("#");
1244 if (n >= 0)
1245 str.erase(n, str.length()-n);
1246
1247 if (verbose>1) err << line << ": " << str << std::endl;
1248
1249 // work around spirit crankiness by removing extraneous
1250 // whitespace. there is probably a more elegant solution, but
1251 // this only broke with the latest spirit (with the switchover to
1252 // "classic"), i don't want to spend too much time figuring it
1253 // out.
1254 string stripped = consolidate_whitespace(str);
1255 if (stripped.length() && big.length() && big[big.length()-1] != ' ') big += " ";
1256
1257 line_pos[big.length()] = line;
1258 line++;
1259 big += stripped;
1260 }
1261
1262 if (verbose > 2) err << "whole file is: \"" << big << "\"" << std::endl;
1263
1264 crush_grammar crushg;
1265 const char *start = big.c_str();
1266 //tree_parse_info<const char *> info = ast_parse(start, crushg, space_p);
9f95a23c
TL
1267 auto info = ast_parse(start, crushg, boost::spirit::space_p);
1268
7c673cae
FG
1269 // parse error?
1270 if (!info.full) {
1271 int cpos = info.stop - start;
1272 //out << "cpos " << cpos << std::endl;
1273 //out << " linemap " << line_pos << std::endl;
11fdf7f2 1274 ceph_assert(!line_pos.empty());
7c673cae
FG
1275 map<int,int>::iterator p = line_pos.upper_bound(cpos);
1276 if (p != line_pos.begin())
1277 --p;
1278 int line = p->second;
1279 int pos = cpos - p->first;
1280 err << infn << ":" << line //<< ":" << (pos+1)
1281 << " error: parse error at '" << line_val[line].substr(pos) << "'" << std::endl;
1282 return -1;
1283 }
1284
1285 int r = adjust_bucket_item_place(info.trees.begin());
1286 if (r < 0) {
1287 return r;
1288 }
1289 //out << "parsing succeeded\n";
1290 //dump(info.trees.begin());
1291 return parse_crush(info.trees.begin());
1292}