Statistics
| Branch: | Tag: | Revision:

root / lib / cmdlib / base.py @ 87ed6b79

History | View | Annotate | Download (21.6 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
    jid, livelockfile = self.lu.wconfdcontext
69
    self.lu.wconfd.Client().TryUpdateLocks(jid, livelockfile, req)
70
    self.lu.wconfdlocks = self.lu.wconfd.Client().ListLocks(jid, livelockfile)
71

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

    
77
  def FreeLocksLevel(self, level):
78
    jid, livelockfile = self.lu.wconfdcontext
79
    self.lu.wconfd.Client().FreeLocksLevel(jid, livelockfile, level)
80
    self.lu.wconfdlocks = self.lu.wconfd.Client().ListLocks(jid, livelockfile)
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, rpc_runner, wconfdcontext, wconfd):
107
    """Constructor for LogicalUnit.
108

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

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

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

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

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

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

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

    
152
    # Tasklets
153
    self.tasklets = None
154

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

    
158
    self.CheckArguments()
159

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

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

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

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

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

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

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

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

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

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

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

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

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

229
    """
230
    pass
231

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

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

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

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

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

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

263
    Examples::
264

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

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

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

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

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

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

302
    """
303

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

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

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

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

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

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

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

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

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

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

354
    """
355
    raise NotImplementedError
356

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

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

368
    """
369
    raise NotImplementedError
370

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
483
    del self.recalculate_locks[level]
484

    
485

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

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

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

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

499
    This just raises an error.
500

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

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

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

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

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

    
516

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

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

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

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

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

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

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

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

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

549
    """
550
    pass
551

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

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

559
    """
560
    raise NotImplementedError
561

    
562

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

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

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

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

576
    """
577
    self.use_locking = use_locking
578

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

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

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

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

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

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

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

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

    
612
    # Return expanded names
613
    return self.wanted
614

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

618
    See L{LogicalUnit.ExpandNames}.
619

620
    """
621
    raise NotImplementedError()
622

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

626
    See L{LogicalUnit.DeclareLocks}.
627

628
    """
629
    raise NotImplementedError()
630

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

634
    @return: Query data object
635

636
    """
637
    raise NotImplementedError()
638

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

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

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

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