Rename OpRemoveInstance and LURemoveInstance
[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
38 from ganeti import constants
39 from ganeti import errors
40 from ganeti import ht
41
42
43 # Common opcode attributes
44
45 #: output fields for a query operation
46 _POutputFields = ("output_fields", ht.NoDefault, ht.TListOf(ht.TNonEmptyString))
47
48 #: the shutdown timeout
49 _PShutdownTimeout = ("shutdown_timeout", constants.DEFAULT_SHUTDOWN_TIMEOUT,
50                      ht.TPositiveInt)
51
52 #: the force parameter
53 _PForce = ("force", False, ht.TBool)
54
55 #: a required instance name (for single-instance LUs)
56 _PInstanceName = ("instance_name", ht.NoDefault, ht.TNonEmptyString)
57
58 #: Whether to ignore offline nodes
59 _PIgnoreOfflineNodes = ("ignore_offline_nodes", False, ht.TBool)
60
61 #: a required node name (for single-node LUs)
62 _PNodeName = ("node_name", ht.NoDefault, ht.TNonEmptyString)
63
64 #: a required node group name (for single-group LUs)
65 _PGroupName = ("group_name", ht.NoDefault, ht.TNonEmptyString)
66
67 #: Migration type (live/non-live)
68 _PMigrationMode = ("mode", None,
69                    ht.TOr(ht.TNone, ht.TElemOf(constants.HT_MIGRATION_MODES)))
70
71 #: Obsolete 'live' migration mode (boolean)
72 _PMigrationLive = ("live", None, ht.TMaybeBool)
73
74 #: Tag type
75 _PTagKind = ("kind", ht.NoDefault, ht.TElemOf(constants.VALID_TAG_TYPES))
76
77 #: List of tag strings
78 _PTags = ("tags", ht.NoDefault, ht.TListOf(ht.TNonEmptyString))
79
80
81 def RequireFileStorage():
82   """Checks that file storage is enabled.
83
84   While it doesn't really fit into this module, L{utils} was deemed too large
85   of a dependency to be imported for just one or two functions.
86
87   @raise errors.OpPrereqError: when file storage is disabled
88
89   """
90   if not constants.ENABLE_FILE_STORAGE:
91     raise errors.OpPrereqError("File storage disabled at configure time",
92                                errors.ECODE_INVAL)
93
94
95 def _CheckDiskTemplate(template):
96   """Ensure a given disk template is valid.
97
98   """
99   if template not in constants.DISK_TEMPLATES:
100     # Using str.join directly to avoid importing utils for CommaJoin
101     msg = ("Invalid disk template name '%s', valid templates are: %s" %
102            (template, ", ".join(constants.DISK_TEMPLATES)))
103     raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
104   if template == constants.DT_FILE:
105     RequireFileStorage()
106   return True
107
108
109 def _CheckStorageType(storage_type):
110   """Ensure a given storage type is valid.
111
112   """
113   if storage_type not in constants.VALID_STORAGE_TYPES:
114     raise errors.OpPrereqError("Unknown storage type: %s" % storage_type,
115                                errors.ECODE_INVAL)
116   if storage_type == constants.ST_FILE:
117     RequireFileStorage()
118   return True
119
120
121 #: Storage type parameter
122 _PStorageType = ("storage_type", ht.NoDefault, _CheckStorageType)
123
124
125 class _AutoOpParamSlots(type):
126   """Meta class for opcode definitions.
127
128   """
129   def __new__(mcs, name, bases, attrs):
130     """Called when a class should be created.
131
132     @param mcs: The meta class
133     @param name: Name of created class
134     @param bases: Base classes
135     @type attrs: dict
136     @param attrs: Class attributes
137
138     """
139     assert "__slots__" not in attrs, \
140       "Class '%s' defines __slots__ when it should use OP_PARAMS" % name
141     assert "OP_ID" in attrs, "Class '%s' is missing OP_ID attribute" % name
142
143     # Always set OP_PARAMS to avoid duplicates in BaseOpCode.GetAllParams
144     params = attrs.setdefault("OP_PARAMS", [])
145
146     # Use parameter names as slots
147     slots = [pname for (pname, _, _) in params]
148
149     assert "OP_DSC_FIELD" not in attrs or attrs["OP_DSC_FIELD"] in slots, \
150       "Class '%s' uses unknown field in OP_DSC_FIELD" % name
151
152     attrs["__slots__"] = slots
153
154     return type.__new__(mcs, name, bases, attrs)
155
156
157 class BaseOpCode(object):
158   """A simple serializable object.
159
160   This object serves as a parent class for OpCode without any custom
161   field handling.
162
163   """
164   __metaclass__ = _AutoOpParamSlots
165
166   OP_ID = None
167
168   def __init__(self, **kwargs):
169     """Constructor for BaseOpCode.
170
171     The constructor takes only keyword arguments and will set
172     attributes on this object based on the passed arguments. As such,
173     it means that you should not pass arguments which are not in the
174     __slots__ attribute for this class.
175
176     """
177     slots = self._all_slots()
178     for key in kwargs:
179       if key not in slots:
180         raise TypeError("Object %s doesn't support the parameter '%s'" %
181                         (self.__class__.__name__, key))
182       setattr(self, key, kwargs[key])
183
184   def __getstate__(self):
185     """Generic serializer.
186
187     This method just returns the contents of the instance as a
188     dictionary.
189
190     @rtype:  C{dict}
191     @return: the instance attributes and their values
192
193     """
194     state = {}
195     for name in self._all_slots():
196       if hasattr(self, name):
197         state[name] = getattr(self, name)
198     return state
199
200   def __setstate__(self, state):
201     """Generic unserializer.
202
203     This method just restores from the serialized state the attributes
204     of the current instance.
205
206     @param state: the serialized opcode data
207     @type state:  C{dict}
208
209     """
210     if not isinstance(state, dict):
211       raise ValueError("Invalid data to __setstate__: expected dict, got %s" %
212                        type(state))
213
214     for name in self._all_slots():
215       if name not in state and hasattr(self, name):
216         delattr(self, name)
217
218     for name in state:
219       setattr(self, name, state[name])
220
221   @classmethod
222   def _all_slots(cls):
223     """Compute the list of all declared slots for a class.
224
225     """
226     slots = []
227     for parent in cls.__mro__:
228       slots.extend(getattr(parent, "__slots__", []))
229     return slots
230
231   @classmethod
232   def GetAllParams(cls):
233     """Compute list of all parameters for an opcode.
234
235     """
236     slots = []
237     for parent in cls.__mro__:
238       slots.extend(getattr(parent, "OP_PARAMS", []))
239     return slots
240
241   def Validate(self, set_defaults):
242     """Validate opcode parameters, optionally setting default values.
243
244     @type set_defaults: bool
245     @param set_defaults: Whether to set default values
246     @raise errors.OpPrereqError: When a parameter value doesn't match
247                                  requirements
248
249     """
250     for (attr_name, default, test) in self.GetAllParams():
251       assert test == ht.NoType or callable(test)
252
253       if not hasattr(self, attr_name):
254         if default == ht.NoDefault:
255           raise errors.OpPrereqError("Required parameter '%s.%s' missing" %
256                                      (self.OP_ID, attr_name),
257                                      errors.ECODE_INVAL)
258         elif set_defaults:
259           if callable(default):
260             dval = default()
261           else:
262             dval = default
263           setattr(self, attr_name, dval)
264
265       if test == ht.NoType:
266         # no tests here
267         continue
268
269       if set_defaults or hasattr(self, attr_name):
270         attr_val = getattr(self, attr_name)
271         if not test(attr_val):
272           logging.error("OpCode %s, parameter %s, has invalid type %s/value %s",
273                         self.OP_ID, attr_name, type(attr_val), attr_val)
274           raise errors.OpPrereqError("Parameter '%s.%s' fails validation" %
275                                      (self.OP_ID, attr_name),
276                                      errors.ECODE_INVAL)
277
278
279 class OpCode(BaseOpCode):
280   """Abstract OpCode.
281
282   This is the root of the actual OpCode hierarchy. All clases derived
283   from this class should override OP_ID.
284
285   @cvar OP_ID: The ID of this opcode. This should be unique amongst all
286                children of this class.
287   @cvar OP_DSC_FIELD: The name of a field whose value will be included in the
288                       string returned by Summary(); see the docstring of that
289                       method for details).
290   @cvar OP_PARAMS: List of opcode attributes, the default values they should
291                    get if not already defined, and types they must match.
292   @cvar WITH_LU: Boolean that specifies whether this should be included in
293       mcpu's dispatch table
294   @ivar dry_run: Whether the LU should be run in dry-run mode, i.e. just
295                  the check steps
296   @ivar priority: Opcode priority for queue
297
298   """
299   OP_ID = "OP_ABSTRACT"
300   WITH_LU = True
301   OP_PARAMS = [
302     ("dry_run", None, ht.TMaybeBool),
303     ("debug_level", None, ht.TOr(ht.TNone, ht.TPositiveInt)),
304     ("priority", constants.OP_PRIO_DEFAULT,
305      ht.TElemOf(constants.OP_PRIO_SUBMIT_VALID)),
306     ]
307
308   def __getstate__(self):
309     """Specialized getstate for opcodes.
310
311     This method adds to the state dictionary the OP_ID of the class,
312     so that on unload we can identify the correct class for
313     instantiating the opcode.
314
315     @rtype:   C{dict}
316     @return:  the state as a dictionary
317
318     """
319     data = BaseOpCode.__getstate__(self)
320     data["OP_ID"] = self.OP_ID
321     return data
322
323   @classmethod
324   def LoadOpCode(cls, data):
325     """Generic load opcode method.
326
327     The method identifies the correct opcode class from the dict-form
328     by looking for a OP_ID key, if this is not found, or its value is
329     not available in this module as a child of this class, we fail.
330
331     @type data:  C{dict}
332     @param data: the serialized opcode
333
334     """
335     if not isinstance(data, dict):
336       raise ValueError("Invalid data to LoadOpCode (%s)" % type(data))
337     if "OP_ID" not in data:
338       raise ValueError("Invalid data to LoadOpcode, missing OP_ID")
339     op_id = data["OP_ID"]
340     op_class = None
341     if op_id in OP_MAPPING:
342       op_class = OP_MAPPING[op_id]
343     else:
344       raise ValueError("Invalid data to LoadOpCode: OP_ID %s unsupported" %
345                        op_id)
346     op = op_class()
347     new_data = data.copy()
348     del new_data["OP_ID"]
349     op.__setstate__(new_data)
350     return op
351
352   def Summary(self):
353     """Generates a summary description of this opcode.
354
355     The summary is the value of the OP_ID attribute (without the "OP_" prefix),
356     plus the value of the OP_DSC_FIELD attribute, if one was defined; this field
357     should allow to easily identify the operation (for an instance creation job,
358     e.g., it would be the instance name).
359
360     """
361     # all OP_ID start with OP_, we remove that
362     txt = self.OP_ID[3:]
363     field_name = getattr(self, "OP_DSC_FIELD", None)
364     if field_name:
365       field_value = getattr(self, field_name, None)
366       if isinstance(field_value, (list, tuple)):
367         field_value = ",".join(str(i) for i in field_value)
368       txt = "%s(%s)" % (txt, field_value)
369     return txt
370
371
372 # cluster opcodes
373
374 class OpClusterPostInit(OpCode):
375   """Post cluster initialization.
376
377   This opcode does not touch the cluster at all. Its purpose is to run hooks
378   after the cluster has been initialized.
379
380   """
381   OP_ID = "OP_CLUSTER_POST_INIT"
382
383
384 class OpClusterDestroy(OpCode):
385   """Destroy the cluster.
386
387   This opcode has no other parameters. All the state is irreversibly
388   lost after the execution of this opcode.
389
390   """
391   OP_ID = "OP_CLUSTER_DESTROY"
392
393
394 class OpClusterQuery(OpCode):
395   """Query cluster information."""
396   OP_ID = "OP_CLUSTER_QUERY"
397
398
399 class OpClusterVerify(OpCode):
400   """Verify the cluster state.
401
402   @type skip_checks: C{list}
403   @ivar skip_checks: steps to be skipped from the verify process; this
404                      needs to be a subset of
405                      L{constants.VERIFY_OPTIONAL_CHECKS}; currently
406                      only L{constants.VERIFY_NPLUSONE_MEM} can be passed
407
408   """
409   OP_ID = "OP_CLUSTER_VERIFY"
410   OP_PARAMS = [
411     ("skip_checks", ht.EmptyList,
412      ht.TListOf(ht.TElemOf(constants.VERIFY_OPTIONAL_CHECKS))),
413     ("verbose", False, ht.TBool),
414     ("error_codes", False, ht.TBool),
415     ("debug_simulate_errors", False, ht.TBool),
416     ]
417
418
419 class OpClusterVerifyDisks(OpCode):
420   """Verify the cluster disks.
421
422   Parameters: none
423
424   Result: a tuple of four elements:
425     - list of node names with bad data returned (unreachable, etc.)
426     - dict of node names with broken volume groups (values: error msg)
427     - list of instances with degraded disks (that should be activated)
428     - dict of instances with missing logical volumes (values: (node, vol)
429       pairs with details about the missing volumes)
430
431   In normal operation, all lists should be empty. A non-empty instance
432   list (3rd element of the result) is still ok (errors were fixed) but
433   non-empty node list means some node is down, and probably there are
434   unfixable drbd errors.
435
436   Note that only instances that are drbd-based are taken into
437   consideration. This might need to be revisited in the future.
438
439   """
440   OP_ID = "OP_CLUSTER_VERIFY_DISKS"
441
442
443 class OpClusterRepairDiskSizes(OpCode):
444   """Verify the disk sizes of the instances and fixes configuration
445   mimatches.
446
447   Parameters: optional instances list, in case we want to restrict the
448   checks to only a subset of the instances.
449
450   Result: a list of tuples, (instance, disk, new-size) for changed
451   configurations.
452
453   In normal operation, the list should be empty.
454
455   @type instances: list
456   @ivar instances: the list of instances to check, or empty for all instances
457
458   """
459   OP_ID = "OP_CLUSTER_REPAIR_DISK_SIZES"
460   OP_PARAMS = [
461     ("instances", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
462     ]
463
464
465 class OpClusterConfigQuery(OpCode):
466   """Query cluster configuration values."""
467   OP_ID = "OP_CLUSTER_CONFIG_QUERY"
468   OP_PARAMS = [
469     _POutputFields
470     ]
471
472
473 class OpClusterRename(OpCode):
474   """Rename the cluster.
475
476   @type name: C{str}
477   @ivar name: The new name of the cluster. The name and/or the master IP
478               address will be changed to match the new name and its IP
479               address.
480
481   """
482   OP_ID = "OP_CLUSTER_RENAME"
483   OP_DSC_FIELD = "name"
484   OP_PARAMS = [
485     ("name", ht.NoDefault, ht.TNonEmptyString),
486     ]
487
488
489 class OpClusterSetParams(OpCode):
490   """Change the parameters of the cluster.
491
492   @type vg_name: C{str} or C{None}
493   @ivar vg_name: The new volume group name or None to disable LVM usage.
494
495   """
496   OP_ID = "OP_CLUSTER_SET_PARAMS"
497   OP_PARAMS = [
498     ("vg_name", None, ht.TMaybeString),
499     ("enabled_hypervisors", None,
500      ht.TOr(ht.TAnd(ht.TListOf(ht.TElemOf(constants.HYPER_TYPES)), ht.TTrue),
501             ht.TNone)),
502     ("hvparams", None, ht.TOr(ht.TDictOf(ht.TNonEmptyString, ht.TDict),
503                               ht.TNone)),
504     ("beparams", None, ht.TOr(ht.TDict, ht.TNone)),
505     ("os_hvp", None, ht.TOr(ht.TDictOf(ht.TNonEmptyString, ht.TDict),
506                             ht.TNone)),
507     ("osparams", None, ht.TOr(ht.TDictOf(ht.TNonEmptyString, ht.TDict),
508                               ht.TNone)),
509     ("candidate_pool_size", None, ht.TOr(ht.TStrictPositiveInt, ht.TNone)),
510     ("uid_pool", None, ht.NoType),
511     ("add_uids", None, ht.NoType),
512     ("remove_uids", None, ht.NoType),
513     ("maintain_node_health", None, ht.TMaybeBool),
514     ("prealloc_wipe_disks", None, ht.TMaybeBool),
515     ("nicparams", None, ht.TOr(ht.TDict, ht.TNone)),
516     ("ndparams", None, ht.TOr(ht.TDict, ht.TNone)),
517     ("drbd_helper", None, ht.TOr(ht.TString, ht.TNone)),
518     ("default_iallocator", None, ht.TOr(ht.TString, ht.TNone)),
519     ("master_netdev", None, ht.TOr(ht.TString, ht.TNone)),
520     ("reserved_lvs", None, ht.TOr(ht.TListOf(ht.TNonEmptyString), ht.TNone)),
521     ("hidden_os", None, ht.TOr(ht.TListOf(
522           ht.TAnd(ht.TList,
523                 ht.TIsLength(2),
524                 ht.TMap(lambda v: v[0], ht.TElemOf(constants.DDMS_VALUES)))),
525           ht.TNone)),
526     ("blacklisted_os", None, ht.TOr(ht.TListOf(
527           ht.TAnd(ht.TList,
528                 ht.TIsLength(2),
529                 ht.TMap(lambda v: v[0], ht.TElemOf(constants.DDMS_VALUES)))),
530           ht.TNone)),
531     ]
532
533
534 class OpClusterRedistConf(OpCode):
535   """Force a full push of the cluster configuration.
536
537   """
538   OP_ID = "OP_CLUSTER_REDIST_CONF"
539
540
541 class OpQuery(OpCode):
542   """Query for resources/items.
543
544   @ivar what: Resources to query for, must be one of L{constants.QR_OP_QUERY}
545   @ivar fields: List of fields to retrieve
546   @ivar filter: Query filter
547
548   """
549   OP_ID = "OP_QUERY"
550   OP_PARAMS = [
551     ("what", ht.NoDefault, ht.TElemOf(constants.QR_OP_QUERY)),
552     ("fields", ht.NoDefault, ht.TListOf(ht.TNonEmptyString)),
553     ("filter", None, ht.TOr(ht.TNone,
554                             ht.TListOf(ht.TOr(ht.TNonEmptyString, ht.TList)))),
555     ]
556
557
558 class OpQueryFields(OpCode):
559   """Query for available resource/item fields.
560
561   @ivar what: Resources to query for, must be one of L{constants.QR_OP_QUERY}
562   @ivar fields: List of fields to retrieve
563
564   """
565   OP_ID = "OP_QUERY_FIELDS"
566   OP_PARAMS = [
567     ("what", ht.NoDefault, ht.TElemOf(constants.QR_OP_QUERY)),
568     ("fields", None, ht.TOr(ht.TNone, ht.TListOf(ht.TNonEmptyString))),
569     ]
570
571
572 class OpOobCommand(OpCode):
573   """Interact with OOB."""
574   OP_ID = "OP_OOB_COMMAND"
575   OP_PARAMS = [
576     _PNodeName,
577     ("command", None, ht.TElemOf(constants.OOB_COMMANDS)),
578     ("timeout", constants.OOB_TIMEOUT, ht.TInt),
579     ]
580
581
582 # node opcodes
583
584 class OpRemoveNode(OpCode):
585   """Remove a node.
586
587   @type node_name: C{str}
588   @ivar node_name: The name of the node to remove. If the node still has
589                    instances on it, the operation will fail.
590
591   """
592   OP_ID = "OP_NODE_REMOVE"
593   OP_DSC_FIELD = "node_name"
594   OP_PARAMS = [
595     _PNodeName,
596     ]
597
598
599 class OpAddNode(OpCode):
600   """Add a node to the cluster.
601
602   @type node_name: C{str}
603   @ivar node_name: The name of the node to add. This can be a short name,
604                    but it will be expanded to the FQDN.
605   @type primary_ip: IP address
606   @ivar primary_ip: The primary IP of the node. This will be ignored when the
607                     opcode is submitted, but will be filled during the node
608                     add (so it will be visible in the job query).
609   @type secondary_ip: IP address
610   @ivar secondary_ip: The secondary IP of the node. This needs to be passed
611                       if the cluster has been initialized in 'dual-network'
612                       mode, otherwise it must not be given.
613   @type readd: C{bool}
614   @ivar readd: Whether to re-add an existing node to the cluster. If
615                this is not passed, then the operation will abort if the node
616                name is already in the cluster; use this parameter to 'repair'
617                a node that had its configuration broken, or was reinstalled
618                without removal from the cluster.
619   @type group: C{str}
620   @ivar group: The node group to which this node will belong.
621   @type vm_capable: C{bool}
622   @ivar vm_capable: The vm_capable node attribute
623   @type master_capable: C{bool}
624   @ivar master_capable: The master_capable node attribute
625
626   """
627   OP_ID = "OP_NODE_ADD"
628   OP_DSC_FIELD = "node_name"
629   OP_PARAMS = [
630     _PNodeName,
631     ("primary_ip", None, ht.NoType),
632     ("secondary_ip", None, ht.TMaybeString),
633     ("readd", False, ht.TBool),
634     ("group", None, ht.TMaybeString),
635     ("master_capable", None, ht.TMaybeBool),
636     ("vm_capable", None, ht.TMaybeBool),
637     ("ndparams", None, ht.TOr(ht.TDict, ht.TNone)),
638     ]
639
640
641 class OpQueryNodes(OpCode):
642   """Compute the list of nodes."""
643   OP_ID = "OP_NODE_QUERY"
644   OP_PARAMS = [
645     _POutputFields,
646     ("names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
647     ("use_locking", False, ht.TBool),
648     ]
649
650
651 class OpQueryNodeVolumes(OpCode):
652   """Get list of volumes on node."""
653   OP_ID = "OP_NODE_QUERYVOLS"
654   OP_PARAMS = [
655     _POutputFields,
656     ("nodes", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
657     ]
658
659
660 class OpQueryNodeStorage(OpCode):
661   """Get information on storage for node(s)."""
662   OP_ID = "OP_NODE_QUERY_STORAGE"
663   OP_PARAMS = [
664     _POutputFields,
665     _PStorageType,
666     ("nodes", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
667     ("name", None, ht.TMaybeString),
668     ]
669
670
671 class OpModifyNodeStorage(OpCode):
672   """Modifies the properies of a storage unit"""
673   OP_ID = "OP_NODE_MODIFY_STORAGE"
674   OP_PARAMS = [
675     _PNodeName,
676     _PStorageType,
677     ("name", ht.NoDefault, ht.TNonEmptyString),
678     ("changes", ht.NoDefault, ht.TDict),
679     ]
680
681
682 class OpRepairNodeStorage(OpCode):
683   """Repairs the volume group on a node."""
684   OP_ID = "OP_REPAIR_NODE_STORAGE"
685   OP_DSC_FIELD = "node_name"
686   OP_PARAMS = [
687     _PNodeName,
688     _PStorageType,
689     ("name", ht.NoDefault, ht.TNonEmptyString),
690     ("ignore_consistency", False, ht.TBool),
691     ]
692
693
694 class OpSetNodeParams(OpCode):
695   """Change the parameters of a node."""
696   OP_ID = "OP_NODE_SET_PARAMS"
697   OP_DSC_FIELD = "node_name"
698   OP_PARAMS = [
699     _PNodeName,
700     _PForce,
701     ("master_candidate", None, ht.TMaybeBool),
702     ("offline", None, ht.TMaybeBool),
703     ("drained", None, ht.TMaybeBool),
704     ("auto_promote", False, ht.TBool),
705     ("master_capable", None, ht.TMaybeBool),
706     ("vm_capable", None, ht.TMaybeBool),
707     ("secondary_ip", None, ht.TMaybeString),
708     ("ndparams", None, ht.TOr(ht.TDict, ht.TNone)),
709     ("powered", None, ht.TMaybeBool),
710     ]
711
712
713 class OpPowercycleNode(OpCode):
714   """Tries to powercycle a node."""
715   OP_ID = "OP_NODE_POWERCYCLE"
716   OP_DSC_FIELD = "node_name"
717   OP_PARAMS = [
718     _PNodeName,
719     _PForce,
720     ]
721
722
723 class OpMigrateNode(OpCode):
724   """Migrate all instances from a node."""
725   OP_ID = "OP_NODE_MIGRATE"
726   OP_DSC_FIELD = "node_name"
727   OP_PARAMS = [
728     _PNodeName,
729     _PMigrationMode,
730     _PMigrationLive,
731     ]
732
733
734 class OpNodeEvacuationStrategy(OpCode):
735   """Compute the evacuation strategy for a list of nodes."""
736   OP_ID = "OP_NODE_EVAC_STRATEGY"
737   OP_DSC_FIELD = "nodes"
738   OP_PARAMS = [
739     ("nodes", ht.NoDefault, ht.TListOf(ht.TNonEmptyString)),
740     ("remote_node", None, ht.TMaybeString),
741     ("iallocator", None, ht.TMaybeString),
742     ]
743
744
745 # instance opcodes
746
747 class OpInstanceCreate(OpCode):
748   """Create an instance.
749
750   @ivar instance_name: Instance name
751   @ivar mode: Instance creation mode (one of L{constants.INSTANCE_CREATE_MODES})
752   @ivar source_handshake: Signed handshake from source (remote import only)
753   @ivar source_x509_ca: Source X509 CA in PEM format (remote import only)
754   @ivar source_instance_name: Previous name of instance (remote import only)
755   @ivar source_shutdown_timeout: Shutdown timeout used for source instance
756     (remote import only)
757
758   """
759   OP_ID = "OP_INSTANCE_CREATE"
760   OP_DSC_FIELD = "instance_name"
761   OP_PARAMS = [
762     _PInstanceName,
763     ("beparams", ht.EmptyDict, ht.TDict),
764     ("disks", ht.NoDefault, ht.TListOf(ht.TDict)),
765     ("disk_template", ht.NoDefault, _CheckDiskTemplate),
766     ("file_driver", None, ht.TOr(ht.TNone, ht.TElemOf(constants.FILE_DRIVER))),
767     ("file_storage_dir", None, ht.TMaybeString),
768     ("force_variant", False, ht.TBool),
769     ("hvparams", ht.EmptyDict, ht.TDict),
770     ("hypervisor", None, ht.TMaybeString),
771     ("iallocator", None, ht.TMaybeString),
772     ("identify_defaults", False, ht.TBool),
773     ("ip_check", True, ht.TBool),
774     ("mode", ht.NoDefault, ht.TElemOf(constants.INSTANCE_CREATE_MODES)),
775     ("name_check", True, ht.TBool),
776     ("nics", ht.NoDefault, ht.TListOf(ht.TDict)),
777     ("no_install", None, ht.TMaybeBool),
778     ("osparams", ht.EmptyDict, ht.TDict),
779     ("os_type", None, ht.TMaybeString),
780     ("pnode", None, ht.TMaybeString),
781     ("snode", None, ht.TMaybeString),
782     ("source_handshake", None, ht.TOr(ht.TList, ht.TNone)),
783     ("source_instance_name", None, ht.TMaybeString),
784     ("source_shutdown_timeout", constants.DEFAULT_SHUTDOWN_TIMEOUT,
785      ht.TPositiveInt),
786     ("source_x509_ca", None, ht.TMaybeString),
787     ("src_node", None, ht.TMaybeString),
788     ("src_path", None, ht.TMaybeString),
789     ("start", True, ht.TBool),
790     ("wait_for_sync", True, ht.TBool),
791     ]
792
793
794 class OpInstanceReinstall(OpCode):
795   """Reinstall an instance's OS."""
796   OP_ID = "OP_INSTANCE_REINSTALL"
797   OP_DSC_FIELD = "instance_name"
798   OP_PARAMS = [
799     _PInstanceName,
800     ("os_type", None, ht.TMaybeString),
801     ("force_variant", False, ht.TBool),
802     ("osparams", None, ht.TOr(ht.TDict, ht.TNone)),
803     ]
804
805
806 class OpInstanceRemove(OpCode):
807   """Remove an instance."""
808   OP_ID = "OP_INSTANCE_REMOVE"
809   OP_DSC_FIELD = "instance_name"
810   OP_PARAMS = [
811     _PInstanceName,
812     _PShutdownTimeout,
813     ("ignore_failures", False, ht.TBool),
814     ]
815
816
817 class OpRenameInstance(OpCode):
818   """Rename an instance."""
819   OP_ID = "OP_INSTANCE_RENAME"
820   OP_PARAMS = [
821     _PInstanceName,
822     ("new_name", ht.NoDefault, ht.TNonEmptyString),
823     ("ip_check", False, ht.TBool),
824     ("name_check", True, ht.TBool),
825     ]
826
827
828 class OpStartupInstance(OpCode):
829   """Startup an instance."""
830   OP_ID = "OP_INSTANCE_STARTUP"
831   OP_DSC_FIELD = "instance_name"
832   OP_PARAMS = [
833     _PInstanceName,
834     _PForce,
835     _PIgnoreOfflineNodes,
836     ("hvparams", ht.EmptyDict, ht.TDict),
837     ("beparams", ht.EmptyDict, ht.TDict),
838     ]
839
840
841 class OpShutdownInstance(OpCode):
842   """Shutdown an instance."""
843   OP_ID = "OP_INSTANCE_SHUTDOWN"
844   OP_DSC_FIELD = "instance_name"
845   OP_PARAMS = [
846     _PInstanceName,
847     _PIgnoreOfflineNodes,
848     ("timeout", constants.DEFAULT_SHUTDOWN_TIMEOUT, ht.TPositiveInt),
849     ]
850
851
852 class OpInstanceReboot(OpCode):
853   """Reboot an instance."""
854   OP_ID = "OP_INSTANCE_REBOOT"
855   OP_DSC_FIELD = "instance_name"
856   OP_PARAMS = [
857     _PInstanceName,
858     _PShutdownTimeout,
859     ("ignore_secondaries", False, ht.TBool),
860     ("reboot_type", ht.NoDefault, ht.TElemOf(constants.REBOOT_TYPES)),
861     ]
862
863
864 class OpReplaceDisks(OpCode):
865   """Replace the disks of an instance."""
866   OP_ID = "OP_INSTANCE_REPLACE_DISKS"
867   OP_DSC_FIELD = "instance_name"
868   OP_PARAMS = [
869     _PInstanceName,
870     ("mode", ht.NoDefault, ht.TElemOf(constants.REPLACE_MODES)),
871     ("disks", ht.EmptyList, ht.TListOf(ht.TPositiveInt)),
872     ("remote_node", None, ht.TMaybeString),
873     ("iallocator", None, ht.TMaybeString),
874     ("early_release", False, ht.TBool),
875     ]
876
877
878 class OpInstanceFailover(OpCode):
879   """Failover an instance."""
880   OP_ID = "OP_INSTANCE_FAILOVER"
881   OP_DSC_FIELD = "instance_name"
882   OP_PARAMS = [
883     _PInstanceName,
884     _PShutdownTimeout,
885     ("ignore_consistency", False, ht.TBool),
886     ]
887
888
889 class OpInstanceMigrate(OpCode):
890   """Migrate an instance.
891
892   This migrates (without shutting down an instance) to its secondary
893   node.
894
895   @ivar instance_name: the name of the instance
896   @ivar mode: the migration mode (live, non-live or None for auto)
897
898   """
899   OP_ID = "OP_INSTANCE_MIGRATE"
900   OP_DSC_FIELD = "instance_name"
901   OP_PARAMS = [
902     _PInstanceName,
903     _PMigrationMode,
904     _PMigrationLive,
905     ("cleanup", False, ht.TBool),
906     ]
907
908
909 class OpInstanceMove(OpCode):
910   """Move an instance.
911
912   This move (with shutting down an instance and data copying) to an
913   arbitrary node.
914
915   @ivar instance_name: the name of the instance
916   @ivar target_node: the destination node
917
918   """
919   OP_ID = "OP_INSTANCE_MOVE"
920   OP_DSC_FIELD = "instance_name"
921   OP_PARAMS = [
922     _PInstanceName,
923     _PShutdownTimeout,
924     ("target_node", ht.NoDefault, ht.TNonEmptyString),
925     ]
926
927
928 class OpInstanceConsole(OpCode):
929   """Connect to an instance's console."""
930   OP_ID = "OP_INSTANCE_CONSOLE"
931   OP_DSC_FIELD = "instance_name"
932   OP_PARAMS = [
933     _PInstanceName
934     ]
935
936
937 class OpInstanceActivateDisks(OpCode):
938   """Activate an instance's disks."""
939   OP_ID = "OP_INSTANCE_ACTIVATE_DISKS"
940   OP_DSC_FIELD = "instance_name"
941   OP_PARAMS = [
942     _PInstanceName,
943     ("ignore_size", False, ht.TBool),
944     ]
945
946
947 class OpInstanceDeactivateDisks(OpCode):
948   """Deactivate an instance's disks."""
949   OP_ID = "OP_INSTANCE_DEACTIVATE_DISKS"
950   OP_DSC_FIELD = "instance_name"
951   OP_PARAMS = [
952     _PInstanceName
953     ]
954
955
956 class OpInstanceRecreateDisks(OpCode):
957   """Deactivate an instance's disks."""
958   OP_ID = "OP_INSTANCE_RECREATE_DISKS"
959   OP_DSC_FIELD = "instance_name"
960   OP_PARAMS = [
961     _PInstanceName,
962     ("disks", ht.EmptyList, ht.TListOf(ht.TPositiveInt)),
963     ]
964
965
966 class OpInstanceQuery(OpCode):
967   """Compute the list of instances."""
968   OP_ID = "OP_INSTANCE_QUERY"
969   OP_PARAMS = [
970     _POutputFields,
971     ("names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
972     ("use_locking", False, ht.TBool),
973     ]
974
975
976 class OpInstanceQueryData(OpCode):
977   """Compute the run-time status of instances."""
978   OP_ID = "OP_INSTANCE_QUERY_DATA"
979   OP_PARAMS = [
980     ("instances", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
981     ("static", False, ht.TBool),
982     ]
983
984
985 class OpSetInstanceParams(OpCode):
986   """Change the parameters of an instance."""
987   OP_ID = "OP_INSTANCE_SET_PARAMS"
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.TOr(ht.TDict, ht.TNone)),
1001     ]
1002
1003
1004 class OpInstanceGrowDisk(OpCode):
1005   """Grow a disk of an instance."""
1006   OP_ID = "OP_INSTANCE_GROW_DISK"
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_ID = "OP_GROUP_ADD"
1021   OP_DSC_FIELD = "group_name"
1022   OP_PARAMS = [
1023     _PGroupName,
1024     ("ndparams", None, ht.TOr(ht.TDict, ht.TNone)),
1025     ("alloc_policy", None,
1026      ht.TOr(ht.TNone, ht.TElemOf(constants.VALID_ALLOC_POLICIES))),
1027     ]
1028
1029
1030 class OpGroupAssignNodes(OpCode):
1031   """Assign nodes to a node group."""
1032   OP_ID = "OP_GROUP_ASSIGN_NODES"
1033   OP_DSC_FIELD = "group_name"
1034   OP_PARAMS = [
1035     _PGroupName,
1036     _PForce,
1037     ("nodes", ht.NoDefault, ht.TListOf(ht.TNonEmptyString)),
1038     ]
1039
1040
1041 class OpGroupQuery(OpCode):
1042   """Compute the list of node groups."""
1043   OP_ID = "OP_GROUP_QUERY"
1044   OP_PARAMS = [
1045     _POutputFields,
1046     ("names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
1047     ]
1048
1049
1050 class OpGroupSetParams(OpCode):
1051   """Change the parameters of a node group."""
1052   OP_ID = "OP_GROUP_SET_PARAMS"
1053   OP_DSC_FIELD = "group_name"
1054   OP_PARAMS = [
1055     _PGroupName,
1056     ("ndparams", None, ht.TOr(ht.TDict, ht.TNone)),
1057     ("alloc_policy", None, ht.TOr(ht.TNone,
1058                                   ht.TElemOf(constants.VALID_ALLOC_POLICIES))),
1059     ]
1060
1061
1062 class OpGroupRemove(OpCode):
1063   """Remove a node group from the cluster."""
1064   OP_ID = "OP_GROUP_REMOVE"
1065   OP_DSC_FIELD = "group_name"
1066   OP_PARAMS = [
1067     _PGroupName,
1068     ]
1069
1070
1071 class OpGroupRename(OpCode):
1072   """Rename a node group in the cluster."""
1073   OP_ID = "OP_GROUP_RENAME"
1074   OP_DSC_FIELD = "old_name"
1075   OP_PARAMS = [
1076     ("old_name", ht.NoDefault, ht.TNonEmptyString),
1077     ("new_name", ht.NoDefault, ht.TNonEmptyString),
1078     ]
1079
1080
1081 # OS opcodes
1082 class OpDiagnoseOS(OpCode):
1083   """Compute the list of guest operating systems."""
1084   OP_ID = "OP_OS_DIAGNOSE"
1085   OP_PARAMS = [
1086     _POutputFields,
1087     ("names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
1088     ]
1089
1090
1091 # Exports opcodes
1092 class OpBackupQuery(OpCode):
1093   """Compute the list of exported images."""
1094   OP_ID = "OP_BACKUP_QUERY"
1095   OP_PARAMS = [
1096     ("nodes", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
1097     ("use_locking", False, ht.TBool),
1098     ]
1099
1100
1101 class OpBackupPrepare(OpCode):
1102   """Prepares an instance export.
1103
1104   @ivar instance_name: Instance name
1105   @ivar mode: Export mode (one of L{constants.EXPORT_MODES})
1106
1107   """
1108   OP_ID = "OP_BACKUP_PREPARE"
1109   OP_DSC_FIELD = "instance_name"
1110   OP_PARAMS = [
1111     _PInstanceName,
1112     ("mode", ht.NoDefault, ht.TElemOf(constants.EXPORT_MODES)),
1113     ]
1114
1115
1116 class OpBackupExport(OpCode):
1117   """Export an instance.
1118
1119   For local exports, the export destination is the node name. For remote
1120   exports, the export destination is a list of tuples, each consisting of
1121   hostname/IP address, port, HMAC and HMAC salt. The HMAC is calculated using
1122   the cluster domain secret over the value "${index}:${hostname}:${port}". The
1123   destination X509 CA must be a signed certificate.
1124
1125   @ivar mode: Export mode (one of L{constants.EXPORT_MODES})
1126   @ivar target_node: Export destination
1127   @ivar x509_key_name: X509 key to use (remote export only)
1128   @ivar destination_x509_ca: Destination X509 CA in PEM format (remote export
1129                              only)
1130
1131   """
1132   OP_ID = "OP_BACKUP_EXPORT"
1133   OP_DSC_FIELD = "instance_name"
1134   OP_PARAMS = [
1135     _PInstanceName,
1136     _PShutdownTimeout,
1137     # TODO: Rename target_node as it changes meaning for different export modes
1138     # (e.g. "destination")
1139     ("target_node", ht.NoDefault, ht.TOr(ht.TNonEmptyString, ht.TList)),
1140     ("shutdown", True, ht.TBool),
1141     ("remove_instance", False, ht.TBool),
1142     ("ignore_remove_failures", False, ht.TBool),
1143     ("mode", constants.EXPORT_MODE_LOCAL, ht.TElemOf(constants.EXPORT_MODES)),
1144     ("x509_key_name", None, ht.TOr(ht.TList, ht.TNone)),
1145     ("destination_x509_ca", None, ht.TMaybeString),
1146     ]
1147
1148
1149 class OpBackupRemove(OpCode):
1150   """Remove an instance's export."""
1151   OP_ID = "OP_BACKUP_REMOVE"
1152   OP_DSC_FIELD = "instance_name"
1153   OP_PARAMS = [
1154     _PInstanceName,
1155     ]
1156
1157
1158 # Tags opcodes
1159 class OpGetTags(OpCode):
1160   """Returns the tags of the given object."""
1161   OP_ID = "OP_TAGS_GET"
1162   OP_DSC_FIELD = "name"
1163   OP_PARAMS = [
1164     _PTagKind,
1165     # Name is only meaningful for nodes and instances
1166     ("name", ht.NoDefault, ht.TMaybeString),
1167     ]
1168
1169
1170 class OpSearchTags(OpCode):
1171   """Searches the tags in the cluster for a given pattern."""
1172   OP_ID = "OP_TAGS_SEARCH"
1173   OP_DSC_FIELD = "pattern"
1174   OP_PARAMS = [
1175     ("pattern", ht.NoDefault, ht.TNonEmptyString),
1176     ]
1177
1178
1179 class OpAddTags(OpCode):
1180   """Add a list of tags on a given object."""
1181   OP_ID = "OP_TAGS_SET"
1182   OP_PARAMS = [
1183     _PTagKind,
1184     _PTags,
1185     # Name is only meaningful for nodes and instances
1186     ("name", ht.NoDefault, ht.TMaybeString),
1187     ]
1188
1189
1190 class OpDelTags(OpCode):
1191   """Remove a list of tags from a given object."""
1192   OP_ID = "OP_TAGS_DEL"
1193   OP_PARAMS = [
1194     _PTagKind,
1195     _PTags,
1196     # Name is only meaningful for nodes and instances
1197     ("name", ht.NoDefault, ht.TMaybeString),
1198     ]
1199
1200 # Test opcodes
1201 class OpTestDelay(OpCode):
1202   """Sleeps for a configured amount of time.
1203
1204   This is used just for debugging and testing.
1205
1206   Parameters:
1207     - duration: the time to sleep
1208     - on_master: if true, sleep on the master
1209     - on_nodes: list of nodes in which to sleep
1210
1211   If the on_master parameter is true, it will execute a sleep on the
1212   master (before any node sleep).
1213
1214   If the on_nodes list is not empty, it will sleep on those nodes
1215   (after the sleep on the master, if that is enabled).
1216
1217   As an additional feature, the case of duration < 0 will be reported
1218   as an execution error, so this opcode can be used as a failure
1219   generator. The case of duration == 0 will not be treated specially.
1220
1221   """
1222   OP_ID = "OP_TEST_DELAY"
1223   OP_DSC_FIELD = "duration"
1224   OP_PARAMS = [
1225     ("duration", ht.NoDefault, ht.TFloat),
1226     ("on_master", True, ht.TBool),
1227     ("on_nodes", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
1228     ("repeat", 0, ht.TPositiveInt)
1229     ]
1230
1231
1232 class OpTestAllocator(OpCode):
1233   """Allocator framework testing.
1234
1235   This opcode has two modes:
1236     - gather and return allocator input for a given mode (allocate new
1237       or replace secondary) and a given instance definition (direction
1238       'in')
1239     - run a selected allocator for a given operation (as above) and
1240       return the allocator output (direction 'out')
1241
1242   """
1243   OP_ID = "OP_TEST_ALLOCATOR"
1244   OP_DSC_FIELD = "allocator"
1245   OP_PARAMS = [
1246     ("direction", ht.NoDefault,
1247      ht.TElemOf(constants.VALID_IALLOCATOR_DIRECTIONS)),
1248     ("mode", ht.NoDefault, ht.TElemOf(constants.VALID_IALLOCATOR_MODES)),
1249     ("name", ht.NoDefault, ht.TNonEmptyString),
1250     ("nics", ht.NoDefault, ht.TOr(ht.TNone, ht.TListOf(
1251       ht.TDictOf(ht.TElemOf(["mac", "ip", "bridge"]),
1252                ht.TOr(ht.TNone, ht.TNonEmptyString))))),
1253     ("disks", ht.NoDefault, ht.TOr(ht.TNone, ht.TList)),
1254     ("hypervisor", None, ht.TMaybeString),
1255     ("allocator", None, ht.TMaybeString),
1256     ("tags", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
1257     ("mem_size", None, ht.TOr(ht.TNone, ht.TPositiveInt)),
1258     ("vcpus", None, ht.TOr(ht.TNone, ht.TPositiveInt)),
1259     ("os", None, ht.TMaybeString),
1260     ("disk_template", None, ht.TMaybeString),
1261     ("evac_nodes", None, ht.TOr(ht.TNone, ht.TListOf(ht.TNonEmptyString))),
1262     ]
1263
1264
1265 class OpTestJobqueue(OpCode):
1266   """Utility opcode to test some aspects of the job queue.
1267
1268   """
1269   OP_ID = "OP_TEST_JQUEUE"
1270   OP_PARAMS = [
1271     ("notify_waitlock", False, ht.TBool),
1272     ("notify_exec", False, ht.TBool),
1273     ("log_messages", ht.EmptyList, ht.TListOf(ht.TString)),
1274     ("fail", False, ht.TBool),
1275     ]
1276
1277
1278 class OpTestDummy(OpCode):
1279   """Utility opcode used by unittests.
1280
1281   """
1282   OP_ID = "OP_TEST_DUMMY"
1283   OP_PARAMS = [
1284     ("result", ht.NoDefault, ht.NoType),
1285     ("messages", ht.NoDefault, ht.NoType),
1286     ("fail", ht.NoDefault, ht.NoType),
1287     ]
1288   WITH_LU = False
1289
1290
1291 def _GetOpList():
1292   """Returns list of all defined opcodes.
1293
1294   Does not eliminate duplicates by C{OP_ID}.
1295
1296   """
1297   return [v for v in globals().values()
1298           if (isinstance(v, type) and issubclass(v, OpCode) and
1299               hasattr(v, "OP_ID") and v is not OpCode)]
1300
1301
1302 OP_MAPPING = dict((v.OP_ID, v) for v in _GetOpList())