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