ht: Add new check for numbers
[ganeti-local] / lib / opcodes.py
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())