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