Merge branch 'devel-2.5'
[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=R0903
35
36 import logging
37 import re
38
39 from ganeti import compat
40 from ganeti import constants
41 from ganeti import errors
42 from ganeti import ht
43
44
45 # Common opcode attributes
46
47 #: output fields for a query operation
48 _POutputFields = ("output_fields", ht.NoDefault, ht.TListOf(ht.TNonEmptyString),
49                   "Selected output fields")
50
51 #: the shutdown timeout
52 _PShutdownTimeout = \
53   ("shutdown_timeout", constants.DEFAULT_SHUTDOWN_TIMEOUT, ht.TPositiveInt,
54    "How long to wait for instance to shut down")
55
56 #: the force parameter
57 _PForce = ("force", False, ht.TBool, "Whether to force the operation")
58
59 #: a required instance name (for single-instance LUs)
60 _PInstanceName = ("instance_name", ht.NoDefault, ht.TNonEmptyString,
61                   "Instance name")
62
63 #: Whether to ignore offline nodes
64 _PIgnoreOfflineNodes = ("ignore_offline_nodes", False, ht.TBool,
65                         "Whether to ignore offline nodes")
66
67 #: a required node name (for single-node LUs)
68 _PNodeName = ("node_name", ht.NoDefault, ht.TNonEmptyString, "Node name")
69
70 #: a required node group name (for single-group LUs)
71 _PGroupName = ("group_name", ht.NoDefault, ht.TNonEmptyString, "Group name")
72
73 #: Migration type (live/non-live)
74 _PMigrationMode = ("mode", None,
75                    ht.TOr(ht.TNone, ht.TElemOf(constants.HT_MIGRATION_MODES)),
76                    "Migration mode")
77
78 #: Obsolete 'live' migration mode (boolean)
79 _PMigrationLive = ("live", None, ht.TMaybeBool,
80                    "Legacy setting for live migration, do not use")
81
82 #: Tag type
83 _PTagKind = ("kind", ht.NoDefault, ht.TElemOf(constants.VALID_TAG_TYPES), None)
84
85 #: List of tag strings
86 _PTags = ("tags", ht.NoDefault, ht.TListOf(ht.TNonEmptyString), None)
87
88 _PForceVariant = ("force_variant", False, ht.TBool,
89                   "Whether to force an unknown OS variant")
90
91 _PWaitForSync = ("wait_for_sync", True, ht.TBool,
92                  "Whether to wait for the disk to synchronize")
93
94 _PIgnoreConsistency = ("ignore_consistency", False, ht.TBool,
95                        "Whether to ignore disk consistency")
96
97 _PStorageName = ("name", ht.NoDefault, ht.TMaybeString, "Storage name")
98
99 _PUseLocking = ("use_locking", False, ht.TBool,
100                 "Whether to use synchronization")
101
102 _PNameCheck = ("name_check", True, ht.TBool, "Whether to check name")
103
104 _PNodeGroupAllocPolicy = \
105   ("alloc_policy", None,
106    ht.TOr(ht.TNone, ht.TElemOf(constants.VALID_ALLOC_POLICIES)),
107    "Instance allocation policy")
108
109 _PGroupNodeParams = ("ndparams", None, ht.TMaybeDict,
110                      "Default node parameters for group")
111
112 _PQueryWhat = ("what", ht.NoDefault, ht.TElemOf(constants.QR_VIA_OP),
113                "Resource(s) to query for")
114
115 _PEarlyRelease = ("early_release", False, ht.TBool,
116                   "Whether to release locks as soon as possible")
117
118 _PIpCheckDoc = "Whether to ensure instance's IP address is inactive"
119
120 #: Do not remember instance state changes
121 _PNoRemember = ("no_remember", False, ht.TBool,
122                 "Do not remember the state change")
123
124 #: Target node for instance migration/failover
125 _PMigrationTargetNode = ("target_node", None, ht.TMaybeString,
126                          "Target node for shared-storage instances")
127
128 _PStartupPaused = ("startup_paused", False, ht.TBool,
129                    "Pause instance at startup")
130
131 _PVerbose = ("verbose", False, ht.TBool, "Verbose mode")
132
133 # Parameters for cluster verification
134 _PDebugSimulateErrors = ("debug_simulate_errors", False, ht.TBool,
135                          "Whether to simulate errors (useful for debugging)")
136 _PErrorCodes = ("error_codes", False, ht.TBool, "Error codes")
137 _PSkipChecks = ("skip_checks", ht.EmptyList,
138                 ht.TListOf(ht.TElemOf(constants.VERIFY_OPTIONAL_CHECKS)),
139                 "Which checks to skip")
140
141 #: OP_ID conversion regular expression
142 _OPID_RE = re.compile("([a-z])([A-Z])")
143
144 #: Utility function for L{OpClusterSetParams}
145 _TestClusterOsList = ht.TOr(ht.TNone,
146   ht.TListOf(ht.TAnd(ht.TList, ht.TIsLength(2),
147     ht.TMap(ht.WithDesc("GetFirstItem")(compat.fst),
148             ht.TElemOf(constants.DDMS_VALUES)))))
149
150
151 # TODO: Generate check from constants.INIC_PARAMS_TYPES
152 #: Utility function for testing NIC definitions
153 _TestNicDef = ht.TDictOf(ht.TElemOf(constants.INIC_PARAMS),
154                          ht.TOr(ht.TNone, ht.TNonEmptyString))
155
156 _TSetParamsResultItemItems = [
157   ht.Comment("name of changed parameter")(ht.TNonEmptyString),
158   ht.TAny,
159   ]
160
161 _TSetParamsResult = \
162   ht.TListOf(ht.TAnd(ht.TIsLength(len(_TSetParamsResultItemItems)),
163                      ht.TItems(_TSetParamsResultItemItems)))
164
165 _SUMMARY_PREFIX = {
166   "CLUSTER_": "C_",
167   "GROUP_": "G_",
168   "NODE_": "N_",
169   "INSTANCE_": "I_",
170   }
171
172 #: Attribute name for dependencies
173 DEPEND_ATTR = "depends"
174
175 #: Attribute name for comment
176 COMMENT_ATTR = "comment"
177
178
179 def _NameToId(name):
180   """Convert an opcode class name to an OP_ID.
181
182   @type name: string
183   @param name: the class name, as OpXxxYyy
184   @rtype: string
185   @return: the name in the OP_XXXX_YYYY format
186
187   """
188   if not name.startswith("Op"):
189     return None
190   # Note: (?<=[a-z])(?=[A-Z]) would be ideal, since it wouldn't
191   # consume any input, and hence we would just have all the elements
192   # in the list, one by one; but it seems that split doesn't work on
193   # non-consuming input, hence we have to process the input string a
194   # bit
195   name = _OPID_RE.sub(r"\1,\2", name)
196   elems = name.split(",")
197   return "_".join(n.upper() for n in elems)
198
199
200 def RequireFileStorage():
201   """Checks that file storage is enabled.
202
203   While it doesn't really fit into this module, L{utils} was deemed too large
204   of a dependency to be imported for just one or two functions.
205
206   @raise errors.OpPrereqError: when file storage is disabled
207
208   """
209   if not constants.ENABLE_FILE_STORAGE:
210     raise errors.OpPrereqError("File storage disabled at configure time",
211                                errors.ECODE_INVAL)
212
213
214 def RequireSharedFileStorage():
215   """Checks that shared file storage is enabled.
216
217   While it doesn't really fit into this module, L{utils} was deemed too large
218   of a dependency to be imported for just one or two functions.
219
220   @raise errors.OpPrereqError: when shared file storage is disabled
221
222   """
223   if not constants.ENABLE_SHARED_FILE_STORAGE:
224     raise errors.OpPrereqError("Shared file storage disabled at"
225                                " configure time", errors.ECODE_INVAL)
226
227
228 @ht.WithDesc("CheckFileStorage")
229 def _CheckFileStorage(value):
230   """Ensures file storage is enabled if used.
231
232   """
233   if value == constants.DT_FILE:
234     RequireFileStorage()
235   elif value == constants.DT_SHARED_FILE:
236     RequireSharedFileStorage()
237   return True
238
239
240 def _BuildDiskTemplateCheck(accept_none):
241   """Builds check for disk template.
242
243   @type accept_none: bool
244   @param accept_none: whether to accept None as a correct value
245   @rtype: callable
246
247   """
248   template_check = ht.TElemOf(constants.DISK_TEMPLATES)
249
250   if accept_none:
251     template_check = ht.TOr(template_check, ht.TNone)
252
253   return ht.TAnd(template_check, _CheckFileStorage)
254
255
256 def _CheckStorageType(storage_type):
257   """Ensure a given storage type is valid.
258
259   """
260   if storage_type not in constants.VALID_STORAGE_TYPES:
261     raise errors.OpPrereqError("Unknown storage type: %s" % storage_type,
262                                errors.ECODE_INVAL)
263   if storage_type == constants.ST_FILE:
264     RequireFileStorage()
265   return True
266
267
268 #: Storage type parameter
269 _PStorageType = ("storage_type", ht.NoDefault, _CheckStorageType,
270                  "Storage type")
271
272
273 class _AutoOpParamSlots(type):
274   """Meta class for opcode definitions.
275
276   """
277   def __new__(mcs, name, bases, attrs):
278     """Called when a class should be created.
279
280     @param mcs: The meta class
281     @param name: Name of created class
282     @param bases: Base classes
283     @type attrs: dict
284     @param attrs: Class attributes
285
286     """
287     assert "__slots__" not in attrs, \
288       "Class '%s' defines __slots__ when it should use OP_PARAMS" % name
289     assert "OP_ID" not in attrs, "Class '%s' defining OP_ID" % name
290
291     attrs["OP_ID"] = _NameToId(name)
292
293     # Always set OP_PARAMS to avoid duplicates in BaseOpCode.GetAllParams
294     params = attrs.setdefault("OP_PARAMS", [])
295
296     # Use parameter names as slots
297     slots = [pname for (pname, _, _, _) in params]
298
299     assert "OP_DSC_FIELD" not in attrs or attrs["OP_DSC_FIELD"] in slots, \
300       "Class '%s' uses unknown field in OP_DSC_FIELD" % name
301
302     attrs["__slots__"] = slots
303
304     return type.__new__(mcs, name, bases, attrs)
305
306
307 class BaseOpCode(object):
308   """A simple serializable object.
309
310   This object serves as a parent class for OpCode without any custom
311   field handling.
312
313   """
314   # pylint: disable=E1101
315   # as OP_ID is dynamically defined
316   __metaclass__ = _AutoOpParamSlots
317
318   def __init__(self, **kwargs):
319     """Constructor for BaseOpCode.
320
321     The constructor takes only keyword arguments and will set
322     attributes on this object based on the passed arguments. As such,
323     it means that you should not pass arguments which are not in the
324     __slots__ attribute for this class.
325
326     """
327     slots = self._all_slots()
328     for key in kwargs:
329       if key not in slots:
330         raise TypeError("Object %s doesn't support the parameter '%s'" %
331                         (self.__class__.__name__, key))
332       setattr(self, key, kwargs[key])
333
334   def __getstate__(self):
335     """Generic serializer.
336
337     This method just returns the contents of the instance as a
338     dictionary.
339
340     @rtype:  C{dict}
341     @return: the instance attributes and their values
342
343     """
344     state = {}
345     for name in self._all_slots():
346       if hasattr(self, name):
347         state[name] = getattr(self, name)
348     return state
349
350   def __setstate__(self, state):
351     """Generic unserializer.
352
353     This method just restores from the serialized state the attributes
354     of the current instance.
355
356     @param state: the serialized opcode data
357     @type state:  C{dict}
358
359     """
360     if not isinstance(state, dict):
361       raise ValueError("Invalid data to __setstate__: expected dict, got %s" %
362                        type(state))
363
364     for name in self._all_slots():
365       if name not in state and hasattr(self, name):
366         delattr(self, name)
367
368     for name in state:
369       setattr(self, name, state[name])
370
371   @classmethod
372   def _all_slots(cls):
373     """Compute the list of all declared slots for a class.
374
375     """
376     slots = []
377     for parent in cls.__mro__:
378       slots.extend(getattr(parent, "__slots__", []))
379     return slots
380
381   @classmethod
382   def GetAllParams(cls):
383     """Compute list of all parameters for an opcode.
384
385     """
386     slots = []
387     for parent in cls.__mro__:
388       slots.extend(getattr(parent, "OP_PARAMS", []))
389     return slots
390
391   def Validate(self, set_defaults):
392     """Validate opcode parameters, optionally setting default values.
393
394     @type set_defaults: bool
395     @param set_defaults: Whether to set default values
396     @raise errors.OpPrereqError: When a parameter value doesn't match
397                                  requirements
398
399     """
400     for (attr_name, default, test, _) in self.GetAllParams():
401       assert test == ht.NoType or callable(test)
402
403       if not hasattr(self, attr_name):
404         if default == ht.NoDefault:
405           raise errors.OpPrereqError("Required parameter '%s.%s' missing" %
406                                      (self.OP_ID, attr_name),
407                                      errors.ECODE_INVAL)
408         elif set_defaults:
409           if callable(default):
410             dval = default()
411           else:
412             dval = default
413           setattr(self, attr_name, dval)
414
415       if test == ht.NoType:
416         # no tests here
417         continue
418
419       if set_defaults or hasattr(self, attr_name):
420         attr_val = getattr(self, attr_name)
421         if not test(attr_val):
422           logging.error("OpCode %s, parameter %s, has invalid type %s/value %s",
423                         self.OP_ID, attr_name, type(attr_val), attr_val)
424           raise errors.OpPrereqError("Parameter '%s.%s' fails validation" %
425                                      (self.OP_ID, attr_name),
426                                      errors.ECODE_INVAL)
427
428
429 def _BuildJobDepCheck(relative):
430   """Builds check for job dependencies (L{DEPEND_ATTR}).
431
432   @type relative: bool
433   @param relative: Whether to accept relative job IDs (negative)
434   @rtype: callable
435
436   """
437   if relative:
438     job_id = ht.TOr(ht.TJobId, ht.TRelativeJobId)
439   else:
440     job_id = ht.TJobId
441
442   job_dep = \
443     ht.TAnd(ht.TIsLength(2),
444             ht.TItems([job_id,
445                        ht.TListOf(ht.TElemOf(constants.JOBS_FINALIZED))]))
446
447   return ht.TOr(ht.TNone, ht.TListOf(job_dep))
448
449
450 TNoRelativeJobDependencies = _BuildJobDepCheck(False)
451
452 #: List of submission status and job ID as returned by C{SubmitManyJobs}
453 _TJobIdListItem = \
454   ht.TAnd(ht.TIsLength(2),
455           ht.TItems([ht.Comment("success")(ht.TBool),
456                      ht.Comment("Job ID if successful, error message"
457                                 " otherwise")(ht.TOr(ht.TString,
458                                                      ht.TJobId))]))
459 TJobIdList = ht.TListOf(_TJobIdListItem)
460
461 #: Result containing only list of submitted jobs
462 TJobIdListOnly = ht.TStrictDict(True, True, {
463   constants.JOB_IDS_KEY: ht.Comment("List of submitted jobs")(TJobIdList),
464   })
465
466
467 class OpCode(BaseOpCode):
468   """Abstract OpCode.
469
470   This is the root of the actual OpCode hierarchy. All clases derived
471   from this class should override OP_ID.
472
473   @cvar OP_ID: The ID of this opcode. This should be unique amongst all
474                children of this class.
475   @cvar OP_DSC_FIELD: The name of a field whose value will be included in the
476                       string returned by Summary(); see the docstring of that
477                       method for details).
478   @cvar OP_PARAMS: List of opcode attributes, the default values they should
479                    get if not already defined, and types they must match.
480   @cvar OP_RESULT: Callable to verify opcode result
481   @cvar WITH_LU: Boolean that specifies whether this should be included in
482       mcpu's dispatch table
483   @ivar dry_run: Whether the LU should be run in dry-run mode, i.e. just
484                  the check steps
485   @ivar priority: Opcode priority for queue
486
487   """
488   # pylint: disable=E1101
489   # as OP_ID is dynamically defined
490   WITH_LU = True
491   OP_PARAMS = [
492     ("dry_run", None, ht.TMaybeBool, "Run checks only, don't execute"),
493     ("debug_level", None, ht.TOr(ht.TNone, ht.TPositiveInt), "Debug level"),
494     ("priority", constants.OP_PRIO_DEFAULT,
495      ht.TElemOf(constants.OP_PRIO_SUBMIT_VALID), "Opcode priority"),
496     (DEPEND_ATTR, None, _BuildJobDepCheck(True),
497      "Job dependencies; if used through ``SubmitManyJobs`` relative (negative)"
498      " job IDs can be used"),
499     (COMMENT_ATTR, None, ht.TMaybeString,
500      "Comment describing the purpose of the opcode"),
501     ]
502   OP_RESULT = None
503
504   def __getstate__(self):
505     """Specialized getstate for opcodes.
506
507     This method adds to the state dictionary the OP_ID of the class,
508     so that on unload we can identify the correct class for
509     instantiating the opcode.
510
511     @rtype:   C{dict}
512     @return:  the state as a dictionary
513
514     """
515     data = BaseOpCode.__getstate__(self)
516     data["OP_ID"] = self.OP_ID
517     return data
518
519   @classmethod
520   def LoadOpCode(cls, data):
521     """Generic load opcode method.
522
523     The method identifies the correct opcode class from the dict-form
524     by looking for a OP_ID key, if this is not found, or its value is
525     not available in this module as a child of this class, we fail.
526
527     @type data:  C{dict}
528     @param data: the serialized opcode
529
530     """
531     if not isinstance(data, dict):
532       raise ValueError("Invalid data to LoadOpCode (%s)" % type(data))
533     if "OP_ID" not in data:
534       raise ValueError("Invalid data to LoadOpcode, missing OP_ID")
535     op_id = data["OP_ID"]
536     op_class = None
537     if op_id in OP_MAPPING:
538       op_class = OP_MAPPING[op_id]
539     else:
540       raise ValueError("Invalid data to LoadOpCode: OP_ID %s unsupported" %
541                        op_id)
542     op = op_class()
543     new_data = data.copy()
544     del new_data["OP_ID"]
545     op.__setstate__(new_data)
546     return op
547
548   def Summary(self):
549     """Generates a summary description of this opcode.
550
551     The summary is the value of the OP_ID attribute (without the "OP_"
552     prefix), plus the value of the OP_DSC_FIELD attribute, if one was
553     defined; this field should allow to easily identify the operation
554     (for an instance creation job, e.g., it would be the instance
555     name).
556
557     """
558     assert self.OP_ID is not None and len(self.OP_ID) > 3
559     # all OP_ID start with OP_, we remove that
560     txt = self.OP_ID[3:]
561     field_name = getattr(self, "OP_DSC_FIELD", None)
562     if field_name:
563       field_value = getattr(self, field_name, None)
564       if isinstance(field_value, (list, tuple)):
565         field_value = ",".join(str(i) for i in field_value)
566       txt = "%s(%s)" % (txt, field_value)
567     return txt
568
569   def TinySummary(self):
570     """Generates a compact summary description of the opcode.
571
572     """
573     assert self.OP_ID.startswith("OP_")
574
575     text = self.OP_ID[3:]
576
577     for (prefix, supplement) in _SUMMARY_PREFIX.items():
578       if text.startswith(prefix):
579         return supplement + text[len(prefix):]
580
581     return text
582
583
584 # cluster opcodes
585
586 class OpClusterPostInit(OpCode):
587   """Post cluster initialization.
588
589   This opcode does not touch the cluster at all. Its purpose is to run hooks
590   after the cluster has been initialized.
591
592   """
593
594
595 class OpClusterDestroy(OpCode):
596   """Destroy the cluster.
597
598   This opcode has no other parameters. All the state is irreversibly
599   lost after the execution of this opcode.
600
601   """
602
603
604 class OpClusterQuery(OpCode):
605   """Query cluster information."""
606
607
608 class OpClusterVerify(OpCode):
609   """Submits all jobs necessary to verify the cluster.
610
611   """
612   OP_PARAMS = [
613     _PDebugSimulateErrors,
614     _PErrorCodes,
615     _PSkipChecks,
616     _PVerbose,
617     ("group_name", None, ht.TMaybeString, "Group to verify")
618     ]
619   OP_RESULT = TJobIdListOnly
620
621
622 class OpClusterVerifyConfig(OpCode):
623   """Verify the cluster config.
624
625   """
626   OP_PARAMS = [
627     _PDebugSimulateErrors,
628     _PErrorCodes,
629     _PVerbose,
630     ]
631   OP_RESULT = ht.TBool
632
633
634 class OpClusterVerifyGroup(OpCode):
635   """Run verify on a node group from the cluster.
636
637   @type skip_checks: C{list}
638   @ivar skip_checks: steps to be skipped from the verify process; this
639                      needs to be a subset of
640                      L{constants.VERIFY_OPTIONAL_CHECKS}; currently
641                      only L{constants.VERIFY_NPLUSONE_MEM} can be passed
642
643   """
644   OP_DSC_FIELD = "group_name"
645   OP_PARAMS = [
646     _PGroupName,
647     _PDebugSimulateErrors,
648     _PErrorCodes,
649     _PSkipChecks,
650     _PVerbose,
651     ]
652   OP_RESULT = ht.TBool
653
654
655 class OpClusterVerifyDisks(OpCode):
656   """Verify the cluster disks.
657
658   """
659   OP_RESULT = TJobIdListOnly
660
661
662 class OpGroupVerifyDisks(OpCode):
663   """Verifies the status of all disks in a node group.
664
665   Result: a tuple of three elements:
666     - dict of node names with issues (values: error msg)
667     - list of instances with degraded disks (that should be activated)
668     - dict of instances with missing logical volumes (values: (node, vol)
669       pairs with details about the missing volumes)
670
671   In normal operation, all lists should be empty. A non-empty instance
672   list (3rd element of the result) is still ok (errors were fixed) but
673   non-empty node list means some node is down, and probably there are
674   unfixable drbd errors.
675
676   Note that only instances that are drbd-based are taken into
677   consideration. This might need to be revisited in the future.
678
679   """
680   OP_DSC_FIELD = "group_name"
681   OP_PARAMS = [
682     _PGroupName,
683     ]
684   OP_RESULT = \
685     ht.TAnd(ht.TIsLength(3),
686             ht.TItems([ht.TDictOf(ht.TString, ht.TString),
687                        ht.TListOf(ht.TString),
688                        ht.TDictOf(ht.TString, ht.TListOf(ht.TString))]))
689
690
691 class OpClusterRepairDiskSizes(OpCode):
692   """Verify the disk sizes of the instances and fixes configuration
693   mimatches.
694
695   Parameters: optional instances list, in case we want to restrict the
696   checks to only a subset of the instances.
697
698   Result: a list of tuples, (instance, disk, new-size) for changed
699   configurations.
700
701   In normal operation, the list should be empty.
702
703   @type instances: list
704   @ivar instances: the list of instances to check, or empty for all instances
705
706   """
707   OP_PARAMS = [
708     ("instances", ht.EmptyList, ht.TListOf(ht.TNonEmptyString), None),
709     ]
710
711
712 class OpClusterConfigQuery(OpCode):
713   """Query cluster configuration values."""
714   OP_PARAMS = [
715     _POutputFields
716     ]
717
718
719 class OpClusterRename(OpCode):
720   """Rename the cluster.
721
722   @type name: C{str}
723   @ivar name: The new name of the cluster. The name and/or the master IP
724               address will be changed to match the new name and its IP
725               address.
726
727   """
728   OP_DSC_FIELD = "name"
729   OP_PARAMS = [
730     ("name", ht.NoDefault, ht.TNonEmptyString, None),
731     ]
732
733
734 class OpClusterSetParams(OpCode):
735   """Change the parameters of the cluster.
736
737   @type vg_name: C{str} or C{None}
738   @ivar vg_name: The new volume group name or None to disable LVM usage.
739
740   """
741   OP_PARAMS = [
742     ("vg_name", None, ht.TMaybeString, "Volume group name"),
743     ("enabled_hypervisors", None,
744      ht.TOr(ht.TAnd(ht.TListOf(ht.TElemOf(constants.HYPER_TYPES)), ht.TTrue),
745             ht.TNone),
746      "List of enabled hypervisors"),
747     ("hvparams", None, ht.TOr(ht.TDictOf(ht.TNonEmptyString, ht.TDict),
748                               ht.TNone),
749      "Cluster-wide hypervisor parameter defaults, hypervisor-dependent"),
750     ("beparams", None, ht.TOr(ht.TDict, ht.TNone),
751      "Cluster-wide backend parameter defaults"),
752     ("os_hvp", None, ht.TOr(ht.TDictOf(ht.TNonEmptyString, ht.TDict),
753                             ht.TNone),
754      "Cluster-wide per-OS hypervisor parameter defaults"),
755     ("osparams", None, ht.TOr(ht.TDictOf(ht.TNonEmptyString, ht.TDict),
756                               ht.TNone),
757      "Cluster-wide OS parameter defaults"),
758     ("candidate_pool_size", None, ht.TOr(ht.TStrictPositiveInt, ht.TNone),
759      "Master candidate pool size"),
760     ("uid_pool", None, ht.NoType,
761      "Set UID pool, must be list of lists describing UID ranges (two items,"
762      " start and end inclusive)"),
763     ("add_uids", None, ht.NoType,
764      "Extend UID pool, must be list of lists describing UID ranges (two"
765      " items, start and end inclusive) to be added"),
766     ("remove_uids", None, ht.NoType,
767      "Shrink UID pool, must be list of lists describing UID ranges (two"
768      " items, start and end inclusive) to be removed"),
769     ("maintain_node_health", None, ht.TMaybeBool,
770      "Whether to automatically maintain node health"),
771     ("prealloc_wipe_disks", None, ht.TMaybeBool,
772      "Whether to wipe disks before allocating them to instances"),
773     ("nicparams", None, ht.TMaybeDict, "Cluster-wide NIC parameter defaults"),
774     ("ndparams", None, ht.TMaybeDict, "Cluster-wide node parameter defaults"),
775     ("drbd_helper", None, ht.TOr(ht.TString, ht.TNone), "DRBD helper program"),
776     ("default_iallocator", None, ht.TOr(ht.TString, ht.TNone),
777      "Default iallocator for cluster"),
778     ("master_netdev", None, ht.TOr(ht.TString, ht.TNone),
779      "Master network device"),
780     ("reserved_lvs", None, ht.TOr(ht.TListOf(ht.TNonEmptyString), ht.TNone),
781      "List of reserved LVs"),
782     ("hidden_os", None, _TestClusterOsList,
783      "Modify list of hidden operating systems. Each modification must have"
784      " two items, the operation and the OS name. The operation can be"
785      " ``%s`` or ``%s``." % (constants.DDM_ADD, constants.DDM_REMOVE)),
786     ("blacklisted_os", None, _TestClusterOsList,
787      "Modify list of blacklisted operating systems. Each modification must have"
788      " two items, the operation and the OS name. The operation can be"
789      " ``%s`` or ``%s``." % (constants.DDM_ADD, constants.DDM_REMOVE)),
790     ]
791
792
793 class OpClusterRedistConf(OpCode):
794   """Force a full push of the cluster configuration.
795
796   """
797
798
799 class OpQuery(OpCode):
800   """Query for resources/items.
801
802   @ivar what: Resources to query for, must be one of L{constants.QR_VIA_OP}
803   @ivar fields: List of fields to retrieve
804   @ivar filter: Query filter
805
806   """
807   OP_DSC_FIELD = "what"
808   OP_PARAMS = [
809     _PQueryWhat,
810     _PUseLocking,
811     ("fields", ht.NoDefault, ht.TListOf(ht.TNonEmptyString),
812      "Requested fields"),
813     ("filter", None, ht.TOr(ht.TNone, ht.TListOf),
814      "Query filter"),
815     ]
816
817
818 class OpQueryFields(OpCode):
819   """Query for available resource/item fields.
820
821   @ivar what: Resources to query for, must be one of L{constants.QR_VIA_OP}
822   @ivar fields: List of fields to retrieve
823
824   """
825   OP_DSC_FIELD = "what"
826   OP_PARAMS = [
827     _PQueryWhat,
828     ("fields", None, ht.TOr(ht.TNone, ht.TListOf(ht.TNonEmptyString)),
829      "Requested fields; if not given, all are returned"),
830     ]
831
832
833 class OpOobCommand(OpCode):
834   """Interact with OOB."""
835   OP_PARAMS = [
836     ("node_names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString),
837      "List of nodes to run the OOB command against"),
838     ("command", None, ht.TElemOf(constants.OOB_COMMANDS),
839      "OOB command to be run"),
840     ("timeout", constants.OOB_TIMEOUT, ht.TInt,
841      "Timeout before the OOB helper will be terminated"),
842     ("ignore_status", False, ht.TBool,
843      "Ignores the node offline status for power off"),
844     ("power_delay", constants.OOB_POWER_DELAY, ht.TPositiveFloat,
845      "Time in seconds to wait between powering on nodes"),
846     ]
847
848
849 # node opcodes
850
851 class OpNodeRemove(OpCode):
852   """Remove a node.
853
854   @type node_name: C{str}
855   @ivar node_name: The name of the node to remove. If the node still has
856                    instances on it, the operation will fail.
857
858   """
859   OP_DSC_FIELD = "node_name"
860   OP_PARAMS = [
861     _PNodeName,
862     ]
863
864
865 class OpNodeAdd(OpCode):
866   """Add a node to the cluster.
867
868   @type node_name: C{str}
869   @ivar node_name: The name of the node to add. This can be a short name,
870                    but it will be expanded to the FQDN.
871   @type primary_ip: IP address
872   @ivar primary_ip: The primary IP of the node. This will be ignored when the
873                     opcode is submitted, but will be filled during the node
874                     add (so it will be visible in the job query).
875   @type secondary_ip: IP address
876   @ivar secondary_ip: The secondary IP of the node. This needs to be passed
877                       if the cluster has been initialized in 'dual-network'
878                       mode, otherwise it must not be given.
879   @type readd: C{bool}
880   @ivar readd: Whether to re-add an existing node to the cluster. If
881                this is not passed, then the operation will abort if the node
882                name is already in the cluster; use this parameter to 'repair'
883                a node that had its configuration broken, or was reinstalled
884                without removal from the cluster.
885   @type group: C{str}
886   @ivar group: The node group to which this node will belong.
887   @type vm_capable: C{bool}
888   @ivar vm_capable: The vm_capable node attribute
889   @type master_capable: C{bool}
890   @ivar master_capable: The master_capable node attribute
891
892   """
893   OP_DSC_FIELD = "node_name"
894   OP_PARAMS = [
895     _PNodeName,
896     ("primary_ip", None, ht.NoType, "Primary IP address"),
897     ("secondary_ip", None, ht.TMaybeString, "Secondary IP address"),
898     ("readd", False, ht.TBool, "Whether node is re-added to cluster"),
899     ("group", None, ht.TMaybeString, "Initial node group"),
900     ("master_capable", None, ht.TMaybeBool,
901      "Whether node can become master or master candidate"),
902     ("vm_capable", None, ht.TMaybeBool,
903      "Whether node can host instances"),
904     ("ndparams", None, ht.TMaybeDict, "Node parameters"),
905     ]
906
907
908 class OpNodeQuery(OpCode):
909   """Compute the list of nodes."""
910   OP_PARAMS = [
911     _POutputFields,
912     _PUseLocking,
913     ("names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString),
914      "Empty list to query all nodes, node names otherwise"),
915     ]
916
917
918 class OpNodeQueryvols(OpCode):
919   """Get list of volumes on node."""
920   OP_PARAMS = [
921     _POutputFields,
922     ("nodes", ht.EmptyList, ht.TListOf(ht.TNonEmptyString),
923      "Empty list to query all nodes, node names otherwise"),
924     ]
925
926
927 class OpNodeQueryStorage(OpCode):
928   """Get information on storage for node(s)."""
929   OP_PARAMS = [
930     _POutputFields,
931     _PStorageType,
932     ("nodes", ht.EmptyList, ht.TListOf(ht.TNonEmptyString), "List of nodes"),
933     ("name", None, ht.TMaybeString, "Storage name"),
934     ]
935
936
937 class OpNodeModifyStorage(OpCode):
938   """Modifies the properies of a storage unit"""
939   OP_PARAMS = [
940     _PNodeName,
941     _PStorageType,
942     _PStorageName,
943     ("changes", ht.NoDefault, ht.TDict, "Requested changes"),
944     ]
945
946
947 class OpRepairNodeStorage(OpCode):
948   """Repairs the volume group on a node."""
949   OP_DSC_FIELD = "node_name"
950   OP_PARAMS = [
951     _PNodeName,
952     _PStorageType,
953     _PStorageName,
954     _PIgnoreConsistency,
955     ]
956
957
958 class OpNodeSetParams(OpCode):
959   """Change the parameters of a node."""
960   OP_DSC_FIELD = "node_name"
961   OP_PARAMS = [
962     _PNodeName,
963     _PForce,
964     ("master_candidate", None, ht.TMaybeBool,
965      "Whether the node should become a master candidate"),
966     ("offline", None, ht.TMaybeBool,
967      "Whether the node should be marked as offline"),
968     ("drained", None, ht.TMaybeBool,
969      "Whether the node should be marked as drained"),
970     ("auto_promote", False, ht.TBool,
971      "Whether node(s) should be promoted to master candidate if necessary"),
972     ("master_capable", None, ht.TMaybeBool,
973      "Denote whether node can become master or master candidate"),
974     ("vm_capable", None, ht.TMaybeBool,
975      "Denote whether node can host instances"),
976     ("secondary_ip", None, ht.TMaybeString,
977      "Change node's secondary IP address"),
978     ("ndparams", None, ht.TMaybeDict, "Set node parameters"),
979     ("powered", None, ht.TMaybeBool,
980      "Whether the node should be marked as powered"),
981     ]
982   OP_RESULT = _TSetParamsResult
983
984
985 class OpNodePowercycle(OpCode):
986   """Tries to powercycle a node."""
987   OP_DSC_FIELD = "node_name"
988   OP_PARAMS = [
989     _PNodeName,
990     _PForce,
991     ]
992
993
994 class OpNodeMigrate(OpCode):
995   """Migrate all instances from a node."""
996   OP_DSC_FIELD = "node_name"
997   OP_PARAMS = [
998     _PNodeName,
999     _PMigrationMode,
1000     _PMigrationLive,
1001     _PMigrationTargetNode,
1002     ("iallocator", None, ht.TMaybeString,
1003      "Iallocator for deciding the target node for shared-storage instances"),
1004     ]
1005
1006
1007 class OpNodeEvacuate(OpCode):
1008   """Evacuate instances off a number of nodes."""
1009   OP_DSC_FIELD = "node_name"
1010   OP_PARAMS = [
1011     _PEarlyRelease,
1012     _PNodeName,
1013     ("remote_node", None, ht.TMaybeString, "New secondary node"),
1014     ("iallocator", None, ht.TMaybeString, "Iallocator for computing solution"),
1015     ("mode", ht.NoDefault, ht.TElemOf(constants.IALLOCATOR_NEVAC_MODES),
1016      "Node evacuation mode"),
1017     ]
1018   OP_RESULT = TJobIdListOnly
1019
1020
1021 # instance opcodes
1022
1023 class OpInstanceCreate(OpCode):
1024   """Create an instance.
1025
1026   @ivar instance_name: Instance name
1027   @ivar mode: Instance creation mode (one of L{constants.INSTANCE_CREATE_MODES})
1028   @ivar source_handshake: Signed handshake from source (remote import only)
1029   @ivar source_x509_ca: Source X509 CA in PEM format (remote import only)
1030   @ivar source_instance_name: Previous name of instance (remote import only)
1031   @ivar source_shutdown_timeout: Shutdown timeout used for source instance
1032     (remote import only)
1033
1034   """
1035   OP_DSC_FIELD = "instance_name"
1036   OP_PARAMS = [
1037     _PInstanceName,
1038     _PForceVariant,
1039     _PWaitForSync,
1040     _PNameCheck,
1041     ("beparams", ht.EmptyDict, ht.TDict, "Backend parameters for instance"),
1042     ("disks", ht.NoDefault,
1043      # TODO: Generate check from constants.IDISK_PARAMS_TYPES
1044      ht.TListOf(ht.TDictOf(ht.TElemOf(constants.IDISK_PARAMS),
1045                            ht.TOr(ht.TNonEmptyString, ht.TInt))),
1046      "Disk descriptions, for example ``[{\"%s\": 100}, {\"%s\": 5}]``;"
1047      " each disk definition must contain a ``%s`` value and"
1048      " can contain an optional ``%s`` value denoting the disk access mode"
1049      " (%s)" %
1050      (constants.IDISK_SIZE, constants.IDISK_SIZE, constants.IDISK_SIZE,
1051       constants.IDISK_MODE,
1052       " or ".join("``%s``" % i for i in sorted(constants.DISK_ACCESS_SET)))),
1053     ("disk_template", ht.NoDefault, _BuildDiskTemplateCheck(True),
1054      "Disk template"),
1055     ("file_driver", None, ht.TOr(ht.TNone, ht.TElemOf(constants.FILE_DRIVER)),
1056      "Driver for file-backed disks"),
1057     ("file_storage_dir", None, ht.TMaybeString,
1058      "Directory for storing file-backed disks"),
1059     ("hvparams", ht.EmptyDict, ht.TDict,
1060      "Hypervisor parameters for instance, hypervisor-dependent"),
1061     ("hypervisor", None, ht.TMaybeString, "Hypervisor"),
1062     ("iallocator", None, ht.TMaybeString,
1063      "Iallocator for deciding which node(s) to use"),
1064     ("identify_defaults", False, ht.TBool,
1065      "Reset instance parameters to default if equal"),
1066     ("ip_check", True, ht.TBool, _PIpCheckDoc),
1067     ("mode", ht.NoDefault, ht.TElemOf(constants.INSTANCE_CREATE_MODES),
1068      "Instance creation mode"),
1069     ("nics", ht.NoDefault, ht.TListOf(_TestNicDef),
1070      "List of NIC (network interface) definitions, for example"
1071      " ``[{}, {}, {\"%s\": \"198.51.100.4\"}]``; each NIC definition can"
1072      " contain the optional values %s" %
1073      (constants.INIC_IP,
1074       ", ".join("``%s``" % i for i in sorted(constants.INIC_PARAMS)))),
1075     ("no_install", None, ht.TMaybeBool,
1076      "Do not install the OS (will disable automatic start)"),
1077     ("osparams", ht.EmptyDict, ht.TDict, "OS parameters for instance"),
1078     ("os_type", None, ht.TMaybeString, "Operating system"),
1079     ("pnode", None, ht.TMaybeString, "Primary node"),
1080     ("snode", None, ht.TMaybeString, "Secondary node"),
1081     ("source_handshake", None, ht.TOr(ht.TList, ht.TNone),
1082      "Signed handshake from source (remote import only)"),
1083     ("source_instance_name", None, ht.TMaybeString,
1084      "Source instance name (remote import only)"),
1085     ("source_shutdown_timeout", constants.DEFAULT_SHUTDOWN_TIMEOUT,
1086      ht.TPositiveInt,
1087      "How long source instance was given to shut down (remote import only)"),
1088     ("source_x509_ca", None, ht.TMaybeString,
1089      "Source X509 CA in PEM format (remote import only)"),
1090     ("src_node", None, ht.TMaybeString, "Source node for import"),
1091     ("src_path", None, ht.TMaybeString, "Source directory for import"),
1092     ("start", True, ht.TBool, "Whether to start instance after creation"),
1093     ("tags", ht.EmptyList, ht.TListOf(ht.TNonEmptyString), "Instance tags"),
1094     ]
1095   OP_RESULT = ht.Comment("instance nodes")(ht.TListOf(ht.TNonEmptyString))
1096
1097
1098 class OpInstanceReinstall(OpCode):
1099   """Reinstall an instance's OS."""
1100   OP_DSC_FIELD = "instance_name"
1101   OP_PARAMS = [
1102     _PInstanceName,
1103     _PForceVariant,
1104     ("os_type", None, ht.TMaybeString, "Instance operating system"),
1105     ("osparams", None, ht.TMaybeDict, "Temporary OS parameters"),
1106     ]
1107
1108
1109 class OpInstanceRemove(OpCode):
1110   """Remove an instance."""
1111   OP_DSC_FIELD = "instance_name"
1112   OP_PARAMS = [
1113     _PInstanceName,
1114     _PShutdownTimeout,
1115     ("ignore_failures", False, ht.TBool,
1116      "Whether to ignore failures during removal"),
1117     ]
1118
1119
1120 class OpInstanceRename(OpCode):
1121   """Rename an instance."""
1122   OP_PARAMS = [
1123     _PInstanceName,
1124     _PNameCheck,
1125     ("new_name", ht.NoDefault, ht.TNonEmptyString, "New instance name"),
1126     ("ip_check", False, ht.TBool, _PIpCheckDoc),
1127     ]
1128   OP_RESULT = ht.Comment("New instance name")(ht.TNonEmptyString)
1129
1130
1131 class OpInstanceStartup(OpCode):
1132   """Startup an instance."""
1133   OP_DSC_FIELD = "instance_name"
1134   OP_PARAMS = [
1135     _PInstanceName,
1136     _PForce,
1137     _PIgnoreOfflineNodes,
1138     ("hvparams", ht.EmptyDict, ht.TDict,
1139      "Temporary hypervisor parameters, hypervisor-dependent"),
1140     ("beparams", ht.EmptyDict, ht.TDict, "Temporary backend parameters"),
1141     _PNoRemember,
1142     _PStartupPaused,
1143     ]
1144
1145
1146 class OpInstanceShutdown(OpCode):
1147   """Shutdown an instance."""
1148   OP_DSC_FIELD = "instance_name"
1149   OP_PARAMS = [
1150     _PInstanceName,
1151     _PIgnoreOfflineNodes,
1152     ("timeout", constants.DEFAULT_SHUTDOWN_TIMEOUT, ht.TPositiveInt,
1153      "How long to wait for instance to shut down"),
1154     _PNoRemember,
1155     ]
1156
1157
1158 class OpInstanceReboot(OpCode):
1159   """Reboot an instance."""
1160   OP_DSC_FIELD = "instance_name"
1161   OP_PARAMS = [
1162     _PInstanceName,
1163     _PShutdownTimeout,
1164     ("ignore_secondaries", False, ht.TBool,
1165      "Whether to start the instance even if secondary disks are failing"),
1166     ("reboot_type", ht.NoDefault, ht.TElemOf(constants.REBOOT_TYPES),
1167      "How to reboot instance"),
1168     ]
1169
1170
1171 class OpInstanceReplaceDisks(OpCode):
1172   """Replace the disks of an instance."""
1173   OP_DSC_FIELD = "instance_name"
1174   OP_PARAMS = [
1175     _PInstanceName,
1176     _PEarlyRelease,
1177     ("mode", ht.NoDefault, ht.TElemOf(constants.REPLACE_MODES),
1178      "Replacement mode"),
1179     ("disks", ht.EmptyList, ht.TListOf(ht.TPositiveInt),
1180      "Disk indexes"),
1181     ("remote_node", None, ht.TMaybeString, "New secondary node"),
1182     ("iallocator", None, ht.TMaybeString,
1183      "Iallocator for deciding new secondary node"),
1184     ]
1185
1186
1187 class OpInstanceFailover(OpCode):
1188   """Failover an instance."""
1189   OP_DSC_FIELD = "instance_name"
1190   OP_PARAMS = [
1191     _PInstanceName,
1192     _PShutdownTimeout,
1193     _PIgnoreConsistency,
1194     _PMigrationTargetNode,
1195     ("iallocator", None, ht.TMaybeString,
1196      "Iallocator for deciding the target node for shared-storage instances"),
1197     ]
1198
1199
1200 class OpInstanceMigrate(OpCode):
1201   """Migrate an instance.
1202
1203   This migrates (without shutting down an instance) to its secondary
1204   node.
1205
1206   @ivar instance_name: the name of the instance
1207   @ivar mode: the migration mode (live, non-live or None for auto)
1208
1209   """
1210   OP_DSC_FIELD = "instance_name"
1211   OP_PARAMS = [
1212     _PInstanceName,
1213     _PMigrationMode,
1214     _PMigrationLive,
1215     _PMigrationTargetNode,
1216     ("cleanup", False, ht.TBool,
1217      "Whether a previously failed migration should be cleaned up"),
1218     ("iallocator", None, ht.TMaybeString,
1219      "Iallocator for deciding the target node for shared-storage instances"),
1220     ("allow_failover", False, ht.TBool,
1221      "Whether we can fallback to failover if migration is not possible"),
1222     ]
1223
1224
1225 class OpInstanceMove(OpCode):
1226   """Move an instance.
1227
1228   This move (with shutting down an instance and data copying) to an
1229   arbitrary node.
1230
1231   @ivar instance_name: the name of the instance
1232   @ivar target_node: the destination node
1233
1234   """
1235   OP_DSC_FIELD = "instance_name"
1236   OP_PARAMS = [
1237     _PInstanceName,
1238     _PShutdownTimeout,
1239     ("target_node", ht.NoDefault, ht.TNonEmptyString, "Target node"),
1240     _PIgnoreConsistency,
1241     ]
1242
1243
1244 class OpInstanceConsole(OpCode):
1245   """Connect to an instance's console."""
1246   OP_DSC_FIELD = "instance_name"
1247   OP_PARAMS = [
1248     _PInstanceName
1249     ]
1250
1251
1252 class OpInstanceActivateDisks(OpCode):
1253   """Activate an instance's disks."""
1254   OP_DSC_FIELD = "instance_name"
1255   OP_PARAMS = [
1256     _PInstanceName,
1257     ("ignore_size", False, ht.TBool, "Whether to ignore recorded size"),
1258     ]
1259
1260
1261 class OpInstanceDeactivateDisks(OpCode):
1262   """Deactivate an instance's disks."""
1263   OP_DSC_FIELD = "instance_name"
1264   OP_PARAMS = [
1265     _PInstanceName,
1266     _PForce,
1267     ]
1268
1269
1270 class OpInstanceRecreateDisks(OpCode):
1271   """Deactivate an instance's disks."""
1272   OP_DSC_FIELD = "instance_name"
1273   OP_PARAMS = [
1274     _PInstanceName,
1275     ("disks", ht.EmptyList, ht.TListOf(ht.TPositiveInt),
1276      "List of disk indexes"),
1277     ("nodes", ht.EmptyList, ht.TListOf(ht.TNonEmptyString),
1278      "New instance nodes, if relocation is desired"),
1279     ]
1280
1281
1282 class OpInstanceQuery(OpCode):
1283   """Compute the list of instances."""
1284   OP_PARAMS = [
1285     _POutputFields,
1286     _PUseLocking,
1287     ("names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString),
1288      "Empty list to query all instances, instance names otherwise"),
1289     ]
1290
1291
1292 class OpInstanceQueryData(OpCode):
1293   """Compute the run-time status of instances."""
1294   OP_PARAMS = [
1295     _PUseLocking,
1296     ("instances", ht.EmptyList, ht.TListOf(ht.TNonEmptyString),
1297      "Instance names"),
1298     ("static", False, ht.TBool,
1299      "Whether to only return configuration data without querying"
1300      " nodes"),
1301     ]
1302
1303
1304 class OpInstanceSetParams(OpCode):
1305   """Change the parameters of an instance."""
1306   OP_DSC_FIELD = "instance_name"
1307   OP_PARAMS = [
1308     _PInstanceName,
1309     _PForce,
1310     _PForceVariant,
1311     # TODO: Use _TestNicDef
1312     ("nics", ht.EmptyList, ht.TList,
1313      "List of NIC changes. Each item is of the form ``(op, settings)``."
1314      " ``op`` can be ``%s`` to add a new NIC with the specified settings,"
1315      " ``%s`` to remove the last NIC or a number to modify the settings"
1316      " of the NIC with that index." %
1317      (constants.DDM_ADD, constants.DDM_REMOVE)),
1318     ("disks", ht.EmptyList, ht.TList, "List of disk changes. See ``nics``."),
1319     ("beparams", ht.EmptyDict, ht.TDict, "Per-instance backend parameters"),
1320     ("hvparams", ht.EmptyDict, ht.TDict,
1321      "Per-instance hypervisor parameters, hypervisor-dependent"),
1322     ("disk_template", None, ht.TOr(ht.TNone, _BuildDiskTemplateCheck(False)),
1323      "Disk template for instance"),
1324     ("remote_node", None, ht.TMaybeString,
1325      "Secondary node (used when changing disk template)"),
1326     ("os_name", None, ht.TMaybeString,
1327      "Change instance's OS name. Does not reinstall the instance."),
1328     ("osparams", None, ht.TMaybeDict, "Per-instance OS parameters"),
1329     ("wait_for_sync", True, ht.TBool,
1330      "Whether to wait for the disk to synchronize, when changing template"),
1331     ]
1332   OP_RESULT = _TSetParamsResult
1333
1334
1335 class OpInstanceGrowDisk(OpCode):
1336   """Grow a disk of an instance."""
1337   OP_DSC_FIELD = "instance_name"
1338   OP_PARAMS = [
1339     _PInstanceName,
1340     _PWaitForSync,
1341     ("disk", ht.NoDefault, ht.TInt, "Disk index"),
1342     ("amount", ht.NoDefault, ht.TInt,
1343      "Amount of disk space to add (megabytes)"),
1344     ]
1345
1346
1347 class OpInstanceChangeGroup(OpCode):
1348   """Moves an instance to another node group."""
1349   OP_DSC_FIELD = "instance_name"
1350   OP_PARAMS = [
1351     _PInstanceName,
1352     _PEarlyRelease,
1353     ("iallocator", None, ht.TMaybeString, "Iallocator for computing solution"),
1354     ("target_groups", None, ht.TOr(ht.TNone, ht.TListOf(ht.TNonEmptyString)),
1355      "Destination group names or UUIDs (defaults to \"all but current group\""),
1356     ]
1357   OP_RESULT = TJobIdListOnly
1358
1359
1360 # Node group opcodes
1361
1362 class OpGroupAdd(OpCode):
1363   """Add a node group to the cluster."""
1364   OP_DSC_FIELD = "group_name"
1365   OP_PARAMS = [
1366     _PGroupName,
1367     _PNodeGroupAllocPolicy,
1368     _PGroupNodeParams,
1369     ]
1370
1371
1372 class OpGroupAssignNodes(OpCode):
1373   """Assign nodes to a node group."""
1374   OP_DSC_FIELD = "group_name"
1375   OP_PARAMS = [
1376     _PGroupName,
1377     _PForce,
1378     ("nodes", ht.NoDefault, ht.TListOf(ht.TNonEmptyString),
1379      "List of nodes to assign"),
1380     ]
1381
1382
1383 class OpGroupQuery(OpCode):
1384   """Compute the list of node groups."""
1385   OP_PARAMS = [
1386     _POutputFields,
1387     ("names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString),
1388      "Empty list to query all groups, group names otherwise"),
1389     ]
1390
1391
1392 class OpGroupSetParams(OpCode):
1393   """Change the parameters of a node group."""
1394   OP_DSC_FIELD = "group_name"
1395   OP_PARAMS = [
1396     _PGroupName,
1397     _PNodeGroupAllocPolicy,
1398     _PGroupNodeParams,
1399     ]
1400   OP_RESULT = _TSetParamsResult
1401
1402
1403 class OpGroupRemove(OpCode):
1404   """Remove a node group from the cluster."""
1405   OP_DSC_FIELD = "group_name"
1406   OP_PARAMS = [
1407     _PGroupName,
1408     ]
1409
1410
1411 class OpGroupRename(OpCode):
1412   """Rename a node group in the cluster."""
1413   OP_PARAMS = [
1414     _PGroupName,
1415     ("new_name", ht.NoDefault, ht.TNonEmptyString, "New group name"),
1416     ]
1417   OP_RESULT = ht.Comment("New group name")(ht.TNonEmptyString)
1418
1419
1420 class OpGroupEvacuate(OpCode):
1421   """Evacuate a node group in the cluster."""
1422   OP_DSC_FIELD = "group_name"
1423   OP_PARAMS = [
1424     _PGroupName,
1425     _PEarlyRelease,
1426     ("iallocator", None, ht.TMaybeString, "Iallocator for computing solution"),
1427     ("target_groups", None, ht.TOr(ht.TNone, ht.TListOf(ht.TNonEmptyString)),
1428      "Destination group names or UUIDs"),
1429     ]
1430   OP_RESULT = TJobIdListOnly
1431
1432
1433 # OS opcodes
1434 class OpOsDiagnose(OpCode):
1435   """Compute the list of guest operating systems."""
1436   OP_PARAMS = [
1437     _POutputFields,
1438     ("names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString),
1439      "Which operating systems to diagnose"),
1440     ]
1441
1442
1443 # Exports opcodes
1444 class OpBackupQuery(OpCode):
1445   """Compute the list of exported images."""
1446   OP_PARAMS = [
1447     _PUseLocking,
1448     ("nodes", ht.EmptyList, ht.TListOf(ht.TNonEmptyString),
1449      "Empty list to query all nodes, node names otherwise"),
1450     ]
1451
1452
1453 class OpBackupPrepare(OpCode):
1454   """Prepares an instance export.
1455
1456   @ivar instance_name: Instance name
1457   @ivar mode: Export mode (one of L{constants.EXPORT_MODES})
1458
1459   """
1460   OP_DSC_FIELD = "instance_name"
1461   OP_PARAMS = [
1462     _PInstanceName,
1463     ("mode", ht.NoDefault, ht.TElemOf(constants.EXPORT_MODES),
1464      "Export mode"),
1465     ]
1466
1467
1468 class OpBackupExport(OpCode):
1469   """Export an instance.
1470
1471   For local exports, the export destination is the node name. For remote
1472   exports, the export destination is a list of tuples, each consisting of
1473   hostname/IP address, port, HMAC and HMAC salt. The HMAC is calculated using
1474   the cluster domain secret over the value "${index}:${hostname}:${port}". The
1475   destination X509 CA must be a signed certificate.
1476
1477   @ivar mode: Export mode (one of L{constants.EXPORT_MODES})
1478   @ivar target_node: Export destination
1479   @ivar x509_key_name: X509 key to use (remote export only)
1480   @ivar destination_x509_ca: Destination X509 CA in PEM format (remote export
1481                              only)
1482
1483   """
1484   OP_DSC_FIELD = "instance_name"
1485   OP_PARAMS = [
1486     _PInstanceName,
1487     _PShutdownTimeout,
1488     # TODO: Rename target_node as it changes meaning for different export modes
1489     # (e.g. "destination")
1490     ("target_node", ht.NoDefault, ht.TOr(ht.TNonEmptyString, ht.TList),
1491      "Destination information, depends on export mode"),
1492     ("shutdown", True, ht.TBool, "Whether to shutdown instance before export"),
1493     ("remove_instance", False, ht.TBool,
1494      "Whether to remove instance after export"),
1495     ("ignore_remove_failures", False, ht.TBool,
1496      "Whether to ignore failures while removing instances"),
1497     ("mode", constants.EXPORT_MODE_LOCAL, ht.TElemOf(constants.EXPORT_MODES),
1498      "Export mode"),
1499     ("x509_key_name", None, ht.TOr(ht.TList, ht.TNone),
1500      "Name of X509 key (remote export only)"),
1501     ("destination_x509_ca", None, ht.TMaybeString,
1502      "Destination X509 CA (remote export only)"),
1503     ]
1504
1505
1506 class OpBackupRemove(OpCode):
1507   """Remove an instance's export."""
1508   OP_DSC_FIELD = "instance_name"
1509   OP_PARAMS = [
1510     _PInstanceName,
1511     ]
1512
1513
1514 # Tags opcodes
1515 class OpTagsGet(OpCode):
1516   """Returns the tags of the given object."""
1517   OP_DSC_FIELD = "name"
1518   OP_PARAMS = [
1519     _PTagKind,
1520     # Name is only meaningful for nodes and instances
1521     ("name", ht.NoDefault, ht.TMaybeString, None),
1522     ]
1523
1524
1525 class OpTagsSearch(OpCode):
1526   """Searches the tags in the cluster for a given pattern."""
1527   OP_DSC_FIELD = "pattern"
1528   OP_PARAMS = [
1529     ("pattern", ht.NoDefault, ht.TNonEmptyString, None),
1530     ]
1531
1532
1533 class OpTagsSet(OpCode):
1534   """Add a list of tags on a given object."""
1535   OP_PARAMS = [
1536     _PTagKind,
1537     _PTags,
1538     # Name is only meaningful for nodes and instances
1539     ("name", ht.NoDefault, ht.TMaybeString, None),
1540     ]
1541
1542
1543 class OpTagsDel(OpCode):
1544   """Remove a list of tags from a given object."""
1545   OP_PARAMS = [
1546     _PTagKind,
1547     _PTags,
1548     # Name is only meaningful for nodes and instances
1549     ("name", ht.NoDefault, ht.TMaybeString, None),
1550     ]
1551
1552
1553 # Test opcodes
1554 class OpTestDelay(OpCode):
1555   """Sleeps for a configured amount of time.
1556
1557   This is used just for debugging and testing.
1558
1559   Parameters:
1560     - duration: the time to sleep
1561     - on_master: if true, sleep on the master
1562     - on_nodes: list of nodes in which to sleep
1563
1564   If the on_master parameter is true, it will execute a sleep on the
1565   master (before any node sleep).
1566
1567   If the on_nodes list is not empty, it will sleep on those nodes
1568   (after the sleep on the master, if that is enabled).
1569
1570   As an additional feature, the case of duration < 0 will be reported
1571   as an execution error, so this opcode can be used as a failure
1572   generator. The case of duration == 0 will not be treated specially.
1573
1574   """
1575   OP_DSC_FIELD = "duration"
1576   OP_PARAMS = [
1577     ("duration", ht.NoDefault, ht.TNumber, None),
1578     ("on_master", True, ht.TBool, None),
1579     ("on_nodes", ht.EmptyList, ht.TListOf(ht.TNonEmptyString), None),
1580     ("repeat", 0, ht.TPositiveInt, None),
1581     ]
1582
1583
1584 class OpTestAllocator(OpCode):
1585   """Allocator framework testing.
1586
1587   This opcode has two modes:
1588     - gather and return allocator input for a given mode (allocate new
1589       or replace secondary) and a given instance definition (direction
1590       'in')
1591     - run a selected allocator for a given operation (as above) and
1592       return the allocator output (direction 'out')
1593
1594   """
1595   OP_DSC_FIELD = "allocator"
1596   OP_PARAMS = [
1597     ("direction", ht.NoDefault,
1598      ht.TElemOf(constants.VALID_IALLOCATOR_DIRECTIONS), None),
1599     ("mode", ht.NoDefault, ht.TElemOf(constants.VALID_IALLOCATOR_MODES), None),
1600     ("name", ht.NoDefault, ht.TNonEmptyString, None),
1601     ("nics", ht.NoDefault, ht.TOr(ht.TNone, ht.TListOf(
1602      ht.TDictOf(ht.TElemOf([constants.INIC_MAC, constants.INIC_IP, "bridge"]),
1603                 ht.TOr(ht.TNone, ht.TNonEmptyString)))), None),
1604     ("disks", ht.NoDefault, ht.TOr(ht.TNone, ht.TList), None),
1605     ("hypervisor", None, ht.TMaybeString, None),
1606     ("allocator", None, ht.TMaybeString, None),
1607     ("tags", ht.EmptyList, ht.TListOf(ht.TNonEmptyString), None),
1608     ("memory", None, ht.TOr(ht.TNone, ht.TPositiveInt), None),
1609     ("vcpus", None, ht.TOr(ht.TNone, ht.TPositiveInt), None),
1610     ("os", None, ht.TMaybeString, None),
1611     ("disk_template", None, ht.TMaybeString, None),
1612     ("instances", None, ht.TOr(ht.TNone, ht.TListOf(ht.TNonEmptyString)),
1613      None),
1614     ("evac_mode", None,
1615      ht.TOr(ht.TNone, ht.TElemOf(constants.IALLOCATOR_NEVAC_MODES)), None),
1616     ("target_groups", None, ht.TOr(ht.TNone, ht.TListOf(ht.TNonEmptyString)),
1617      None),
1618     ]
1619
1620
1621 class OpTestJqueue(OpCode):
1622   """Utility opcode to test some aspects of the job queue.
1623
1624   """
1625   OP_PARAMS = [
1626     ("notify_waitlock", False, ht.TBool, None),
1627     ("notify_exec", False, ht.TBool, None),
1628     ("log_messages", ht.EmptyList, ht.TListOf(ht.TString), None),
1629     ("fail", False, ht.TBool, None),
1630     ]
1631
1632
1633 class OpTestDummy(OpCode):
1634   """Utility opcode used by unittests.
1635
1636   """
1637   OP_PARAMS = [
1638     ("result", ht.NoDefault, ht.NoType, None),
1639     ("messages", ht.NoDefault, ht.NoType, None),
1640     ("fail", ht.NoDefault, ht.NoType, None),
1641     ("submit_jobs", None, ht.NoType, None),
1642     ]
1643   WITH_LU = False
1644
1645
1646 def _GetOpList():
1647   """Returns list of all defined opcodes.
1648
1649   Does not eliminate duplicates by C{OP_ID}.
1650
1651   """
1652   return [v for v in globals().values()
1653           if (isinstance(v, type) and issubclass(v, OpCode) and
1654               hasattr(v, "OP_ID") and v is not OpCode)]
1655
1656
1657 OP_MAPPING = dict((v.OP_ID, v) for v in _GetOpList())