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