Package ganeti :: Package cmdlib :: Module misc
[hide private]
[frames] | no frames]

Source Code for Module ganeti.cmdlib.misc

  1  # 
  2  # 
  3   
  4  # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc. 
  5  # 
  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. 
 10  # 
 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. 
 15  # 
 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 
 19  # 02110-1301, USA. 
 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 
36 37 38 -class LUOobCommand(NoHooksLU):
39 """Logical unit for OOB handling. 40 41 """ 42 REQ_BGL = False 43 _SKIP_MASTER = (constants.OOB_POWER_OFF, constants.OOB_POWER_CYCLE) 44
45 - def ExpandNames(self):
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 # Acquire node allocation lock only if all nodes are affected 63 self.needed_locks[locking.LEVEL_NODE_ALLOC] = locking.ALL_SET
64
65 - def CheckPrereq(self):
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 # 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) 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 # 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) 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
187 - def _CheckPayload(self, result):
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
223 224 -class ExtStorageQuery(QueryBase):
225 FIELDS = query.EXTSTORAGE_FIELDS 226
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 231 lu.needed_locks = {} 232 #self.share_locks[locking.LEVEL_NODE] = 1 233 #self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET 234 235 # The following variables interact with _QueryBase._GetNames 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
243 - def DeclareLocks(self, lu, level):
244 pass
245 246 @staticmethod
247 - def _DiagnoseByProvider(rlist):
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 # 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: 271 continue 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 276 all_es[name] = {} 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)) 282 return all_es
283
284 - def _GetQueryData(self, lu):
285 """Computes the list of nodes and their attributes. 286 287 """ 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) 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 # 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 308 # nodegroup2: status 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 # Compute the provider's parameters 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 # First entry 341 parameters.update(node_params) 342 else: 343 # Filter out inconsistent values 344 parameters.intersection_update(node_params) 345 346 params = list(parameters) 347 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, 351 parameters=params) 352 353 data[es_name] = info 354 355 # Prepare data in requested order 356 return [data[name] for name in self._GetNames(lu, pol.keys(), None) 357 if name in data]
358
359 360 -class LUExtStorageDiagnose(NoHooksLU):
361 """Logical unit for ExtStorage diagnose/query. 362 363 """ 364 REQ_BGL = False 365
366 - def CheckArguments(self):
367 self.eq = ExtStorageQuery(qlang.MakeSimpleFilter("name", self.op.names), 368 self.op.output_fields, False)
369
370 - def ExpandNames(self):
371 self.eq.ExpandNames(self)
372
373 - def Exec(self, feedback_fn):
374 return self.eq.OldStyleQuery(self)
375
376 377 -class LURestrictedCommand(NoHooksLU):
378 """Logical unit for executing restricted commands. 379 380 """ 381 REQ_BGL = False 382
383 - def ExpandNames(self):
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
394 - def CheckPrereq(self):
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 # Check if correct locks are held 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