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