Statistics
| Branch: | Tag: | Revision:

root / lib / cmdlib / base.py @ b3fc101f

History | View | Annotate | Download (21.5 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21

    
22
"""Base classes and functions for cmdlib."""
23

    
24
import logging
25

    
26
from ganeti import errors
27
from ganeti import constants
28
from ganeti import locking
29
from ganeti import query
30
from ganeti import utils
31
from ganeti.cmdlib.common import ExpandInstanceUuidAndName
32

    
33

    
34
class ResultWithJobs:
35
  """Data container for LU results with jobs.
36

37
  Instances of this class returned from L{LogicalUnit.Exec} will be recognized
38
  by L{mcpu._ProcessResult}. The latter will then submit the jobs
39
  contained in the C{jobs} attribute and include the job IDs in the opcode
40
  result.
41

42
  """
43
  def __init__(self, jobs, **kwargs):
44
    """Initializes this class.
45

46
    Additional return values can be specified as keyword arguments.
47

48
    @type jobs: list of lists of L{opcode.OpCode}
49
    @param jobs: A list of lists of opcode objects
50

51
    """
52
    self.jobs = jobs
53
    self.other = kwargs
54

    
55

    
56
class LUWConfdClient(object):
57
  """Wrapper class for wconfd client calls from LUs.
58

59
  Correctly updates the cache of the LU's owned locks
60
  when leaving. Also transparently adds the context
61
  for resource requests.
62

63
  """
64
  def __init__(self, lu):
65
    self.lu = lu
66

    
67
  def TryUpdateLocks(self, req):
68
    self.lu.wconfd.Client().TryUpdateLocks(self.lu.wconfdcontext, req)
69
    self.lu.wconfdlocks = \
70
      self.lu.wconfd.Client().ListLocks(self.lu.wconfdcontext)
71

    
72
  def DownGradeLocksLevel(self, level):
73
    self.lu.wconfd.Client().DownGradeLocksLevel(self.lu.wconfdcontext, level)
74
    self.lu.wconfdlocks = \
75
      self.lu.wconfd.Client().ListLocks(self.lu.wconfdcontext)
76

    
77
  def FreeLocksLevel(self, level):
78
    self.lu.wconfd.Client().FreeLocksLevel(self.lu.wconfdcontext, level)
79
    self.lu.wconfdlocks = \
80
      self.lu.wconfd.Client().ListLocks(self.lu.wconfdcontext)
81

    
82

    
83
class LogicalUnit(object):
84
  """Logical Unit base class.
85

86
  Subclasses must follow these rules:
87
    - implement ExpandNames
88
    - implement CheckPrereq (except when tasklets are used)
89
    - implement Exec (except when tasklets are used)
90
    - implement BuildHooksEnv
91
    - implement BuildHooksNodes
92
    - redefine HPATH and HTYPE
93
    - optionally redefine their run requirements:
94
        REQ_BGL: the LU needs to hold the Big Ganeti Lock exclusively
95

96
  Note that all commands require root permissions.
97

98
  @ivar dry_run_result: the value (if any) that will be returned to the caller
99
      in dry-run mode (signalled by opcode dry_run parameter)
100

101
  """
102
  HPATH = None
103
  HTYPE = None
104
  REQ_BGL = True
105

    
106
  def __init__(self, processor, op, context, cfg,
107
               rpc_runner, wconfdcontext, wconfd):
108
    """Constructor for LogicalUnit.
109

110
    This needs to be overridden in derived classes in order to check op
111
    validity.
112

113
    @type wconfdcontext: (int, string)
114
    @param wconfdcontext: the identity of the logical unit to represent itself
115
        to wconfd when asking for resources; it is given as job id and livelock
116
        file.
117
    @param wconfd: the wconfd class to use; dependency injection to allow
118
        testability.
119

120
    """
121
    self.proc = processor
122
    self.op = op
123
    self.cfg = cfg
124
    self.wconfdlocks = []
125
    self.wconfdcontext = wconfdcontext
126
    self.context = context
127
    self.rpc = rpc_runner
128
    self.wconfd = wconfd # wconfd module to use, for testing
129

    
130
    # Dictionaries used to declare locking needs to mcpu
131
    self.needed_locks = None
132
    self.share_locks = dict.fromkeys(locking.LEVELS, 0)
133
    self.opportunistic_locks = dict.fromkeys(locking.LEVELS, False)
134

    
135
    self.add_locks = {}
136
    self.remove_locks = {}
137

    
138
    # Used to force good behavior when calling helper functions
139
    self.recalculate_locks = {}
140

    
141
    # logging
142
    self.Log = processor.Log # pylint: disable=C0103
143
    self.LogWarning = processor.LogWarning # pylint: disable=C0103
144
    self.LogInfo = processor.LogInfo # pylint: disable=C0103
145
    self.LogStep = processor.LogStep # pylint: disable=C0103
146
    # support for dry-run
147
    self.dry_run_result = None
148
    # support for generic debug attribute
149
    if (not hasattr(self.op, "debug_level") or
150
        not isinstance(self.op.debug_level, int)):
151
      self.op.debug_level = 0
152

    
153
    # Tasklets
154
    self.tasklets = None
155

    
156
    # Validate opcode parameters and set defaults
157
    self.op.Validate(True)
158

    
159
    self.CheckArguments()
160

    
161
  def WConfdClient(self):
162
    return LUWConfdClient(self)
163

    
164
  def owned_locks(self, level):
165
    """Return the list of locks owned by the LU at a given level.
166

167
    This method assumes that is field wconfdlocks is set correctly
168
    by mcpu.
169

170
    """
171
    levelprefix = "%s/" % (locking.LEVEL_NAMES[level],)
172
    locks = set([lock[0][len(levelprefix):]
173
                for lock in self.wconfdlocks
174
                if lock[0].startswith(levelprefix)])
175
    expand_fns = {
176
      locking.LEVEL_CLUSTER: (lambda: [locking.BGL]),
177
      locking.LEVEL_INSTANCE: self.cfg.GetInstanceList,
178
      locking.LEVEL_NODE_ALLOC: (lambda: [locking.NAL]),
179
      locking.LEVEL_NODEGROUP: self.cfg.GetNodeGroupList,
180
      locking.LEVEL_NODE: self.cfg.GetNodeList,
181
      locking.LEVEL_NODE_RES: self.cfg.GetNodeList,
182
      locking.LEVEL_NETWORK: self.cfg.GetNetworkList,
183
      }
184
    if locking.LOCKSET_NAME in locks:
185
      return expand_fns[level]()
186
    else:
187
      return locks
188

    
189
  def release_request(self, level, names):
190
    """Return a request to release the specified locks of the given level.
191

192
    Correctly break up the group lock to do so.
193

194
    """
195
    levelprefix = "%s/" % (locking.LEVEL_NAMES[level],)
196
    release = [[levelprefix + lock, "release"] for lock in names]
197

    
198
    # if we break up the set-lock, make sure we ask for the rest of it.
199
    setlock = levelprefix + locking.LOCKSET_NAME
200
    if [setlock, "exclusive"] in self.wconfdlocks:
201
      owned = self.owned_locks(level)
202
      request = [[levelprefix + lock, "exclusive"]
203
                 for lock in owned
204
                 if lock not in names]
205
    elif [setlock, "shared"] in self.wconfdlocks:
206
      owned = self.owned_locks(level)
207
      request = [[levelprefix + lock, "shared"]
208
                 for lock in owned
209
                 if lock not in names]
210
    else:
211
      request = []
212

    
213
    return release + [[setlock, "release"]] + request
214

    
215
  def CheckArguments(self):
216
    """Check syntactic validity for the opcode arguments.
217

218
    This method is for doing a simple syntactic check and ensure
219
    validity of opcode parameters, without any cluster-related
220
    checks. While the same can be accomplished in ExpandNames and/or
221
    CheckPrereq, doing these separate is better because:
222

223
      - ExpandNames is left as as purely a lock-related function
224
      - CheckPrereq is run after we have acquired locks (and possible
225
        waited for them)
226

227
    The function is allowed to change the self.op attribute so that
228
    later methods can no longer worry about missing parameters.
229

230
    """
231
    pass
232

    
233
  def ExpandNames(self):
234
    """Expand names for this LU.
235

236
    This method is called before starting to execute the opcode, and it should
237
    update all the parameters of the opcode to their canonical form (e.g. a
238
    short node name must be fully expanded after this method has successfully
239
    completed). This way locking, hooks, logging, etc. can work correctly.
240

241
    LUs which implement this method must also populate the self.needed_locks
242
    member, as a dict with lock levels as keys, and a list of needed lock names
243
    as values. Rules:
244

245
      - use an empty dict if you don't need any lock
246
      - if you don't need any lock at a particular level omit that
247
        level (note that in this case C{DeclareLocks} won't be called
248
        at all for that level)
249
      - if you need locks at a level, but you can't calculate it in
250
        this function, initialise that level with an empty list and do
251
        further processing in L{LogicalUnit.DeclareLocks} (see that
252
        function's docstring)
253
      - don't put anything for the BGL level
254
      - if you want all locks at a level use L{locking.ALL_SET} as a value
255

256
    If you need to share locks (rather than acquire them exclusively) at one
257
    level you can modify self.share_locks, setting a true value (usually 1) for
258
    that level. By default locks are not shared.
259

260
    This function can also define a list of tasklets, which then will be
261
    executed in order instead of the usual LU-level CheckPrereq and Exec
262
    functions, if those are not defined by the LU.
263

264
    Examples::
265

266
      # Acquire all nodes and one instance
267
      self.needed_locks = {
268
        locking.LEVEL_NODE: locking.ALL_SET,
269
        locking.LEVEL_INSTANCE: ['instance1.example.com'],
270
      }
271
      # Acquire just two nodes
272
      self.needed_locks = {
273
        locking.LEVEL_NODE: ['node1-uuid', 'node2-uuid'],
274
      }
275
      # Acquire no locks
276
      self.needed_locks = {} # No, you can't leave it to the default value None
277

278
    """
279
    # The implementation of this method is mandatory only if the new LU is
280
    # concurrent, so that old LUs don't need to be changed all at the same
281
    # time.
282
    if self.REQ_BGL:
283
      self.needed_locks = {} # Exclusive LUs don't need locks.
284
    else:
285
      raise NotImplementedError
286

    
287
  def DeclareLocks(self, level):
288
    """Declare LU locking needs for a level
289

290
    While most LUs can just declare their locking needs at ExpandNames time,
291
    sometimes there's the need to calculate some locks after having acquired
292
    the ones before. This function is called just before acquiring locks at a
293
    particular level, but after acquiring the ones at lower levels, and permits
294
    such calculations. It can be used to modify self.needed_locks, and by
295
    default it does nothing.
296

297
    This function is only called if you have something already set in
298
    self.needed_locks for the level.
299

300
    @param level: Locking level which is going to be locked
301
    @type level: member of L{ganeti.locking.LEVELS}
302

303
    """
304

    
305
  def CheckPrereq(self):
306
    """Check prerequisites for this LU.
307

308
    This method should check that the prerequisites for the execution
309
    of this LU are fulfilled. It can do internode communication, but
310
    it should be idempotent - no cluster or system changes are
311
    allowed.
312

313
    The method should raise errors.OpPrereqError in case something is
314
    not fulfilled. Its return value is ignored.
315

316
    This method should also update all the parameters of the opcode to
317
    their canonical form if it hasn't been done by ExpandNames before.
318

319
    """
320
    if self.tasklets is not None:
321
      for (idx, tl) in enumerate(self.tasklets):
322
        logging.debug("Checking prerequisites for tasklet %s/%s",
323
                      idx + 1, len(self.tasklets))
324
        tl.CheckPrereq()
325
    else:
326
      pass
327

    
328
  def Exec(self, feedback_fn):
329
    """Execute the LU.
330

331
    This method should implement the actual work. It should raise
332
    errors.OpExecError for failures that are somewhat dealt with in
333
    code, or expected.
334

335
    """
336
    if self.tasklets is not None:
337
      for (idx, tl) in enumerate(self.tasklets):
338
        logging.debug("Executing tasklet %s/%s", idx + 1, len(self.tasklets))
339
        tl.Exec(feedback_fn)
340
    else:
341
      raise NotImplementedError
342

    
343
  def BuildHooksEnv(self):
344
    """Build hooks environment for this LU.
345

346
    @rtype: dict
347
    @return: Dictionary containing the environment that will be used for
348
      running the hooks for this LU. The keys of the dict must not be prefixed
349
      with "GANETI_"--that'll be added by the hooks runner. The hooks runner
350
      will extend the environment with additional variables. If no environment
351
      should be defined, an empty dictionary should be returned (not C{None}).
352
    @note: If the C{HPATH} attribute of the LU class is C{None}, this function
353
      will not be called.
354

355
    """
356
    raise NotImplementedError
357

    
358
  def BuildHooksNodes(self):
359
    """Build list of nodes to run LU's hooks.
360

361
    @rtype: tuple; (list, list)
362
    @return: Tuple containing a list of node UUIDs on which the hook
363
      should run before the execution and a list of node UUIDs on which the
364
      hook should run after the execution.
365
      No nodes should be returned as an empty list (and not None).
366
    @note: If the C{HPATH} attribute of the LU class is C{None}, this function
367
      will not be called.
368

369
    """
370
    raise NotImplementedError
371

    
372
  def PreparePostHookNodes(self, post_hook_node_uuids):
373
    """Extend list of nodes to run the post LU hook.
374

375
    This method allows LUs to change the list of node UUIDs on which the
376
    post hook should run after the LU has been executed but before the post
377
    hook is run.
378

379
    @type post_hook_node_uuids: list
380
    @param post_hook_node_uuids: The initial list of node UUIDs to run the
381
      post hook on, as returned by L{BuildHooksNodes}.
382
    @rtype: list
383
    @return: list of node UUIDs on which the post hook should run. The default
384
      implementation returns the passed in C{post_hook_node_uuids}, but
385
      custom implementations can choose to alter the list.
386

387
    """
388
    # For consistency with HooksCallBack we ignore the "could be a function"
389
    # warning
390
    # pylint: disable=R0201
391
    return post_hook_node_uuids
392

    
393
  def HooksCallBack(self, phase, hook_results, feedback_fn, lu_result):
394
    """Notify the LU about the results of its hooks.
395

396
    This method is called every time a hooks phase is executed, and notifies
397
    the Logical Unit about the hooks' result. The LU can then use it to alter
398
    its result based on the hooks.  By default the method does nothing and the
399
    previous result is passed back unchanged but any LU can define it if it
400
    wants to use the local cluster hook-scripts somehow.
401

402
    @param phase: one of L{constants.HOOKS_PHASE_POST} or
403
        L{constants.HOOKS_PHASE_PRE}; it denotes the hooks phase
404
    @param hook_results: the results of the multi-node hooks rpc call
405
    @param feedback_fn: function used send feedback back to the caller
406
    @param lu_result: the previous Exec result this LU had, or None
407
        in the PRE phase
408
    @return: the new Exec result, based on the previous result
409
        and hook results
410

411
    """
412
    # API must be kept, thus we ignore the unused argument and could
413
    # be a function warnings
414
    # pylint: disable=W0613,R0201
415
    return lu_result
416

    
417
  def _ExpandAndLockInstance(self):
418
    """Helper function to expand and lock an instance.
419

420
    Many LUs that work on an instance take its name in self.op.instance_name
421
    and need to expand it and then declare the expanded name for locking. This
422
    function does it, and then updates self.op.instance_name to the expanded
423
    name. It also initializes needed_locks as a dict, if this hasn't been done
424
    before.
425

426
    """
427
    if self.needed_locks is None:
428
      self.needed_locks = {}
429
    else:
430
      assert locking.LEVEL_INSTANCE not in self.needed_locks, \
431
        "_ExpandAndLockInstance called with instance-level locks set"
432
    (self.op.instance_uuid, self.op.instance_name) = \
433
      ExpandInstanceUuidAndName(self.cfg, self.op.instance_uuid,
434
                                self.op.instance_name)
435
    self.needed_locks[locking.LEVEL_INSTANCE] = self.op.instance_name
436

    
437
  def _LockInstancesNodes(self, primary_only=False,
438
                          level=locking.LEVEL_NODE):
439
    """Helper function to declare instances' nodes for locking.
440

441
    This function should be called after locking one or more instances to lock
442
    their nodes. Its effect is populating self.needed_locks[locking.LEVEL_NODE]
443
    with all primary or secondary nodes for instances already locked and
444
    present in self.needed_locks[locking.LEVEL_INSTANCE].
445

446
    It should be called from DeclareLocks, and for safety only works if
447
    self.recalculate_locks[locking.LEVEL_NODE] is set.
448

449
    In the future it may grow parameters to just lock some instance's nodes, or
450
    to just lock primaries or secondary nodes, if needed.
451

452
    If should be called in DeclareLocks in a way similar to::
453

454
      if level == locking.LEVEL_NODE:
455
        self._LockInstancesNodes()
456

457
    @type primary_only: boolean
458
    @param primary_only: only lock primary nodes of locked instances
459
    @param level: Which lock level to use for locking nodes
460

461
    """
462
    assert level in self.recalculate_locks, \
463
      "_LockInstancesNodes helper function called with no nodes to recalculate"
464

    
465
    # TODO: check if we're really been called with the instance locks held
466

    
467
    # For now we'll replace self.needed_locks[locking.LEVEL_NODE], but in the
468
    # future we might want to have different behaviors depending on the value
469
    # of self.recalculate_locks[locking.LEVEL_NODE]
470
    wanted_node_uuids = []
471
    locked_i = self.owned_locks(locking.LEVEL_INSTANCE)
472
    for _, instance in self.cfg.GetMultiInstanceInfoByName(locked_i):
473
      wanted_node_uuids.append(instance.primary_node)
474
      if not primary_only:
475
        wanted_node_uuids.extend(instance.secondary_nodes)
476

    
477
    if self.recalculate_locks[level] == constants.LOCKS_REPLACE:
478
      self.needed_locks[level] = wanted_node_uuids
479
    elif self.recalculate_locks[level] == constants.LOCKS_APPEND:
480
      self.needed_locks[level].extend(wanted_node_uuids)
481
    else:
482
      raise errors.ProgrammerError("Unknown recalculation mode")
483

    
484
    del self.recalculate_locks[level]
485

    
486

    
487
class NoHooksLU(LogicalUnit): # pylint: disable=W0223
488
  """Simple LU which runs no hooks.
489

490
  This LU is intended as a parent for other LogicalUnits which will
491
  run no hooks, in order to reduce duplicate code.
492

493
  """
494
  HPATH = None
495
  HTYPE = None
496

    
497
  def BuildHooksEnv(self):
498
    """Empty BuildHooksEnv for NoHooksLu.
499

500
    This just raises an error.
501

502
    """
503
    raise AssertionError("BuildHooksEnv called for NoHooksLUs")
504

    
505
  def BuildHooksNodes(self):
506
    """Empty BuildHooksNodes for NoHooksLU.
507

508
    """
509
    raise AssertionError("BuildHooksNodes called for NoHooksLU")
510

    
511
  def PreparePostHookNodes(self, post_hook_node_uuids):
512
    """Empty PreparePostHookNodes for NoHooksLU.
513

514
    """
515
    raise AssertionError("PreparePostHookNodes called for NoHooksLU")
516

    
517

    
518
class Tasklet:
519
  """Tasklet base class.
520

521
  Tasklets are subcomponents for LUs. LUs can consist entirely of tasklets or
522
  they can mix legacy code with tasklets. Locking needs to be done in the LU,
523
  tasklets know nothing about locks.
524

525
  Subclasses must follow these rules:
526
    - Implement CheckPrereq
527
    - Implement Exec
528

529
  """
530
  def __init__(self, lu):
531
    self.lu = lu
532

    
533
    # Shortcuts
534
    self.cfg = lu.cfg
535
    self.rpc = lu.rpc
536

    
537
  def CheckPrereq(self):
538
    """Check prerequisites for this tasklets.
539

540
    This method should check whether the prerequisites for the execution of
541
    this tasklet are fulfilled. It can do internode communication, but it
542
    should be idempotent - no cluster or system changes are allowed.
543

544
    The method should raise errors.OpPrereqError in case something is not
545
    fulfilled. Its return value is ignored.
546

547
    This method should also update all parameters to their canonical form if it
548
    hasn't been done before.
549

550
    """
551
    pass
552

    
553
  def Exec(self, feedback_fn):
554
    """Execute the tasklet.
555

556
    This method should implement the actual work. It should raise
557
    errors.OpExecError for failures that are somewhat dealt with in code, or
558
    expected.
559

560
    """
561
    raise NotImplementedError
562

    
563

    
564
class QueryBase:
565
  """Base for query utility classes.
566

567
  """
568
  #: Attribute holding field definitions
569
  FIELDS = None
570

    
571
  #: Field to sort by
572
  SORT_FIELD = "name"
573

    
574
  def __init__(self, qfilter, fields, use_locking):
575
    """Initializes this class.
576

577
    """
578
    self.use_locking = use_locking
579

    
580
    self.query = query.Query(self.FIELDS, fields, qfilter=qfilter,
581
                             namefield=self.SORT_FIELD)
582
    self.requested_data = self.query.RequestedData()
583
    self.names = self.query.RequestedNames()
584

    
585
    # Sort only if no names were requested
586
    self.sort_by_name = not self.names
587

    
588
    self.do_locking = None
589
    self.wanted = None
590

    
591
  def _GetNames(self, lu, all_names, lock_level):
592
    """Helper function to determine names asked for in the query.
593

594
    """
595
    if self.do_locking:
596
      names = lu.owned_locks(lock_level)
597
    else:
598
      names = all_names
599

    
600
    if self.wanted == locking.ALL_SET:
601
      assert not self.names
602
      # caller didn't specify names, so ordering is not important
603
      return utils.NiceSort(names)
604

    
605
    # caller specified names and we must keep the same order
606
    assert self.names
607

    
608
    missing = set(self.wanted).difference(names)
609
    if missing:
610
      raise errors.OpExecError("Some items were removed before retrieving"
611
                               " their data: %s" % missing)
612

    
613
    # Return expanded names
614
    return self.wanted
615

    
616
  def ExpandNames(self, lu):
617
    """Expand names for this query.
618

619
    See L{LogicalUnit.ExpandNames}.
620

621
    """
622
    raise NotImplementedError()
623

    
624
  def DeclareLocks(self, lu, level):
625
    """Declare locks for this query.
626

627
    See L{LogicalUnit.DeclareLocks}.
628

629
    """
630
    raise NotImplementedError()
631

    
632
  def _GetQueryData(self, lu):
633
    """Collects all data for this query.
634

635
    @return: Query data object
636

637
    """
638
    raise NotImplementedError()
639

    
640
  def NewStyleQuery(self, lu):
641
    """Collect data and execute query.
642

643
    """
644
    return query.GetQueryResponse(self.query, self._GetQueryData(lu),
645
                                  sort_by_name=self.sort_by_name)
646

    
647
  def OldStyleQuery(self, lu):
648
    """Collect data and execute query.
649

650
    """
651
    return self.query.OldStyleQuery(self._GetQueryData(lu),
652
                                    sort_by_name=self.sort_by_name)