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