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