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