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  # All rights reserved. 
  6  # 
  7  # Redistribution and use in source and binary forms, with or without 
  8  # modification, are permitted provided that the following conditions are 
  9  # met: 
 10  # 
 11  # 1. Redistributions of source code must retain the above copyright notice, 
 12  # this list of conditions and the following disclaimer. 
 13  # 
 14  # 2. Redistributions in binary form must reproduce the above copyright 
 15  # notice, this list of conditions and the following disclaimer in the 
 16  # documentation and/or other materials provided with the distribution. 
 17  # 
 18  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 
 19  # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 
 20  # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
 21  # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 
 22  # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 23  # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
 24  # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
 25  # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
 26  # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
 27  # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
 28  # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 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 constants 
 37  from ganeti import errors 
 38  from ganeti import locking 
 39  from ganeti import qlang 
 40  from ganeti import query 
 41  from ganeti import utils 
 42  from ganeti.cmdlib.base import NoHooksLU, QueryBase 
 43  from ganeti.cmdlib.common import ( 
 44    GetWantedNodes, 
 45    SupportsOob, 
 46    ExpandNodeUuidAndName 
 47  ) 
48 49 50 -class LUOobCommand(NoHooksLU):
51 """Logical unit for OOB handling. 52 53 """ 54 REQ_BGL = False 55 _SKIP_MASTER = (constants.OOB_POWER_OFF, constants.OOB_POWER_CYCLE) 56
57 - def ExpandNames(self):
58 """Gather locks we need. 59 60 """ 61 if self.op.node_names: 62 (self.op.node_uuids, self.op.node_names) = \ 63 GetWantedNodes(self, self.op.node_names) 64 lock_node_uuids = self.op.node_uuids 65 else: 66 lock_node_uuids = locking.ALL_SET 67 68 self.needed_locks = { 69 locking.LEVEL_NODE: lock_node_uuids, 70 }
71
72 - def CheckPrereq(self):
73 """Check prerequisites. 74 75 This checks: 76 - the node exists in the configuration 77 - OOB is supported 78 79 Any errors are signaled by raising errors.OpPrereqError. 80 81 """ 82 self.nodes = [] 83 self.master_node_uuid = self.cfg.GetMasterNode() 84 master_node_obj = self.cfg.GetNodeInfo(self.master_node_uuid) 85 86 assert self.op.power_delay >= 0.0 87 88 if self.op.node_uuids: 89 if (self.op.command in self._SKIP_MASTER and 90 master_node_obj.uuid in self.op.node_uuids): 91 master_oob_handler = SupportsOob(self.cfg, master_node_obj) 92 93 if master_oob_handler: 94 additional_text = ("run '%s %s %s' if you want to operate on the" 95 " master regardless") % (master_oob_handler, 96 self.op.command, 97 master_node_obj.name) 98 else: 99 additional_text = "it does not support out-of-band operations" 100 101 raise errors.OpPrereqError(("Operating on the master node %s is not" 102 " allowed for %s; %s") % 103 (master_node_obj.name, self.op.command, 104 additional_text), errors.ECODE_INVAL) 105 else: 106 self.op.node_uuids = self.cfg.GetNodeList() 107 if self.op.command in self._SKIP_MASTER: 108 self.op.node_uuids.remove(master_node_obj.uuid) 109 110 if self.op.command in self._SKIP_MASTER: 111 assert master_node_obj.uuid not in self.op.node_uuids 112 113 for node_uuid in self.op.node_uuids: 114 node = self.cfg.GetNodeInfo(node_uuid) 115 if node is None: 116 raise errors.OpPrereqError("Node %s not found" % node_uuid, 117 errors.ECODE_NOENT) 118 119 self.nodes.append(node) 120 121 if (not self.op.ignore_status and 122 (self.op.command == constants.OOB_POWER_OFF and not node.offline)): 123 raise errors.OpPrereqError(("Cannot power off node %s because it is" 124 " not marked offline") % node.name, 125 errors.ECODE_STATE)
126
127 - def Exec(self, feedback_fn):
128 """Execute OOB and return result if we expect any. 129 130 """ 131 ret = [] 132 133 for idx, node in enumerate(utils.NiceSort(self.nodes, 134 key=lambda node: node.name)): 135 node_entry = [(constants.RS_NORMAL, node.name)] 136 ret.append(node_entry) 137 138 oob_program = SupportsOob(self.cfg, node) 139 140 if not oob_program: 141 node_entry.append((constants.RS_UNAVAIL, None)) 142 continue 143 144 logging.info("Executing out-of-band command '%s' using '%s' on %s", 145 self.op.command, oob_program, node.name) 146 result = self.rpc.call_run_oob(self.master_node_uuid, oob_program, 147 self.op.command, node.name, 148 self.op.timeout) 149 150 if result.fail_msg: 151 self.LogWarning("Out-of-band RPC failed on node '%s': %s", 152 node.name, result.fail_msg) 153 node_entry.append((constants.RS_NODATA, None)) 154 else: 155 try: 156 self._CheckPayload(result) 157 except errors.OpExecError, err: 158 self.LogWarning("Payload returned by node '%s' is not valid: %s", 159 node.name, err) 160 node_entry.append((constants.RS_NODATA, None)) 161 else: 162 if self.op.command == constants.OOB_HEALTH: 163 # For health we should log important events 164 for item, status in result.payload: 165 if status in [constants.OOB_STATUS_WARNING, 166 constants.OOB_STATUS_CRITICAL]: 167 self.LogWarning("Item '%s' on node '%s' has status '%s'", 168 item, node.name, status) 169 170 if self.op.command == constants.OOB_POWER_ON: 171 node.powered = True 172 elif self.op.command == constants.OOB_POWER_OFF: 173 node.powered = False 174 elif self.op.command == constants.OOB_POWER_STATUS: 175 powered = result.payload[constants.OOB_POWER_STATUS_POWERED] 176 if powered != node.powered: 177 logging.warning(("Recorded power state (%s) of node '%s' does not" 178 " match actual power state (%s)"), node.powered, 179 node.name, powered) 180 181 # For configuration changing commands we should update the node 182 if self.op.command in (constants.OOB_POWER_ON, 183 constants.OOB_POWER_OFF): 184 self.cfg.Update(node, feedback_fn) 185 186 node_entry.append((constants.RS_NORMAL, result.payload)) 187 188 if (self.op.command == constants.OOB_POWER_ON and 189 idx < len(self.nodes) - 1): 190 time.sleep(self.op.power_delay) 191 192 return ret
193
194 - def _CheckPayload(self, result):
195 """Checks if the payload is valid. 196 197 @param result: RPC result 198 @raises errors.OpExecError: If payload is not valid 199 200 """ 201 errs = [] 202 if self.op.command == constants.OOB_HEALTH: 203 if not isinstance(result.payload, list): 204 errs.append("command 'health' is expected to return a list but got %s" % 205 type(result.payload)) 206 else: 207 for item, status in result.payload: 208 if status not in constants.OOB_STATUSES: 209 errs.append("health item '%s' has invalid status '%s'" % 210 (item, status)) 211 212 if self.op.command == constants.OOB_POWER_STATUS: 213 if not isinstance(result.payload, dict): 214 errs.append("power-status is expected to return a dict but got %s" % 215 type(result.payload)) 216 217 if self.op.command in [ 218 constants.OOB_POWER_ON, 219 constants.OOB_POWER_OFF, 220 constants.OOB_POWER_CYCLE, 221 ]: 222 if result.payload is not None: 223 errs.append("%s is expected to not return payload but got '%s'" % 224 (self.op.command, result.payload)) 225 226 if errs: 227 raise errors.OpExecError("Check of out-of-band payload failed due to %s" % 228 utils.CommaJoin(errs))
229
230 231 -class ExtStorageQuery(QueryBase):
232 FIELDS = query.EXTSTORAGE_FIELDS 233
234 - def ExpandNames(self, lu):
235 # Lock all nodes in shared mode 236 # Temporary removal of locks, should be reverted later 237 # TODO: reintroduce locks when they are lighter-weight 238 lu.needed_locks = {} 239 #self.share_locks[locking.LEVEL_NODE] = 1 240 #self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET 241 242 # The following variables interact with _QueryBase._GetNames 243 if self.names: 244 self.wanted = [lu.cfg.GetNodeInfoByName(name).uuid for name in self.names] 245 else: 246 self.wanted = locking.ALL_SET 247 248 self.do_locking = self.use_locking
249
250 - def DeclareLocks(self, lu, level):
251 pass
252 253 @staticmethod
254 - def _DiagnoseByProvider(rlist):
255 """Remaps a per-node return list into an a per-provider per-node dictionary 256 257 @param rlist: a map with node uuids as keys and ExtStorage objects as values 258 259 @rtype: dict 260 @return: a dictionary with extstorage providers as keys and as 261 value another map, with node uuids as keys and tuples of 262 (path, status, diagnose, parameters) as values, eg:: 263 264 {"provider1": {"node_uuid1": [(/usr/lib/..., True, "", [])] 265 "node_uuid2": [(/srv/..., False, "missing file")] 266 "node_uuid3": [(/srv/..., True, "", [])] 267 } 268 269 """ 270 all_es = {} 271 # we build here the list of nodes that didn't fail the RPC (at RPC 272 # level), so that nodes with a non-responding node daemon don't 273 # make all OSes invalid 274 good_nodes = [node_uuid for node_uuid in rlist 275 if not rlist[node_uuid].fail_msg] 276 for node_uuid, nr in rlist.items(): 277 if nr.fail_msg or not nr.payload: 278 continue 279 for (name, path, status, diagnose, params) in nr.payload: 280 if name not in all_es: 281 # build a list of nodes for this os containing empty lists 282 # for each node in node_list 283 all_es[name] = {} 284 for nuuid in good_nodes: 285 all_es[name][nuuid] = [] 286 # convert params from [name, help] to (name, help) 287 params = [tuple(v) for v in params] 288 all_es[name][node_uuid].append((path, status, diagnose, params)) 289 return all_es
290
291 - def _GetQueryData(self, lu):
292 """Computes the list of nodes and their attributes. 293 294 """ 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)) 299 300 data = {} 301 302 nodegroup_list = lu.cfg.GetNodeGroupList() 303 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 309 # nodegroup2: status 310 # } 311 ndgrp_data = {} 312 for nodegroup in nodegroup_list: 313 ndgrp = lu.cfg.GetNodeGroup(nodegroup) 314 315 nodegroup_nodes = ndgrp.members 316 nodegroup_name = ndgrp.name 317 node_statuses = [] 318 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) 324 else: 325 node_statuses.append(False) 326 327 if False in node_statuses: 328 ndgrp_data[nodegroup_name] = False 329 else: 330 ndgrp_data[nodegroup_name] = True 331 332 # Compute the provider's parameters 333 parameters = set() 334 for idx, esl in enumerate(es_data.values()): 335 valid = bool(esl and esl[0][1]) 336 if not valid: 337 break 338 339 node_params = esl[0][3] 340 if idx == 0: 341 # First entry 342 parameters.update(node_params) 343 else: 344 # Filter out inconsistent values 345 parameters.intersection_update(node_params) 346 347 params = list(parameters) 348 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, 352 parameters=params) 353 354 data[es_name] = info 355 356 # Prepare data in requested order 357 return [data[name] for name in self._GetNames(lu, pol.keys(), None) 358 if name in data]
359
360 361 -class LUExtStorageDiagnose(NoHooksLU):
362 """Logical unit for ExtStorage diagnose/query. 363 364 """ 365 REQ_BGL = False 366
367 - def CheckArguments(self):
368 self.eq = ExtStorageQuery(qlang.MakeSimpleFilter("name", self.op.names), 369 self.op.output_fields, False)
370
371 - def ExpandNames(self):
372 self.eq.ExpandNames(self)
373
374 - def Exec(self, feedback_fn):
375 return self.eq.OldStyleQuery(self)
376
377 378 -class LURestrictedCommand(NoHooksLU):
379 """Logical unit for executing restricted commands. 380 381 """ 382 REQ_BGL = False 383
384 - def ExpandNames(self):
385 if self.op.nodes: 386 (self.op.node_uuids, self.op.nodes) = GetWantedNodes(self, self.op.nodes) 387 388 self.needed_locks = { 389 locking.LEVEL_NODE: self.op.node_uuids, 390 } 391 self.share_locks = { 392 locking.LEVEL_NODE: not self.op.use_locking, 393 }
394
395 - def CheckPrereq(self):
396 """Check prerequisites. 397 398 """
399
400 - def Exec(self, feedback_fn):
401 """Execute restricted command and return output. 402 403 """ 404 owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE)) 405 406 # Check if correct locks are held 407 assert set(self.op.node_uuids).issubset(owned_nodes) 408 409 rpcres = self.rpc.call_restricted_command(self.op.node_uuids, 410 self.op.command) 411 412 result = [] 413 414 for node_uuid in self.op.node_uuids: 415 nres = rpcres[node_uuid] 416 if nres.fail_msg: 417 msg = ("Command '%s' on node '%s' failed: %s" % 418 (self.op.command, self.cfg.GetNodeName(node_uuid), 419 nres.fail_msg)) 420 result.append((False, msg)) 421 else: 422 result.append((True, nres.payload)) 423 424 return result
425
426 427 -class LURepairCommand(NoHooksLU):
428 """Logical unit for executing repair commands. 429 430 """ 431 REQ_BGL = False 432
433 - def ExpandNames(self):
434 self.node_uuid, _ = ExpandNodeUuidAndName(self.cfg, None, self.op.node_name) 435 436 self.needed_locks = { 437 locking.LEVEL_NODE: self.node_uuid, 438 } 439 self.share_locks = { 440 locking.LEVEL_NODE: False, 441 }
442
443 - def CheckPrereq(self):
444 """Check prerequisites. 445 446 """
447
448 - def Exec(self, feedback_fn):
449 """Execute restricted command and return output. 450 451 """ 452 owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE)) 453 assert self.node_uuid in owned_nodes 454 return self.rpc.call_repair_command(self.op.node_name, 455 self.op.command, 456 self.op.input).data[1]
457