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