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