Statistics
| Branch: | Tag: | Revision:

root / lib / opcodes.py @ 697f49d5

History | View | Annotate | Download (36.3 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
#: Do not remember instance state changes
85
_PNoRemember = ("no_remember", False, ht.TBool)
86

    
87
#: OP_ID conversion regular expression
88
_OPID_RE = re.compile("([a-z])([A-Z])")
89

    
90

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

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

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

    
111

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

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

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

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

    
125

    
126
def _CheckDiskTemplate(template):
127
  """Ensure a given disk template is valid.
128

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

    
139

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

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

    
151

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

    
155

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

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

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

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

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

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

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

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

    
185
    attrs["__slots__"] = slots
186

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

    
189

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
311

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
407

    
408
# cluster opcodes
409

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

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

416
  """
417

    
418

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

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

425
  """
426

    
427

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

    
431

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

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

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

    
450

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

454
  Parameters: none
455

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

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

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

471
  """
472

    
473

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

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

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

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

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

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

    
494

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

    
501

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

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

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

    
516

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

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

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

    
560

    
561
class OpClusterRedistConf(OpCode):
562
  """Force a full push of the cluster configuration.
563

564
  """
565

    
566

    
567
class OpQuery(OpCode):
568
  """Query for resources/items.
569

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

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

    
582

    
583
class OpQueryFields(OpCode):
584
  """Query for available resource/item fields.
585

586
  @ivar what: Resources to query for, must be one of L{constants.QR_OP_QUERY}
587
  @ivar fields: List of fields to retrieve
588

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

    
595

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

    
604

    
605
# node opcodes
606

    
607
class OpNodeRemove(OpCode):
608
  """Remove a node.
609

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

614
  """
615
  OP_DSC_FIELD = "node_name"
616
  OP_PARAMS = [
617
    _PNodeName,
618
    ]
619

    
620

    
621
class OpNodeAdd(OpCode):
622
  """Add a node to the cluster.
623

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

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

    
661

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

    
670

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

    
678

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

    
688

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

    
698

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

    
709

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

    
727

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

    
736

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

    
746

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

    
756

    
757
# instance opcodes
758

    
759
class OpInstanceCreate(OpCode):
760
  """Create an instance.
761

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

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

    
804

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

    
815

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

    
825

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

    
835

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

    
848

    
849
class OpInstanceShutdown(OpCode):
850
  """Shutdown an instance."""
851
  OP_DSC_FIELD = "instance_name"
852
  OP_PARAMS = [
853
    _PInstanceName,
854
    _PIgnoreOfflineNodes,
855
    _PNoRemember,
856
    ("timeout", constants.DEFAULT_SHUTDOWN_TIMEOUT, ht.TPositiveInt),
857
    ]
858

    
859

    
860
class OpInstanceReboot(OpCode):
861
  """Reboot an instance."""
862
  OP_DSC_FIELD = "instance_name"
863
  OP_PARAMS = [
864
    _PInstanceName,
865
    _PShutdownTimeout,
866
    ("ignore_secondaries", False, ht.TBool),
867
    ("reboot_type", ht.NoDefault, ht.TElemOf(constants.REBOOT_TYPES)),
868
    ]
869

    
870

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

    
883

    
884
class OpInstanceFailover(OpCode):
885
  """Failover an instance."""
886
  OP_DSC_FIELD = "instance_name"
887
  OP_PARAMS = [
888
    _PInstanceName,
889
    _PShutdownTimeout,
890
    _PIgnoreConsistency,
891
    ]
892

    
893

    
894
class OpInstanceMigrate(OpCode):
895
  """Migrate an instance.
896

897
  This migrates (without shutting down an instance) to its secondary
898
  node.
899

900
  @ivar instance_name: the name of the instance
901
  @ivar mode: the migration mode (live, non-live or None for auto)
902

903
  """
904
  OP_DSC_FIELD = "instance_name"
905
  OP_PARAMS = [
906
    _PInstanceName,
907
    _PMigrationMode,
908
    _PMigrationLive,
909
    ("cleanup", False, ht.TBool),
910
    ]
911

    
912

    
913
class OpInstanceMove(OpCode):
914
  """Move an instance.
915

916
  This move (with shutting down an instance and data copying) to an
917
  arbitrary node.
918

919
  @ivar instance_name: the name of the instance
920
  @ivar target_node: the destination node
921

922
  """
923
  OP_DSC_FIELD = "instance_name"
924
  OP_PARAMS = [
925
    _PInstanceName,
926
    _PShutdownTimeout,
927
    _PIgnoreConsistency,
928
    ("target_node", ht.NoDefault, ht.TNonEmptyString),
929
    ]
930

    
931

    
932
class OpInstanceConsole(OpCode):
933
  """Connect to an instance's console."""
934
  OP_DSC_FIELD = "instance_name"
935
  OP_PARAMS = [
936
    _PInstanceName
937
    ]
938

    
939

    
940
class OpInstanceActivateDisks(OpCode):
941
  """Activate an instance's disks."""
942
  OP_DSC_FIELD = "instance_name"
943
  OP_PARAMS = [
944
    _PInstanceName,
945
    ("ignore_size", False, ht.TBool),
946
    ]
947

    
948

    
949
class OpInstanceDeactivateDisks(OpCode):
950
  """Deactivate an instance's disks."""
951
  OP_DSC_FIELD = "instance_name"
952
  OP_PARAMS = [
953
    _PInstanceName,
954
    _PForce,
955
    ]
956

    
957

    
958
class OpInstanceRecreateDisks(OpCode):
959
  """Deactivate an instance's disks."""
960
  OP_DSC_FIELD = "instance_name"
961
  OP_PARAMS = [
962
    _PInstanceName,
963
    ("disks", ht.EmptyList, ht.TListOf(ht.TPositiveInt)),
964
    ("nodes", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
965
    ]
966

    
967

    
968
class OpInstanceQuery(OpCode):
969
  """Compute the list of instances."""
970
  OP_PARAMS = [
971
    _POutputFields,
972
    ("names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
973
    ("use_locking", False, ht.TBool),
974
    ]
975

    
976

    
977
class OpInstanceQueryData(OpCode):
978
  """Compute the run-time status of instances."""
979
  OP_PARAMS = [
980
    ("instances", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
981
    ("static", False, ht.TBool),
982
    ("use_locking", False, ht.TBool),
983
    ]
984

    
985

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

    
1004

    
1005
class OpInstanceGrowDisk(OpCode):
1006
  """Grow a disk of an instance."""
1007
  OP_DSC_FIELD = "instance_name"
1008
  OP_PARAMS = [
1009
    _PInstanceName,
1010
    ("disk", ht.NoDefault, ht.TInt),
1011
    ("amount", ht.NoDefault, ht.TInt),
1012
    ("wait_for_sync", True, ht.TBool),
1013
    ]
1014

    
1015

    
1016
# Node group opcodes
1017

    
1018
class OpGroupAdd(OpCode):
1019
  """Add a node group to the cluster."""
1020
  OP_DSC_FIELD = "group_name"
1021
  OP_PARAMS = [
1022
    _PGroupName,
1023
    ("ndparams", None, ht.TMaybeDict),
1024
    ("alloc_policy", None,
1025
     ht.TOr(ht.TNone, ht.TElemOf(constants.VALID_ALLOC_POLICIES))),
1026
    ]
1027

    
1028

    
1029
class OpGroupAssignNodes(OpCode):
1030
  """Assign nodes to a node group."""
1031
  OP_DSC_FIELD = "group_name"
1032
  OP_PARAMS = [
1033
    _PGroupName,
1034
    _PForce,
1035
    ("nodes", ht.NoDefault, ht.TListOf(ht.TNonEmptyString)),
1036
    ]
1037

    
1038

    
1039
class OpGroupQuery(OpCode):
1040
  """Compute the list of node groups."""
1041
  OP_PARAMS = [
1042
    _POutputFields,
1043
    ("names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
1044
    ]
1045

    
1046

    
1047
class OpGroupSetParams(OpCode):
1048
  """Change the parameters of a node group."""
1049
  OP_DSC_FIELD = "group_name"
1050
  OP_PARAMS = [
1051
    _PGroupName,
1052
    ("ndparams", None, ht.TMaybeDict),
1053
    ("alloc_policy", None, ht.TOr(ht.TNone,
1054
                                  ht.TElemOf(constants.VALID_ALLOC_POLICIES))),
1055
    ]
1056

    
1057

    
1058
class OpGroupRemove(OpCode):
1059
  """Remove a node group from the cluster."""
1060
  OP_DSC_FIELD = "group_name"
1061
  OP_PARAMS = [
1062
    _PGroupName,
1063
    ]
1064

    
1065

    
1066
class OpGroupRename(OpCode):
1067
  """Rename a node group in the cluster."""
1068
  OP_DSC_FIELD = "old_name"
1069
  OP_PARAMS = [
1070
    ("old_name", ht.NoDefault, ht.TNonEmptyString),
1071
    ("new_name", ht.NoDefault, ht.TNonEmptyString),
1072
    ]
1073

    
1074

    
1075
# OS opcodes
1076
class OpOsDiagnose(OpCode):
1077
  """Compute the list of guest operating systems."""
1078
  OP_PARAMS = [
1079
    _POutputFields,
1080
    ("names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
1081
    ]
1082

    
1083

    
1084
# Exports opcodes
1085
class OpBackupQuery(OpCode):
1086
  """Compute the list of exported images."""
1087
  OP_PARAMS = [
1088
    ("nodes", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
1089
    ("use_locking", False, ht.TBool),
1090
    ]
1091

    
1092

    
1093
class OpBackupPrepare(OpCode):
1094
  """Prepares an instance export.
1095

1096
  @ivar instance_name: Instance name
1097
  @ivar mode: Export mode (one of L{constants.EXPORT_MODES})
1098

1099
  """
1100
  OP_DSC_FIELD = "instance_name"
1101
  OP_PARAMS = [
1102
    _PInstanceName,
1103
    ("mode", ht.NoDefault, ht.TElemOf(constants.EXPORT_MODES)),
1104
    ]
1105

    
1106

    
1107
class OpBackupExport(OpCode):
1108
  """Export an instance.
1109

1110
  For local exports, the export destination is the node name. For remote
1111
  exports, the export destination is a list of tuples, each consisting of
1112
  hostname/IP address, port, HMAC and HMAC salt. The HMAC is calculated using
1113
  the cluster domain secret over the value "${index}:${hostname}:${port}". The
1114
  destination X509 CA must be a signed certificate.
1115

1116
  @ivar mode: Export mode (one of L{constants.EXPORT_MODES})
1117
  @ivar target_node: Export destination
1118
  @ivar x509_key_name: X509 key to use (remote export only)
1119
  @ivar destination_x509_ca: Destination X509 CA in PEM format (remote export
1120
                             only)
1121

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

    
1138

    
1139
class OpBackupRemove(OpCode):
1140
  """Remove an instance's export."""
1141
  OP_DSC_FIELD = "instance_name"
1142
  OP_PARAMS = [
1143
    _PInstanceName,
1144
    ]
1145

    
1146

    
1147
# Tags opcodes
1148
class OpTagsGet(OpCode):
1149
  """Returns the tags of the given object."""
1150
  OP_DSC_FIELD = "name"
1151
  OP_PARAMS = [
1152
    _PTagKind,
1153
    # Name is only meaningful for nodes and instances
1154
    ("name", ht.NoDefault, ht.TMaybeString),
1155
    ]
1156

    
1157

    
1158
class OpTagsSearch(OpCode):
1159
  """Searches the tags in the cluster for a given pattern."""
1160
  OP_DSC_FIELD = "pattern"
1161
  OP_PARAMS = [
1162
    ("pattern", ht.NoDefault, ht.TNonEmptyString),
1163
    ]
1164

    
1165

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

    
1175

    
1176
class OpTagsDel(OpCode):
1177
  """Remove a list of tags from a given object."""
1178
  OP_PARAMS = [
1179
    _PTagKind,
1180
    _PTags,
1181
    # Name is only meaningful for nodes and instances
1182
    ("name", ht.NoDefault, ht.TMaybeString),
1183
    ]
1184

    
1185
# Test opcodes
1186
class OpTestDelay(OpCode):
1187
  """Sleeps for a configured amount of time.
1188

1189
  This is used just for debugging and testing.
1190

1191
  Parameters:
1192
    - duration: the time to sleep
1193
    - on_master: if true, sleep on the master
1194
    - on_nodes: list of nodes in which to sleep
1195

1196
  If the on_master parameter is true, it will execute a sleep on the
1197
  master (before any node sleep).
1198

1199
  If the on_nodes list is not empty, it will sleep on those nodes
1200
  (after the sleep on the master, if that is enabled).
1201

1202
  As an additional feature, the case of duration < 0 will be reported
1203
  as an execution error, so this opcode can be used as a failure
1204
  generator. The case of duration == 0 will not be treated specially.
1205

1206
  """
1207
  OP_DSC_FIELD = "duration"
1208
  OP_PARAMS = [
1209
    ("duration", ht.NoDefault, ht.TNumber),
1210
    ("on_master", True, ht.TBool),
1211
    ("on_nodes", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
1212
    ("repeat", 0, ht.TPositiveInt)
1213
    ]
1214

    
1215

    
1216
class OpTestAllocator(OpCode):
1217
  """Allocator framework testing.
1218

1219
  This opcode has two modes:
1220
    - gather and return allocator input for a given mode (allocate new
1221
      or replace secondary) and a given instance definition (direction
1222
      'in')
1223
    - run a selected allocator for a given operation (as above) and
1224
      return the allocator output (direction 'out')
1225

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

    
1247

    
1248
class OpTestJqueue(OpCode):
1249
  """Utility opcode to test some aspects of the job queue.
1250

1251
  """
1252
  OP_PARAMS = [
1253
    ("notify_waitlock", False, ht.TBool),
1254
    ("notify_exec", False, ht.TBool),
1255
    ("log_messages", ht.EmptyList, ht.TListOf(ht.TString)),
1256
    ("fail", False, ht.TBool),
1257
    ]
1258

    
1259

    
1260
class OpTestDummy(OpCode):
1261
  """Utility opcode used by unittests.
1262

1263
  """
1264
  OP_PARAMS = [
1265
    ("result", ht.NoDefault, ht.NoType),
1266
    ("messages", ht.NoDefault, ht.NoType),
1267
    ("fail", ht.NoDefault, ht.NoType),
1268
    ]
1269
  WITH_LU = False
1270

    
1271

    
1272
def _GetOpList():
1273
  """Returns list of all defined opcodes.
1274

1275
  Does not eliminate duplicates by C{OP_ID}.
1276

1277
  """
1278
  return [v for v in globals().values()
1279
          if (isinstance(v, type) and issubclass(v, OpCode) and
1280
              hasattr(v, "OP_ID") and v is not OpCode)]
1281

    
1282

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