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