Statistics
| Branch: | Tag: | Revision:

root / lib / opcodes.py @ 8c9ee749

History | View | Annotate | Download (35.8 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 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
"""OpCodes module
23

24
This module implements the data structures which define the cluster
25
operations - the so-called opcodes.
26

27
Every operation which modifies the cluster state is expressed via
28
opcodes.
29

30
"""
31

    
32
# this are practically structures, so disable the message about too
33
# few public methods:
34
# pylint: disable-msg=R0903
35

    
36
import logging
37
import re
38
import operator
39

    
40
from ganeti import constants
41
from ganeti import errors
42
from ganeti import ht
43

    
44

    
45
# Common opcode attributes
46

    
47
#: output fields for a query operation
48
_POutputFields = ("output_fields", ht.NoDefault, ht.TListOf(ht.TNonEmptyString))
49

    
50
#: the shutdown timeout
51
_PShutdownTimeout = ("shutdown_timeout", constants.DEFAULT_SHUTDOWN_TIMEOUT,
52
                     ht.TPositiveInt)
53

    
54
#: the force parameter
55
_PForce = ("force", False, ht.TBool)
56

    
57
#: a required instance name (for single-instance LUs)
58
_PInstanceName = ("instance_name", ht.NoDefault, ht.TNonEmptyString)
59

    
60
#: Whether to ignore offline nodes
61
_PIgnoreOfflineNodes = ("ignore_offline_nodes", False, ht.TBool)
62

    
63
#: a required node name (for single-node LUs)
64
_PNodeName = ("node_name", ht.NoDefault, ht.TNonEmptyString)
65

    
66
#: a required node group name (for single-group LUs)
67
_PGroupName = ("group_name", ht.NoDefault, ht.TNonEmptyString)
68

    
69
#: Migration type (live/non-live)
70
_PMigrationMode = ("mode", None,
71
                   ht.TOr(ht.TNone, ht.TElemOf(constants.HT_MIGRATION_MODES)))
72

    
73
#: Obsolete 'live' migration mode (boolean)
74
_PMigrationLive = ("live", None, ht.TMaybeBool)
75

    
76
#: Tag type
77
_PTagKind = ("kind", ht.NoDefault, ht.TElemOf(constants.VALID_TAG_TYPES))
78

    
79
#: List of tag strings
80
_PTags = ("tags", ht.NoDefault, ht.TListOf(ht.TNonEmptyString))
81

    
82
#: OP_ID conversion regular expression
83
_OPID_RE = re.compile("([a-z])([A-Z])")
84

    
85
#: Utility function for L{OpClusterSetParams}
86
_TestClusterOsList = ht.TOr(ht.TNone,
87
  ht.TListOf(ht.TAnd(ht.TList, ht.TIsLength(2),
88
    ht.TMap(ht.WithDesc("GetFirstItem")(operator.itemgetter(0)),
89
            ht.TElemOf(constants.DDMS_VALUES)))))
90

    
91

    
92
def _NameToId(name):
93
  """Convert an opcode class name to an OP_ID.
94

95
  @type name: string
96
  @param name: the class name, as OpXxxYyy
97
  @rtype: string
98
  @return: the name in the OP_XXXX_YYYY format
99

100
  """
101
  if not name.startswith("Op"):
102
    return None
103
  # Note: (?<=[a-z])(?=[A-Z]) would be ideal, since it wouldn't
104
  # consume any input, and hence we would just have all the elements
105
  # in the list, one by one; but it seems that split doesn't work on
106
  # non-consuming input, hence we have to process the input string a
107
  # bit
108
  name = _OPID_RE.sub(r"\1,\2", name)
109
  elems = name.split(",")
110
  return "_".join(n.upper() for n in elems)
111

    
112

    
113
def RequireFileStorage():
114
  """Checks that file storage is enabled.
115

116
  While it doesn't really fit into this module, L{utils} was deemed too large
117
  of a dependency to be imported for just one or two functions.
118

119
  @raise errors.OpPrereqError: when file storage is disabled
120

121
  """
122
  if not constants.ENABLE_FILE_STORAGE:
123
    raise errors.OpPrereqError("File storage disabled at configure time",
124
                               errors.ECODE_INVAL)
125

    
126

    
127
@ht.WithDesc("CheckFileStorage")
128
def _CheckFileStorage(value):
129
  """Ensures file storage is enabled if used.
130

131
  """
132
  if value == constants.DT_FILE:
133
    RequireFileStorage()
134
  return True
135

    
136

    
137
_CheckDiskTemplate = ht.TAnd(ht.TElemOf(constants.DISK_TEMPLATES),
138
                             _CheckFileStorage)
139

    
140

    
141
def _CheckStorageType(storage_type):
142
  """Ensure a given storage type is valid.
143

144
  """
145
  if storage_type not in constants.VALID_STORAGE_TYPES:
146
    raise errors.OpPrereqError("Unknown storage type: %s" % storage_type,
147
                               errors.ECODE_INVAL)
148
  if storage_type == constants.ST_FILE:
149
    RequireFileStorage()
150
  return True
151

    
152

    
153
#: Storage type parameter
154
_PStorageType = ("storage_type", ht.NoDefault, _CheckStorageType)
155

    
156

    
157
class _AutoOpParamSlots(type):
158
  """Meta class for opcode definitions.
159

160
  """
161
  def __new__(mcs, name, bases, attrs):
162
    """Called when a class should be created.
163

164
    @param mcs: The meta class
165
    @param name: Name of created class
166
    @param bases: Base classes
167
    @type attrs: dict
168
    @param attrs: Class attributes
169

170
    """
171
    assert "__slots__" not in attrs, \
172
      "Class '%s' defines __slots__ when it should use OP_PARAMS" % name
173
    assert "OP_ID" not in attrs, "Class '%s' defining OP_ID" % name
174

    
175
    attrs["OP_ID"] = _NameToId(name)
176

    
177
    # Always set OP_PARAMS to avoid duplicates in BaseOpCode.GetAllParams
178
    params = attrs.setdefault("OP_PARAMS", [])
179

    
180
    # Use parameter names as slots
181
    slots = [pname for (pname, _, _) in params]
182

    
183
    assert "OP_DSC_FIELD" not in attrs or attrs["OP_DSC_FIELD"] in slots, \
184
      "Class '%s' uses unknown field in OP_DSC_FIELD" % name
185

    
186
    attrs["__slots__"] = slots
187

    
188
    return type.__new__(mcs, name, bases, attrs)
189

    
190

    
191
class BaseOpCode(object):
192
  """A simple serializable object.
193

194
  This object serves as a parent class for OpCode without any custom
195
  field handling.
196

197
  """
198
  # pylint: disable-msg=E1101
199
  # as OP_ID is dynamically defined
200
  __metaclass__ = _AutoOpParamSlots
201

    
202
  def __init__(self, **kwargs):
203
    """Constructor for BaseOpCode.
204

205
    The constructor takes only keyword arguments and will set
206
    attributes on this object based on the passed arguments. As such,
207
    it means that you should not pass arguments which are not in the
208
    __slots__ attribute for this class.
209

210
    """
211
    slots = self._all_slots()
212
    for key in kwargs:
213
      if key not in slots:
214
        raise TypeError("Object %s doesn't support the parameter '%s'" %
215
                        (self.__class__.__name__, key))
216
      setattr(self, key, kwargs[key])
217

    
218
  def __getstate__(self):
219
    """Generic serializer.
220

221
    This method just returns the contents of the instance as a
222
    dictionary.
223

224
    @rtype:  C{dict}
225
    @return: the instance attributes and their values
226

227
    """
228
    state = {}
229
    for name in self._all_slots():
230
      if hasattr(self, name):
231
        state[name] = getattr(self, name)
232
    return state
233

    
234
  def __setstate__(self, state):
235
    """Generic unserializer.
236

237
    This method just restores from the serialized state the attributes
238
    of the current instance.
239

240
    @param state: the serialized opcode data
241
    @type state:  C{dict}
242

243
    """
244
    if not isinstance(state, dict):
245
      raise ValueError("Invalid data to __setstate__: expected dict, got %s" %
246
                       type(state))
247

    
248
    for name in self._all_slots():
249
      if name not in state and hasattr(self, name):
250
        delattr(self, name)
251

    
252
    for name in state:
253
      setattr(self, name, state[name])
254

    
255
  @classmethod
256
  def _all_slots(cls):
257
    """Compute the list of all declared slots for a class.
258

259
    """
260
    slots = []
261
    for parent in cls.__mro__:
262
      slots.extend(getattr(parent, "__slots__", []))
263
    return slots
264

    
265
  @classmethod
266
  def GetAllParams(cls):
267
    """Compute list of all parameters for an opcode.
268

269
    """
270
    slots = []
271
    for parent in cls.__mro__:
272
      slots.extend(getattr(parent, "OP_PARAMS", []))
273
    return slots
274

    
275
  def Validate(self, set_defaults):
276
    """Validate opcode parameters, optionally setting default values.
277

278
    @type set_defaults: bool
279
    @param set_defaults: Whether to set default values
280
    @raise errors.OpPrereqError: When a parameter value doesn't match
281
                                 requirements
282

283
    """
284
    for (attr_name, default, test) in self.GetAllParams():
285
      assert test == ht.NoType or callable(test)
286

    
287
      if not hasattr(self, attr_name):
288
        if default == ht.NoDefault:
289
          raise errors.OpPrereqError("Required parameter '%s.%s' missing" %
290
                                     (self.OP_ID, attr_name),
291
                                     errors.ECODE_INVAL)
292
        elif set_defaults:
293
          if callable(default):
294
            dval = default()
295
          else:
296
            dval = default
297
          setattr(self, attr_name, dval)
298

    
299
      if test == ht.NoType:
300
        # no tests here
301
        continue
302

    
303
      if set_defaults or hasattr(self, attr_name):
304
        attr_val = getattr(self, attr_name)
305
        if not test(attr_val):
306
          logging.error("OpCode %s, parameter %s, has invalid type %s/value %s",
307
                        self.OP_ID, attr_name, type(attr_val), attr_val)
308
          raise errors.OpPrereqError("Parameter '%s.%s' fails validation" %
309
                                     (self.OP_ID, attr_name),
310
                                     errors.ECODE_INVAL)
311

    
312

    
313
class OpCode(BaseOpCode):
314
  """Abstract OpCode.
315

316
  This is the root of the actual OpCode hierarchy. All clases derived
317
  from this class should override OP_ID.
318

319
  @cvar OP_ID: The ID of this opcode. This should be unique amongst all
320
               children of this class.
321
  @cvar OP_DSC_FIELD: The name of a field whose value will be included in the
322
                      string returned by Summary(); see the docstring of that
323
                      method for details).
324
  @cvar OP_PARAMS: List of opcode attributes, the default values they should
325
                   get if not already defined, and types they must match.
326
  @cvar WITH_LU: Boolean that specifies whether this should be included in
327
      mcpu's dispatch table
328
  @ivar dry_run: Whether the LU should be run in dry-run mode, i.e. just
329
                 the check steps
330
  @ivar priority: Opcode priority for queue
331

332
  """
333
  # pylint: disable-msg=E1101
334
  # as OP_ID is dynamically defined
335
  WITH_LU = True
336
  OP_PARAMS = [
337
    ("dry_run", None, ht.TMaybeBool),
338
    ("debug_level", None, ht.TOr(ht.TNone, ht.TPositiveInt)),
339
    ("priority", constants.OP_PRIO_DEFAULT,
340
     ht.TElemOf(constants.OP_PRIO_SUBMIT_VALID)),
341
    ]
342

    
343
  def __getstate__(self):
344
    """Specialized getstate for opcodes.
345

346
    This method adds to the state dictionary the OP_ID of the class,
347
    so that on unload we can identify the correct class for
348
    instantiating the opcode.
349

350
    @rtype:   C{dict}
351
    @return:  the state as a dictionary
352

353
    """
354
    data = BaseOpCode.__getstate__(self)
355
    data["OP_ID"] = self.OP_ID
356
    return data
357

    
358
  @classmethod
359
  def LoadOpCode(cls, data):
360
    """Generic load opcode method.
361

362
    The method identifies the correct opcode class from the dict-form
363
    by looking for a OP_ID key, if this is not found, or its value is
364
    not available in this module as a child of this class, we fail.
365

366
    @type data:  C{dict}
367
    @param data: the serialized opcode
368

369
    """
370
    if not isinstance(data, dict):
371
      raise ValueError("Invalid data to LoadOpCode (%s)" % type(data))
372
    if "OP_ID" not in data:
373
      raise ValueError("Invalid data to LoadOpcode, missing OP_ID")
374
    op_id = data["OP_ID"]
375
    op_class = None
376
    if op_id in OP_MAPPING:
377
      op_class = OP_MAPPING[op_id]
378
    else:
379
      raise ValueError("Invalid data to LoadOpCode: OP_ID %s unsupported" %
380
                       op_id)
381
    op = op_class()
382
    new_data = data.copy()
383
    del new_data["OP_ID"]
384
    op.__setstate__(new_data)
385
    return op
386

    
387
  def Summary(self):
388
    """Generates a summary description of this opcode.
389

390
    The summary is the value of the OP_ID attribute (without the "OP_"
391
    prefix), plus the value of the OP_DSC_FIELD attribute, if one was
392
    defined; this field should allow to easily identify the operation
393
    (for an instance creation job, e.g., it would be the instance
394
    name).
395

396
    """
397
    assert self.OP_ID is not None and len(self.OP_ID) > 3
398
    # all OP_ID start with OP_, we remove that
399
    txt = self.OP_ID[3:]
400
    field_name = getattr(self, "OP_DSC_FIELD", None)
401
    if field_name:
402
      field_value = getattr(self, field_name, None)
403
      if isinstance(field_value, (list, tuple)):
404
        field_value = ",".join(str(i) for i in field_value)
405
      txt = "%s(%s)" % (txt, field_value)
406
    return txt
407

    
408

    
409
# cluster opcodes
410

    
411
class OpClusterPostInit(OpCode):
412
  """Post cluster initialization.
413

414
  This opcode does not touch the cluster at all. Its purpose is to run hooks
415
  after the cluster has been initialized.
416

417
  """
418

    
419

    
420
class OpClusterDestroy(OpCode):
421
  """Destroy the cluster.
422

423
  This opcode has no other parameters. All the state is irreversibly
424
  lost after the execution of this opcode.
425

426
  """
427

    
428

    
429
class OpClusterQuery(OpCode):
430
  """Query cluster information."""
431

    
432

    
433
class OpClusterVerify(OpCode):
434
  """Verify the cluster state.
435

436
  @type skip_checks: C{list}
437
  @ivar skip_checks: steps to be skipped from the verify process; this
438
                     needs to be a subset of
439
                     L{constants.VERIFY_OPTIONAL_CHECKS}; currently
440
                     only L{constants.VERIFY_NPLUSONE_MEM} can be passed
441

442
  """
443
  OP_PARAMS = [
444
    ("skip_checks", ht.EmptyList,
445
     ht.TListOf(ht.TElemOf(constants.VERIFY_OPTIONAL_CHECKS))),
446
    ("verbose", False, ht.TBool),
447
    ("error_codes", False, ht.TBool),
448
    ("debug_simulate_errors", False, ht.TBool),
449
    ]
450

    
451

    
452
class OpClusterVerifyDisks(OpCode):
453
  """Verify the cluster disks.
454

455
  Parameters: none
456

457
  Result: a tuple of four elements:
458
    - list of node names with bad data returned (unreachable, etc.)
459
    - dict of node names with broken volume groups (values: error msg)
460
    - list of instances with degraded disks (that should be activated)
461
    - dict of instances with missing logical volumes (values: (node, vol)
462
      pairs with details about the missing volumes)
463

464
  In normal operation, all lists should be empty. A non-empty instance
465
  list (3rd element of the result) is still ok (errors were fixed) but
466
  non-empty node list means some node is down, and probably there are
467
  unfixable drbd errors.
468

469
  Note that only instances that are drbd-based are taken into
470
  consideration. This might need to be revisited in the future.
471

472
  """
473

    
474

    
475
class OpClusterRepairDiskSizes(OpCode):
476
  """Verify the disk sizes of the instances and fixes configuration
477
  mimatches.
478

479
  Parameters: optional instances list, in case we want to restrict the
480
  checks to only a subset of the instances.
481

482
  Result: a list of tuples, (instance, disk, new-size) for changed
483
  configurations.
484

485
  In normal operation, the list should be empty.
486

487
  @type instances: list
488
  @ivar instances: the list of instances to check, or empty for all instances
489

490
  """
491
  OP_PARAMS = [
492
    ("instances", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
493
    ]
494

    
495

    
496
class OpClusterConfigQuery(OpCode):
497
  """Query cluster configuration values."""
498
  OP_PARAMS = [
499
    _POutputFields
500
    ]
501

    
502

    
503
class OpClusterRename(OpCode):
504
  """Rename the cluster.
505

506
  @type name: C{str}
507
  @ivar name: The new name of the cluster. The name and/or the master IP
508
              address will be changed to match the new name and its IP
509
              address.
510

511
  """
512
  OP_DSC_FIELD = "name"
513
  OP_PARAMS = [
514
    ("name", ht.NoDefault, ht.TNonEmptyString),
515
    ]
516

    
517

    
518
class OpClusterSetParams(OpCode):
519
  """Change the parameters of the cluster.
520

521
  @type vg_name: C{str} or C{None}
522
  @ivar vg_name: The new volume group name or None to disable LVM usage.
523

524
  """
525
  OP_PARAMS = [
526
    ("vg_name", None, ht.TMaybeString),
527
    ("enabled_hypervisors", None,
528
     ht.TOr(ht.TAnd(ht.TListOf(ht.TElemOf(constants.HYPER_TYPES)), ht.TTrue),
529
            ht.TNone)),
530
    ("hvparams", None, ht.TOr(ht.TDictOf(ht.TNonEmptyString, ht.TDict),
531
                              ht.TNone)),
532
    ("beparams", None, ht.TOr(ht.TDict, ht.TNone)),
533
    ("os_hvp", None, ht.TOr(ht.TDictOf(ht.TNonEmptyString, ht.TDict),
534
                            ht.TNone)),
535
    ("osparams", None, ht.TOr(ht.TDictOf(ht.TNonEmptyString, ht.TDict),
536
                              ht.TNone)),
537
    ("candidate_pool_size", None, ht.TOr(ht.TStrictPositiveInt, ht.TNone)),
538
    ("uid_pool", None, ht.NoType),
539
    ("add_uids", None, ht.NoType),
540
    ("remove_uids", None, ht.NoType),
541
    ("maintain_node_health", None, ht.TMaybeBool),
542
    ("prealloc_wipe_disks", None, ht.TMaybeBool),
543
    ("nicparams", None, ht.TMaybeDict),
544
    ("ndparams", None, ht.TMaybeDict),
545
    ("drbd_helper", None, ht.TOr(ht.TString, ht.TNone)),
546
    ("default_iallocator", None, ht.TOr(ht.TString, ht.TNone)),
547
    ("master_netdev", None, ht.TOr(ht.TString, ht.TNone)),
548
    ("reserved_lvs", None, ht.TOr(ht.TListOf(ht.TNonEmptyString), ht.TNone)),
549
    ("hidden_os", None, _TestClusterOsList),
550
    ("blacklisted_os", None, _TestClusterOsList),
551
    ]
552

    
553

    
554
class OpClusterRedistConf(OpCode):
555
  """Force a full push of the cluster configuration.
556

557
  """
558

    
559

    
560
class OpQuery(OpCode):
561
  """Query for resources/items.
562

563
  @ivar what: Resources to query for, must be one of L{constants.QR_OP_QUERY}
564
  @ivar fields: List of fields to retrieve
565
  @ivar filter: Query filter
566

567
  """
568
  OP_PARAMS = [
569
    ("what", ht.NoDefault, ht.TElemOf(constants.QR_OP_QUERY)),
570
    ("fields", ht.NoDefault, ht.TListOf(ht.TNonEmptyString)),
571
    ("filter", None, ht.TOr(ht.TNone,
572
                            ht.TListOf(ht.TOr(ht.TNonEmptyString, ht.TList)))),
573
    ]
574

    
575

    
576
class OpQueryFields(OpCode):
577
  """Query for available resource/item fields.
578

579
  @ivar what: Resources to query for, must be one of L{constants.QR_OP_QUERY}
580
  @ivar fields: List of fields to retrieve
581

582
  """
583
  OP_PARAMS = [
584
    ("what", ht.NoDefault, ht.TElemOf(constants.QR_OP_QUERY)),
585
    ("fields", None, ht.TOr(ht.TNone, ht.TListOf(ht.TNonEmptyString))),
586
    ]
587

    
588

    
589
class OpOobCommand(OpCode):
590
  """Interact with OOB."""
591
  OP_PARAMS = [
592
    ("node_names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
593
    ("command", None, ht.TElemOf(constants.OOB_COMMANDS)),
594
    ("timeout", constants.OOB_TIMEOUT, ht.TInt),
595
    ("ignore_status", False, ht.TBool),
596
    ("force_master", False, ht.TBool),
597
    ]
598

    
599

    
600
# node opcodes
601

    
602
class OpNodeRemove(OpCode):
603
  """Remove a node.
604

605
  @type node_name: C{str}
606
  @ivar node_name: The name of the node to remove. If the node still has
607
                   instances on it, the operation will fail.
608

609
  """
610
  OP_DSC_FIELD = "node_name"
611
  OP_PARAMS = [
612
    _PNodeName,
613
    ]
614

    
615

    
616
class OpNodeAdd(OpCode):
617
  """Add a node to the cluster.
618

619
  @type node_name: C{str}
620
  @ivar node_name: The name of the node to add. This can be a short name,
621
                   but it will be expanded to the FQDN.
622
  @type primary_ip: IP address
623
  @ivar primary_ip: The primary IP of the node. This will be ignored when the
624
                    opcode is submitted, but will be filled during the node
625
                    add (so it will be visible in the job query).
626
  @type secondary_ip: IP address
627
  @ivar secondary_ip: The secondary IP of the node. This needs to be passed
628
                      if the cluster has been initialized in 'dual-network'
629
                      mode, otherwise it must not be given.
630
  @type readd: C{bool}
631
  @ivar readd: Whether to re-add an existing node to the cluster. If
632
               this is not passed, then the operation will abort if the node
633
               name is already in the cluster; use this parameter to 'repair'
634
               a node that had its configuration broken, or was reinstalled
635
               without removal from the cluster.
636
  @type group: C{str}
637
  @ivar group: The node group to which this node will belong.
638
  @type vm_capable: C{bool}
639
  @ivar vm_capable: The vm_capable node attribute
640
  @type master_capable: C{bool}
641
  @ivar master_capable: The master_capable node attribute
642

643
  """
644
  OP_DSC_FIELD = "node_name"
645
  OP_PARAMS = [
646
    _PNodeName,
647
    ("primary_ip", None, ht.NoType),
648
    ("secondary_ip", None, ht.TMaybeString),
649
    ("readd", False, ht.TBool),
650
    ("group", None, ht.TMaybeString),
651
    ("master_capable", None, ht.TMaybeBool),
652
    ("vm_capable", None, ht.TMaybeBool),
653
    ("ndparams", None, ht.TMaybeDict),
654
    ]
655

    
656

    
657
class OpNodeQuery(OpCode):
658
  """Compute the list of nodes."""
659
  OP_PARAMS = [
660
    _POutputFields,
661
    ("names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
662
    ("use_locking", False, ht.TBool),
663
    ]
664

    
665

    
666
class OpNodeQueryvols(OpCode):
667
  """Get list of volumes on node."""
668
  OP_PARAMS = [
669
    _POutputFields,
670
    ("nodes", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
671
    ]
672

    
673

    
674
class OpNodeQueryStorage(OpCode):
675
  """Get information on storage for node(s)."""
676
  OP_PARAMS = [
677
    _POutputFields,
678
    _PStorageType,
679
    ("nodes", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
680
    ("name", None, ht.TMaybeString),
681
    ]
682

    
683

    
684
class OpNodeModifyStorage(OpCode):
685
  """Modifies the properies of a storage unit"""
686
  OP_PARAMS = [
687
    _PNodeName,
688
    _PStorageType,
689
    ("name", ht.NoDefault, ht.TNonEmptyString),
690
    ("changes", ht.NoDefault, ht.TDict),
691
    ]
692

    
693

    
694
class OpRepairNodeStorage(OpCode):
695
  """Repairs the volume group on a node."""
696
  OP_DSC_FIELD = "node_name"
697
  OP_PARAMS = [
698
    _PNodeName,
699
    _PStorageType,
700
    ("name", ht.NoDefault, ht.TNonEmptyString),
701
    ("ignore_consistency", False, ht.TBool),
702
    ]
703

    
704

    
705
class OpNodeSetParams(OpCode):
706
  """Change the parameters of a node."""
707
  OP_DSC_FIELD = "node_name"
708
  OP_PARAMS = [
709
    _PNodeName,
710
    _PForce,
711
    ("master_candidate", None, ht.TMaybeBool),
712
    ("offline", None, ht.TMaybeBool),
713
    ("drained", None, ht.TMaybeBool),
714
    ("auto_promote", False, ht.TBool),
715
    ("master_capable", None, ht.TMaybeBool),
716
    ("vm_capable", None, ht.TMaybeBool),
717
    ("secondary_ip", None, ht.TMaybeString),
718
    ("ndparams", None, ht.TMaybeDict),
719
    ("powered", None, ht.TMaybeBool),
720
    ]
721

    
722

    
723
class OpNodePowercycle(OpCode):
724
  """Tries to powercycle a node."""
725
  OP_DSC_FIELD = "node_name"
726
  OP_PARAMS = [
727
    _PNodeName,
728
    _PForce,
729
    ]
730

    
731

    
732
class OpNodeMigrate(OpCode):
733
  """Migrate all instances from a node."""
734
  OP_DSC_FIELD = "node_name"
735
  OP_PARAMS = [
736
    _PNodeName,
737
    _PMigrationMode,
738
    _PMigrationLive,
739
    ]
740

    
741

    
742
class OpNodeEvacStrategy(OpCode):
743
  """Compute the evacuation strategy for a list of nodes."""
744
  OP_DSC_FIELD = "nodes"
745
  OP_PARAMS = [
746
    ("nodes", ht.NoDefault, ht.TListOf(ht.TNonEmptyString)),
747
    ("remote_node", None, ht.TMaybeString),
748
    ("iallocator", None, ht.TMaybeString),
749
    ]
750

    
751

    
752
# instance opcodes
753

    
754
class OpInstanceCreate(OpCode):
755
  """Create an instance.
756

757
  @ivar instance_name: Instance name
758
  @ivar mode: Instance creation mode (one of L{constants.INSTANCE_CREATE_MODES})
759
  @ivar source_handshake: Signed handshake from source (remote import only)
760
  @ivar source_x509_ca: Source X509 CA in PEM format (remote import only)
761
  @ivar source_instance_name: Previous name of instance (remote import only)
762
  @ivar source_shutdown_timeout: Shutdown timeout used for source instance
763
    (remote import only)
764

765
  """
766
  OP_DSC_FIELD = "instance_name"
767
  OP_PARAMS = [
768
    _PInstanceName,
769
    ("beparams", ht.EmptyDict, ht.TDict),
770
    ("disks", ht.NoDefault, ht.TListOf(ht.TDict)),
771
    ("disk_template", ht.NoDefault, _CheckDiskTemplate),
772
    ("file_driver", None, ht.TOr(ht.TNone, ht.TElemOf(constants.FILE_DRIVER))),
773
    ("file_storage_dir", None, ht.TMaybeString),
774
    ("force_variant", False, ht.TBool),
775
    ("hvparams", ht.EmptyDict, ht.TDict),
776
    ("hypervisor", None, ht.TMaybeString),
777
    ("iallocator", None, ht.TMaybeString),
778
    ("identify_defaults", False, ht.TBool),
779
    ("ip_check", True, ht.TBool),
780
    ("mode", ht.NoDefault, ht.TElemOf(constants.INSTANCE_CREATE_MODES)),
781
    ("name_check", True, ht.TBool),
782
    ("nics", ht.NoDefault, ht.TListOf(ht.TDict)),
783
    ("no_install", None, ht.TMaybeBool),
784
    ("osparams", ht.EmptyDict, ht.TDict),
785
    ("os_type", None, ht.TMaybeString),
786
    ("pnode", None, ht.TMaybeString),
787
    ("snode", None, ht.TMaybeString),
788
    ("source_handshake", None, ht.TOr(ht.TList, ht.TNone)),
789
    ("source_instance_name", None, ht.TMaybeString),
790
    ("source_shutdown_timeout", constants.DEFAULT_SHUTDOWN_TIMEOUT,
791
     ht.TPositiveInt),
792
    ("source_x509_ca", None, ht.TMaybeString),
793
    ("src_node", None, ht.TMaybeString),
794
    ("src_path", None, ht.TMaybeString),
795
    ("start", True, ht.TBool),
796
    ("wait_for_sync", True, ht.TBool),
797
    ]
798

    
799

    
800
class OpInstanceReinstall(OpCode):
801
  """Reinstall an instance's OS."""
802
  OP_DSC_FIELD = "instance_name"
803
  OP_PARAMS = [
804
    _PInstanceName,
805
    ("os_type", None, ht.TMaybeString),
806
    ("force_variant", False, ht.TBool),
807
    ("osparams", None, ht.TMaybeDict),
808
    ]
809

    
810

    
811
class OpInstanceRemove(OpCode):
812
  """Remove an instance."""
813
  OP_DSC_FIELD = "instance_name"
814
  OP_PARAMS = [
815
    _PInstanceName,
816
    _PShutdownTimeout,
817
    ("ignore_failures", False, ht.TBool),
818
    ]
819

    
820

    
821
class OpInstanceRename(OpCode):
822
  """Rename an instance."""
823
  OP_PARAMS = [
824
    _PInstanceName,
825
    ("new_name", ht.NoDefault, ht.TNonEmptyString),
826
    ("ip_check", False, ht.TBool),
827
    ("name_check", True, ht.TBool),
828
    ]
829

    
830

    
831
class OpInstanceStartup(OpCode):
832
  """Startup an instance."""
833
  OP_DSC_FIELD = "instance_name"
834
  OP_PARAMS = [
835
    _PInstanceName,
836
    _PForce,
837
    _PIgnoreOfflineNodes,
838
    ("hvparams", ht.EmptyDict, ht.TDict),
839
    ("beparams", ht.EmptyDict, ht.TDict),
840
    ]
841

    
842

    
843
class OpInstanceShutdown(OpCode):
844
  """Shutdown an instance."""
845
  OP_DSC_FIELD = "instance_name"
846
  OP_PARAMS = [
847
    _PInstanceName,
848
    _PIgnoreOfflineNodes,
849
    ("timeout", constants.DEFAULT_SHUTDOWN_TIMEOUT, ht.TPositiveInt),
850
    ]
851

    
852

    
853
class OpInstanceReboot(OpCode):
854
  """Reboot an instance."""
855
  OP_DSC_FIELD = "instance_name"
856
  OP_PARAMS = [
857
    _PInstanceName,
858
    _PShutdownTimeout,
859
    ("ignore_secondaries", False, ht.TBool),
860
    ("reboot_type", ht.NoDefault, ht.TElemOf(constants.REBOOT_TYPES)),
861
    ]
862

    
863

    
864
class OpInstanceReplaceDisks(OpCode):
865
  """Replace the disks of an instance."""
866
  OP_DSC_FIELD = "instance_name"
867
  OP_PARAMS = [
868
    _PInstanceName,
869
    ("mode", ht.NoDefault, ht.TElemOf(constants.REPLACE_MODES)),
870
    ("disks", ht.EmptyList, ht.TListOf(ht.TPositiveInt)),
871
    ("remote_node", None, ht.TMaybeString),
872
    ("iallocator", None, ht.TMaybeString),
873
    ("early_release", False, ht.TBool),
874
    ]
875

    
876

    
877
class OpInstanceFailover(OpCode):
878
  """Failover an instance."""
879
  OP_DSC_FIELD = "instance_name"
880
  OP_PARAMS = [
881
    _PInstanceName,
882
    _PShutdownTimeout,
883
    ("ignore_consistency", False, ht.TBool),
884
    ]
885

    
886

    
887
class OpInstanceMigrate(OpCode):
888
  """Migrate an instance.
889

890
  This migrates (without shutting down an instance) to its secondary
891
  node.
892

893
  @ivar instance_name: the name of the instance
894
  @ivar mode: the migration mode (live, non-live or None for auto)
895

896
  """
897
  OP_DSC_FIELD = "instance_name"
898
  OP_PARAMS = [
899
    _PInstanceName,
900
    _PMigrationMode,
901
    _PMigrationLive,
902
    ("cleanup", False, ht.TBool),
903
    ]
904

    
905

    
906
class OpInstanceMove(OpCode):
907
  """Move an instance.
908

909
  This move (with shutting down an instance and data copying) to an
910
  arbitrary node.
911

912
  @ivar instance_name: the name of the instance
913
  @ivar target_node: the destination node
914

915
  """
916
  OP_DSC_FIELD = "instance_name"
917
  OP_PARAMS = [
918
    _PInstanceName,
919
    _PShutdownTimeout,
920
    ("target_node", ht.NoDefault, ht.TNonEmptyString),
921
    ]
922

    
923

    
924
class OpInstanceConsole(OpCode):
925
  """Connect to an instance's console."""
926
  OP_DSC_FIELD = "instance_name"
927
  OP_PARAMS = [
928
    _PInstanceName
929
    ]
930

    
931

    
932
class OpInstanceActivateDisks(OpCode):
933
  """Activate an instance's disks."""
934
  OP_DSC_FIELD = "instance_name"
935
  OP_PARAMS = [
936
    _PInstanceName,
937
    ("ignore_size", False, ht.TBool),
938
    ]
939

    
940

    
941
class OpInstanceDeactivateDisks(OpCode):
942
  """Deactivate an instance's disks."""
943
  OP_DSC_FIELD = "instance_name"
944
  OP_PARAMS = [
945
    _PInstanceName,
946
    _PForce,
947
    ]
948

    
949

    
950
class OpInstanceRecreateDisks(OpCode):
951
  """Deactivate an instance's disks."""
952
  OP_DSC_FIELD = "instance_name"
953
  OP_PARAMS = [
954
    _PInstanceName,
955
    ("disks", ht.EmptyList, ht.TListOf(ht.TPositiveInt)),
956
    ]
957

    
958

    
959
class OpInstanceQuery(OpCode):
960
  """Compute the list of instances."""
961
  OP_PARAMS = [
962
    _POutputFields,
963
    ("names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
964
    ("use_locking", False, ht.TBool),
965
    ]
966

    
967

    
968
class OpInstanceQueryData(OpCode):
969
  """Compute the run-time status of instances."""
970
  OP_PARAMS = [
971
    ("instances", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
972
    ("static", False, ht.TBool),
973
    ]
974

    
975

    
976
class OpInstanceSetParams(OpCode):
977
  """Change the parameters of an instance."""
978
  OP_DSC_FIELD = "instance_name"
979
  OP_PARAMS = [
980
    _PInstanceName,
981
    _PForce,
982
    ("nics", ht.EmptyList, ht.TList),
983
    ("disks", ht.EmptyList, ht.TList),
984
    ("beparams", ht.EmptyDict, ht.TDict),
985
    ("hvparams", ht.EmptyDict, ht.TDict),
986
    ("disk_template", None, ht.TOr(ht.TNone, _CheckDiskTemplate)),
987
    ("remote_node", None, ht.TMaybeString),
988
    ("os_name", None, ht.TMaybeString),
989
    ("force_variant", False, ht.TBool),
990
    ("osparams", None, ht.TMaybeDict),
991
    ]
992

    
993

    
994
class OpInstanceGrowDisk(OpCode):
995
  """Grow a disk of an instance."""
996
  OP_DSC_FIELD = "instance_name"
997
  OP_PARAMS = [
998
    _PInstanceName,
999
    ("disk", ht.NoDefault, ht.TInt),
1000
    ("amount", ht.NoDefault, ht.TInt),
1001
    ("wait_for_sync", True, ht.TBool),
1002
    ]
1003

    
1004

    
1005
# Node group opcodes
1006

    
1007
class OpGroupAdd(OpCode):
1008
  """Add a node group to the cluster."""
1009
  OP_DSC_FIELD = "group_name"
1010
  OP_PARAMS = [
1011
    _PGroupName,
1012
    ("ndparams", None, ht.TMaybeDict),
1013
    ("alloc_policy", None,
1014
     ht.TOr(ht.TNone, ht.TElemOf(constants.VALID_ALLOC_POLICIES))),
1015
    ]
1016

    
1017

    
1018
class OpGroupAssignNodes(OpCode):
1019
  """Assign nodes to a node group."""
1020
  OP_DSC_FIELD = "group_name"
1021
  OP_PARAMS = [
1022
    _PGroupName,
1023
    _PForce,
1024
    ("nodes", ht.NoDefault, ht.TListOf(ht.TNonEmptyString)),
1025
    ]
1026

    
1027

    
1028
class OpGroupQuery(OpCode):
1029
  """Compute the list of node groups."""
1030
  OP_PARAMS = [
1031
    _POutputFields,
1032
    ("names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
1033
    ]
1034

    
1035

    
1036
class OpGroupSetParams(OpCode):
1037
  """Change the parameters of a node group."""
1038
  OP_DSC_FIELD = "group_name"
1039
  OP_PARAMS = [
1040
    _PGroupName,
1041
    ("ndparams", None, ht.TMaybeDict),
1042
    ("alloc_policy", None, ht.TOr(ht.TNone,
1043
                                  ht.TElemOf(constants.VALID_ALLOC_POLICIES))),
1044
    ]
1045

    
1046

    
1047
class OpGroupRemove(OpCode):
1048
  """Remove a node group from the cluster."""
1049
  OP_DSC_FIELD = "group_name"
1050
  OP_PARAMS = [
1051
    _PGroupName,
1052
    ]
1053

    
1054

    
1055
class OpGroupRename(OpCode):
1056
  """Rename a node group in the cluster."""
1057
  OP_DSC_FIELD = "old_name"
1058
  OP_PARAMS = [
1059
    ("old_name", ht.NoDefault, ht.TNonEmptyString),
1060
    ("new_name", ht.NoDefault, ht.TNonEmptyString),
1061
    ]
1062

    
1063

    
1064
# OS opcodes
1065
class OpOsDiagnose(OpCode):
1066
  """Compute the list of guest operating systems."""
1067
  OP_PARAMS = [
1068
    _POutputFields,
1069
    ("names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
1070
    ]
1071

    
1072

    
1073
# Exports opcodes
1074
class OpBackupQuery(OpCode):
1075
  """Compute the list of exported images."""
1076
  OP_PARAMS = [
1077
    ("nodes", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
1078
    ("use_locking", False, ht.TBool),
1079
    ]
1080

    
1081

    
1082
class OpBackupPrepare(OpCode):
1083
  """Prepares an instance export.
1084

1085
  @ivar instance_name: Instance name
1086
  @ivar mode: Export mode (one of L{constants.EXPORT_MODES})
1087

1088
  """
1089
  OP_DSC_FIELD = "instance_name"
1090
  OP_PARAMS = [
1091
    _PInstanceName,
1092
    ("mode", ht.NoDefault, ht.TElemOf(constants.EXPORT_MODES)),
1093
    ]
1094

    
1095

    
1096
class OpBackupExport(OpCode):
1097
  """Export an instance.
1098

1099
  For local exports, the export destination is the node name. For remote
1100
  exports, the export destination is a list of tuples, each consisting of
1101
  hostname/IP address, port, HMAC and HMAC salt. The HMAC is calculated using
1102
  the cluster domain secret over the value "${index}:${hostname}:${port}". The
1103
  destination X509 CA must be a signed certificate.
1104

1105
  @ivar mode: Export mode (one of L{constants.EXPORT_MODES})
1106
  @ivar target_node: Export destination
1107
  @ivar x509_key_name: X509 key to use (remote export only)
1108
  @ivar destination_x509_ca: Destination X509 CA in PEM format (remote export
1109
                             only)
1110

1111
  """
1112
  OP_DSC_FIELD = "instance_name"
1113
  OP_PARAMS = [
1114
    _PInstanceName,
1115
    _PShutdownTimeout,
1116
    # TODO: Rename target_node as it changes meaning for different export modes
1117
    # (e.g. "destination")
1118
    ("target_node", ht.NoDefault, ht.TOr(ht.TNonEmptyString, ht.TList)),
1119
    ("shutdown", True, ht.TBool),
1120
    ("remove_instance", False, ht.TBool),
1121
    ("ignore_remove_failures", False, ht.TBool),
1122
    ("mode", constants.EXPORT_MODE_LOCAL, ht.TElemOf(constants.EXPORT_MODES)),
1123
    ("x509_key_name", None, ht.TOr(ht.TList, ht.TNone)),
1124
    ("destination_x509_ca", None, ht.TMaybeString),
1125
    ]
1126

    
1127

    
1128
class OpBackupRemove(OpCode):
1129
  """Remove an instance's export."""
1130
  OP_DSC_FIELD = "instance_name"
1131
  OP_PARAMS = [
1132
    _PInstanceName,
1133
    ]
1134

    
1135

    
1136
# Tags opcodes
1137
class OpTagsGet(OpCode):
1138
  """Returns the tags of the given object."""
1139
  OP_DSC_FIELD = "name"
1140
  OP_PARAMS = [
1141
    _PTagKind,
1142
    # Name is only meaningful for nodes and instances
1143
    ("name", ht.NoDefault, ht.TMaybeString),
1144
    ]
1145

    
1146

    
1147
class OpTagsSearch(OpCode):
1148
  """Searches the tags in the cluster for a given pattern."""
1149
  OP_DSC_FIELD = "pattern"
1150
  OP_PARAMS = [
1151
    ("pattern", ht.NoDefault, ht.TNonEmptyString),
1152
    ]
1153

    
1154

    
1155
class OpTagsSet(OpCode):
1156
  """Add a list of tags on a given object."""
1157
  OP_PARAMS = [
1158
    _PTagKind,
1159
    _PTags,
1160
    # Name is only meaningful for nodes and instances
1161
    ("name", ht.NoDefault, ht.TMaybeString),
1162
    ]
1163

    
1164

    
1165
class OpTagsDel(OpCode):
1166
  """Remove a list of tags from a given object."""
1167
  OP_PARAMS = [
1168
    _PTagKind,
1169
    _PTags,
1170
    # Name is only meaningful for nodes and instances
1171
    ("name", ht.NoDefault, ht.TMaybeString),
1172
    ]
1173

    
1174
# Test opcodes
1175
class OpTestDelay(OpCode):
1176
  """Sleeps for a configured amount of time.
1177

1178
  This is used just for debugging and testing.
1179

1180
  Parameters:
1181
    - duration: the time to sleep
1182
    - on_master: if true, sleep on the master
1183
    - on_nodes: list of nodes in which to sleep
1184

1185
  If the on_master parameter is true, it will execute a sleep on the
1186
  master (before any node sleep).
1187

1188
  If the on_nodes list is not empty, it will sleep on those nodes
1189
  (after the sleep on the master, if that is enabled).
1190

1191
  As an additional feature, the case of duration < 0 will be reported
1192
  as an execution error, so this opcode can be used as a failure
1193
  generator. The case of duration == 0 will not be treated specially.
1194

1195
  """
1196
  OP_DSC_FIELD = "duration"
1197
  OP_PARAMS = [
1198
    ("duration", ht.NoDefault, ht.TFloat),
1199
    ("on_master", True, ht.TBool),
1200
    ("on_nodes", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
1201
    ("repeat", 0, ht.TPositiveInt)
1202
    ]
1203

    
1204

    
1205
class OpTestAllocator(OpCode):
1206
  """Allocator framework testing.
1207

1208
  This opcode has two modes:
1209
    - gather and return allocator input for a given mode (allocate new
1210
      or replace secondary) and a given instance definition (direction
1211
      'in')
1212
    - run a selected allocator for a given operation (as above) and
1213
      return the allocator output (direction 'out')
1214

1215
  """
1216
  OP_DSC_FIELD = "allocator"
1217
  OP_PARAMS = [
1218
    ("direction", ht.NoDefault,
1219
     ht.TElemOf(constants.VALID_IALLOCATOR_DIRECTIONS)),
1220
    ("mode", ht.NoDefault, ht.TElemOf(constants.VALID_IALLOCATOR_MODES)),
1221
    ("name", ht.NoDefault, ht.TNonEmptyString),
1222
    ("nics", ht.NoDefault, ht.TOr(ht.TNone, ht.TListOf(
1223
      ht.TDictOf(ht.TElemOf(["mac", "ip", "bridge"]),
1224
               ht.TOr(ht.TNone, ht.TNonEmptyString))))),
1225
    ("disks", ht.NoDefault, ht.TOr(ht.TNone, ht.TList)),
1226
    ("hypervisor", None, ht.TMaybeString),
1227
    ("allocator", None, ht.TMaybeString),
1228
    ("tags", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
1229
    ("mem_size", None, ht.TOr(ht.TNone, ht.TPositiveInt)),
1230
    ("vcpus", None, ht.TOr(ht.TNone, ht.TPositiveInt)),
1231
    ("os", None, ht.TMaybeString),
1232
    ("disk_template", None, ht.TMaybeString),
1233
    ("evac_nodes", None, ht.TOr(ht.TNone, ht.TListOf(ht.TNonEmptyString))),
1234
    ]
1235

    
1236

    
1237
class OpTestJqueue(OpCode):
1238
  """Utility opcode to test some aspects of the job queue.
1239

1240
  """
1241
  OP_PARAMS = [
1242
    ("notify_waitlock", False, ht.TBool),
1243
    ("notify_exec", False, ht.TBool),
1244
    ("log_messages", ht.EmptyList, ht.TListOf(ht.TString)),
1245
    ("fail", False, ht.TBool),
1246
    ]
1247

    
1248

    
1249
class OpTestDummy(OpCode):
1250
  """Utility opcode used by unittests.
1251

1252
  """
1253
  OP_PARAMS = [
1254
    ("result", ht.NoDefault, ht.NoType),
1255
    ("messages", ht.NoDefault, ht.NoType),
1256
    ("fail", ht.NoDefault, ht.NoType),
1257
    ]
1258
  WITH_LU = False
1259

    
1260

    
1261
def _GetOpList():
1262
  """Returns list of all defined opcodes.
1263

1264
  Does not eliminate duplicates by C{OP_ID}.
1265

1266
  """
1267
  return [v for v in globals().values()
1268
          if (isinstance(v, type) and issubclass(v, OpCode) and
1269
              hasattr(v, "OP_ID") and v is not OpCode)]
1270

    
1271

    
1272
OP_MAPPING = dict((v.OP_ID, v) for v in _GetOpList())