]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | from collections import namedtuple |
2 | from rest.app.util import memoize | |
3 | ||
4 | from rest.logger import logger | |
5 | log = logger() | |
6 | ||
7 | ||
8 | CRUSH_RULE_TYPE_REPLICATED = 1 | |
9 | CRUSH_RULE_TYPE_ERASURE = 3 | |
10 | ||
11 | ||
12 | ServiceId = namedtuple('ServiceId', ['fsid', 'service_type', 'service_id']) | |
13 | ||
14 | ||
15 | MON = 'mon' | |
16 | OSD = 'osd' | |
17 | MDS = 'mds' | |
18 | POOL = 'pool' | |
19 | OSD_MAP = 'osd_map' | |
20 | CRUSH_RULE = 'crush_rule' | |
21 | CLUSTER = 'cluster' | |
22 | SERVER = 'server' | |
23 | ||
24 | ||
25 | class SyncObject(object): | |
26 | """ | |
27 | An object from a Ceph cluster that we are maintaining | |
28 | a copy of on the Calamari server. | |
29 | ||
30 | We wrap these JSON-serializable objects in a python object to: | |
31 | ||
32 | - Decorate them with things like id-to-entry dicts | |
33 | - Have a generic way of seeing the version of an object | |
34 | ||
35 | """ | |
36 | def __init__(self, version, data): | |
37 | self.version = version | |
38 | self.data = data | |
39 | ||
40 | @classmethod | |
41 | def cmp(cls, a, b): | |
42 | """ | |
43 | Slight bastardization of cmp. Takes two versions, | |
44 | and returns a cmp-like value, except that if versions | |
45 | are not sortable we only return 0 or 1. | |
46 | """ | |
47 | # Version is something unique per version (like a hash) | |
48 | return 1 if a != b else 0 | |
49 | ||
50 | ||
51 | class VersionedSyncObject(SyncObject): | |
52 | @classmethod | |
53 | def cmp(cls, a, b): | |
54 | # Version is something numeric like an epoch | |
55 | return cmp(a, b) | |
56 | ||
57 | ||
58 | class OsdMap(VersionedSyncObject): | |
59 | str = OSD_MAP | |
60 | ||
61 | def __init__(self, version, data): | |
62 | super(OsdMap, self).__init__(version, data) | |
63 | if data is not None: | |
64 | self.osds_by_id = dict([(o['osd'], o) for o in data['osds']]) | |
65 | self.pools_by_id = dict([(p['pool'], p) for p in data['pools']]) | |
66 | self.osd_tree_node_by_id = dict([(o['id'], o) for o in data['tree']['nodes'] if o['id'] >= 0]) | |
67 | ||
68 | # Special case Yuck | |
69 | flags = data.get('flags', '').replace('pauserd,pausewr', 'pause') | |
70 | tokenized_flags = flags.split(',') | |
71 | ||
72 | self.flags = dict([(x, x in tokenized_flags) for x in OSD_FLAGS]) | |
73 | else: | |
74 | self.osds_by_id = {} | |
75 | self.pools_by_id = {} | |
76 | self.osd_tree_node_by_id = {} | |
77 | self.flags = dict([(x, False) for x in OSD_FLAGS]) | |
78 | ||
79 | @property | |
80 | def osd_metadata(self): | |
81 | return self.data['osd_metadata'] | |
82 | ||
83 | @memoize | |
84 | def get_tree_nodes_by_id(self): | |
85 | return dict((n["id"], n) for n in self.data['tree']["nodes"]) | |
86 | ||
87 | def _get_crush_rule_osds(self, rule): | |
88 | nodes_by_id = self.get_tree_nodes_by_id() | |
89 | ||
90 | def _gather_leaf_ids(node): | |
91 | if node['id'] >= 0: | |
92 | return set([node['id']]) | |
93 | ||
94 | result = set() | |
95 | for child_id in node['children']: | |
96 | if child_id >= 0: | |
97 | result.add(child_id) | |
98 | else: | |
99 | result |= _gather_leaf_ids(nodes_by_id[child_id]) | |
100 | ||
101 | return result | |
102 | ||
103 | def _gather_descendent_ids(node, typ): | |
104 | result = set() | |
105 | for child_id in node['children']: | |
106 | child_node = nodes_by_id[child_id] | |
107 | if child_node['type'] == typ: | |
108 | result.add(child_node['id']) | |
109 | elif 'children' in child_node: | |
110 | result |= _gather_descendent_ids(child_node, typ) | |
111 | ||
112 | return result | |
113 | ||
114 | def _gather_osds(root, steps): | |
115 | if root['id'] >= 0: | |
116 | return set([root['id']]) | |
117 | ||
118 | osds = set() | |
119 | step = steps[0] | |
120 | if step['op'] == 'choose_firstn': | |
121 | # Choose all descendents of the current node of type 'type' | |
122 | d = _gather_descendent_ids(root, step['type']) | |
123 | for desc_node in [nodes_by_id[i] for i in d]: | |
124 | osds |= _gather_osds(desc_node, steps[1:]) | |
125 | elif step['op'] == 'chooseleaf_firstn': | |
126 | # Choose all descendents of the current node of type 'type', | |
127 | # and select all leaves beneath those | |
128 | for desc_node in [nodes_by_id[i] for i in _gather_descendent_ids(root, step['type'])]: | |
129 | # Short circuit another iteration to find the emit | |
130 | # and assume anything we've done a chooseleaf on | |
131 | # is going to be part of the selected set of osds | |
132 | osds |= _gather_leaf_ids(desc_node) | |
133 | elif step['op'] == 'emit': | |
134 | if root['id'] >= 0: | |
135 | osds |= root['id'] | |
136 | ||
137 | return osds | |
138 | ||
139 | osds = set() | |
140 | for i, step in enumerate(rule['steps']): | |
141 | if step['op'] == 'take': | |
142 | osds |= _gather_osds(nodes_by_id[step['item']], rule['steps'][i + 1:]) | |
143 | return osds | |
144 | ||
145 | @property | |
146 | @memoize | |
147 | def osds_by_rule_id(self): | |
148 | result = {} | |
149 | for rule in self.data['crush']['rules']: | |
150 | result[rule['rule_id']] = list(self._get_crush_rule_osds(rule)) | |
151 | ||
152 | return result | |
153 | ||
154 | @property | |
155 | @memoize | |
156 | def osds_by_pool(self): | |
157 | """ | |
158 | Get the OSDS which may be used in this pool | |
159 | ||
160 | :return dict of pool ID to OSD IDs in the pool | |
161 | """ | |
162 | ||
163 | result = {} | |
164 | for pool_id, pool in self.pools_by_id.items(): | |
165 | osds = None | |
166 | for rule in [r for r in self.data['crush']['rules'] if r['ruleset'] == pool['crush_ruleset']]: | |
167 | if rule['min_size'] <= pool['size'] <= rule['max_size']: | |
168 | osds = self.osds_by_rule_id[rule['rule_id']] | |
169 | ||
170 | if osds is None: | |
171 | # Fallthrough, the pool size didn't fall within any of the rules in its ruleset, Calamari | |
172 | # doesn't understand. Just report all OSDs instead of failing horribly. | |
173 | log.error("Cannot determine OSDS for pool %s" % pool_id) | |
174 | osds = self.osds_by_id.keys() | |
175 | ||
176 | result[pool_id] = osds | |
177 | ||
178 | return result | |
179 | ||
180 | @property | |
181 | @memoize | |
182 | def osd_pools(self): | |
183 | """ | |
184 | A dict of OSD ID to list of pool IDs | |
185 | """ | |
186 | osds = dict([(osd_id, []) for osd_id in self.osds_by_id.keys()]) | |
187 | for pool_id in self.pools_by_id.keys(): | |
188 | for in_pool_id in self.osds_by_pool[pool_id]: | |
189 | osds[in_pool_id].append(pool_id) | |
190 | ||
191 | return osds | |
192 | ||
193 | ||
194 | class FsMap(VersionedSyncObject): | |
195 | str = 'fs_map' | |
196 | ||
197 | ||
198 | class MonMap(VersionedSyncObject): | |
199 | str = 'mon_map' | |
200 | ||
201 | ||
202 | class MonStatus(VersionedSyncObject): | |
203 | str = 'mon_status' | |
204 | ||
205 | def __init__(self, version, data): | |
206 | super(MonStatus, self).__init__(version, data) | |
207 | if data is not None: | |
208 | self.mons_by_rank = dict([(m['rank'], m) for m in data['monmap']['mons']]) | |
209 | else: | |
210 | self.mons_by_rank = {} | |
211 | ||
212 | ||
213 | class PgSummary(SyncObject): | |
214 | """ | |
215 | A summary of the state of PGs in the cluster, reported by pool and by OSD. | |
216 | """ | |
217 | str = 'pg_summary' | |
218 | ||
219 | ||
220 | class Health(SyncObject): | |
221 | str = 'health' | |
222 | ||
223 | ||
224 | class Config(SyncObject): | |
225 | str = 'config' | |
226 | ||
227 | ||
228 | class NotFound(Exception): | |
229 | def __init__(self, object_type, object_id): | |
230 | self.object_type = object_type | |
231 | self.object_id = object_id | |
232 | ||
233 | def __str__(self): | |
234 | return "Object of type %s with id %s not found" % (self.object_type, self.object_id) | |
235 | ||
236 | ||
237 | # The objects that ClusterMonitor keeps copies of from the mon | |
238 | SYNC_OBJECT_TYPES = [FsMap, OsdMap, MonMap, MonStatus, PgSummary, Health, Config] | |
239 | SYNC_OBJECT_STR_TYPE = dict((t.str, t) for t in SYNC_OBJECT_TYPES) | |
240 | ||
241 | USER_REQUEST_COMPLETE = 'complete' | |
242 | USER_REQUEST_SUBMITTED = 'submitted' | |
243 | ||
244 | # List of allowable things to send as ceph commands to OSDs | |
245 | OSD_IMPLEMENTED_COMMANDS = ('scrub', 'deep_scrub', 'repair') | |
246 | OSD_FLAGS = ('pause', 'noup', 'nodown', 'noout', 'noin', 'nobackfill', 'norecover', 'noscrub', 'nodeep-scrub') | |
247 | ||
248 | # Severity codes for Calamari events | |
249 | CRITICAL = 1 | |
250 | ERROR = 2 | |
251 | WARNING = 3 | |
252 | RECOVERY = 4 | |
253 | INFO = 5 | |
254 | ||
255 | SEVERITIES = { | |
256 | CRITICAL: "CRITICAL", | |
257 | ERROR: "ERROR", | |
258 | WARNING: "WARNING", | |
259 | RECOVERY: "RECOVERY", | |
260 | INFO: "INFO" | |
261 | } | |
262 | ||
263 | STR_TO_SEVERITY = dict([(b, a) for (a, b) in SEVERITIES.items()]) | |
264 | ||
265 | ||
266 | def severity_str(severity): | |
267 | return SEVERITIES[severity] | |
268 | ||
269 | ||
270 | def severity_from_str(severitry_str): | |
271 | return STR_TO_SEVERITY[severitry_str] |