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