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_names = GetWantedNodes(self, self.op.node_names)
51 lock_names = self.op.node_names
53 lock_names = locking.ALL_SET
56 locking.LEVEL_NODE: lock_names,
59 self.share_locks[locking.LEVEL_NODE_ALLOC] = 1
61 if not self.op.node_names:
62 # Acquire node allocation lock only if all nodes are affected
63 self.needed_locks[locking.LEVEL_NODE_ALLOC] = locking.ALL_SET
65 def CheckPrereq(self):
66 """Check prerequisites.
69 - the node exists in the configuration
72 Any errors are signaled by raising errors.OpPrereqError.
76 self.master_node = self.cfg.GetMasterNode()
78 assert self.op.power_delay >= 0.0
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)
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,
92 additional_text = "it does not support out-of-band operations"
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)
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)
103 if self.op.command in self._SKIP_MASTER:
104 assert self.master_node not in self.op.node_names
106 for (node_name, node) in self.cfg.GetMultiNodeInfo(self.op.node_names):
108 raise errors.OpPrereqError("Node %s not found" % node_name,
111 self.nodes.append(node)
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,
119 def Exec(self, feedback_fn):
120 """Execute OOB and return result if we expect any.
123 master_node = self.master_node
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)
131 oob_program = SupportsOob(self.cfg, node)
134 node_entry.append((constants.RS_UNAVAIL, None))
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,
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))
149 self._CheckPayload(result)
150 except errors.OpExecError, err:
151 self.LogWarning("Payload returned by node '%s' is not valid: %s",
153 node_entry.append((constants.RS_NODATA, None))
155 if self.op.command == constants.OOB_HEALTH:
156 # For health we should log important events
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)
163 if self.op.command == constants.OOB_POWER_ON:
165 elif self.op.command == constants.OOB_POWER_OFF:
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,
174 # For configuration changing commands we should update the node
175 if self.op.command in (constants.OOB_POWER_ON,
176 constants.OOB_POWER_OFF):
177 self.cfg.Update(node, feedback_fn)
179 node_entry.append((constants.RS_NORMAL, result.payload))
181 if (self.op.command == constants.OOB_POWER_ON and
182 idx < len(self.nodes) - 1):
183 time.sleep(self.op.power_delay)
187 def _CheckPayload(self, result):
188 """Checks if the payload is valid.
190 @param result: RPC result
191 @raises errors.OpExecError: If payload is not valid
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))
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'" %
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))
210 if self.op.command in [
211 constants.OOB_POWER_ON,
212 constants.OOB_POWER_OFF,
213 constants.OOB_POWER_CYCLE,
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))
220 raise errors.OpExecError("Check of out-of-band payload failed due to %s" %
221 utils.CommaJoin(errs))
224 class ExtStorageQuery(QueryBase):
225 FIELDS = query.EXTSTORAGE_FIELDS
227 def ExpandNames(self, lu):
228 # Lock all nodes in shared mode
229 # Temporary removal of locks, should be reverted later
230 # TODO: reintroduce locks when they are lighter-weight
232 #self.share_locks[locking.LEVEL_NODE] = 1
233 #self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
235 # The following variables interact with _QueryBase._GetNames
237 self.wanted = self.names
239 self.wanted = locking.ALL_SET
241 self.do_locking = self.use_locking
243 def DeclareLocks(self, lu, level):
247 def _DiagnoseByProvider(rlist):
248 """Remaps a per-node return list into an a per-provider per-node dictionary
250 @param rlist: a map with node names as keys and ExtStorage objects as values
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::
257 {"provider1": {"node1": [(/usr/lib/..., True, "", [])]
258 "node2": [(/srv/..., False, "missing file")]
259 "node3": [(/srv/..., True, "", [])]
264 # we build here the list of nodes that didn't fail the RPC (at RPC
265 # level), so that nodes with a non-responding node daemon don't
266 # make all OSes invalid
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:
272 for (name, path, status, diagnose, params) in nr.payload:
273 if name not in all_es:
274 # build a list of nodes for this os containing empty lists
275 # for each node in node_list
277 for nname in good_nodes:
278 all_es[name][nname] = []
279 # convert params from [name, help] to (name, help)
280 params = [tuple(v) for v in params]
281 all_es[name][node_name].append((path, status, diagnose, params))
284 def _GetQueryData(self, lu):
285 """Computes the list of nodes and their attributes.
288 # Locking is not used
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)
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))
301 nodegroup_list = lu.cfg.GetNodeGroupList()
303 for (es_name, es_data) in pol.items():
304 # For every provider compute the nodegroup validity.
305 # To do this we need to check the validity of each node in es_data
306 # and then construct the corresponding nodegroup dict:
307 # { nodegroup1: status
311 for nodegroup in nodegroup_list:
312 ndgrp = lu.cfg.GetNodeGroup(nodegroup)
314 nodegroup_nodes = ndgrp.members
315 nodegroup_name = ndgrp.name
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)
324 node_statuses.append(False)
326 if False in node_statuses:
327 ndgrp_data[nodegroup_name] = False
329 ndgrp_data[nodegroup_name] = True
331 # Compute the provider's parameters
333 for idx, esl in enumerate(es_data.values()):
334 valid = bool(esl and esl[0][1])
338 node_params = esl[0][3]
341 parameters.update(node_params)
343 # Filter out inconsistent values
344 parameters.intersection_update(node_params)
346 params = list(parameters)
348 # Now fill all the info for this provider
349 info = query.ExtStorageInfo(name=es_name, node_status=es_data,
350 nodegroup_status=ndgrp_data,
355 # Prepare data in requested order
356 return [data[name] for name in self._GetNames(lu, pol.keys(), None)
360 class LUExtStorageDiagnose(NoHooksLU):
361 """Logical unit for ExtStorage diagnose/query.
366 def CheckArguments(self):
367 self.eq = ExtStorageQuery(qlang.MakeSimpleFilter("name", self.op.names),
368 self.op.output_fields, False)
370 def ExpandNames(self):
371 self.eq.ExpandNames(self)
373 def Exec(self, feedback_fn):
374 return self.eq.OldStyleQuery(self)
377 class LURestrictedCommand(NoHooksLU):
378 """Logical unit for executing restricted commands.
383 def ExpandNames(self):
385 self.op.nodes = GetWantedNodes(self, self.op.nodes)
387 self.needed_locks = {
388 locking.LEVEL_NODE: self.op.nodes,
391 locking.LEVEL_NODE: not self.op.use_locking,
394 def CheckPrereq(self):
395 """Check prerequisites.
399 def Exec(self, feedback_fn):
400 """Execute restricted command and return output.
403 owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE))
405 # Check if correct locks are held
406 assert set(self.op.nodes).issubset(owned_nodes)
408 rpcres = self.rpc.call_restricted_command(self.op.nodes, self.op.command)
412 for node_name in self.op.nodes:
413 nres = rpcres[node_name]
415 msg = ("Command '%s' on node '%s' failed: %s" %
416 (self.op.command, node_name, nres.fail_msg))
417 result.append((False, msg))
419 result.append((True, nres.payload))