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