Statistics
| Branch: | Tag: | Revision:

root / lib / opcodes.py @ 4b9e8c93

History | View | Annotate | Download (36.1 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

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

    
43

    
44
# Common opcode attributes
45

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

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

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

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

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

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

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

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

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

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

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

    
81
#: Ignore consistency
82
_PIgnoreConsistency = ("ignore_consistency", False, ht.TBool)
83

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

    
87

    
88
def _NameToId(name):
89
  """Convert an opcode class name to an OP_ID.
90

91
  @type name: string
92
  @param name: the class name, as OpXxxYyy
93
  @rtype: string
94
  @return: the name in the OP_XXXX_YYYY format
95

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

    
108

    
109
def RequireFileStorage():
110
  """Checks that file storage is enabled.
111

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

115
  @raise errors.OpPrereqError: when file storage is disabled
116

117
  """
118
  if not constants.ENABLE_FILE_STORAGE:
119
    raise errors.OpPrereqError("File storage disabled at configure time",
120
                               errors.ECODE_INVAL)
121

    
122

    
123
def _CheckDiskTemplate(template):
124
  """Ensure a given disk template is valid.
125

126
  """
127
  if template not in constants.DISK_TEMPLATES:
128
    # Using str.join directly to avoid importing utils for CommaJoin
129
    msg = ("Invalid disk template name '%s', valid templates are: %s" %
130
           (template, ", ".join(constants.DISK_TEMPLATES)))
131
    raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
132
  if template == constants.DT_FILE:
133
    RequireFileStorage()
134
  return True
135

    
136

    
137
def _CheckStorageType(storage_type):
138
  """Ensure a given storage type is valid.
139

140
  """
141
  if storage_type not in constants.VALID_STORAGE_TYPES:
142
    raise errors.OpPrereqError("Unknown storage type: %s" % storage_type,
143
                               errors.ECODE_INVAL)
144
  if storage_type == constants.ST_FILE:
145
    RequireFileStorage()
146
  return True
147

    
148

    
149
#: Storage type parameter
150
_PStorageType = ("storage_type", ht.NoDefault, _CheckStorageType)
151

    
152

    
153
class _AutoOpParamSlots(type):
154
  """Meta class for opcode definitions.
155

156
  """
157
  def __new__(mcs, name, bases, attrs):
158
    """Called when a class should be created.
159

160
    @param mcs: The meta class
161
    @param name: Name of created class
162
    @param bases: Base classes
163
    @type attrs: dict
164
    @param attrs: Class attributes
165

166
    """
167
    assert "__slots__" not in attrs, \
168
      "Class '%s' defines __slots__ when it should use OP_PARAMS" % name
169
    assert "OP_ID" not in attrs, "Class '%s' defining OP_ID" % name
170

    
171
    attrs["OP_ID"] = _NameToId(name)
172

    
173
    # Always set OP_PARAMS to avoid duplicates in BaseOpCode.GetAllParams
174
    params = attrs.setdefault("OP_PARAMS", [])
175

    
176
    # Use parameter names as slots
177
    slots = [pname for (pname, _, _) in params]
178

    
179
    assert "OP_DSC_FIELD" not in attrs or attrs["OP_DSC_FIELD"] in slots, \
180
      "Class '%s' uses unknown field in OP_DSC_FIELD" % name
181

    
182
    attrs["__slots__"] = slots
183

    
184
    return type.__new__(mcs, name, bases, attrs)
185

    
186

    
187
class BaseOpCode(object):
188
  """A simple serializable object.
189

190
  This object serves as a parent class for OpCode without any custom
191
  field handling.
192

193
  """
194
  # pylint: disable-msg=E1101
195
  # as OP_ID is dynamically defined
196
  __metaclass__ = _AutoOpParamSlots
197

    
198
  def __init__(self, **kwargs):
199
    """Constructor for BaseOpCode.
200

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

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

    
214
  def __getstate__(self):
215
    """Generic serializer.
216

217
    This method just returns the contents of the instance as a
218
    dictionary.
219

220
    @rtype:  C{dict}
221
    @return: the instance attributes and their values
222

223
    """
224
    state = {}
225
    for name in self._all_slots():
226
      if hasattr(self, name):
227
        state[name] = getattr(self, name)
228
    return state
229

    
230
  def __setstate__(self, state):
231
    """Generic unserializer.
232

233
    This method just restores from the serialized state the attributes
234
    of the current instance.
235

236
    @param state: the serialized opcode data
237
    @type state:  C{dict}
238

239
    """
240
    if not isinstance(state, dict):
241
      raise ValueError("Invalid data to __setstate__: expected dict, got %s" %
242
                       type(state))
243

    
244
    for name in self._all_slots():
245
      if name not in state and hasattr(self, name):
246
        delattr(self, name)
247

    
248
    for name in state:
249
      setattr(self, name, state[name])
250

    
251
  @classmethod
252
  def _all_slots(cls):
253
    """Compute the list of all declared slots for a class.
254

255
    """
256
    slots = []
257
    for parent in cls.__mro__:
258
      slots.extend(getattr(parent, "__slots__", []))
259
    return slots
260

    
261
  @classmethod
262
  def GetAllParams(cls):
263
    """Compute list of all parameters for an opcode.
264

265
    """
266
    slots = []
267
    for parent in cls.__mro__:
268
      slots.extend(getattr(parent, "OP_PARAMS", []))
269
    return slots
270

    
271
  def Validate(self, set_defaults):
272
    """Validate opcode parameters, optionally setting default values.
273

274
    @type set_defaults: bool
275
    @param set_defaults: Whether to set default values
276
    @raise errors.OpPrereqError: When a parameter value doesn't match
277
                                 requirements
278

279
    """
280
    for (attr_name, default, test) in self.GetAllParams():
281
      assert test == ht.NoType or callable(test)
282

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

    
295
      if test == ht.NoType:
296
        # no tests here
297
        continue
298

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

    
308

    
309
class OpCode(BaseOpCode):
310
  """Abstract OpCode.
311

312
  This is the root of the actual OpCode hierarchy. All clases derived
313
  from this class should override OP_ID.
314

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

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

    
339
  def __getstate__(self):
340
    """Specialized getstate for opcodes.
341

342
    This method adds to the state dictionary the OP_ID of the class,
343
    so that on unload we can identify the correct class for
344
    instantiating the opcode.
345

346
    @rtype:   C{dict}
347
    @return:  the state as a dictionary
348

349
    """
350
    data = BaseOpCode.__getstate__(self)
351
    data["OP_ID"] = self.OP_ID
352
    return data
353

    
354
  @classmethod
355
  def LoadOpCode(cls, data):
356
    """Generic load opcode method.
357

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

362
    @type data:  C{dict}
363
    @param data: the serialized opcode
364

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

    
383
  def Summary(self):
384
    """Generates a summary description of this opcode.
385

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

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

    
404

    
405
# cluster opcodes
406

    
407
class OpClusterPostInit(OpCode):
408
  """Post cluster initialization.
409

410
  This opcode does not touch the cluster at all. Its purpose is to run hooks
411
  after the cluster has been initialized.
412

413
  """
414

    
415

    
416
class OpClusterDestroy(OpCode):
417
  """Destroy the cluster.
418

419
  This opcode has no other parameters. All the state is irreversibly
420
  lost after the execution of this opcode.
421

422
  """
423

    
424

    
425
class OpClusterQuery(OpCode):
426
  """Query cluster information."""
427

    
428

    
429
class OpClusterVerify(OpCode):
430
  """Verify the cluster state.
431

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

438
  """
439
  OP_PARAMS = [
440
    ("skip_checks", ht.EmptyList,
441
     ht.TListOf(ht.TElemOf(constants.VERIFY_OPTIONAL_CHECKS))),
442
    ("verbose", False, ht.TBool),
443
    ("error_codes", False, ht.TBool),
444
    ("debug_simulate_errors", False, ht.TBool),
445
    ]
446

    
447

    
448
class OpClusterVerifyDisks(OpCode):
449
  """Verify the cluster disks.
450

451
  Parameters: none
452

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

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

465
  Note that only instances that are drbd-based are taken into
466
  consideration. This might need to be revisited in the future.
467

468
  """
469

    
470

    
471
class OpClusterRepairDiskSizes(OpCode):
472
  """Verify the disk sizes of the instances and fixes configuration
473
  mimatches.
474

475
  Parameters: optional instances list, in case we want to restrict the
476
  checks to only a subset of the instances.
477

478
  Result: a list of tuples, (instance, disk, new-size) for changed
479
  configurations.
480

481
  In normal operation, the list should be empty.
482

483
  @type instances: list
484
  @ivar instances: the list of instances to check, or empty for all instances
485

486
  """
487
  OP_PARAMS = [
488
    ("instances", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
489
    ]
490

    
491

    
492
class OpClusterConfigQuery(OpCode):
493
  """Query cluster configuration values."""
494
  OP_PARAMS = [
495
    _POutputFields
496
    ]
497

    
498

    
499
class OpClusterRename(OpCode):
500
  """Rename the cluster.
501

502
  @type name: C{str}
503
  @ivar name: The new name of the cluster. The name and/or the master IP
504
              address will be changed to match the new name and its IP
505
              address.
506

507
  """
508
  OP_DSC_FIELD = "name"
509
  OP_PARAMS = [
510
    ("name", ht.NoDefault, ht.TNonEmptyString),
511
    ]
512

    
513

    
514
class OpClusterSetParams(OpCode):
515
  """Change the parameters of the cluster.
516

517
  @type vg_name: C{str} or C{None}
518
  @ivar vg_name: The new volume group name or None to disable LVM usage.
519

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

    
557

    
558
class OpClusterRedistConf(OpCode):
559
  """Force a full push of the cluster configuration.
560

561
  """
562

    
563

    
564
class OpQuery(OpCode):
565
  """Query for resources/items.
566

567
  @ivar what: Resources to query for, must be one of L{constants.QR_OP_QUERY}
568
  @ivar fields: List of fields to retrieve
569
  @ivar filter: Query filter
570

571
  """
572
  OP_PARAMS = [
573
    ("what", ht.NoDefault, ht.TElemOf(constants.QR_OP_QUERY)),
574
    ("fields", ht.NoDefault, ht.TListOf(ht.TNonEmptyString)),
575
    ("filter", None, ht.TOr(ht.TNone,
576
                            ht.TListOf(ht.TOr(ht.TNonEmptyString, ht.TList)))),
577
    ]
578

    
579

    
580
class OpQueryFields(OpCode):
581
  """Query for available resource/item fields.
582

583
  @ivar what: Resources to query for, must be one of L{constants.QR_OP_QUERY}
584
  @ivar fields: List of fields to retrieve
585

586
  """
587
  OP_PARAMS = [
588
    ("what", ht.NoDefault, ht.TElemOf(constants.QR_OP_QUERY)),
589
    ("fields", None, ht.TOr(ht.TNone, ht.TListOf(ht.TNonEmptyString))),
590
    ]
591

    
592

    
593
class OpOobCommand(OpCode):
594
  """Interact with OOB."""
595
  OP_PARAMS = [
596
    ("node_names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
597
    ("command", None, ht.TElemOf(constants.OOB_COMMANDS)),
598
    ("timeout", constants.OOB_TIMEOUT, ht.TInt),
599
    ]
600

    
601

    
602
# node opcodes
603

    
604
class OpNodeRemove(OpCode):
605
  """Remove a node.
606

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

611
  """
612
  OP_DSC_FIELD = "node_name"
613
  OP_PARAMS = [
614
    _PNodeName,
615
    ]
616

    
617

    
618
class OpNodeAdd(OpCode):
619
  """Add a node to the cluster.
620

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

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

    
658

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

    
667

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

    
675

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

    
685

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

    
695

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

    
706

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

    
724

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

    
733

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

    
743

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

    
753

    
754
# instance opcodes
755

    
756
class OpInstanceCreate(OpCode):
757
  """Create an instance.
758

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

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

    
801

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

    
812

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

    
822

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

    
832

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

    
844

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

    
854

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

    
865

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

    
878

    
879
class OpInstanceFailover(OpCode):
880
  """Failover an instance."""
881
  OP_DSC_FIELD = "instance_name"
882
  OP_PARAMS = [
883
    _PInstanceName,
884
    _PShutdownTimeout,
885
    _PIgnoreConsistency,
886
    ]
887

    
888

    
889
class OpInstanceMigrate(OpCode):
890
  """Migrate an instance.
891

892
  This migrates (without shutting down an instance) to its secondary
893
  node.
894

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

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

    
907

    
908
class OpInstanceMove(OpCode):
909
  """Move an instance.
910

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

914
  @ivar instance_name: the name of the instance
915
  @ivar target_node: the destination node
916

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

    
925

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

    
933

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

    
942

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

    
951

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

    
961

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

    
970

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

    
979

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

    
998

    
999
class OpInstanceGrowDisk(OpCode):
1000
  """Grow a disk of an instance."""
1001
  OP_DSC_FIELD = "instance_name"
1002
  OP_PARAMS = [
1003
    _PInstanceName,
1004
    ("disk", ht.NoDefault, ht.TInt),
1005
    ("amount", ht.NoDefault, ht.TInt),
1006
    ("wait_for_sync", True, ht.TBool),
1007
    ]
1008

    
1009

    
1010
# Node group opcodes
1011

    
1012
class OpGroupAdd(OpCode):
1013
  """Add a node group to the cluster."""
1014
  OP_DSC_FIELD = "group_name"
1015
  OP_PARAMS = [
1016
    _PGroupName,
1017
    ("ndparams", None, ht.TMaybeDict),
1018
    ("alloc_policy", None,
1019
     ht.TOr(ht.TNone, ht.TElemOf(constants.VALID_ALLOC_POLICIES))),
1020
    ]
1021

    
1022

    
1023
class OpGroupAssignNodes(OpCode):
1024
  """Assign nodes to a node group."""
1025
  OP_DSC_FIELD = "group_name"
1026
  OP_PARAMS = [
1027
    _PGroupName,
1028
    _PForce,
1029
    ("nodes", ht.NoDefault, ht.TListOf(ht.TNonEmptyString)),
1030
    ]
1031

    
1032

    
1033
class OpGroupQuery(OpCode):
1034
  """Compute the list of node groups."""
1035
  OP_PARAMS = [
1036
    _POutputFields,
1037
    ("names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
1038
    ]
1039

    
1040

    
1041
class OpGroupSetParams(OpCode):
1042
  """Change the parameters of a node group."""
1043
  OP_DSC_FIELD = "group_name"
1044
  OP_PARAMS = [
1045
    _PGroupName,
1046
    ("ndparams", None, ht.TMaybeDict),
1047
    ("alloc_policy", None, ht.TOr(ht.TNone,
1048
                                  ht.TElemOf(constants.VALID_ALLOC_POLICIES))),
1049
    ]
1050

    
1051

    
1052
class OpGroupRemove(OpCode):
1053
  """Remove a node group from the cluster."""
1054
  OP_DSC_FIELD = "group_name"
1055
  OP_PARAMS = [
1056
    _PGroupName,
1057
    ]
1058

    
1059

    
1060
class OpGroupRename(OpCode):
1061
  """Rename a node group in the cluster."""
1062
  OP_DSC_FIELD = "old_name"
1063
  OP_PARAMS = [
1064
    ("old_name", ht.NoDefault, ht.TNonEmptyString),
1065
    ("new_name", ht.NoDefault, ht.TNonEmptyString),
1066
    ]
1067

    
1068

    
1069
# OS opcodes
1070
class OpOsDiagnose(OpCode):
1071
  """Compute the list of guest operating systems."""
1072
  OP_PARAMS = [
1073
    _POutputFields,
1074
    ("names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
1075
    ]
1076

    
1077

    
1078
# Exports opcodes
1079
class OpBackupQuery(OpCode):
1080
  """Compute the list of exported images."""
1081
  OP_PARAMS = [
1082
    ("nodes", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
1083
    ("use_locking", False, ht.TBool),
1084
    ]
1085

    
1086

    
1087
class OpBackupPrepare(OpCode):
1088
  """Prepares an instance export.
1089

1090
  @ivar instance_name: Instance name
1091
  @ivar mode: Export mode (one of L{constants.EXPORT_MODES})
1092

1093
  """
1094
  OP_DSC_FIELD = "instance_name"
1095
  OP_PARAMS = [
1096
    _PInstanceName,
1097
    ("mode", ht.NoDefault, ht.TElemOf(constants.EXPORT_MODES)),
1098
    ]
1099

    
1100

    
1101
class OpBackupExport(OpCode):
1102
  """Export an instance.
1103

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

1110
  @ivar mode: Export mode (one of L{constants.EXPORT_MODES})
1111
  @ivar target_node: Export destination
1112
  @ivar x509_key_name: X509 key to use (remote export only)
1113
  @ivar destination_x509_ca: Destination X509 CA in PEM format (remote export
1114
                             only)
1115

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

    
1132

    
1133
class OpBackupRemove(OpCode):
1134
  """Remove an instance's export."""
1135
  OP_DSC_FIELD = "instance_name"
1136
  OP_PARAMS = [
1137
    _PInstanceName,
1138
    ]
1139

    
1140

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

    
1151

    
1152
class OpTagsSearch(OpCode):
1153
  """Searches the tags in the cluster for a given pattern."""
1154
  OP_DSC_FIELD = "pattern"
1155
  OP_PARAMS = [
1156
    ("pattern", ht.NoDefault, ht.TNonEmptyString),
1157
    ]
1158

    
1159

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

    
1169

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

    
1179
# Test opcodes
1180
class OpTestDelay(OpCode):
1181
  """Sleeps for a configured amount of time.
1182

1183
  This is used just for debugging and testing.
1184

1185
  Parameters:
1186
    - duration: the time to sleep
1187
    - on_master: if true, sleep on the master
1188
    - on_nodes: list of nodes in which to sleep
1189

1190
  If the on_master parameter is true, it will execute a sleep on the
1191
  master (before any node sleep).
1192

1193
  If the on_nodes list is not empty, it will sleep on those nodes
1194
  (after the sleep on the master, if that is enabled).
1195

1196
  As an additional feature, the case of duration < 0 will be reported
1197
  as an execution error, so this opcode can be used as a failure
1198
  generator. The case of duration == 0 will not be treated specially.
1199

1200
  """
1201
  OP_DSC_FIELD = "duration"
1202
  OP_PARAMS = [
1203
    ("duration", ht.NoDefault, ht.TFloat),
1204
    ("on_master", True, ht.TBool),
1205
    ("on_nodes", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
1206
    ("repeat", 0, ht.TPositiveInt)
1207
    ]
1208

    
1209

    
1210
class OpTestAllocator(OpCode):
1211
  """Allocator framework testing.
1212

1213
  This opcode has two modes:
1214
    - gather and return allocator input for a given mode (allocate new
1215
      or replace secondary) and a given instance definition (direction
1216
      'in')
1217
    - run a selected allocator for a given operation (as above) and
1218
      return the allocator output (direction 'out')
1219

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

    
1241

    
1242
class OpTestJqueue(OpCode):
1243
  """Utility opcode to test some aspects of the job queue.
1244

1245
  """
1246
  OP_PARAMS = [
1247
    ("notify_waitlock", False, ht.TBool),
1248
    ("notify_exec", False, ht.TBool),
1249
    ("log_messages", ht.EmptyList, ht.TListOf(ht.TString)),
1250
    ("fail", False, ht.TBool),
1251
    ]
1252

    
1253

    
1254
class OpTestDummy(OpCode):
1255
  """Utility opcode used by unittests.
1256

1257
  """
1258
  OP_PARAMS = [
1259
    ("result", ht.NoDefault, ht.NoType),
1260
    ("messages", ht.NoDefault, ht.NoType),
1261
    ("fail", ht.NoDefault, ht.NoType),
1262
    ]
1263
  WITH_LU = False
1264

    
1265

    
1266
def _GetOpList():
1267
  """Returns list of all defined opcodes.
1268

1269
  Does not eliminate duplicates by C{OP_ID}.
1270

1271
  """
1272
  return [v for v in globals().values()
1273
          if (isinstance(v, type) and issubclass(v, OpCode) and
1274
              hasattr(v, "OP_ID") and v is not OpCode)]
1275

    
1276

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