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