]>
Commit | Line | Data |
---|---|---|
7c673cae | 1 | from collections import namedtuple |
7c673cae FG |
2 | |
3 | ||
4 | CRUSH_RULE_TYPE_REPLICATED = 1 | |
5 | CRUSH_RULE_TYPE_ERASURE = 3 | |
6 | ||
7 | ||
8 | ServiceId = namedtuple('ServiceId', ['fsid', 'service_type', 'service_id']) | |
9 | ||
10 | ||
11 | MON = 'mon' | |
12 | OSD = 'osd' | |
13 | MDS = 'mds' | |
14 | POOL = 'pool' | |
15 | OSD_MAP = 'osd_map' | |
16 | CRUSH_RULE = 'crush_rule' | |
17 | CLUSTER = 'cluster' | |
18 | SERVER = 'server' | |
19 | ||
20 | ||
31f18b77 FG |
21 | def memoize(function): |
22 | def wrapper(*args): | |
23 | self = args[0] | |
24 | if not hasattr(self, "_memo"): | |
25 | self._memo = {} | |
7c673cae | 26 | |
31f18b77 FG |
27 | if args in self._memo: |
28 | return self._memo[args] | |
29 | else: | |
30 | rv = function(*args) | |
31 | self._memo[args] = rv | |
32 | return rv | |
33 | return wrapper | |
7c673cae | 34 | |
7c673cae | 35 | |
31f18b77 FG |
36 | OSD_FLAGS = ('pause', 'noup', 'nodown', 'noout', 'noin', 'nobackfill', |
37 | 'norecover', 'noscrub', 'nodeep-scrub') | |
7c673cae | 38 | |
31f18b77 FG |
39 | class DataWrapper(object): |
40 | def __init__(self, data): | |
41 | self.data = data | |
7c673cae FG |
42 | |
43 | ||
31f18b77 | 44 | class OsdMap(DataWrapper): |
7c673cae FG |
45 | str = OSD_MAP |
46 | ||
31f18b77 FG |
47 | def __init__(self, data): |
48 | super(OsdMap, self).__init__(data) | |
7c673cae FG |
49 | if data is not None: |
50 | self.osds_by_id = dict([(o['osd'], o) for o in data['osds']]) | |
51 | self.pools_by_id = dict([(p['pool'], p) for p in data['pools']]) | |
52 | self.osd_tree_node_by_id = dict([(o['id'], o) for o in data['tree']['nodes'] if o['id'] >= 0]) | |
53 | ||
54 | # Special case Yuck | |
55 | flags = data.get('flags', '').replace('pauserd,pausewr', 'pause') | |
56 | tokenized_flags = flags.split(',') | |
57 | ||
58 | self.flags = dict([(x, x in tokenized_flags) for x in OSD_FLAGS]) | |
59 | else: | |
60 | self.osds_by_id = {} | |
61 | self.pools_by_id = {} | |
62 | self.osd_tree_node_by_id = {} | |
63 | self.flags = dict([(x, False) for x in OSD_FLAGS]) | |
64 | ||
65 | @property | |
66 | def osd_metadata(self): | |
67 | return self.data['osd_metadata'] | |
68 | ||
69 | @memoize | |
70 | def get_tree_nodes_by_id(self): | |
71 | return dict((n["id"], n) for n in self.data['tree']["nodes"]) | |
72 | ||
73 | def _get_crush_rule_osds(self, rule): | |
74 | nodes_by_id = self.get_tree_nodes_by_id() | |
75 | ||
76 | def _gather_leaf_ids(node): | |
77 | if node['id'] >= 0: | |
78 | return set([node['id']]) | |
79 | ||
80 | result = set() | |
81 | for child_id in node['children']: | |
82 | if child_id >= 0: | |
83 | result.add(child_id) | |
84 | else: | |
85 | result |= _gather_leaf_ids(nodes_by_id[child_id]) | |
86 | ||
87 | return result | |
88 | ||
89 | def _gather_descendent_ids(node, typ): | |
90 | result = set() | |
91 | for child_id in node['children']: | |
92 | child_node = nodes_by_id[child_id] | |
93 | if child_node['type'] == typ: | |
94 | result.add(child_node['id']) | |
95 | elif 'children' in child_node: | |
96 | result |= _gather_descendent_ids(child_node, typ) | |
97 | ||
98 | return result | |
99 | ||
100 | def _gather_osds(root, steps): | |
101 | if root['id'] >= 0: | |
102 | return set([root['id']]) | |
103 | ||
104 | osds = set() | |
105 | step = steps[0] | |
106 | if step['op'] == 'choose_firstn': | |
107 | # Choose all descendents of the current node of type 'type' | |
108 | d = _gather_descendent_ids(root, step['type']) | |
109 | for desc_node in [nodes_by_id[i] for i in d]: | |
110 | osds |= _gather_osds(desc_node, steps[1:]) | |
111 | elif step['op'] == 'chooseleaf_firstn': | |
112 | # Choose all descendents of the current node of type 'type', | |
113 | # and select all leaves beneath those | |
114 | for desc_node in [nodes_by_id[i] for i in _gather_descendent_ids(root, step['type'])]: | |
115 | # Short circuit another iteration to find the emit | |
116 | # and assume anything we've done a chooseleaf on | |
117 | # is going to be part of the selected set of osds | |
118 | osds |= _gather_leaf_ids(desc_node) | |
119 | elif step['op'] == 'emit': | |
120 | if root['id'] >= 0: | |
121 | osds |= root['id'] | |
122 | ||
123 | return osds | |
124 | ||
125 | osds = set() | |
126 | for i, step in enumerate(rule['steps']): | |
127 | if step['op'] == 'take': | |
128 | osds |= _gather_osds(nodes_by_id[step['item']], rule['steps'][i + 1:]) | |
129 | return osds | |
130 | ||
131 | @property | |
132 | @memoize | |
133 | def osds_by_rule_id(self): | |
134 | result = {} | |
135 | for rule in self.data['crush']['rules']: | |
136 | result[rule['rule_id']] = list(self._get_crush_rule_osds(rule)) | |
137 | ||
138 | return result | |
139 | ||
140 | @property | |
141 | @memoize | |
142 | def osds_by_pool(self): | |
143 | """ | |
144 | Get the OSDS which may be used in this pool | |
145 | ||
146 | :return dict of pool ID to OSD IDs in the pool | |
147 | """ | |
148 | ||
149 | result = {} | |
150 | for pool_id, pool in self.pools_by_id.items(): | |
151 | osds = None | |
152 | for rule in [r for r in self.data['crush']['rules'] if r['ruleset'] == pool['crush_ruleset']]: | |
153 | if rule['min_size'] <= pool['size'] <= rule['max_size']: | |
154 | osds = self.osds_by_rule_id[rule['rule_id']] | |
155 | ||
156 | if osds is None: | |
157 | # Fallthrough, the pool size didn't fall within any of the rules in its ruleset, Calamari | |
158 | # doesn't understand. Just report all OSDs instead of failing horribly. | |
7c673cae FG |
159 | osds = self.osds_by_id.keys() |
160 | ||
161 | result[pool_id] = osds | |
162 | ||
163 | return result | |
164 | ||
165 | @property | |
166 | @memoize | |
167 | def osd_pools(self): | |
168 | """ | |
169 | A dict of OSD ID to list of pool IDs | |
170 | """ | |
171 | osds = dict([(osd_id, []) for osd_id in self.osds_by_id.keys()]) | |
172 | for pool_id in self.pools_by_id.keys(): | |
173 | for in_pool_id in self.osds_by_pool[pool_id]: | |
174 | osds[in_pool_id].append(pool_id) | |
175 | ||
176 | return osds | |
177 | ||
178 | ||
31f18b77 | 179 | class FsMap(DataWrapper): |
7c673cae FG |
180 | str = 'fs_map' |
181 | ||
224ce89b WB |
182 | def get_filesystem(self, fscid): |
183 | for fs in self.data['filesystems']: | |
184 | if fs['id'] == fscid: | |
185 | return fs | |
186 | ||
187 | raise NotFound("filesystem", fscid) | |
188 | ||
7c673cae | 189 | |
31f18b77 | 190 | class MonMap(DataWrapper): |
7c673cae FG |
191 | str = 'mon_map' |
192 | ||
193 | ||
31f18b77 | 194 | class MonStatus(DataWrapper): |
7c673cae FG |
195 | str = 'mon_status' |
196 | ||
31f18b77 FG |
197 | def __init__(self, data): |
198 | super(MonStatus, self).__init__(data) | |
7c673cae FG |
199 | if data is not None: |
200 | self.mons_by_rank = dict([(m['rank'], m) for m in data['monmap']['mons']]) | |
201 | else: | |
202 | self.mons_by_rank = {} | |
203 | ||
204 | ||
31f18b77 | 205 | class PgSummary(DataWrapper): |
7c673cae FG |
206 | """ |
207 | A summary of the state of PGs in the cluster, reported by pool and by OSD. | |
208 | """ | |
209 | str = 'pg_summary' | |
210 | ||
211 | ||
31f18b77 | 212 | class Health(DataWrapper): |
7c673cae FG |
213 | str = 'health' |
214 | ||
215 | ||
31f18b77 | 216 | class Config(DataWrapper): |
7c673cae FG |
217 | str = 'config' |
218 | ||
219 | ||
220 | class NotFound(Exception): | |
221 | def __init__(self, object_type, object_id): | |
222 | self.object_type = object_type | |
223 | self.object_id = object_id | |
224 | ||
225 | def __str__(self): | |
226 | return "Object of type %s with id %s not found" % (self.object_type, self.object_id) | |
227 |