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