]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- |
2 | // vim: ts=8 sw=2 smarttab | |
3 | /* | |
4 | * Ceph - scalable distributed file system | |
5 | * | |
6 | * Copyright (C) 2016 John Spray <john.spray@redhat.com> | |
7 | * | |
8 | * This is free software; you can redistribute it and/or | |
9 | * modify it under the terms of the GNU Lesser General Public | |
10 | * License version 2.1, as published by the Free Software | |
11 | * Foundation. See file COPYING. | |
12 | */ | |
13 | ||
14 | #ifndef MGR_MAP_H_ | |
15 | #define MGR_MAP_H_ | |
16 | ||
17 | #include <sstream> | |
11fdf7f2 | 18 | #include <set> |
7c673cae FG |
19 | |
20 | #include "msg/msg_types.h" | |
7c673cae | 21 | #include "include/encoding.h" |
11fdf7f2 | 22 | #include "include/utime.h" |
9f95a23c TL |
23 | #include "common/Formatter.h" |
24 | #include "common/ceph_releases.h" | |
11fdf7f2 TL |
25 | #include "common/version.h" |
26 | #include "common/options.h" | |
27 | #include "common/Clock.h" | |
7c673cae | 28 | |
11fdf7f2 TL |
29 | |
30 | class MgrMap | |
7c673cae FG |
31 | { |
32 | public: | |
11fdf7f2 TL |
33 | struct ModuleOption { |
34 | std::string name; | |
35 | uint8_t type = Option::TYPE_STR; // Option::type_t TYPE_* | |
36 | uint8_t level = Option::LEVEL_ADVANCED; // Option::level_t LEVEL_* | |
37 | uint32_t flags = 0; // Option::flag_t FLAG_* | |
38 | std::string default_value; | |
39 | std::string min, max; | |
40 | std::set<std::string> enum_allowed; | |
41 | std::string desc, long_desc; | |
42 | std::set<std::string> tags; | |
43 | std::set<std::string> see_also; | |
7c673cae | 44 | |
9f95a23c | 45 | void encode(ceph::buffer::list& bl) const { |
11fdf7f2 TL |
46 | ENCODE_START(1, 1, bl); |
47 | encode(name, bl); | |
48 | encode(type, bl); | |
49 | encode(level, bl); | |
50 | encode(flags, bl); | |
51 | encode(default_value, bl); | |
52 | encode(min, bl); | |
53 | encode(max, bl); | |
54 | encode(enum_allowed, bl); | |
55 | encode(desc, bl); | |
56 | encode(long_desc, bl); | |
57 | encode(tags, bl); | |
58 | encode(see_also, bl); | |
59 | ENCODE_FINISH(bl); | |
60 | } | |
9f95a23c | 61 | void decode(ceph::buffer::list::const_iterator& p) { |
11fdf7f2 TL |
62 | DECODE_START(1, p); |
63 | decode(name, p); | |
64 | decode(type, p); | |
65 | decode(level, p); | |
66 | decode(flags, p); | |
67 | decode(default_value, p); | |
68 | decode(min, p); | |
69 | decode(max, p); | |
70 | decode(enum_allowed, p); | |
71 | decode(desc, p); | |
72 | decode(long_desc, p); | |
73 | decode(tags, p); | |
74 | decode(see_also, p); | |
75 | DECODE_FINISH(p); | |
76 | } | |
9f95a23c | 77 | void dump(ceph::Formatter *f) const { |
11fdf7f2 TL |
78 | f->dump_string("name", name); |
79 | f->dump_string("type", Option::type_to_str( | |
80 | static_cast<Option::type_t>(type))); | |
81 | f->dump_string("level", Option::level_to_str( | |
82 | static_cast<Option::level_t>(level))); | |
83 | f->dump_unsigned("flags", flags); | |
84 | f->dump_string("default_value", default_value); | |
85 | f->dump_string("min", min); | |
86 | f->dump_string("max", max); | |
87 | f->open_array_section("enum_allowed"); | |
88 | for (auto& i : enum_allowed) { | |
89 | f->dump_string("value", i); | |
90 | } | |
91 | f->close_section(); | |
92 | f->dump_string("desc", desc); | |
93 | f->dump_string("long_desc", long_desc); | |
94 | f->open_array_section("tags"); | |
95 | for (auto& i : tags) { | |
96 | f->dump_string("tag", i); | |
97 | } | |
98 | f->close_section(); | |
99 | f->open_array_section("see_also"); | |
100 | for (auto& i : see_also) { | |
101 | f->dump_string("option", i); | |
102 | } | |
103 | f->close_section(); | |
104 | } | |
105 | }; | |
7c673cae | 106 | |
11fdf7f2 | 107 | class ModuleInfo |
7c673cae | 108 | { |
11fdf7f2 TL |
109 | public: |
110 | std::string name; | |
111 | bool can_run = true; | |
112 | std::string error_string; | |
113 | std::map<std::string,ModuleOption> module_options; | |
114 | ||
115 | // We do not include the module's `failed` field in the beacon, | |
116 | // because it is exposed via health checks. | |
9f95a23c | 117 | void encode(ceph::buffer::list &bl) const { |
11fdf7f2 TL |
118 | ENCODE_START(2, 1, bl); |
119 | encode(name, bl); | |
120 | encode(can_run, bl); | |
121 | encode(error_string, bl); | |
122 | encode(module_options, bl); | |
123 | ENCODE_FINISH(bl); | |
124 | } | |
125 | ||
9f95a23c | 126 | void decode(ceph::buffer::list::const_iterator &bl) { |
11fdf7f2 TL |
127 | DECODE_START(1, bl); |
128 | decode(name, bl); | |
129 | decode(can_run, bl); | |
130 | decode(error_string, bl); | |
131 | if (struct_v >= 2) { | |
132 | decode(module_options, bl); | |
133 | } | |
134 | DECODE_FINISH(bl); | |
135 | } | |
136 | ||
137 | bool operator==(const ModuleInfo &rhs) const | |
138 | { | |
139 | return (name == rhs.name) && (can_run == rhs.can_run); | |
140 | } | |
141 | ||
9f95a23c | 142 | void dump(ceph::Formatter *f) const { |
11fdf7f2 TL |
143 | f->open_object_section("module"); |
144 | f->dump_string("name", name); | |
145 | f->dump_bool("can_run", can_run); | |
146 | f->dump_string("error_string", error_string); | |
147 | f->open_object_section("module_options"); | |
148 | for (auto& i : module_options) { | |
149 | f->dump_object(i.first.c_str(), i.second); | |
150 | } | |
151 | f->close_section(); | |
152 | f->close_section(); | |
153 | } | |
154 | }; | |
7c673cae | 155 | |
11fdf7f2 | 156 | class StandbyInfo |
7c673cae | 157 | { |
11fdf7f2 | 158 | public: |
9f95a23c | 159 | uint64_t gid = 0; |
11fdf7f2 TL |
160 | std::string name; |
161 | std::vector<ModuleInfo> available_modules; | |
9f95a23c | 162 | uint64_t mgr_features = 0; |
11fdf7f2 TL |
163 | |
164 | StandbyInfo(uint64_t gid_, const std::string &name_, | |
9f95a23c TL |
165 | const std::vector<ModuleInfo>& am, |
166 | uint64_t feat) | |
167 | : gid(gid_), name(name_), available_modules(am), | |
168 | mgr_features(feat) | |
11fdf7f2 TL |
169 | {} |
170 | ||
9f95a23c | 171 | StandbyInfo() {} |
11fdf7f2 | 172 | |
9f95a23c | 173 | void encode(ceph::buffer::list& bl) const |
11fdf7f2 | 174 | { |
9f95a23c | 175 | ENCODE_START(4, 1, bl); |
11fdf7f2 TL |
176 | encode(gid, bl); |
177 | encode(name, bl); | |
178 | std::set<std::string> old_available_modules; | |
179 | for (const auto &i : available_modules) { | |
180 | old_available_modules.insert(i.name); | |
181 | } | |
182 | encode(old_available_modules, bl); // version 2 | |
183 | encode(available_modules, bl); // version 3 | |
9f95a23c | 184 | encode(mgr_features, bl); // v4 |
11fdf7f2 | 185 | ENCODE_FINISH(bl); |
224ce89b | 186 | } |
7c673cae | 187 | |
9f95a23c | 188 | void decode(ceph::buffer::list::const_iterator& p) |
11fdf7f2 | 189 | { |
9f95a23c | 190 | DECODE_START(4, p); |
11fdf7f2 TL |
191 | decode(gid, p); |
192 | decode(name, p); | |
193 | if (struct_v >= 2) { | |
194 | std::set<std::string> old_available_modules; | |
195 | decode(old_available_modules, p); | |
196 | if (struct_v < 3) { | |
197 | for (const auto &name : old_available_modules) { | |
198 | MgrMap::ModuleInfo info; | |
199 | info.name = name; | |
200 | available_modules.push_back(std::move(info)); | |
201 | } | |
202 | } | |
203 | } | |
204 | if (struct_v >= 3) { | |
205 | decode(available_modules, p); | |
206 | } | |
9f95a23c TL |
207 | if (struct_v >= 4) { |
208 | decode(mgr_features, p); | |
209 | } | |
11fdf7f2 TL |
210 | DECODE_FINISH(p); |
211 | } | |
212 | ||
213 | bool have_module(const std::string &module_name) const | |
214 | { | |
215 | auto it = std::find_if(available_modules.begin(), | |
216 | available_modules.end(), | |
217 | [module_name](const ModuleInfo &m) -> bool { | |
218 | return m.name == module_name; | |
219 | }); | |
220 | ||
221 | return it != available_modules.end(); | |
222 | } | |
223 | }; | |
224 | ||
7c673cae | 225 | epoch_t epoch = 0; |
9f95a23c | 226 | epoch_t last_failure_osd_epoch = 0; |
7c673cae FG |
227 | |
228 | /// global_id of the ceph-mgr instance selected as a leader | |
229 | uint64_t active_gid = 0; | |
230 | /// server address reported by the leader once it is active | |
11fdf7f2 | 231 | entity_addrvec_t active_addrs; |
7c673cae FG |
232 | /// whether the nominated leader is active (i.e. has initialized its server) |
233 | bool available = false; | |
234 | /// the name (foo in mgr.<foo>) of the active daemon | |
235 | std::string active_name; | |
11fdf7f2 TL |
236 | /// when the active mgr became active, or we lost the active mgr |
237 | utime_t active_change; | |
9f95a23c TL |
238 | /// features |
239 | uint64_t active_mgr_features = 0; | |
240 | ||
f67539c2 | 241 | std::vector<entity_addrvec_t> clients; // for blocklist |
7c673cae FG |
242 | |
243 | std::map<uint64_t, StandbyInfo> standbys; | |
244 | ||
11fdf7f2 | 245 | // Modules which are enabled |
224ce89b | 246 | std::set<std::string> modules; |
11fdf7f2 TL |
247 | |
248 | // Modules which should always be enabled. A manager daemon will enable | |
249 | // modules from the union of this set and the `modules` set above, latest | |
250 | // active version. | |
251 | std::map<uint32_t, std::set<std::string>> always_on_modules; | |
252 | ||
253 | // Modules which are reported to exist | |
254 | std::vector<ModuleInfo> available_modules; | |
224ce89b | 255 | |
3efd9988 FG |
256 | // Map of module name to URI, indicating services exposed by |
257 | // running modules on the active mgr daemon. | |
258 | std::map<std::string, std::string> services; | |
259 | ||
7c673cae | 260 | epoch_t get_epoch() const { return epoch; } |
9f95a23c | 261 | epoch_t get_last_failure_osd_epoch() const { return last_failure_osd_epoch; } |
f67539c2 | 262 | const entity_addrvec_t& get_active_addrs() const { return active_addrs; } |
7c673cae FG |
263 | uint64_t get_active_gid() const { return active_gid; } |
264 | bool get_available() const { return available; } | |
265 | const std::string &get_active_name() const { return active_name; } | |
11fdf7f2 | 266 | const utime_t& get_active_change() const { return active_change; } |
f67539c2 | 267 | int get_num_standby() const { return standbys.size(); } |
7c673cae | 268 | |
224ce89b | 269 | bool all_support_module(const std::string& module) { |
11fdf7f2 | 270 | if (!have_module(module)) { |
224ce89b WB |
271 | return false; |
272 | } | |
273 | for (auto& p : standbys) { | |
11fdf7f2 | 274 | if (!p.second.have_module(module)) { |
224ce89b WB |
275 | return false; |
276 | } | |
277 | } | |
278 | return true; | |
279 | } | |
280 | ||
11fdf7f2 TL |
281 | bool have_module(const std::string &module_name) const |
282 | { | |
283 | for (const auto &i : available_modules) { | |
284 | if (i.name == module_name) { | |
285 | return true; | |
286 | } | |
287 | } | |
288 | ||
289 | return false; | |
290 | } | |
291 | ||
292 | const ModuleInfo *get_module_info(const std::string &module_name) const { | |
293 | for (const auto &i : available_modules) { | |
294 | if (i.name == module_name) { | |
295 | return &i; | |
296 | } | |
297 | } | |
298 | return nullptr; | |
299 | } | |
300 | ||
301 | bool can_run_module(const std::string &module_name, std::string *error) const | |
302 | { | |
303 | for (const auto &i : available_modules) { | |
304 | if (i.name == module_name) { | |
305 | *error = i.error_string; | |
306 | return i.can_run; | |
307 | } | |
308 | } | |
309 | ||
310 | std::ostringstream oss; | |
311 | oss << "Module '" << module_name << "' does not exist"; | |
312 | throw std::logic_error(oss.str()); | |
313 | } | |
314 | ||
315 | bool module_enabled(const std::string& module_name) const | |
316 | { | |
317 | return modules.find(module_name) != modules.end(); | |
318 | } | |
319 | ||
320 | bool any_supports_module(const std::string& module) const { | |
321 | if (have_module(module)) { | |
322 | return true; | |
323 | } | |
324 | for (auto& p : standbys) { | |
325 | if (p.second.have_module(module)) { | |
326 | return true; | |
327 | } | |
328 | } | |
329 | return false; | |
330 | } | |
331 | ||
9f95a23c | 332 | bool have_name(const std::string& name) const { |
c07f9fc5 FG |
333 | if (active_name == name) { |
334 | return true; | |
335 | } | |
336 | for (auto& p : standbys) { | |
337 | if (p.second.name == name) { | |
338 | return true; | |
339 | } | |
340 | } | |
341 | return false; | |
342 | } | |
343 | ||
344 | std::set<std::string> get_all_names() const { | |
345 | std::set<std::string> ls; | |
346 | if (active_name.size()) { | |
347 | ls.insert(active_name); | |
348 | } | |
349 | for (auto& p : standbys) { | |
350 | ls.insert(p.second.name); | |
351 | } | |
352 | return ls; | |
353 | } | |
354 | ||
11fdf7f2 | 355 | std::set<std::string> get_always_on_modules() const { |
f67539c2 TL |
356 | unsigned rnum = to_integer<uint32_t>(ceph_release()); |
357 | auto it = always_on_modules.find(rnum); | |
358 | if (it == always_on_modules.end()) { | |
359 | // ok, try the most recent release | |
360 | if (always_on_modules.empty()) { | |
361 | return {}; // ugh | |
362 | } | |
363 | --it; | |
364 | if (it->first < rnum) { | |
365 | return it->second; | |
366 | } | |
367 | return {}; // wth | |
368 | } | |
11fdf7f2 TL |
369 | return it->second; |
370 | } | |
371 | ||
9f95a23c | 372 | void encode(ceph::buffer::list& bl, uint64_t features) const |
7c673cae | 373 | { |
11fdf7f2 TL |
374 | if (!HAVE_FEATURE(features, SERVER_NAUTILUS)) { |
375 | ENCODE_START(5, 1, bl); | |
376 | encode(epoch, bl); | |
377 | encode(active_addrs.legacy_addr(), bl, features); | |
378 | encode(active_gid, bl); | |
379 | encode(available, bl); | |
380 | encode(active_name, bl); | |
381 | encode(standbys, bl); | |
382 | encode(modules, bl); | |
383 | ||
9f95a23c | 384 | // Pre-version 4 std::string std::list of available modules |
11fdf7f2 TL |
385 | // (replaced by direct encode of ModuleInfo below) |
386 | std::set<std::string> old_available_modules; | |
387 | for (const auto &i : available_modules) { | |
388 | old_available_modules.insert(i.name); | |
389 | } | |
390 | encode(old_available_modules, bl); | |
391 | ||
392 | encode(services, bl); | |
393 | encode(available_modules, bl); | |
394 | ENCODE_FINISH(bl); | |
395 | return; | |
396 | } | |
9f95a23c | 397 | ENCODE_START(11, 6, bl); |
11fdf7f2 TL |
398 | encode(epoch, bl); |
399 | encode(active_addrs, bl, features); | |
400 | encode(active_gid, bl); | |
401 | encode(available, bl); | |
402 | encode(active_name, bl); | |
403 | encode(standbys, bl); | |
404 | encode(modules, bl); | |
405 | encode(services, bl); | |
406 | encode(available_modules, bl); | |
407 | encode(active_change, bl); | |
408 | encode(always_on_modules, bl); | |
9f95a23c TL |
409 | encode(active_mgr_features, bl); |
410 | encode(last_failure_osd_epoch, bl); | |
411 | encode(clients, bl, features); | |
7c673cae | 412 | ENCODE_FINISH(bl); |
11fdf7f2 | 413 | return; |
7c673cae FG |
414 | } |
415 | ||
9f95a23c | 416 | void decode(ceph::buffer::list::const_iterator& p) |
7c673cae | 417 | { |
9f95a23c | 418 | DECODE_START(11, p); |
11fdf7f2 TL |
419 | decode(epoch, p); |
420 | decode(active_addrs, p); | |
421 | decode(active_gid, p); | |
422 | decode(available, p); | |
423 | decode(active_name, p); | |
424 | decode(standbys, p); | |
224ce89b | 425 | if (struct_v >= 2) { |
11fdf7f2 TL |
426 | decode(modules, p); |
427 | ||
428 | if (struct_v < 6) { | |
429 | // Reconstitute ModuleInfos from names | |
430 | std::set<std::string> module_name_list; | |
431 | decode(module_name_list, p); | |
432 | // Only need to unpack this field if we won't have the full | |
433 | // MgrMap::ModuleInfo structures added in v4 | |
434 | if (struct_v < 4) { | |
435 | for (const auto &i : module_name_list) { | |
436 | MgrMap::ModuleInfo info; | |
437 | info.name = i; | |
438 | available_modules.push_back(std::move(info)); | |
439 | } | |
440 | } | |
441 | } | |
224ce89b | 442 | } |
3efd9988 | 443 | if (struct_v >= 3) { |
11fdf7f2 TL |
444 | decode(services, p); |
445 | } | |
446 | if (struct_v >= 4) { | |
447 | decode(available_modules, p); | |
448 | } | |
449 | if (struct_v >= 7) { | |
450 | decode(active_change, p); | |
451 | } else { | |
452 | active_change = {}; | |
453 | } | |
454 | if (struct_v >= 8) { | |
455 | decode(always_on_modules, p); | |
3efd9988 | 456 | } |
9f95a23c TL |
457 | if (struct_v >= 9) { |
458 | decode(active_mgr_features, p); | |
459 | } | |
460 | if (struct_v >= 10) { | |
461 | decode(last_failure_osd_epoch, p); | |
462 | } | |
463 | if (struct_v >= 11) { | |
464 | decode(clients, p); | |
465 | } | |
7c673cae FG |
466 | DECODE_FINISH(p); |
467 | } | |
468 | ||
9f95a23c | 469 | void dump(ceph::Formatter *f) const { |
7c673cae FG |
470 | f->dump_int("epoch", epoch); |
471 | f->dump_int("active_gid", get_active_gid()); | |
472 | f->dump_string("active_name", get_active_name()); | |
11fdf7f2 TL |
473 | f->dump_object("active_addrs", active_addrs); |
474 | f->dump_stream("active_addr") << active_addrs.get_legacy_str(); | |
475 | f->dump_stream("active_change") << active_change; | |
9f95a23c | 476 | f->dump_unsigned("active_mgr_features", active_mgr_features); |
7c673cae FG |
477 | f->dump_bool("available", available); |
478 | f->open_array_section("standbys"); | |
479 | for (const auto &i : standbys) { | |
480 | f->open_object_section("standby"); | |
481 | f->dump_int("gid", i.second.gid); | |
482 | f->dump_string("name", i.second.name); | |
9f95a23c | 483 | f->dump_unsigned("mgr_features", i.second.mgr_features); |
224ce89b | 484 | f->open_array_section("available_modules"); |
11fdf7f2 TL |
485 | for (const auto& j : i.second.available_modules) { |
486 | j.dump(f); | |
224ce89b WB |
487 | } |
488 | f->close_section(); | |
7c673cae FG |
489 | f->close_section(); |
490 | } | |
224ce89b WB |
491 | f->close_section(); |
492 | f->open_array_section("modules"); | |
493 | for (auto& i : modules) { | |
494 | f->dump_string("module", i); | |
495 | } | |
496 | f->close_section(); | |
497 | f->open_array_section("available_modules"); | |
11fdf7f2 TL |
498 | for (const auto& j : available_modules) { |
499 | j.dump(f); | |
224ce89b | 500 | } |
3efd9988 FG |
501 | f->close_section(); |
502 | ||
503 | f->open_object_section("services"); | |
504 | for (const auto &i : services) { | |
505 | f->dump_string(i.first.c_str(), i.second); | |
506 | } | |
7c673cae | 507 | f->close_section(); |
11fdf7f2 TL |
508 | |
509 | f->open_object_section("always_on_modules"); | |
510 | for (auto& v : always_on_modules) { | |
511 | f->open_array_section(ceph_release_name(v.first)); | |
512 | for (auto& m : v.second) { | |
513 | f->dump_string("module", m); | |
514 | } | |
515 | f->close_section(); | |
516 | } | |
9f95a23c TL |
517 | f->dump_int("last_failure_osd_epoch", last_failure_osd_epoch); |
518 | f->open_array_section("active_clients"); | |
519 | for (const auto &c : clients) { | |
520 | f->dump_object("client", c); | |
521 | } | |
522 | f->close_section(); | |
11fdf7f2 | 523 | f->close_section(); |
7c673cae FG |
524 | } |
525 | ||
9f95a23c | 526 | static void generate_test_instances(std::list<MgrMap*> &l) { |
7c673cae FG |
527 | l.push_back(new MgrMap); |
528 | } | |
529 | ||
9f95a23c | 530 | void print_summary(ceph::Formatter *f, std::ostream *ss) const |
7c673cae FG |
531 | { |
532 | // One or the other, not both | |
11fdf7f2 | 533 | ceph_assert((ss != nullptr) != (f != nullptr)); |
7c673cae | 534 | if (f) { |
9f95a23c TL |
535 | f->dump_bool("available", available); |
536 | f->dump_int("num_standbys", standbys.size()); | |
537 | f->open_array_section("modules"); | |
538 | for (auto& i : modules) { | |
539 | f->dump_string("module", i); | |
540 | } | |
541 | f->close_section(); | |
542 | f->open_object_section("services"); | |
543 | for (const auto &i : services) { | |
544 | f->dump_string(i.first.c_str(), i.second); | |
545 | } | |
546 | f->close_section(); | |
7c673cae | 547 | } else { |
11fdf7f2 | 548 | utime_t now = ceph_clock_now(); |
7c673cae | 549 | if (get_active_gid() != 0) { |
31f18b77 | 550 | *ss << get_active_name(); |
7c673cae FG |
551 | if (!available) { |
552 | // If the daemon hasn't gone active yet, indicate that. | |
11fdf7f2 | 553 | *ss << "(active, starting"; |
31f18b77 | 554 | } else { |
11fdf7f2 | 555 | *ss << "(active"; |
7c673cae | 556 | } |
11fdf7f2 TL |
557 | if (active_change) { |
558 | *ss << ", since " << utimespan_str(now - active_change); | |
559 | } | |
560 | *ss << ")"; | |
7c673cae | 561 | } else { |
31f18b77 | 562 | *ss << "no daemons active"; |
11fdf7f2 TL |
563 | if (active_change) { |
564 | *ss << " (since " << utimespan_str(now - active_change) << ")"; | |
565 | } | |
7c673cae FG |
566 | } |
567 | if (standbys.size()) { | |
31f18b77 | 568 | *ss << ", standbys: "; |
7c673cae FG |
569 | bool first = true; |
570 | for (const auto &i : standbys) { | |
571 | if (!first) { | |
572 | *ss << ", "; | |
573 | } | |
574 | *ss << i.second.name; | |
575 | first = false; | |
576 | } | |
577 | } | |
578 | } | |
579 | } | |
580 | ||
9f95a23c TL |
581 | friend std::ostream& operator<<(std::ostream& out, const MgrMap& m) { |
582 | std::ostringstream ss; | |
7c673cae FG |
583 | m.print_summary(nullptr, &ss); |
584 | return out << ss.str(); | |
585 | } | |
11fdf7f2 | 586 | |
9f95a23c | 587 | friend std::ostream& operator<<(std::ostream& out, const std::vector<ModuleInfo>& mi) { |
11fdf7f2 TL |
588 | for (const auto &i : mi) { |
589 | out << i.name << " "; | |
590 | } | |
591 | return out; | |
592 | } | |
7c673cae FG |
593 | }; |
594 | ||
595 | WRITE_CLASS_ENCODER_FEATURES(MgrMap) | |
11fdf7f2 TL |
596 | WRITE_CLASS_ENCODER(MgrMap::StandbyInfo) |
597 | WRITE_CLASS_ENCODER(MgrMap::ModuleInfo); | |
598 | WRITE_CLASS_ENCODER(MgrMap::ModuleOption); | |
7c673cae FG |
599 | |
600 | #endif | |
601 |