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