4 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 """Miscellaneous logical units that don't fit into any category."""
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
38 class LUOobCommand(NoHooksLU):
39 """Logical unit for OOB handling.
43 _SKIP_MASTER = (constants.OOB_POWER_OFF, constants.OOB_POWER_CYCLE)
45 def ExpandNames(self):
46 """Gather locks we need.
49 if self.op.node_names:
50 (self.op.node_uuids, self.op.node_names) = \
51 GetWantedNodes(self, self.op.node_names)
52 lock_node_uuids = self.op.node_uuids
54 lock_node_uuids = locking.ALL_SET
57 locking.LEVEL_NODE: lock_node_uuids,
60 self.share_locks[locking.LEVEL_NODE_ALLOC] = 1
62 if not self.op.node_names:
63 # Acquire node allocation lock only if all nodes are affected
64 self.needed_locks[locking.LEVEL_NODE_ALLOC] = locking.ALL_SET
66 def CheckPrereq(self):
67 """Check prerequisites.
70 - the node exists in the configuration
73 Any errors are signaled by raising errors.OpPrereqError.
77 self.master_node_uuid = self.cfg.GetMasterNode()
78 master_node_obj = self.cfg.GetNodeInfo(self.master_node_uuid)
80 assert self.op.power_delay >= 0.0
82 if self.op.node_uuids:
83 if (self.op.command in self._SKIP_MASTER and
84 master_node_obj.uuid in self.op.node_uuids):
85 master_oob_handler = SupportsOob(self.cfg, master_node_obj)
87 if master_oob_handler:
88 additional_text = ("run '%s %s %s' if you want to operate on the"
89 " master regardless") % (master_oob_handler,
93 additional_text = "it does not support out-of-band operations"
95 raise errors.OpPrereqError(("Operating on the master node %s is not"
96 " allowed for %s; %s") %
97 (master_node_obj.name, self.op.command,
98 additional_text), errors.ECODE_INVAL)
100 self.op.node_uuids = self.cfg.GetNodeList()
101 if self.op.command in self._SKIP_MASTER:
102 self.op.node_uuids.remove(master_node_obj.uuid)
104 if self.op.command in self._SKIP_MASTER:
105 assert master_node_obj.uuid not in self.op.node_uuids
107 for node_uuid in self.op.node_uuids:
108 node = self.cfg.GetNodeInfo(node_uuid)
110 raise errors.OpPrereqError("Node %s not found" % node_uuid,
113 self.nodes.append(node)
115 if (not self.op.ignore_status and
116 (self.op.command == constants.OOB_POWER_OFF and not node.offline)):
117 raise errors.OpPrereqError(("Cannot power off node %s because it is"
118 " not marked offline") % node.name,
121 def Exec(self, feedback_fn):
122 """Execute OOB and return result if we expect any.
127 for idx, node in enumerate(utils.NiceSort(self.nodes,
128 key=lambda node: node.name)):
129 node_entry = [(constants.RS_NORMAL, node.name)]
130 ret.append(node_entry)
132 oob_program = SupportsOob(self.cfg, node)
135 node_entry.append((constants.RS_UNAVAIL, None))
138 logging.info("Executing out-of-band command '%s' using '%s' on %s",
139 self.op.command, oob_program, node.name)
140 result = self.rpc.call_run_oob(self.master_node_uuid, oob_program,
141 self.op.command, node.name,
145 self.LogWarning("Out-of-band RPC failed on node '%s': %s",
146 node.name, result.fail_msg)
147 node_entry.append((constants.RS_NODATA, None))
150 self._CheckPayload(result)
151 except errors.OpExecError, err:
152 self.LogWarning("Payload returned by node '%s' is not valid: %s",
154 node_entry.append((constants.RS_NODATA, None))
156 if self.op.command == constants.OOB_HEALTH:
157 # For health we should log important events
158 for item, status in result.payload:
159 if status in [constants.OOB_STATUS_WARNING,
160 constants.OOB_STATUS_CRITICAL]:
161 self.LogWarning("Item '%s' on node '%s' has status '%s'",
162 item, node.name, status)
164 if self.op.command == constants.OOB_POWER_ON:
166 elif self.op.command == constants.OOB_POWER_OFF:
168 elif self.op.command == constants.OOB_POWER_STATUS:
169 powered = result.payload[constants.OOB_POWER_STATUS_POWERED]
170 if powered != node.powered:
171 logging.warning(("Recorded power state (%s) of node '%s' does not"
172 " match actual power state (%s)"), node.powered,
175 # For configuration changing commands we should update the node
176 if self.op.command in (constants.OOB_POWER_ON,
177 constants.OOB_POWER_OFF):
178 self.cfg.Update(node, feedback_fn)
180 node_entry.append((constants.RS_NORMAL, result.payload))
182 if (self.op.command == constants.OOB_POWER_ON and
183 idx < len(self.nodes) - 1):
184 time.sleep(self.op.power_delay)
188 def _CheckPayload(self, result):
189 """Checks if the payload is valid.
191 @param result: RPC result
192 @raises errors.OpExecError: If payload is not valid
196 if self.op.command == constants.OOB_HEALTH:
197 if not isinstance(result.payload, list):
198 errs.append("command 'health' is expected to return a list but got %s" %
199 type(result.payload))
201 for item, status in result.payload:
202 if status not in constants.OOB_STATUSES:
203 errs.append("health item '%s' has invalid status '%s'" %
206 if self.op.command == constants.OOB_POWER_STATUS:
207 if not isinstance(result.payload, dict):
208 errs.append("power-status is expected to return a dict but got %s" %
209 type(result.payload))
211 if self.op.command in [
212 constants.OOB_POWER_ON,
213 constants.OOB_POWER_OFF,
214 constants.OOB_POWER_CYCLE,
216 if result.payload is not None:
217 errs.append("%s is expected to not return payload but got '%s'" %
218 (self.op.command, result.payload))
221 raise errors.OpExecError("Check of out-of-band payload failed due to %s" %
222 utils.CommaJoin(errs))
225 class ExtStorageQuery(QueryBase):
226 FIELDS = query.EXTSTORAGE_FIELDS
228 def ExpandNames(self, lu):
229 # Lock all nodes in shared mode
230 # Temporary removal of locks, should be reverted later
231 # TODO: reintroduce locks when they are lighter-weight
233 #self.share_locks[locking.LEVEL_NODE] = 1
234 #self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
236 # The following variables interact with _QueryBase._GetNames
238 self.wanted = [lu.cfg.GetNodeInfoByName(name).uuid for name in self.names]
240 self.wanted = locking.ALL_SET
242 self.do_locking = self.use_locking
244 def DeclareLocks(self, lu, level):
248 def _DiagnoseByProvider(rlist):
249 """Remaps a per-node return list into an a per-provider per-node dictionary
251 @param rlist: a map with node uuids as keys and ExtStorage objects as values
254 @return: a dictionary with extstorage providers as keys and as
255 value another map, with node uuids as keys and tuples of
256 (path, status, diagnose, parameters) as values, eg::
258 {"provider1": {"node_uuid1": [(/usr/lib/..., True, "", [])]
259 "node_uuid2": [(/srv/..., False, "missing file")]
260 "node_uuid3": [(/srv/..., True, "", [])]
265 # we build here the list of nodes that didn't fail the RPC (at RPC
266 # level), so that nodes with a non-responding node daemon don't
267 # make all OSes invalid
268 good_nodes = [node_uuid for node_uuid in rlist
269 if not rlist[node_uuid].fail_msg]
270 for node_uuid, nr in rlist.items():
271 if nr.fail_msg or not nr.payload:
273 for (name, path, status, diagnose, params) in nr.payload:
274 if name not in all_es:
275 # build a list of nodes for this os containing empty lists
276 # for each node in node_list
278 for nuuid in good_nodes:
279 all_es[name][nuuid] = []
280 # convert params from [name, help] to (name, help)
281 params = [tuple(v) for v in params]
282 all_es[name][node_uuid].append((path, status, diagnose, params))
285 def _GetQueryData(self, lu):
286 """Computes the list of nodes and their attributes.
289 # Locking is not used
290 assert not (compat.any(lu.glm.is_owned(level)
291 for level in locking.LEVELS
292 if level != locking.LEVEL_CLUSTER) or
293 self.do_locking or self.use_locking)
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))
302 nodegroup_list = lu.cfg.GetNodeGroupList()
304 for (es_name, es_data) in pol.items():
305 # For every provider compute the nodegroup validity.
306 # To do this we need to check the validity of each node in es_data
307 # and then construct the corresponding nodegroup dict:
308 # { nodegroup1: status
312 for nodegroup in nodegroup_list:
313 ndgrp = lu.cfg.GetNodeGroup(nodegroup)
315 nodegroup_nodes = ndgrp.members
316 nodegroup_name = ndgrp.name
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)
325 node_statuses.append(False)
327 if False in node_statuses:
328 ndgrp_data[nodegroup_name] = False
330 ndgrp_data[nodegroup_name] = True
332 # Compute the provider's parameters
334 for idx, esl in enumerate(es_data.values()):
335 valid = bool(esl and esl[0][1])
339 node_params = esl[0][3]
342 parameters.update(node_params)
344 # Filter out inconsistent values
345 parameters.intersection_update(node_params)
347 params = list(parameters)
349 # Now fill all the info for this provider
350 info = query.ExtStorageInfo(name=es_name, node_status=es_data,
351 nodegroup_status=ndgrp_data,
356 # Prepare data in requested order
357 return [data[name] for name in self._GetNames(lu, pol.keys(), None)
361 class LUExtStorageDiagnose(NoHooksLU):
362 """Logical unit for ExtStorage diagnose/query.
367 def CheckArguments(self):
368 self.eq = ExtStorageQuery(qlang.MakeSimpleFilter("name", self.op.names),
369 self.op.output_fields, False)
371 def ExpandNames(self):
372 self.eq.ExpandNames(self)
374 def Exec(self, feedback_fn):
375 return self.eq.OldStyleQuery(self)
378 class LURestrictedCommand(NoHooksLU):
379 """Logical unit for executing restricted commands.
384 def ExpandNames(self):
386 (self.op.node_uuids, self.op.nodes) = GetWantedNodes(self, self.op.nodes)
388 self.needed_locks = {
389 locking.LEVEL_NODE: self.op.node_uuids,
392 locking.LEVEL_NODE: not self.op.use_locking,
395 def CheckPrereq(self):
396 """Check prerequisites.
400 def Exec(self, feedback_fn):
401 """Execute restricted command and return output.
404 owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE))
406 # Check if correct locks are held
407 assert set(self.op.node_uuids).issubset(owned_nodes)
409 rpcres = self.rpc.call_restricted_command(self.op.node_uuids,
414 for node_uuid in self.op.node_uuids:
415 nres = rpcres[node_uuid]
417 msg = ("Command '%s' on node '%s' failed: %s" %
418 (self.op.command, self.cfg.GetNodeName(node_uuid),
420 result.append((False, msg))
422 result.append((True, nres.payload))