Statistics
| Branch: | Tag: | Revision:

root / lib / opcodes.py @ 7ffdb1d8

History | View | Annotate | Download (37.5 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
#: a required network name
85
_PNetworkName = ("network_name", ht.NoDefault, ht.TNonEmptyString)
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 RequireSharedFileStorage():
127
  """Checks that shared file storage is enabled.
128

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

132
  @raise errors.OpPrereqError: when shared file storage is disabled
133

134
  """
135
  if not constants.ENABLE_SHARED_FILE_STORAGE:
136
    raise errors.OpPrereqError("Shared file storage disabled at"
137
                               " configure time", errors.ECODE_INVAL)
138

    
139

    
140
def _CheckDiskTemplate(template):
141
  """Ensure a given disk template is valid.
142

143
  """
144
  if template not in constants.DISK_TEMPLATES:
145
    # Using str.join directly to avoid importing utils for CommaJoin
146
    msg = ("Invalid disk template name '%s', valid templates are: %s" %
147
           (template, ", ".join(constants.DISK_TEMPLATES)))
148
    raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
149
  if template == constants.DT_FILE:
150
    RequireFileStorage()
151
  elif template == constants.DT_SHARED_FILE:
152
    RequireSharedFileStorage()
153
  return True
154

    
155

    
156
def _CheckStorageType(storage_type):
157
  """Ensure a given storage type is valid.
158

159
  """
160
  if storage_type not in constants.VALID_STORAGE_TYPES:
161
    raise errors.OpPrereqError("Unknown storage type: %s" % storage_type,
162
                               errors.ECODE_INVAL)
163
  if storage_type == constants.ST_FILE:
164
    RequireFileStorage()
165
  return True
166

    
167

    
168
#: Storage type parameter
169
_PStorageType = ("storage_type", ht.NoDefault, _CheckStorageType)
170

    
171

    
172
class _AutoOpParamSlots(type):
173
  """Meta class for opcode definitions.
174

175
  """
176
  def __new__(mcs, name, bases, attrs):
177
    """Called when a class should be created.
178

179
    @param mcs: The meta class
180
    @param name: Name of created class
181
    @param bases: Base classes
182
    @type attrs: dict
183
    @param attrs: Class attributes
184

185
    """
186
    assert "__slots__" not in attrs, \
187
      "Class '%s' defines __slots__ when it should use OP_PARAMS" % name
188
    assert "OP_ID" not in attrs, "Class '%s' defining OP_ID" % name
189

    
190
    attrs["OP_ID"] = _NameToId(name)
191

    
192
    # Always set OP_PARAMS to avoid duplicates in BaseOpCode.GetAllParams
193
    params = attrs.setdefault("OP_PARAMS", [])
194

    
195
    # Use parameter names as slots
196
    slots = [pname for (pname, _, _) in params]
197

    
198
    assert "OP_DSC_FIELD" not in attrs or attrs["OP_DSC_FIELD"] in slots, \
199
      "Class '%s' uses unknown field in OP_DSC_FIELD" % name
200

    
201
    attrs["__slots__"] = slots
202

    
203
    return type.__new__(mcs, name, bases, attrs)
204

    
205

    
206
class BaseOpCode(object):
207
  """A simple serializable object.
208

209
  This object serves as a parent class for OpCode without any custom
210
  field handling.
211

212
  """
213
  # pylint: disable-msg=E1101
214
  # as OP_ID is dynamically defined
215
  __metaclass__ = _AutoOpParamSlots
216

    
217
  def __init__(self, **kwargs):
218
    """Constructor for BaseOpCode.
219

220
    The constructor takes only keyword arguments and will set
221
    attributes on this object based on the passed arguments. As such,
222
    it means that you should not pass arguments which are not in the
223
    __slots__ attribute for this class.
224

225
    """
226
    slots = self._all_slots()
227
    for key in kwargs:
228
      if key not in slots:
229
        raise TypeError("Object %s doesn't support the parameter '%s'" %
230
                        (self.__class__.__name__, key))
231
      setattr(self, key, kwargs[key])
232

    
233
  def __getstate__(self):
234
    """Generic serializer.
235

236
    This method just returns the contents of the instance as a
237
    dictionary.
238

239
    @rtype:  C{dict}
240
    @return: the instance attributes and their values
241

242
    """
243
    state = {}
244
    for name in self._all_slots():
245
      if hasattr(self, name):
246
        state[name] = getattr(self, name)
247
    return state
248

    
249
  def __setstate__(self, state):
250
    """Generic unserializer.
251

252
    This method just restores from the serialized state the attributes
253
    of the current instance.
254

255
    @param state: the serialized opcode data
256
    @type state:  C{dict}
257

258
    """
259
    if not isinstance(state, dict):
260
      raise ValueError("Invalid data to __setstate__: expected dict, got %s" %
261
                       type(state))
262

    
263
    for name in self._all_slots():
264
      if name not in state and hasattr(self, name):
265
        delattr(self, name)
266

    
267
    for name in state:
268
      setattr(self, name, state[name])
269

    
270
  @classmethod
271
  def _all_slots(cls):
272
    """Compute the list of all declared slots for a class.
273

274
    """
275
    slots = []
276
    for parent in cls.__mro__:
277
      slots.extend(getattr(parent, "__slots__", []))
278
    return slots
279

    
280
  @classmethod
281
  def GetAllParams(cls):
282
    """Compute list of all parameters for an opcode.
283

284
    """
285
    slots = []
286
    for parent in cls.__mro__:
287
      slots.extend(getattr(parent, "OP_PARAMS", []))
288
    return slots
289

    
290
  def Validate(self, set_defaults):
291
    """Validate opcode parameters, optionally setting default values.
292

293
    @type set_defaults: bool
294
    @param set_defaults: Whether to set default values
295
    @raise errors.OpPrereqError: When a parameter value doesn't match
296
                                 requirements
297

298
    """
299
    for (attr_name, default, test) in self.GetAllParams():
300
      assert test == ht.NoType or callable(test)
301

    
302
      if not hasattr(self, attr_name):
303
        if default == ht.NoDefault:
304
          raise errors.OpPrereqError("Required parameter '%s.%s' missing" %
305
                                     (self.OP_ID, attr_name),
306
                                     errors.ECODE_INVAL)
307
        elif set_defaults:
308
          if callable(default):
309
            dval = default()
310
          else:
311
            dval = default
312
          setattr(self, attr_name, dval)
313

    
314
      if test == ht.NoType:
315
        # no tests here
316
        continue
317

    
318
      if set_defaults or hasattr(self, attr_name):
319
        attr_val = getattr(self, attr_name)
320
        if not test(attr_val):
321
          logging.error("OpCode %s, parameter %s, has invalid type %s/value %s",
322
                        self.OP_ID, attr_name, type(attr_val), attr_val)
323
          raise errors.OpPrereqError("Parameter '%s.%s' fails validation" %
324
                                     (self.OP_ID, attr_name),
325
                                     errors.ECODE_INVAL)
326

    
327

    
328
class OpCode(BaseOpCode):
329
  """Abstract OpCode.
330

331
  This is the root of the actual OpCode hierarchy. All clases derived
332
  from this class should override OP_ID.
333

334
  @cvar OP_ID: The ID of this opcode. This should be unique amongst all
335
               children of this class.
336
  @cvar OP_DSC_FIELD: The name of a field whose value will be included in the
337
                      string returned by Summary(); see the docstring of that
338
                      method for details).
339
  @cvar OP_PARAMS: List of opcode attributes, the default values they should
340
                   get if not already defined, and types they must match.
341
  @cvar WITH_LU: Boolean that specifies whether this should be included in
342
      mcpu's dispatch table
343
  @ivar dry_run: Whether the LU should be run in dry-run mode, i.e. just
344
                 the check steps
345
  @ivar priority: Opcode priority for queue
346

347
  """
348
  # pylint: disable-msg=E1101
349
  # as OP_ID is dynamically defined
350
  WITH_LU = True
351
  OP_PARAMS = [
352
    ("dry_run", None, ht.TMaybeBool),
353
    ("debug_level", None, ht.TOr(ht.TNone, ht.TPositiveInt)),
354
    ("priority", constants.OP_PRIO_DEFAULT,
355
     ht.TElemOf(constants.OP_PRIO_SUBMIT_VALID)),
356
    ]
357

    
358
  def __getstate__(self):
359
    """Specialized getstate for opcodes.
360

361
    This method adds to the state dictionary the OP_ID of the class,
362
    so that on unload we can identify the correct class for
363
    instantiating the opcode.
364

365
    @rtype:   C{dict}
366
    @return:  the state as a dictionary
367

368
    """
369
    data = BaseOpCode.__getstate__(self)
370
    data["OP_ID"] = self.OP_ID
371
    return data
372

    
373
  @classmethod
374
  def LoadOpCode(cls, data):
375
    """Generic load opcode method.
376

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

381
    @type data:  C{dict}
382
    @param data: the serialized opcode
383

384
    """
385
    if not isinstance(data, dict):
386
      raise ValueError("Invalid data to LoadOpCode (%s)" % type(data))
387
    if "OP_ID" not in data:
388
      raise ValueError("Invalid data to LoadOpcode, missing OP_ID")
389
    op_id = data["OP_ID"]
390
    op_class = None
391
    if op_id in OP_MAPPING:
392
      op_class = OP_MAPPING[op_id]
393
    else:
394
      raise ValueError("Invalid data to LoadOpCode: OP_ID %s unsupported" %
395
                       op_id)
396
    op = op_class()
397
    new_data = data.copy()
398
    del new_data["OP_ID"]
399
    op.__setstate__(new_data)
400
    return op
401

    
402
  def Summary(self):
403
    """Generates a summary description of this opcode.
404

405
    The summary is the value of the OP_ID attribute (without the "OP_"
406
    prefix), plus the value of the OP_DSC_FIELD attribute, if one was
407
    defined; this field should allow to easily identify the operation
408
    (for an instance creation job, e.g., it would be the instance
409
    name).
410

411
    """
412
    assert self.OP_ID is not None and len(self.OP_ID) > 3
413
    # all OP_ID start with OP_, we remove that
414
    txt = self.OP_ID[3:]
415
    field_name = getattr(self, "OP_DSC_FIELD", None)
416
    if field_name:
417
      field_value = getattr(self, field_name, None)
418
      if isinstance(field_value, (list, tuple)):
419
        field_value = ",".join(str(i) for i in field_value)
420
      txt = "%s(%s)" % (txt, field_value)
421
    return txt
422

    
423

    
424
# cluster opcodes
425

    
426
class OpClusterPostInit(OpCode):
427
  """Post cluster initialization.
428

429
  This opcode does not touch the cluster at all. Its purpose is to run hooks
430
  after the cluster has been initialized.
431

432
  """
433

    
434

    
435
class OpClusterDestroy(OpCode):
436
  """Destroy the cluster.
437

438
  This opcode has no other parameters. All the state is irreversibly
439
  lost after the execution of this opcode.
440

441
  """
442

    
443

    
444
class OpClusterQuery(OpCode):
445
  """Query cluster information."""
446

    
447

    
448
class OpClusterVerify(OpCode):
449
  """Verify the cluster state.
450

451
  @type skip_checks: C{list}
452
  @ivar skip_checks: steps to be skipped from the verify process; this
453
                     needs to be a subset of
454
                     L{constants.VERIFY_OPTIONAL_CHECKS}; currently
455
                     only L{constants.VERIFY_NPLUSONE_MEM} can be passed
456

457
  """
458
  OP_PARAMS = [
459
    ("skip_checks", ht.EmptyList,
460
     ht.TListOf(ht.TElemOf(constants.VERIFY_OPTIONAL_CHECKS))),
461
    ("verbose", False, ht.TBool),
462
    ("error_codes", False, ht.TBool),
463
    ("debug_simulate_errors", False, ht.TBool),
464
    ]
465

    
466

    
467
class OpClusterVerifyDisks(OpCode):
468
  """Verify the cluster disks.
469

470
  Parameters: none
471

472
  Result: a tuple of four elements:
473
    - list of node names with bad data returned (unreachable, etc.)
474
    - dict of node names with broken volume groups (values: error msg)
475
    - list of instances with degraded disks (that should be activated)
476
    - dict of instances with missing logical volumes (values: (node, vol)
477
      pairs with details about the missing volumes)
478

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

484
  Note that only instances that are drbd-based are taken into
485
  consideration. This might need to be revisited in the future.
486

487
  """
488

    
489

    
490
class OpClusterRepairDiskSizes(OpCode):
491
  """Verify the disk sizes of the instances and fixes configuration
492
  mimatches.
493

494
  Parameters: optional instances list, in case we want to restrict the
495
  checks to only a subset of the instances.
496

497
  Result: a list of tuples, (instance, disk, new-size) for changed
498
  configurations.
499

500
  In normal operation, the list should be empty.
501

502
  @type instances: list
503
  @ivar instances: the list of instances to check, or empty for all instances
504

505
  """
506
  OP_PARAMS = [
507
    ("instances", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
508
    ]
509

    
510

    
511
class OpClusterConfigQuery(OpCode):
512
  """Query cluster configuration values."""
513
  OP_PARAMS = [
514
    _POutputFields
515
    ]
516

    
517

    
518
class OpClusterRename(OpCode):
519
  """Rename the cluster.
520

521
  @type name: C{str}
522
  @ivar name: The new name of the cluster. The name and/or the master IP
523
              address will be changed to match the new name and its IP
524
              address.
525

526
  """
527
  OP_DSC_FIELD = "name"
528
  OP_PARAMS = [
529
    ("name", ht.NoDefault, ht.TNonEmptyString),
530
    ]
531

    
532

    
533
class OpClusterSetParams(OpCode):
534
  """Change the parameters of the cluster.
535

536
  @type vg_name: C{str} or C{None}
537
  @ivar vg_name: The new volume group name or None to disable LVM usage.
538

539
  """
540
  OP_PARAMS = [
541
    ("vg_name", None, ht.TMaybeString),
542
    ("enabled_hypervisors", None,
543
     ht.TOr(ht.TAnd(ht.TListOf(ht.TElemOf(constants.HYPER_TYPES)), ht.TTrue),
544
            ht.TNone)),
545
    ("hvparams", None, ht.TOr(ht.TDictOf(ht.TNonEmptyString, ht.TDict),
546
                              ht.TNone)),
547
    ("beparams", None, ht.TOr(ht.TDict, ht.TNone)),
548
    ("os_hvp", None, ht.TOr(ht.TDictOf(ht.TNonEmptyString, ht.TDict),
549
                            ht.TNone)),
550
    ("osparams", None, ht.TOr(ht.TDictOf(ht.TNonEmptyString, ht.TDict),
551
                              ht.TNone)),
552
    ("candidate_pool_size", None, ht.TOr(ht.TStrictPositiveInt, ht.TNone)),
553
    ("uid_pool", None, ht.NoType),
554
    ("add_uids", None, ht.NoType),
555
    ("remove_uids", None, ht.NoType),
556
    ("maintain_node_health", None, ht.TMaybeBool),
557
    ("prealloc_wipe_disks", None, ht.TMaybeBool),
558
    ("nicparams", None, ht.TMaybeDict),
559
    ("ndparams", None, ht.TMaybeDict),
560
    ("drbd_helper", None, ht.TOr(ht.TString, ht.TNone)),
561
    ("default_iallocator", None, ht.TOr(ht.TString, ht.TNone)),
562
    ("master_netdev", None, ht.TOr(ht.TString, ht.TNone)),
563
    ("reserved_lvs", None, ht.TOr(ht.TListOf(ht.TNonEmptyString), ht.TNone)),
564
    ("hidden_os", None, ht.TOr(ht.TListOf(
565
          ht.TAnd(ht.TList,
566
                ht.TIsLength(2),
567
                ht.TMap(lambda v: v[0], ht.TElemOf(constants.DDMS_VALUES)))),
568
          ht.TNone)),
569
    ("blacklisted_os", None, ht.TOr(ht.TListOf(
570
          ht.TAnd(ht.TList,
571
                ht.TIsLength(2),
572
                ht.TMap(lambda v: v[0], ht.TElemOf(constants.DDMS_VALUES)))),
573
          ht.TNone)),
574
    ]
575

    
576

    
577
class OpClusterRedistConf(OpCode):
578
  """Force a full push of the cluster configuration.
579

580
  """
581

    
582

    
583
class OpQuery(OpCode):
584
  """Query for resources/items.
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
  @ivar filter: Query filter
589

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

    
598

    
599
class OpQueryFields(OpCode):
600
  """Query for available resource/item fields.
601

602
  @ivar what: Resources to query for, must be one of L{constants.QR_OP_QUERY}
603
  @ivar fields: List of fields to retrieve
604

605
  """
606
  OP_PARAMS = [
607
    ("what", ht.NoDefault, ht.TElemOf(constants.QR_OP_QUERY)),
608
    ("fields", None, ht.TOr(ht.TNone, ht.TListOf(ht.TNonEmptyString))),
609
    ]
610

    
611

    
612
class OpOobCommand(OpCode):
613
  """Interact with OOB."""
614
  OP_PARAMS = [
615
    ("node_names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
616
    ("command", None, ht.TElemOf(constants.OOB_COMMANDS)),
617
    ("timeout", constants.OOB_TIMEOUT, ht.TInt),
618
    ]
619

    
620

    
621
# node opcodes
622

    
623
class OpNodeRemove(OpCode):
624
  """Remove a node.
625

626
  @type node_name: C{str}
627
  @ivar node_name: The name of the node to remove. If the node still has
628
                   instances on it, the operation will fail.
629

630
  """
631
  OP_DSC_FIELD = "node_name"
632
  OP_PARAMS = [
633
    _PNodeName,
634
    ]
635

    
636

    
637
class OpNodeAdd(OpCode):
638
  """Add a node to the cluster.
639

640
  @type node_name: C{str}
641
  @ivar node_name: The name of the node to add. This can be a short name,
642
                   but it will be expanded to the FQDN.
643
  @type primary_ip: IP address
644
  @ivar primary_ip: The primary IP of the node. This will be ignored when the
645
                    opcode is submitted, but will be filled during the node
646
                    add (so it will be visible in the job query).
647
  @type secondary_ip: IP address
648
  @ivar secondary_ip: The secondary IP of the node. This needs to be passed
649
                      if the cluster has been initialized in 'dual-network'
650
                      mode, otherwise it must not be given.
651
  @type readd: C{bool}
652
  @ivar readd: Whether to re-add an existing node to the cluster. If
653
               this is not passed, then the operation will abort if the node
654
               name is already in the cluster; use this parameter to 'repair'
655
               a node that had its configuration broken, or was reinstalled
656
               without removal from the cluster.
657
  @type group: C{str}
658
  @ivar group: The node group to which this node will belong.
659
  @type vm_capable: C{bool}
660
  @ivar vm_capable: The vm_capable node attribute
661
  @type master_capable: C{bool}
662
  @ivar master_capable: The master_capable node attribute
663

664
  """
665
  OP_DSC_FIELD = "node_name"
666
  OP_PARAMS = [
667
    _PNodeName,
668
    ("primary_ip", None, ht.NoType),
669
    ("secondary_ip", None, ht.TMaybeString),
670
    ("readd", False, ht.TBool),
671
    ("group", None, ht.TMaybeString),
672
    ("master_capable", None, ht.TMaybeBool),
673
    ("vm_capable", None, ht.TMaybeBool),
674
    ("ndparams", None, ht.TMaybeDict),
675
    ]
676

    
677

    
678
class OpNodeQuery(OpCode):
679
  """Compute the list of nodes."""
680
  OP_PARAMS = [
681
    _POutputFields,
682
    ("names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
683
    ("use_locking", False, ht.TBool),
684
    ]
685

    
686

    
687
class OpNodeQueryvols(OpCode):
688
  """Get list of volumes on node."""
689
  OP_PARAMS = [
690
    _POutputFields,
691
    ("nodes", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
692
    ]
693

    
694

    
695
class OpNodeQueryStorage(OpCode):
696
  """Get information on storage for node(s)."""
697
  OP_PARAMS = [
698
    _POutputFields,
699
    _PStorageType,
700
    ("nodes", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
701
    ("name", None, ht.TMaybeString),
702
    ]
703

    
704

    
705
class OpNodeModifyStorage(OpCode):
706
  """Modifies the properies of a storage unit"""
707
  OP_PARAMS = [
708
    _PNodeName,
709
    _PStorageType,
710
    ("name", ht.NoDefault, ht.TNonEmptyString),
711
    ("changes", ht.NoDefault, ht.TDict),
712
    ]
713

    
714

    
715
class OpRepairNodeStorage(OpCode):
716
  """Repairs the volume group on a node."""
717
  OP_DSC_FIELD = "node_name"
718
  OP_PARAMS = [
719
    _PNodeName,
720
    _PStorageType,
721
    _PIgnoreConsistency,
722
    ("name", ht.NoDefault, ht.TNonEmptyString),
723
    ]
724

    
725

    
726
class OpNodeSetParams(OpCode):
727
  """Change the parameters of a node."""
728
  OP_DSC_FIELD = "node_name"
729
  OP_PARAMS = [
730
    _PNodeName,
731
    _PForce,
732
    ("master_candidate", None, ht.TMaybeBool),
733
    ("offline", None, ht.TMaybeBool),
734
    ("drained", None, ht.TMaybeBool),
735
    ("auto_promote", False, ht.TBool),
736
    ("master_capable", None, ht.TMaybeBool),
737
    ("vm_capable", None, ht.TMaybeBool),
738
    ("secondary_ip", None, ht.TMaybeString),
739
    ("ndparams", None, ht.TMaybeDict),
740
    ("powered", None, ht.TMaybeBool),
741
    ]
742

    
743

    
744
class OpNodePowercycle(OpCode):
745
  """Tries to powercycle a node."""
746
  OP_DSC_FIELD = "node_name"
747
  OP_PARAMS = [
748
    _PNodeName,
749
    _PForce,
750
    ]
751

    
752

    
753
class OpNodeMigrate(OpCode):
754
  """Migrate all instances from a node."""
755
  OP_DSC_FIELD = "node_name"
756
  OP_PARAMS = [
757
    _PNodeName,
758
    _PMigrationMode,
759
    _PMigrationLive,
760
    ("iallocator", None, ht.TMaybeString),
761
    ]
762

    
763

    
764
class OpNodeEvacStrategy(OpCode):
765
  """Compute the evacuation strategy for a list of nodes."""
766
  OP_DSC_FIELD = "nodes"
767
  OP_PARAMS = [
768
    ("nodes", ht.NoDefault, ht.TListOf(ht.TNonEmptyString)),
769
    ("remote_node", None, ht.TMaybeString),
770
    ("iallocator", None, ht.TMaybeString),
771
    ]
772

    
773

    
774
# instance opcodes
775

    
776
class OpInstanceCreate(OpCode):
777
  """Create an instance.
778

779
  @ivar instance_name: Instance name
780
  @ivar mode: Instance creation mode (one of L{constants.INSTANCE_CREATE_MODES})
781
  @ivar source_handshake: Signed handshake from source (remote import only)
782
  @ivar source_x509_ca: Source X509 CA in PEM format (remote import only)
783
  @ivar source_instance_name: Previous name of instance (remote import only)
784
  @ivar source_shutdown_timeout: Shutdown timeout used for source instance
785
    (remote import only)
786

787
  """
788
  OP_DSC_FIELD = "instance_name"
789
  OP_PARAMS = [
790
    _PInstanceName,
791
    ("beparams", ht.EmptyDict, ht.TDict),
792
    ("disks", ht.NoDefault, ht.TListOf(ht.TDict)),
793
    ("disk_template", ht.NoDefault, _CheckDiskTemplate),
794
    ("file_driver", None, ht.TOr(ht.TNone, ht.TElemOf(constants.FILE_DRIVER))),
795
    ("file_storage_dir", None, ht.TMaybeString),
796
    ("force_variant", False, ht.TBool),
797
    ("hvparams", ht.EmptyDict, ht.TDict),
798
    ("hypervisor", None, ht.TMaybeString),
799
    ("iallocator", None, ht.TMaybeString),
800
    ("identify_defaults", False, ht.TBool),
801
    ("ip_check", True, ht.TBool),
802
    ("mode", ht.NoDefault, ht.TElemOf(constants.INSTANCE_CREATE_MODES)),
803
    ("name_check", True, ht.TBool),
804
    ("nics", ht.NoDefault, ht.TListOf(ht.TDict)),
805
    ("no_install", None, ht.TMaybeBool),
806
    ("osparams", ht.EmptyDict, ht.TDict),
807
    ("os_type", None, ht.TMaybeString),
808
    ("pnode", None, ht.TMaybeString),
809
    ("snode", None, ht.TMaybeString),
810
    ("source_handshake", None, ht.TOr(ht.TList, ht.TNone)),
811
    ("source_instance_name", None, ht.TMaybeString),
812
    ("source_shutdown_timeout", constants.DEFAULT_SHUTDOWN_TIMEOUT,
813
     ht.TPositiveInt),
814
    ("source_x509_ca", None, ht.TMaybeString),
815
    ("src_node", None, ht.TMaybeString),
816
    ("src_path", None, ht.TMaybeString),
817
    ("start", True, ht.TBool),
818
    ("wait_for_sync", True, ht.TBool),
819
    ]
820

    
821

    
822
class OpInstanceReinstall(OpCode):
823
  """Reinstall an instance's OS."""
824
  OP_DSC_FIELD = "instance_name"
825
  OP_PARAMS = [
826
    _PInstanceName,
827
    ("os_type", None, ht.TMaybeString),
828
    ("force_variant", False, ht.TBool),
829
    ("osparams", None, ht.TMaybeDict),
830
    ]
831

    
832

    
833
class OpInstanceRemove(OpCode):
834
  """Remove an instance."""
835
  OP_DSC_FIELD = "instance_name"
836
  OP_PARAMS = [
837
    _PInstanceName,
838
    _PShutdownTimeout,
839
    ("ignore_failures", False, ht.TBool),
840
    ]
841

    
842

    
843
class OpInstanceRename(OpCode):
844
  """Rename an instance."""
845
  OP_PARAMS = [
846
    _PInstanceName,
847
    ("new_name", ht.NoDefault, ht.TNonEmptyString),
848
    ("ip_check", False, ht.TBool),
849
    ("name_check", True, ht.TBool),
850
    ]
851

    
852

    
853
class OpInstanceStartup(OpCode):
854
  """Startup an instance."""
855
  OP_DSC_FIELD = "instance_name"
856
  OP_PARAMS = [
857
    _PInstanceName,
858
    _PForce,
859
    _PIgnoreOfflineNodes,
860
    ("hvparams", ht.EmptyDict, ht.TDict),
861
    ("beparams", ht.EmptyDict, ht.TDict),
862
    ]
863

    
864

    
865
class OpInstanceShutdown(OpCode):
866
  """Shutdown an instance."""
867
  OP_DSC_FIELD = "instance_name"
868
  OP_PARAMS = [
869
    _PInstanceName,
870
    _PIgnoreOfflineNodes,
871
    ("timeout", constants.DEFAULT_SHUTDOWN_TIMEOUT, ht.TPositiveInt),
872
    ]
873

    
874

    
875
class OpInstanceReboot(OpCode):
876
  """Reboot an instance."""
877
  OP_DSC_FIELD = "instance_name"
878
  OP_PARAMS = [
879
    _PInstanceName,
880
    _PShutdownTimeout,
881
    ("ignore_secondaries", False, ht.TBool),
882
    ("reboot_type", ht.NoDefault, ht.TElemOf(constants.REBOOT_TYPES)),
883
    ]
884

    
885

    
886
class OpInstanceReplaceDisks(OpCode):
887
  """Replace the disks of an instance."""
888
  OP_DSC_FIELD = "instance_name"
889
  OP_PARAMS = [
890
    _PInstanceName,
891
    ("mode", ht.NoDefault, ht.TElemOf(constants.REPLACE_MODES)),
892
    ("disks", ht.EmptyList, ht.TListOf(ht.TPositiveInt)),
893
    ("remote_node", None, ht.TMaybeString),
894
    ("iallocator", None, ht.TMaybeString),
895
    ("early_release", False, ht.TBool),
896
    ]
897

    
898

    
899
class OpInstanceFailover(OpCode):
900
  """Failover an instance."""
901
  OP_DSC_FIELD = "instance_name"
902
  OP_PARAMS = [
903
    _PInstanceName,
904
    _PShutdownTimeout,
905
    _PIgnoreConsistency,
906
    ("iallocator", None, ht.TMaybeString),
907
    ("target_node", None, ht.TMaybeString),
908
    ]
909

    
910

    
911
class OpInstanceMigrate(OpCode):
912
  """Migrate an instance.
913

914
  This migrates (without shutting down an instance) to its secondary
915
  node.
916

917
  @ivar instance_name: the name of the instance
918
  @ivar mode: the migration mode (live, non-live or None for auto)
919

920
  """
921
  OP_DSC_FIELD = "instance_name"
922
  OP_PARAMS = [
923
    _PInstanceName,
924
    _PMigrationMode,
925
    _PMigrationLive,
926
    ("cleanup", False, ht.TBool),
927
    ("iallocator", None, ht.TMaybeString),
928
    ("target_node", None, ht.TMaybeString),
929
    ]
930

    
931

    
932
class OpInstanceMove(OpCode):
933
  """Move an instance.
934

935
  This move (with shutting down an instance and data copying) to an
936
  arbitrary node.
937

938
  @ivar instance_name: the name of the instance
939
  @ivar target_node: the destination node
940

941
  """
942
  OP_DSC_FIELD = "instance_name"
943
  OP_PARAMS = [
944
    _PInstanceName,
945
    _PShutdownTimeout,
946
    _PIgnoreConsistency,
947
    ("target_node", ht.NoDefault, ht.TNonEmptyString),
948
    ]
949

    
950

    
951
class OpInstanceConsole(OpCode):
952
  """Connect to an instance's console."""
953
  OP_DSC_FIELD = "instance_name"
954
  OP_PARAMS = [
955
    _PInstanceName
956
    ]
957

    
958

    
959
class OpInstanceActivateDisks(OpCode):
960
  """Activate an instance's disks."""
961
  OP_DSC_FIELD = "instance_name"
962
  OP_PARAMS = [
963
    _PInstanceName,
964
    ("ignore_size", False, ht.TBool),
965
    ]
966

    
967

    
968
class OpInstanceDeactivateDisks(OpCode):
969
  """Deactivate an instance's disks."""
970
  OP_DSC_FIELD = "instance_name"
971
  OP_PARAMS = [
972
    _PInstanceName,
973
    _PForce,
974
    ]
975

    
976

    
977
class OpInstanceRecreateDisks(OpCode):
978
  """Deactivate an instance's disks."""
979
  OP_DSC_FIELD = "instance_name"
980
  OP_PARAMS = [
981
    _PInstanceName,
982
    ("disks", ht.EmptyList, ht.TListOf(ht.TPositiveInt)),
983
    ("nodes", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
984
    ]
985

    
986

    
987
class OpInstanceQuery(OpCode):
988
  """Compute the list of instances."""
989
  OP_PARAMS = [
990
    _POutputFields,
991
    ("names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
992
    ("use_locking", False, ht.TBool),
993
    ]
994

    
995

    
996
class OpInstanceQueryData(OpCode):
997
  """Compute the run-time status of instances."""
998
  OP_PARAMS = [
999
    ("instances", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
1000
    ("static", False, ht.TBool),
1001
    ("use_locking", False, ht.TBool),
1002
    ]
1003

    
1004

    
1005
class OpInstanceSetParams(OpCode):
1006
  """Change the parameters of an instance."""
1007
  OP_DSC_FIELD = "instance_name"
1008
  OP_PARAMS = [
1009
    _PInstanceName,
1010
    _PForce,
1011
    ("nics", ht.EmptyList, ht.TList),
1012
    ("disks", ht.EmptyList, ht.TList),
1013
    ("beparams", ht.EmptyDict, ht.TDict),
1014
    ("hvparams", ht.EmptyDict, ht.TDict),
1015
    ("disk_template", None, ht.TOr(ht.TNone, _CheckDiskTemplate)),
1016
    ("remote_node", None, ht.TMaybeString),
1017
    ("os_name", None, ht.TMaybeString),
1018
    ("force_variant", False, ht.TBool),
1019
    ("osparams", None, ht.TMaybeDict),
1020
    ("wait_for_sync", True, ht.TBool),
1021
    ]
1022

    
1023

    
1024
class OpInstanceGrowDisk(OpCode):
1025
  """Grow a disk of an instance."""
1026
  OP_DSC_FIELD = "instance_name"
1027
  OP_PARAMS = [
1028
    _PInstanceName,
1029
    ("disk", ht.NoDefault, ht.TInt),
1030
    ("amount", ht.NoDefault, ht.TInt),
1031
    ("wait_for_sync", True, ht.TBool),
1032
    ]
1033

    
1034

    
1035
# Node group opcodes
1036

    
1037
class OpGroupAdd(OpCode):
1038
  """Add a node group to the cluster."""
1039
  OP_DSC_FIELD = "group_name"
1040
  OP_PARAMS = [
1041
    _PGroupName,
1042
    ("ndparams", None, ht.TMaybeDict),
1043
    ("alloc_policy", None,
1044
     ht.TOr(ht.TNone, ht.TElemOf(constants.VALID_ALLOC_POLICIES))),
1045
    ]
1046

    
1047

    
1048
class OpGroupAssignNodes(OpCode):
1049
  """Assign nodes to a node group."""
1050
  OP_DSC_FIELD = "group_name"
1051
  OP_PARAMS = [
1052
    _PGroupName,
1053
    _PForce,
1054
    ("nodes", ht.NoDefault, ht.TListOf(ht.TNonEmptyString)),
1055
    ]
1056

    
1057

    
1058
class OpGroupQuery(OpCode):
1059
  """Compute the list of node groups."""
1060
  OP_PARAMS = [
1061
    _POutputFields,
1062
    ("names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
1063
    ]
1064

    
1065

    
1066
class OpGroupSetParams(OpCode):
1067
  """Change the parameters of a node group."""
1068
  OP_DSC_FIELD = "group_name"
1069
  OP_PARAMS = [
1070
    _PGroupName,
1071
    ("ndparams", None, ht.TMaybeDict),
1072
    ("alloc_policy", None, ht.TOr(ht.TNone,
1073
                                  ht.TElemOf(constants.VALID_ALLOC_POLICIES))),
1074
    ("network", None, ht.TOr(ht.TNone, ht.TList)),
1075
    ]
1076

    
1077

    
1078
class OpGroupRemove(OpCode):
1079
  """Remove a node group from the cluster."""
1080
  OP_DSC_FIELD = "group_name"
1081
  OP_PARAMS = [
1082
    _PGroupName,
1083
    ]
1084

    
1085

    
1086
class OpGroupRename(OpCode):
1087
  """Rename a node group in the cluster."""
1088
  OP_DSC_FIELD = "old_name"
1089
  OP_PARAMS = [
1090
    ("old_name", ht.NoDefault, ht.TNonEmptyString),
1091
    ("new_name", ht.NoDefault, ht.TNonEmptyString),
1092
    ]
1093

    
1094

    
1095
# OS opcodes
1096
class OpOsDiagnose(OpCode):
1097
  """Compute the list of guest operating systems."""
1098
  OP_PARAMS = [
1099
    _POutputFields,
1100
    ("names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
1101
    ]
1102

    
1103

    
1104
# Exports opcodes
1105
class OpBackupQuery(OpCode):
1106
  """Compute the list of exported images."""
1107
  OP_PARAMS = [
1108
    ("nodes", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
1109
    ("use_locking", False, ht.TBool),
1110
    ]
1111

    
1112

    
1113
class OpBackupPrepare(OpCode):
1114
  """Prepares an instance export.
1115

1116
  @ivar instance_name: Instance name
1117
  @ivar mode: Export mode (one of L{constants.EXPORT_MODES})
1118

1119
  """
1120
  OP_DSC_FIELD = "instance_name"
1121
  OP_PARAMS = [
1122
    _PInstanceName,
1123
    ("mode", ht.NoDefault, ht.TElemOf(constants.EXPORT_MODES)),
1124
    ]
1125

    
1126

    
1127
class OpBackupExport(OpCode):
1128
  """Export an instance.
1129

1130
  For local exports, the export destination is the node name. For remote
1131
  exports, the export destination is a list of tuples, each consisting of
1132
  hostname/IP address, port, HMAC and HMAC salt. The HMAC is calculated using
1133
  the cluster domain secret over the value "${index}:${hostname}:${port}". The
1134
  destination X509 CA must be a signed certificate.
1135

1136
  @ivar mode: Export mode (one of L{constants.EXPORT_MODES})
1137
  @ivar target_node: Export destination
1138
  @ivar x509_key_name: X509 key to use (remote export only)
1139
  @ivar destination_x509_ca: Destination X509 CA in PEM format (remote export
1140
                             only)
1141

1142
  """
1143
  OP_DSC_FIELD = "instance_name"
1144
  OP_PARAMS = [
1145
    _PInstanceName,
1146
    _PShutdownTimeout,
1147
    # TODO: Rename target_node as it changes meaning for different export modes
1148
    # (e.g. "destination")
1149
    ("target_node", ht.NoDefault, ht.TOr(ht.TNonEmptyString, ht.TList)),
1150
    ("shutdown", True, ht.TBool),
1151
    ("remove_instance", False, ht.TBool),
1152
    ("ignore_remove_failures", False, ht.TBool),
1153
    ("mode", constants.EXPORT_MODE_LOCAL, ht.TElemOf(constants.EXPORT_MODES)),
1154
    ("x509_key_name", None, ht.TOr(ht.TList, ht.TNone)),
1155
    ("destination_x509_ca", None, ht.TMaybeString),
1156
    ]
1157

    
1158

    
1159
class OpBackupRemove(OpCode):
1160
  """Remove an instance's export."""
1161
  OP_DSC_FIELD = "instance_name"
1162
  OP_PARAMS = [
1163
    _PInstanceName,
1164
    ]
1165

    
1166

    
1167
# Tags opcodes
1168
class OpTagsGet(OpCode):
1169
  """Returns the tags of the given object."""
1170
  OP_DSC_FIELD = "name"
1171
  OP_PARAMS = [
1172
    _PTagKind,
1173
    # Name is only meaningful for nodes and instances
1174
    ("name", ht.NoDefault, ht.TMaybeString),
1175
    ]
1176

    
1177

    
1178
class OpTagsSearch(OpCode):
1179
  """Searches the tags in the cluster for a given pattern."""
1180
  OP_DSC_FIELD = "pattern"
1181
  OP_PARAMS = [
1182
    ("pattern", ht.NoDefault, ht.TNonEmptyString),
1183
    ]
1184

    
1185

    
1186
class OpTagsSet(OpCode):
1187
  """Add a list of tags on a given object."""
1188
  OP_PARAMS = [
1189
    _PTagKind,
1190
    _PTags,
1191
    # Name is only meaningful for nodes and instances
1192
    ("name", ht.NoDefault, ht.TMaybeString),
1193
    ]
1194

    
1195

    
1196
class OpTagsDel(OpCode):
1197
  """Remove a list of tags from a given object."""
1198
  OP_PARAMS = [
1199
    _PTagKind,
1200
    _PTags,
1201
    # Name is only meaningful for nodes and instances
1202
    ("name", ht.NoDefault, ht.TMaybeString),
1203
    ]
1204

    
1205
# Test opcodes
1206
class OpTestDelay(OpCode):
1207
  """Sleeps for a configured amount of time.
1208

1209
  This is used just for debugging and testing.
1210

1211
  Parameters:
1212
    - duration: the time to sleep
1213
    - on_master: if true, sleep on the master
1214
    - on_nodes: list of nodes in which to sleep
1215

1216
  If the on_master parameter is true, it will execute a sleep on the
1217
  master (before any node sleep).
1218

1219
  If the on_nodes list is not empty, it will sleep on those nodes
1220
  (after the sleep on the master, if that is enabled).
1221

1222
  As an additional feature, the case of duration < 0 will be reported
1223
  as an execution error, so this opcode can be used as a failure
1224
  generator. The case of duration == 0 will not be treated specially.
1225

1226
  """
1227
  OP_DSC_FIELD = "duration"
1228
  OP_PARAMS = [
1229
    ("duration", ht.NoDefault, ht.TFloat),
1230
    ("on_master", True, ht.TBool),
1231
    ("on_nodes", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
1232
    ("repeat", 0, ht.TPositiveInt)
1233
    ]
1234

    
1235

    
1236
class OpTestAllocator(OpCode):
1237
  """Allocator framework testing.
1238

1239
  This opcode has two modes:
1240
    - gather and return allocator input for a given mode (allocate new
1241
      or replace secondary) and a given instance definition (direction
1242
      'in')
1243
    - run a selected allocator for a given operation (as above) and
1244
      return the allocator output (direction 'out')
1245

1246
  """
1247
  OP_DSC_FIELD = "allocator"
1248
  OP_PARAMS = [
1249
    ("direction", ht.NoDefault,
1250
     ht.TElemOf(constants.VALID_IALLOCATOR_DIRECTIONS)),
1251
    ("mode", ht.NoDefault, ht.TElemOf(constants.VALID_IALLOCATOR_MODES)),
1252
    ("name", ht.NoDefault, ht.TNonEmptyString),
1253
    ("nics", ht.NoDefault, ht.TOr(ht.TNone, ht.TListOf(
1254
      ht.TDictOf(ht.TElemOf(["mac", "ip", "bridge"]),
1255
               ht.TOr(ht.TNone, ht.TNonEmptyString))))),
1256
    ("disks", ht.NoDefault, ht.TOr(ht.TNone, ht.TList)),
1257
    ("hypervisor", None, ht.TMaybeString),
1258
    ("allocator", None, ht.TMaybeString),
1259
    ("tags", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
1260
    ("mem_size", None, ht.TOr(ht.TNone, ht.TPositiveInt)),
1261
    ("vcpus", None, ht.TOr(ht.TNone, ht.TPositiveInt)),
1262
    ("os", None, ht.TMaybeString),
1263
    ("disk_template", None, ht.TMaybeString),
1264
    ("evac_nodes", None, ht.TOr(ht.TNone, ht.TListOf(ht.TNonEmptyString))),
1265
    ]
1266

    
1267

    
1268
class OpTestJqueue(OpCode):
1269
  """Utility opcode to test some aspects of the job queue.
1270

1271
  """
1272
  OP_PARAMS = [
1273
    ("notify_waitlock", False, ht.TBool),
1274
    ("notify_exec", False, ht.TBool),
1275
    ("log_messages", ht.EmptyList, ht.TListOf(ht.TString)),
1276
    ("fail", False, ht.TBool),
1277
    ]
1278

    
1279

    
1280
class OpTestDummy(OpCode):
1281
  """Utility opcode used by unittests.
1282

1283
  """
1284
  OP_PARAMS = [
1285
    ("result", ht.NoDefault, ht.NoType),
1286
    ("messages", ht.NoDefault, ht.NoType),
1287
    ("fail", ht.NoDefault, ht.NoType),
1288
    ]
1289
  WITH_LU = False
1290

    
1291

    
1292
# Network opcodes
1293
class OpNetworkAdd(OpCode):
1294
  """Add an IP network to the cluster."""
1295
  OP_DSC_FIELD = "network_name"
1296
  OP_PARAMS = [
1297
    _PNetworkName,
1298
    ("network", None, ht.TString),
1299
    ("gateway", None, ht.TMaybeString),
1300
    ("reserved_ips", None, ht.TOr(ht.TListOf(ht.TNonEmptyString), ht.TNone)),
1301
    ]
1302

    
1303

    
1304
class OpNetworkQuery(OpCode):
1305
  """Compute the list of networks."""
1306
  OP_PARAMS = [
1307
    _POutputFields,
1308
    ("names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
1309
    ]
1310

    
1311

    
1312
def _GetOpList():
1313
  """Returns list of all defined opcodes.
1314

1315
  Does not eliminate duplicates by C{OP_ID}.
1316

1317
  """
1318
  return [v for v in globals().values()
1319
          if (isinstance(v, type) and issubclass(v, OpCode) and
1320
              hasattr(v, "OP_ID") and v is not OpCode)]
1321

    
1322

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