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 GetWantedNodes, SupportsOob 
44 45 46 -class LUOobCommand(NoHooksLU):
47 """Logical unit for OOB handling. 48 49 """ 50 REQ_BGL = False 51 _SKIP_MASTER = (constants.OOB_POWER_OFF, constants.OOB_POWER_CYCLE) 52
53 - def ExpandNames(self):
54 """Gather locks we need. 55 56 """ 57 if self.op.node_names: 58 (self.op.node_uuids, self.op.node_names) = \ 59 GetWantedNodes(self, self.op.node_names) 60 lock_node_uuids = self.op.node_uuids 61 else: 62 lock_node_uuids = locking.ALL_SET 63 64 self.needed_locks = { 65 locking.LEVEL_NODE: lock_node_uuids, 66 } 67 68 self.share_locks[locking.LEVEL_NODE_ALLOC] = 1 69 70 if not self.op.node_names: 71 # Acquire node allocation lock only if all nodes are affected 72 self.needed_locks[locking.LEVEL_NODE_ALLOC] = locking.ALL_SET
73
74 - def CheckPrereq(self):
75 """Check prerequisites. 76 77 This checks: 78 - the node exists in the configuration 79 - OOB is supported 80 81 Any errors are signaled by raising errors.OpPrereqError. 82 83 """ 84 self.nodes = [] 85 self.master_node_uuid = self.cfg.GetMasterNode() 86 master_node_obj = self.cfg.GetNodeInfo(self.master_node_uuid) 87 88 assert self.op.power_delay >= 0.0 89 90 if self.op.node_uuids: 91 if (self.op.command in self._SKIP_MASTER and 92 master_node_obj.uuid in self.op.node_uuids): 93 master_oob_handler = SupportsOob(self.cfg, master_node_obj) 94 95 if master_oob_handler: 96 additional_text = ("run '%s %s %s' if you want to operate on the" 97 " master regardless") % (master_oob_handler, 98 self.op.command, 99 master_node_obj.name) 100 else: 101 additional_text = "it does not support out-of-band operations" 102 103 raise errors.OpPrereqError(("Operating on the master node %s is not" 104 " allowed for %s; %s") % 105 (master_node_obj.name, self.op.command, 106 additional_text), errors.ECODE_INVAL) 107 else: 108 self.op.node_uuids = self.cfg.GetNodeList() 109 if self.op.command in self._SKIP_MASTER: 110 self.op.node_uuids.remove(master_node_obj.uuid) 111 112 if self.op.command in self._SKIP_MASTER: 113 assert master_node_obj.uuid not in self.op.node_uuids 114 115 for node_uuid in self.op.node_uuids: 116 node = self.cfg.GetNodeInfo(node_uuid) 117 if node is None: 118 raise errors.OpPrereqError("Node %s not found" % node_uuid, 119 errors.ECODE_NOENT) 120 121 self.nodes.append(node) 122 123 if (not self.op.ignore_status and 124 (self.op.command == constants.OOB_POWER_OFF and not node.offline)): 125 raise errors.OpPrereqError(("Cannot power off node %s because it is" 126 " not marked offline") % node.name, 127 errors.ECODE_STATE)
128
129 - def Exec(self, feedback_fn):
130 """Execute OOB and return result if we expect any. 131 132 """ 133 ret = [] 134 135 for idx, node in enumerate(utils.NiceSort(self.nodes, 136 key=lambda node: node.name)): 137 node_entry = [(constants.RS_NORMAL, node.name)] 138 ret.append(node_entry) 139 140 oob_program = SupportsOob(self.cfg, node) 141 142 if not oob_program: 143 node_entry.append((constants.RS_UNAVAIL, None)) 144 continue 145 146 logging.info("Executing out-of-band command '%s' using '%s' on %s", 147 self.op.command, oob_program, node.name) 148 result = self.rpc.call_run_oob(self.master_node_uuid, oob_program, 149 self.op.command, node.name, 150 self.op.timeout) 151 152 if result.fail_msg: 153 self.LogWarning("Out-of-band RPC failed on node '%s': %s", 154 node.name, result.fail_msg) 155 node_entry.append((constants.RS_NODATA, None)) 156 else: 157 try: 158 self._CheckPayload(result) 159 except errors.OpExecError, err: 160 self.LogWarning("Payload returned by node '%s' is not valid: %s", 161 node.name, err) 162 node_entry.append((constants.RS_NODATA, None)) 163 else: 164 if self.op.command == constants.OOB_HEALTH: 165 # For health we should log important events 166 for item, status in result.payload: 167 if status in [constants.OOB_STATUS_WARNING, 168 constants.OOB_STATUS_CRITICAL]: 169 self.LogWarning("Item '%s' on node '%s' has status '%s'", 170 item, node.name, status) 171 172 if self.op.command == constants.OOB_POWER_ON: 173 node.powered = True 174 elif self.op.command == constants.OOB_POWER_OFF: 175 node.powered = False 176 elif self.op.command == constants.OOB_POWER_STATUS: 177 powered = result.payload[constants.OOB_POWER_STATUS_POWERED] 178 if powered != node.powered: 179 logging.warning(("Recorded power state (%s) of node '%s' does not" 180 " match actual power state (%s)"), node.powered, 181 node.name, powered) 182 183 # For configuration changing commands we should update the node 184 if self.op.command in (constants.OOB_POWER_ON, 185 constants.OOB_POWER_OFF): 186 self.cfg.Update(node, feedback_fn) 187 188 node_entry.append((constants.RS_NORMAL, result.payload)) 189 190 if (self.op.command == constants.OOB_POWER_ON and 191 idx < len(self.nodes) - 1): 192 time.sleep(self.op.power_delay) 193 194 return ret
195
196 - def _CheckPayload(self, result):
197 """Checks if the payload is valid. 198 199 @param result: RPC result 200 @raises errors.OpExecError: If payload is not valid 201 202 """ 203 errs = [] 204 if self.op.command == constants.OOB_HEALTH: 205 if not isinstance(result.payload, list): 206 errs.append("command 'health' is expected to return a list but got %s" % 207 type(result.payload)) 208 else: 209 for item, status in result.payload: 210 if status not in constants.OOB_STATUSES: 211 errs.append("health item '%s' has invalid status '%s'" % 212 (item, status)) 213 214 if self.op.command == constants.OOB_POWER_STATUS: 215 if not isinstance(result.payload, dict): 216 errs.append("power-status is expected to return a dict but got %s" % 217 type(result.payload)) 218 219 if self.op.command in [ 220 constants.OOB_POWER_ON, 221 constants.OOB_POWER_OFF, 222 constants.OOB_POWER_CYCLE, 223 ]: 224 if result.payload is not None: 225 errs.append("%s is expected to not return payload but got '%s'" % 226 (self.op.command, result.payload)) 227 228 if errs: 229 raise errors.OpExecError("Check of out-of-band payload failed due to %s" % 230 utils.CommaJoin(errs))
231
232 233 -class ExtStorageQuery(QueryBase):
234 FIELDS = query.EXTSTORAGE_FIELDS 235
236 - def ExpandNames(self, lu):
237 # Lock all nodes in shared mode 238 # Temporary removal of locks, should be reverted later 239 # TODO: reintroduce locks when they are lighter-weight 240 lu.needed_locks = {} 241 #self.share_locks[locking.LEVEL_NODE] = 1 242 #self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET 243 244 # The following variables interact with _QueryBase._GetNames 245 if self.names: 246 self.wanted = [lu.cfg.GetNodeInfoByName(name).uuid for name in self.names] 247 else: 248 self.wanted = locking.ALL_SET 249 250 self.do_locking = self.use_locking
251
252 - def DeclareLocks(self, lu, level):
253 pass
254 255 @staticmethod
256 - def _DiagnoseByProvider(rlist):
257 """Remaps a per-node return list into an a per-provider per-node dictionary 258 259 @param rlist: a map with node uuids as keys and ExtStorage objects as values 260 261 @rtype: dict 262 @return: a dictionary with extstorage providers as keys and as 263 value another map, with node uuids as keys and tuples of 264 (path, status, diagnose, parameters) as values, eg:: 265 266 {"provider1": {"node_uuid1": [(/usr/lib/..., True, "", [])] 267 "node_uuid2": [(/srv/..., False, "missing file")] 268 "node_uuid3": [(/srv/..., True, "", [])] 269 } 270 271 """ 272 all_es = {} 273 # we build here the list of nodes that didn't fail the RPC (at RPC 274 # level), so that nodes with a non-responding node daemon don't 275 # make all OSes invalid 276 good_nodes = [node_uuid for node_uuid in rlist 277 if not rlist[node_uuid].fail_msg] 278 for node_uuid, nr in rlist.items(): 279 if nr.fail_msg or not nr.payload: 280 continue 281 for (name, path, status, diagnose, params) in nr.payload: 282 if name not in all_es: 283 # build a list of nodes for this os containing empty lists 284 # for each node in node_list 285 all_es[name] = {} 286 for nuuid in good_nodes: 287 all_es[name][nuuid] = [] 288 # convert params from [name, help] to (name, help) 289 params = [tuple(v) for v in params] 290 all_es[name][node_uuid].append((path, status, diagnose, params)) 291 return all_es
292
293 - def _GetQueryData(self, lu):
294 """Computes the list of nodes and their attributes. 295 296 """ 297 valid_nodes = [node.uuid 298 for node in lu.cfg.GetAllNodesInfo().values() 299 if not node.offline and node.vm_capable] 300 pol = self._DiagnoseByProvider(lu.rpc.call_extstorage_diagnose(valid_nodes)) 301 302 data = {} 303 304 nodegroup_list = lu.cfg.GetNodeGroupList() 305 306 for (es_name, es_data) in pol.items(): 307 # For every provider compute the nodegroup validity. 308 # To do this we need to check the validity of each node in es_data 309 # and then construct the corresponding nodegroup dict: 310 # { nodegroup1: status 311 # nodegroup2: status 312 # } 313 ndgrp_data = {} 314 for nodegroup in nodegroup_list: 315 ndgrp = lu.cfg.GetNodeGroup(nodegroup) 316 317 nodegroup_nodes = ndgrp.members 318 nodegroup_name = ndgrp.name 319 node_statuses = [] 320 321 for node in nodegroup_nodes: 322 if node in valid_nodes: 323 if es_data[node] != []: 324 node_status = es_data[node][0][1] 325 node_statuses.append(node_status) 326 else: 327 node_statuses.append(False) 328 329 if False in node_statuses: 330 ndgrp_data[nodegroup_name] = False 331 else: 332 ndgrp_data[nodegroup_name] = True 333 334 # Compute the provider's parameters 335 parameters = set() 336 for idx, esl in enumerate(es_data.values()): 337 valid = bool(esl and esl[0][1]) 338 if not valid: 339 break 340 341 node_params = esl[0][3] 342 if idx == 0: 343 # First entry 344 parameters.update(node_params) 345 else: 346 # Filter out inconsistent values 347 parameters.intersection_update(node_params) 348 349 params = list(parameters) 350 351 # Now fill all the info for this provider 352 info = query.ExtStorageInfo(name=es_name, node_status=es_data, 353 nodegroup_status=ndgrp_data, 354 parameters=params) 355 356 data[es_name] = info 357 358 # Prepare data in requested order 359 return [data[name] for name in self._GetNames(lu, pol.keys(), None) 360 if name in data]
361
362 363 -class LUExtStorageDiagnose(NoHooksLU):
364 """Logical unit for ExtStorage diagnose/query. 365 366 """ 367 REQ_BGL = False 368
369 - def CheckArguments(self):
370 self.eq = ExtStorageQuery(qlang.MakeSimpleFilter("name", self.op.names), 371 self.op.output_fields, False)
372
373 - def ExpandNames(self):
374 self.eq.ExpandNames(self)
375
376 - def Exec(self, feedback_fn):
377 return self.eq.OldStyleQuery(self)
378
379 380 -class LURestrictedCommand(NoHooksLU):
381 """Logical unit for executing restricted commands. 382 383 """ 384 REQ_BGL = False 385
386 - def ExpandNames(self):
387 if self.op.nodes: 388 (self.op.node_uuids, self.op.nodes) = GetWantedNodes(self, self.op.nodes) 389 390 self.needed_locks = { 391 locking.LEVEL_NODE: self.op.node_uuids, 392 } 393 self.share_locks = { 394 locking.LEVEL_NODE: not self.op.use_locking, 395 }
396
397 - def CheckPrereq(self):
398 """Check prerequisites. 399 400 """
401
402 - def Exec(self, feedback_fn):
403 """Execute restricted command and return output. 404 405 """ 406 owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE)) 407 408 # Check if correct locks are held 409 assert set(self.op.node_uuids).issubset(owned_nodes) 410 411 rpcres = self.rpc.call_restricted_command(self.op.node_uuids, 412 self.op.command) 413 414 result = [] 415 416 for node_uuid in self.op.node_uuids: 417 nres = rpcres[node_uuid] 418 if nres.fail_msg: 419 msg = ("Command '%s' on node '%s' failed: %s" % 420 (self.op.command, self.cfg.GetNodeName(node_uuid), 421 nres.fail_msg)) 422 result.append((False, msg)) 423 else: 424 result.append((True, nres.payload)) 425 426 return result
427