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

Source Code for Module ganeti.cmdlib.base

  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  """Base classes and functions for cmdlib.""" 
 32   
 33  import logging 
 34   
 35  from ganeti import errors 
 36  from ganeti import constants 
 37  from ganeti import locking 
 38  from ganeti import query 
 39  from ganeti import utils 
 40  from ganeti.cmdlib.common import ExpandInstanceUuidAndName 
 41   
 42   
43 -class ResultWithJobs(object):
44 """Data container for LU results with jobs. 45 46 Instances of this class returned from L{LogicalUnit.Exec} will be recognized 47 by L{mcpu._ProcessResult}. The latter will then submit the jobs 48 contained in the C{jobs} attribute and include the job IDs in the opcode 49 result. 50 51 """
52 - def __init__(self, jobs, **kwargs):
53 """Initializes this class. 54 55 Additional return values can be specified as keyword arguments. 56 57 @type jobs: list of lists of L{opcode.OpCode} 58 @param jobs: A list of lists of opcode objects 59 60 """ 61 self.jobs = jobs 62 self.other = kwargs
63 64
65 -class LUWConfdClient(object):
66 """Wrapper class for wconfd client calls from LUs. 67 68 Correctly updates the cache of the LU's owned locks 69 when leaving. Also transparently adds the context 70 for resource requests. 71 72 """
73 - def __init__(self, lu):
74 self.lu = lu
75
76 - def TryUpdateLocks(self, req):
77 self.lu.wconfd.Client().TryUpdateLocks(self.lu.wconfdcontext, req) 78 self.lu.wconfdlocks = \ 79 self.lu.wconfd.Client().ListLocks(self.lu.wconfdcontext)
80
81 - def DownGradeLocksLevel(self, level):
82 self.lu.wconfd.Client().DownGradeLocksLevel(self.lu.wconfdcontext, level) 83 self.lu.wconfdlocks = \ 84 self.lu.wconfd.Client().ListLocks(self.lu.wconfdcontext)
85
86 - def FreeLocksLevel(self, level):
87 self.lu.wconfd.Client().FreeLocksLevel(self.lu.wconfdcontext, level) 88 self.lu.wconfdlocks = \ 89 self.lu.wconfd.Client().ListLocks(self.lu.wconfdcontext)
90 91
92 -class LogicalUnit(object):
93 """Logical Unit base class. 94 95 Subclasses must follow these rules: 96 - implement ExpandNames 97 - implement CheckPrereq (except when tasklets are used) 98 - implement Exec (except when tasklets are used) 99 - implement BuildHooksEnv 100 - implement BuildHooksNodes 101 - redefine HPATH and HTYPE 102 - optionally redefine their run requirements: 103 REQ_BGL: the LU needs to hold the Big Ganeti Lock exclusively 104 105 Note that all commands require root permissions. 106 107 @ivar dry_run_result: the value (if any) that will be returned to the caller 108 in dry-run mode (signalled by opcode dry_run parameter) 109 110 """ 111 HPATH = None 112 HTYPE = None 113 REQ_BGL = True 114
115 - def __init__(self, processor, op, context, cfg, 116 rpc_runner, wconfdcontext, wconfd):
117 """Constructor for LogicalUnit. 118 119 This needs to be overridden in derived classes in order to check op 120 validity. 121 122 @type wconfdcontext: (int, string) 123 @param wconfdcontext: the identity of the logical unit to represent itself 124 to wconfd when asking for resources; it is given as job id and livelock 125 file. 126 @param wconfd: the wconfd class to use; dependency injection to allow 127 testability. 128 129 """ 130 self.proc = processor 131 self.op = op 132 self.cfg = cfg 133 self.wconfdlocks = [] 134 self.wconfdcontext = wconfdcontext 135 self.context = context 136 self.rpc = rpc_runner 137 self.wconfd = wconfd # wconfd module to use, for testing 138 139 # Dictionaries used to declare locking needs to mcpu 140 self.needed_locks = None 141 self.share_locks = dict.fromkeys(locking.LEVELS, 0) 142 self.opportunistic_locks = dict.fromkeys(locking.LEVELS, False) 143 self.opportunistic_locks_count = dict.fromkeys(locking.LEVELS, 1) 144 145 self.add_locks = {} 146 147 # Used to force good behavior when calling helper functions 148 self.recalculate_locks = {} 149 150 # logging 151 self.Log = processor.Log # pylint: disable=C0103 152 self.LogWarning = processor.LogWarning # pylint: disable=C0103 153 self.LogInfo = processor.LogInfo # pylint: disable=C0103 154 self.LogStep = processor.LogStep # pylint: disable=C0103 155 # support for dry-run 156 self.dry_run_result = None 157 # support for generic debug attribute 158 if (not hasattr(self.op, "debug_level") or 159 not isinstance(self.op.debug_level, int)): 160 self.op.debug_level = 0 161 162 # Tasklets 163 self.tasklets = None 164 165 # Validate opcode parameters and set defaults 166 self.op.Validate(True) 167 168 self.CheckArguments()
169
170 - def WConfdClient(self):
171 return LUWConfdClient(self)
172
173 - def owned_locks(self, level):
174 """Return the list of locks owned by the LU at a given level. 175 176 This method assumes that is field wconfdlocks is set correctly 177 by mcpu. 178 179 """ 180 levelprefix = "%s/" % (locking.LEVEL_NAMES[level],) 181 locks = set([lock[0][len(levelprefix):] 182 for lock in self.wconfdlocks 183 if lock[0].startswith(levelprefix)]) 184 expand_fns = { 185 locking.LEVEL_CLUSTER: (lambda: [locking.BGL]), 186 locking.LEVEL_INSTANCE: 187 lambda: self.cfg.GetInstanceNames(self.cfg.GetInstanceList()), 188 locking.LEVEL_NODE_ALLOC: (lambda: [locking.NAL]), 189 locking.LEVEL_NODEGROUP: self.cfg.GetNodeGroupList, 190 locking.LEVEL_NODE: self.cfg.GetNodeList, 191 locking.LEVEL_NODE_RES: self.cfg.GetNodeList, 192 locking.LEVEL_NETWORK: self.cfg.GetNetworkList, 193 } 194 if locking.LOCKSET_NAME in locks: 195 return expand_fns[level]() 196 else: 197 return locks
198
199 - def release_request(self, level, names):
200 """Return a request to release the specified locks of the given level. 201 202 Correctly break up the group lock to do so. 203 204 """ 205 levelprefix = "%s/" % (locking.LEVEL_NAMES[level],) 206 release = [[levelprefix + lock, "release"] for lock in names] 207 208 # if we break up the set-lock, make sure we ask for the rest of it. 209 setlock = levelprefix + locking.LOCKSET_NAME 210 if [setlock, "exclusive"] in self.wconfdlocks: 211 owned = self.owned_locks(level) 212 request = [[levelprefix + lock, "exclusive"] 213 for lock in owned 214 if lock not in names] 215 elif [setlock, "shared"] in self.wconfdlocks: 216 owned = self.owned_locks(level) 217 request = [[levelprefix + lock, "shared"] 218 for lock in owned 219 if lock not in names] 220 else: 221 request = [] 222 223 return release + [[setlock, "release"]] + request
224
225 - def CheckArguments(self):
226 """Check syntactic validity for the opcode arguments. 227 228 This method is for doing a simple syntactic check and ensure 229 validity of opcode parameters, without any cluster-related 230 checks. While the same can be accomplished in ExpandNames and/or 231 CheckPrereq, doing these separate is better because: 232 233 - ExpandNames is left as as purely a lock-related function 234 - CheckPrereq is run after we have acquired locks (and possible 235 waited for them) 236 237 The function is allowed to change the self.op attribute so that 238 later methods can no longer worry about missing parameters. 239 240 """ 241 pass
242
243 - def ExpandNames(self):
244 """Expand names for this LU. 245 246 This method is called before starting to execute the opcode, and it should 247 update all the parameters of the opcode to their canonical form (e.g. a 248 short node name must be fully expanded after this method has successfully 249 completed). This way locking, hooks, logging, etc. can work correctly. 250 251 LUs which implement this method must also populate the self.needed_locks 252 member, as a dict with lock levels as keys, and a list of needed lock names 253 as values. Rules: 254 255 - use an empty dict if you don't need any lock 256 - if you don't need any lock at a particular level omit that 257 level (note that in this case C{DeclareLocks} won't be called 258 at all for that level) 259 - if you need locks at a level, but you can't calculate it in 260 this function, initialise that level with an empty list and do 261 further processing in L{LogicalUnit.DeclareLocks} (see that 262 function's docstring) 263 - don't put anything for the BGL level 264 - if you want all locks at a level use L{locking.ALL_SET} as a value 265 266 If you need to share locks (rather than acquire them exclusively) at one 267 level you can modify self.share_locks, setting a true value (usually 1) for 268 that level. By default locks are not shared. 269 270 This function can also define a list of tasklets, which then will be 271 executed in order instead of the usual LU-level CheckPrereq and Exec 272 functions, if those are not defined by the LU. 273 274 Examples:: 275 276 # Acquire all nodes and one instance 277 self.needed_locks = { 278 locking.LEVEL_NODE: locking.ALL_SET, 279 locking.LEVEL_INSTANCE: ['instance1.example.com'], 280 } 281 # Acquire just two nodes 282 self.needed_locks = { 283 locking.LEVEL_NODE: ['node1-uuid', 'node2-uuid'], 284 } 285 # Acquire no locks 286 self.needed_locks = {} # No, you can't leave it to the default value None 287 288 """ 289 # The implementation of this method is mandatory only if the new LU is 290 # concurrent, so that old LUs don't need to be changed all at the same 291 # time. 292 if self.REQ_BGL: 293 self.needed_locks = {} # Exclusive LUs don't need locks. 294 else: 295 raise NotImplementedError
296
297 - def DeclareLocks(self, level):
298 """Declare LU locking needs for a level 299 300 While most LUs can just declare their locking needs at ExpandNames time, 301 sometimes there's the need to calculate some locks after having acquired 302 the ones before. This function is called just before acquiring locks at a 303 particular level, but after acquiring the ones at lower levels, and permits 304 such calculations. It can be used to modify self.needed_locks, and by 305 default it does nothing. 306 307 This function is only called if you have something already set in 308 self.needed_locks for the level. 309 310 @param level: Locking level which is going to be locked 311 @type level: member of L{ganeti.locking.LEVELS} 312 313 """
314
315 - def CheckPrereq(self):
316 """Check prerequisites for this LU. 317 318 This method should check that the prerequisites for the execution 319 of this LU are fulfilled. It can do internode communication, but 320 it should be idempotent - no cluster or system changes are 321 allowed. 322 323 The method should raise errors.OpPrereqError in case something is 324 not fulfilled. Its return value is ignored. 325 326 This method should also update all the parameters of the opcode to 327 their canonical form if it hasn't been done by ExpandNames before. 328 329 """ 330 if self.tasklets is not None: 331 for (idx, tl) in enumerate(self.tasklets): 332 logging.debug("Checking prerequisites for tasklet %s/%s", 333 idx + 1, len(self.tasklets)) 334 tl.CheckPrereq() 335 else: 336 pass
337
338 - def Exec(self, feedback_fn):
339 """Execute the LU. 340 341 This method should implement the actual work. It should raise 342 errors.OpExecError for failures that are somewhat dealt with in 343 code, or expected. 344 345 """ 346 if self.tasklets is not None: 347 for (idx, tl) in enumerate(self.tasklets): 348 logging.debug("Executing tasklet %s/%s", idx + 1, len(self.tasklets)) 349 tl.Exec(feedback_fn) 350 else: 351 raise NotImplementedError
352
353 - def BuildHooksEnv(self):
354 """Build hooks environment for this LU. 355 356 @rtype: dict 357 @return: Dictionary containing the environment that will be used for 358 running the hooks for this LU. The keys of the dict must not be prefixed 359 with "GANETI_"--that'll be added by the hooks runner. The hooks runner 360 will extend the environment with additional variables. If no environment 361 should be defined, an empty dictionary should be returned (not C{None}). 362 @note: If the C{HPATH} attribute of the LU class is C{None}, this function 363 will not be called. 364 365 """ 366 raise NotImplementedError
367
368 - def BuildHooksNodes(self):
369 """Build list of nodes to run LU's hooks. 370 371 @rtype: tuple; (list, list) 372 @return: Tuple containing a list of node UUIDs on which the hook 373 should run before the execution and a list of node UUIDs on which the 374 hook should run after the execution. 375 No nodes should be returned as an empty list (and not None). 376 @note: If the C{HPATH} attribute of the LU class is C{None}, this function 377 will not be called. 378 379 """ 380 raise NotImplementedError
381
382 - def PreparePostHookNodes(self, post_hook_node_uuids):
383 """Extend list of nodes to run the post LU hook. 384 385 This method allows LUs to change the list of node UUIDs on which the 386 post hook should run after the LU has been executed but before the post 387 hook is run. 388 389 @type post_hook_node_uuids: list 390 @param post_hook_node_uuids: The initial list of node UUIDs to run the 391 post hook on, as returned by L{BuildHooksNodes}. 392 @rtype: list 393 @return: list of node UUIDs on which the post hook should run. The default 394 implementation returns the passed in C{post_hook_node_uuids}, but 395 custom implementations can choose to alter the list. 396 397 """ 398 # For consistency with HooksCallBack we ignore the "could be a function" 399 # warning 400 # pylint: disable=R0201 401 return post_hook_node_uuids
402
403 - def HooksCallBack(self, phase, hook_results, feedback_fn, lu_result):
404 """Notify the LU about the results of its hooks. 405 406 This method is called every time a hooks phase is executed, and notifies 407 the Logical Unit about the hooks' result. The LU can then use it to alter 408 its result based on the hooks. By default the method does nothing and the 409 previous result is passed back unchanged but any LU can define it if it 410 wants to use the local cluster hook-scripts somehow. 411 412 @param phase: one of L{constants.HOOKS_PHASE_POST} or 413 L{constants.HOOKS_PHASE_PRE}; it denotes the hooks phase 414 @param hook_results: the results of the multi-node hooks rpc call 415 @param feedback_fn: function used send feedback back to the caller 416 @param lu_result: the previous Exec result this LU had, or None 417 in the PRE phase 418 @return: the new Exec result, based on the previous result 419 and hook results 420 421 """ 422 # API must be kept, thus we ignore the unused argument and could 423 # be a function warnings 424 # pylint: disable=W0613,R0201 425 return lu_result
426
427 - def _ExpandAndLockInstance(self):
428 """Helper function to expand and lock an instance. 429 430 Many LUs that work on an instance take its name in self.op.instance_name 431 and need to expand it and then declare the expanded name for locking. This 432 function does it, and then updates self.op.instance_name to the expanded 433 name. It also initializes needed_locks as a dict, if this hasn't been done 434 before. 435 436 """ 437 if self.needed_locks is None: 438 self.needed_locks = {} 439 else: 440 assert locking.LEVEL_INSTANCE not in self.needed_locks, \ 441 "_ExpandAndLockInstance called with instance-level locks set" 442 (self.op.instance_uuid, self.op.instance_name) = \ 443 ExpandInstanceUuidAndName(self.cfg, self.op.instance_uuid, 444 self.op.instance_name) 445 self.needed_locks[locking.LEVEL_INSTANCE] = self.op.instance_name
446
447 - def _LockInstancesNodes(self, primary_only=False, 448 level=locking.LEVEL_NODE):
449 """Helper function to declare instances' nodes for locking. 450 451 This function should be called after locking one or more instances to lock 452 their nodes. Its effect is populating self.needed_locks[locking.LEVEL_NODE] 453 with all primary or secondary nodes for instances already locked and 454 present in self.needed_locks[locking.LEVEL_INSTANCE]. 455 456 It should be called from DeclareLocks, and for safety only works if 457 self.recalculate_locks[locking.LEVEL_NODE] is set. 458 459 In the future it may grow parameters to just lock some instance's nodes, or 460 to just lock primaries or secondary nodes, if needed. 461 462 If should be called in DeclareLocks in a way similar to:: 463 464 if level == locking.LEVEL_NODE: 465 self._LockInstancesNodes() 466 467 @type primary_only: boolean 468 @param primary_only: only lock primary nodes of locked instances 469 @param level: Which lock level to use for locking nodes 470 471 """ 472 assert level in self.recalculate_locks, \ 473 "_LockInstancesNodes helper function called with no nodes to recalculate" 474 475 # TODO: check if we're really been called with the instance locks held 476 477 # For now we'll replace self.needed_locks[locking.LEVEL_NODE], but in the 478 # future we might want to have different behaviors depending on the value 479 # of self.recalculate_locks[locking.LEVEL_NODE] 480 wanted_node_uuids = [] 481 locked_i = self.owned_locks(locking.LEVEL_INSTANCE) 482 for _, instance in self.cfg.GetMultiInstanceInfoByName(locked_i): 483 wanted_node_uuids.append(instance.primary_node) 484 if not primary_only: 485 wanted_node_uuids.extend( 486 self.cfg.GetInstanceSecondaryNodes(instance.uuid)) 487 488 if self.recalculate_locks[level] == constants.LOCKS_REPLACE: 489 self.needed_locks[level] = wanted_node_uuids 490 elif self.recalculate_locks[level] == constants.LOCKS_APPEND: 491 self.needed_locks[level].extend(wanted_node_uuids) 492 else: 493 raise errors.ProgrammerError("Unknown recalculation mode") 494 495 del self.recalculate_locks[level]
496 497
498 -class NoHooksLU(LogicalUnit): # pylint: disable=W0223
499 """Simple LU which runs no hooks. 500 501 This LU is intended as a parent for other LogicalUnits which will 502 run no hooks, in order to reduce duplicate code. 503 504 """ 505 HPATH = None 506 HTYPE = None 507
508 - def BuildHooksEnv(self):
509 """Empty BuildHooksEnv for NoHooksLu. 510 511 This just raises an error. 512 513 """ 514 raise AssertionError("BuildHooksEnv called for NoHooksLUs")
515
516 - def BuildHooksNodes(self):
517 """Empty BuildHooksNodes for NoHooksLU. 518 519 """ 520 raise AssertionError("BuildHooksNodes called for NoHooksLU")
521
522 - def PreparePostHookNodes(self, post_hook_node_uuids):
523 """Empty PreparePostHookNodes for NoHooksLU. 524 525 """ 526 raise AssertionError("PreparePostHookNodes called for NoHooksLU")
527 528
529 -class Tasklet(object):
530 """Tasklet base class. 531 532 Tasklets are subcomponents for LUs. LUs can consist entirely of tasklets or 533 they can mix legacy code with tasklets. Locking needs to be done in the LU, 534 tasklets know nothing about locks. 535 536 Subclasses must follow these rules: 537 - Implement CheckPrereq 538 - Implement Exec 539 540 """
541 - def __init__(self, lu):
542 self.lu = lu 543 544 # Shortcuts 545 self.cfg = lu.cfg 546 self.rpc = lu.rpc
547
548 - def CheckPrereq(self):
549 """Check prerequisites for this tasklets. 550 551 This method should check whether the prerequisites for the execution of 552 this tasklet are fulfilled. It can do internode communication, but it 553 should be idempotent - no cluster or system changes are allowed. 554 555 The method should raise errors.OpPrereqError in case something is not 556 fulfilled. Its return value is ignored. 557 558 This method should also update all parameters to their canonical form if it 559 hasn't been done before. 560 561 """ 562 pass
563
564 - def Exec(self, feedback_fn):
565 """Execute the tasklet. 566 567 This method should implement the actual work. It should raise 568 errors.OpExecError for failures that are somewhat dealt with in code, or 569 expected. 570 571 """ 572 raise NotImplementedError
573 574
575 -class QueryBase(object):
576 """Base for query utility classes. 577 578 """ 579 #: Attribute holding field definitions 580 FIELDS = None 581 582 #: Field to sort by 583 SORT_FIELD = "name" 584
585 - def __init__(self, qfilter, fields, use_locking):
586 """Initializes this class. 587 588 """ 589 self.use_locking = use_locking 590 591 self.query = query.Query(self.FIELDS, fields, qfilter=qfilter, 592 namefield=self.SORT_FIELD) 593 self.requested_data = self.query.RequestedData() 594 self.names = self.query.RequestedNames() 595 596 # Sort only if no names were requested 597 self.sort_by_name = not self.names 598 599 self.do_locking = None 600 self.wanted = None
601
602 - def _GetNames(self, lu, all_names, lock_level):
603 """Helper function to determine names asked for in the query. 604 605 """ 606 if self.do_locking: 607 names = lu.owned_locks(lock_level) 608 else: 609 names = all_names 610 611 if self.wanted == locking.ALL_SET: 612 assert not self.names 613 # caller didn't specify names, so ordering is not important 614 return utils.NiceSort(names) 615 616 # caller specified names and we must keep the same order 617 assert self.names 618 619 missing = set(self.wanted).difference(names) 620 if missing: 621 raise errors.OpExecError("Some items were removed before retrieving" 622 " their data: %s" % missing) 623 624 # Return expanded names 625 return self.wanted
626
627 - def ExpandNames(self, lu):
628 """Expand names for this query. 629 630 See L{LogicalUnit.ExpandNames}. 631 632 """ 633 raise NotImplementedError()
634
635 - def DeclareLocks(self, lu, level):
636 """Declare locks for this query. 637 638 See L{LogicalUnit.DeclareLocks}. 639 640 """ 641 raise NotImplementedError()
642
643 - def _GetQueryData(self, lu):
644 """Collects all data for this query. 645 646 @return: Query data object 647 648 """ 649 raise NotImplementedError()
650
651 - def NewStyleQuery(self, lu):
652 """Collect data and execute query. 653 654 """ 655 return query.GetQueryResponse(self.query, self._GetQueryData(lu), 656 sort_by_name=self.sort_by_name)
657
658 - def OldStyleQuery(self, lu):
659 """Collect data and execute query. 660 661 """ 662 return self.query.OldStyleQuery(self._GetQueryData(lu), 663 sort_by_name=self.sort_by_name)
664