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