1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 """Miscellaneous logical units that don't fit into any category."""
32
33 import logging
34 import time
35
36 from ganeti import constants
37 from ganeti import errors
38 from ganeti import locking
39 from ganeti import qlang
40 from ganeti import query
41 from ganeti import utils
42 from ganeti.cmdlib.base import NoHooksLU, QueryBase
43 from ganeti.cmdlib.common import (
44 GetWantedNodes,
45 SupportsOob,
46 ExpandNodeUuidAndName
47 )
51 """Logical unit for OOB handling.
52
53 """
54 REQ_BGL = False
55 _SKIP_MASTER = (constants.OOB_POWER_OFF, constants.OOB_POWER_CYCLE)
56
58 """Gather locks we need.
59
60 """
61 if self.op.node_names:
62 (self.op.node_uuids, self.op.node_names) = \
63 GetWantedNodes(self, self.op.node_names)
64 lock_node_uuids = self.op.node_uuids
65 else:
66 lock_node_uuids = locking.ALL_SET
67
68 self.needed_locks = {
69 locking.LEVEL_NODE: lock_node_uuids,
70 }
71
73 """Check prerequisites.
74
75 This checks:
76 - the node exists in the configuration
77 - OOB is supported
78
79 Any errors are signaled by raising errors.OpPrereqError.
80
81 """
82 self.nodes = []
83 self.master_node_uuid = self.cfg.GetMasterNode()
84 master_node_obj = self.cfg.GetNodeInfo(self.master_node_uuid)
85
86 assert self.op.power_delay >= 0.0
87
88 if self.op.node_uuids:
89 if (self.op.command in self._SKIP_MASTER and
90 master_node_obj.uuid in self.op.node_uuids):
91 master_oob_handler = SupportsOob(self.cfg, master_node_obj)
92
93 if master_oob_handler:
94 additional_text = ("run '%s %s %s' if you want to operate on the"
95 " master regardless") % (master_oob_handler,
96 self.op.command,
97 master_node_obj.name)
98 else:
99 additional_text = "it does not support out-of-band operations"
100
101 raise errors.OpPrereqError(("Operating on the master node %s is not"
102 " allowed for %s; %s") %
103 (master_node_obj.name, self.op.command,
104 additional_text), errors.ECODE_INVAL)
105 else:
106 self.op.node_uuids = self.cfg.GetNodeList()
107 if self.op.command in self._SKIP_MASTER:
108 self.op.node_uuids.remove(master_node_obj.uuid)
109
110 if self.op.command in self._SKIP_MASTER:
111 assert master_node_obj.uuid not in self.op.node_uuids
112
113 for node_uuid in self.op.node_uuids:
114 node = self.cfg.GetNodeInfo(node_uuid)
115 if node is None:
116 raise errors.OpPrereqError("Node %s not found" % node_uuid,
117 errors.ECODE_NOENT)
118
119 self.nodes.append(node)
120
121 if (not self.op.ignore_status and
122 (self.op.command == constants.OOB_POWER_OFF and not node.offline)):
123 raise errors.OpPrereqError(("Cannot power off node %s because it is"
124 " not marked offline") % node.name,
125 errors.ECODE_STATE)
126
127 - def Exec(self, feedback_fn):
128 """Execute OOB and return result if we expect any.
129
130 """
131 ret = []
132
133 for idx, node in enumerate(utils.NiceSort(self.nodes,
134 key=lambda node: node.name)):
135 node_entry = [(constants.RS_NORMAL, node.name)]
136 ret.append(node_entry)
137
138 oob_program = SupportsOob(self.cfg, node)
139
140 if not oob_program:
141 node_entry.append((constants.RS_UNAVAIL, None))
142 continue
143
144 logging.info("Executing out-of-band command '%s' using '%s' on %s",
145 self.op.command, oob_program, node.name)
146 result = self.rpc.call_run_oob(self.master_node_uuid, oob_program,
147 self.op.command, node.name,
148 self.op.timeout)
149
150 if result.fail_msg:
151 self.LogWarning("Out-of-band RPC failed on node '%s': %s",
152 node.name, result.fail_msg)
153 node_entry.append((constants.RS_NODATA, None))
154 else:
155 try:
156 self._CheckPayload(result)
157 except errors.OpExecError, err:
158 self.LogWarning("Payload returned by node '%s' is not valid: %s",
159 node.name, err)
160 node_entry.append((constants.RS_NODATA, None))
161 else:
162 if self.op.command == constants.OOB_HEALTH:
163
164 for item, status in result.payload:
165 if status in [constants.OOB_STATUS_WARNING,
166 constants.OOB_STATUS_CRITICAL]:
167 self.LogWarning("Item '%s' on node '%s' has status '%s'",
168 item, node.name, status)
169
170 if self.op.command == constants.OOB_POWER_ON:
171 node.powered = True
172 elif self.op.command == constants.OOB_POWER_OFF:
173 node.powered = False
174 elif self.op.command == constants.OOB_POWER_STATUS:
175 powered = result.payload[constants.OOB_POWER_STATUS_POWERED]
176 if powered != node.powered:
177 logging.warning(("Recorded power state (%s) of node '%s' does not"
178 " match actual power state (%s)"), node.powered,
179 node.name, powered)
180
181
182 if self.op.command in (constants.OOB_POWER_ON,
183 constants.OOB_POWER_OFF):
184 self.cfg.Update(node, feedback_fn)
185
186 node_entry.append((constants.RS_NORMAL, result.payload))
187
188 if (self.op.command == constants.OOB_POWER_ON and
189 idx < len(self.nodes) - 1):
190 time.sleep(self.op.power_delay)
191
192 return ret
193
195 """Checks if the payload is valid.
196
197 @param result: RPC result
198 @raises errors.OpExecError: If payload is not valid
199
200 """
201 errs = []
202 if self.op.command == constants.OOB_HEALTH:
203 if not isinstance(result.payload, list):
204 errs.append("command 'health' is expected to return a list but got %s" %
205 type(result.payload))
206 else:
207 for item, status in result.payload:
208 if status not in constants.OOB_STATUSES:
209 errs.append("health item '%s' has invalid status '%s'" %
210 (item, status))
211
212 if self.op.command == constants.OOB_POWER_STATUS:
213 if not isinstance(result.payload, dict):
214 errs.append("power-status is expected to return a dict but got %s" %
215 type(result.payload))
216
217 if self.op.command in [
218 constants.OOB_POWER_ON,
219 constants.OOB_POWER_OFF,
220 constants.OOB_POWER_CYCLE,
221 ]:
222 if result.payload is not None:
223 errs.append("%s is expected to not return payload but got '%s'" %
224 (self.op.command, result.payload))
225
226 if errs:
227 raise errors.OpExecError("Check of out-of-band payload failed due to %s" %
228 utils.CommaJoin(errs))
229
232 FIELDS = query.EXTSTORAGE_FIELDS
233
235
236
237
238 lu.needed_locks = {}
239
240
241
242
243 if self.names:
244 self.wanted = [lu.cfg.GetNodeInfoByName(name).uuid for name in self.names]
245 else:
246 self.wanted = locking.ALL_SET
247
248 self.do_locking = self.use_locking
249
252
253 @staticmethod
255 """Remaps a per-node return list into an a per-provider per-node dictionary
256
257 @param rlist: a map with node uuids as keys and ExtStorage objects as values
258
259 @rtype: dict
260 @return: a dictionary with extstorage providers as keys and as
261 value another map, with node uuids as keys and tuples of
262 (path, status, diagnose, parameters) as values, eg::
263
264 {"provider1": {"node_uuid1": [(/usr/lib/..., True, "", [])]
265 "node_uuid2": [(/srv/..., False, "missing file")]
266 "node_uuid3": [(/srv/..., True, "", [])]
267 }
268
269 """
270 all_es = {}
271
272
273
274 good_nodes = [node_uuid for node_uuid in rlist
275 if not rlist[node_uuid].fail_msg]
276 for node_uuid, nr in rlist.items():
277 if nr.fail_msg or not nr.payload:
278 continue
279 for (name, path, status, diagnose, params) in nr.payload:
280 if name not in all_es:
281
282
283 all_es[name] = {}
284 for nuuid in good_nodes:
285 all_es[name][nuuid] = []
286
287 params = [tuple(v) for v in params]
288 all_es[name][node_uuid].append((path, status, diagnose, params))
289 return all_es
290
292 """Computes the list of nodes and their attributes.
293
294 """
295 valid_nodes = [node.uuid
296 for node in lu.cfg.GetAllNodesInfo().values()
297 if not node.offline and node.vm_capable]
298 pol = self._DiagnoseByProvider(lu.rpc.call_extstorage_diagnose(valid_nodes))
299
300 data = {}
301
302 nodegroup_list = lu.cfg.GetNodeGroupList()
303
304 for (es_name, es_data) in pol.items():
305
306
307
308
309
310
311 ndgrp_data = {}
312 for nodegroup in nodegroup_list:
313 ndgrp = lu.cfg.GetNodeGroup(nodegroup)
314
315 nodegroup_nodes = ndgrp.members
316 nodegroup_name = ndgrp.name
317 node_statuses = []
318
319 for node in nodegroup_nodes:
320 if node in valid_nodes:
321 if es_data[node] != []:
322 node_status = es_data[node][0][1]
323 node_statuses.append(node_status)
324 else:
325 node_statuses.append(False)
326
327 if False in node_statuses:
328 ndgrp_data[nodegroup_name] = False
329 else:
330 ndgrp_data[nodegroup_name] = True
331
332
333 parameters = set()
334 for idx, esl in enumerate(es_data.values()):
335 valid = bool(esl and esl[0][1])
336 if not valid:
337 break
338
339 node_params = esl[0][3]
340 if idx == 0:
341
342 parameters.update(node_params)
343 else:
344
345 parameters.intersection_update(node_params)
346
347 params = list(parameters)
348
349
350 info = query.ExtStorageInfo(name=es_name, node_status=es_data,
351 nodegroup_status=ndgrp_data,
352 parameters=params)
353
354 data[es_name] = info
355
356
357 return [data[name] for name in self._GetNames(lu, pol.keys(), None)
358 if name in data]
359
362 """Logical unit for ExtStorage diagnose/query.
363
364 """
365 REQ_BGL = False
366
370
373
374 - def Exec(self, feedback_fn):
376
379 """Logical unit for executing restricted commands.
380
381 """
382 REQ_BGL = False
383
385 if self.op.nodes:
386 (self.op.node_uuids, self.op.nodes) = GetWantedNodes(self, self.op.nodes)
387
388 self.needed_locks = {
389 locking.LEVEL_NODE: self.op.node_uuids,
390 }
391 self.share_locks = {
392 locking.LEVEL_NODE: not self.op.use_locking,
393 }
394
396 """Check prerequisites.
397
398 """
399
400 - def Exec(self, feedback_fn):
401 """Execute restricted command and return output.
402
403 """
404 owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE))
405
406
407 assert set(self.op.node_uuids).issubset(owned_nodes)
408
409 rpcres = self.rpc.call_restricted_command(self.op.node_uuids,
410 self.op.command)
411
412 result = []
413
414 for node_uuid in self.op.node_uuids:
415 nres = rpcres[node_uuid]
416 if nres.fail_msg:
417 msg = ("Command '%s' on node '%s' failed: %s" %
418 (self.op.command, self.cfg.GetNodeName(node_uuid),
419 nres.fail_msg))
420 result.append((False, msg))
421 else:
422 result.append((True, nres.payload))
423
424 return result
425
428 """Logical unit for executing repair commands.
429
430 """
431 REQ_BGL = False
432
434 self.node_uuid, _ = ExpandNodeUuidAndName(self.cfg, None, self.op.node_name)
435
436 self.needed_locks = {
437 locking.LEVEL_NODE: self.node_uuid,
438 }
439 self.share_locks = {
440 locking.LEVEL_NODE: False,
441 }
442
444 """Check prerequisites.
445
446 """
447
448 - def Exec(self, feedback_fn):
449 """Execute restricted command and return output.
450
451 """
452 owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE))
453 assert self.node_uuid in owned_nodes
454 return self.rpc.call_repair_command(self.op.node_name,
455 self.op.command,
456 self.op.input).data[1]
457