opcodes.py 66.7 KB
Newer Older
Iustin Pop's avatar
Iustin Pop committed
1
#
Iustin Pop's avatar
Iustin Pop committed
2
3
#

4
# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
Iustin Pop's avatar
Iustin Pop committed
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.


"""OpCodes module

This module implements the data structures which define the cluster
operations - the so-called opcodes.

Iustin Pop's avatar
Iustin Pop committed
27
28
Every operation which modifies the cluster state is expressed via
opcodes.
Iustin Pop's avatar
Iustin Pop committed
29
30
31
32
33

"""

# this are practically structures, so disable the message about too
# few public methods:
34
# pylint: disable=R0903
Iustin Pop's avatar
Iustin Pop committed
35

36
import logging
37
import re
38
import ipaddr
39

40
41
42
from ganeti import constants
from ganeti import errors
from ganeti import ht
43
from ganeti import objects
44
from ganeti import outils
45
46
47
48
49


# Common opcode attributes

#: 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
63
_PInstanceName = ("instance_name", ht.NoDefault, ht.TNonEmptyString,
                  "Instance name")
64
65

#: Whether to ignore offline nodes
66
67
_PIgnoreOfflineNodes = ("ignore_offline_nodes", False, ht.TBool,
                        "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
76

#: Migration type (live/non-live)
_PMigrationMode = ("mode", None,
Iustin Pop's avatar
Iustin Pop committed
77
                   ht.TMaybe(ht.TElemOf(constants.HT_MIGRATION_MODES)),
78
                   "Migration mode")
79
80

#: Obsolete 'live' migration mode (boolean)
81
82
_PMigrationLive = ("live", None, ht.TMaybeBool,
                   "Legacy setting for live migration, do not use")
83
84

#: Tag type
85
86
_PTagKind = ("kind", ht.NoDefault, ht.TElemOf(constants.VALID_TAG_TYPES),
             "Tag kind")
87
88

#: List of tag strings
89
90
_PTags = ("tags", ht.NoDefault, ht.TListOf(ht.TNonEmptyString),
          "List of tag names")
91

92
93
94
95
96
97
_PForceVariant = ("force_variant", False, ht.TBool,
                  "Whether to force an unknown OS variant")

_PWaitForSync = ("wait_for_sync", True, ht.TBool,
                 "Whether to wait for the disk to synchronize")

98
99
100
101
_PWaitForSyncFalse = ("wait_for_sync", False, ht.TBool,
                      "Whether to wait for the disk to synchronize"
                      " (defaults to false)")

102
103
104
105
106
107
108
109
110
111
112
113
_PIgnoreConsistency = ("ignore_consistency", False, ht.TBool,
                       "Whether to ignore disk consistency")

_PStorageName = ("name", ht.NoDefault, ht.TMaybeString, "Storage name")

_PUseLocking = ("use_locking", False, ht.TBool,
                "Whether to use synchronization")

_PNameCheck = ("name_check", True, ht.TBool, "Whether to check name")

_PNodeGroupAllocPolicy = \
  ("alloc_policy", None,
Iustin Pop's avatar
Iustin Pop committed
114
   ht.TMaybe(ht.TElemOf(constants.VALID_ALLOC_POLICIES)),
115
116
117
118
119
   "Instance allocation policy")

_PGroupNodeParams = ("ndparams", None, ht.TMaybeDict,
                     "Default node parameters for group")

120
_PQueryWhat = ("what", ht.NoDefault, ht.TElemOf(constants.QR_VIA_OP),
121
122
               "Resource(s) to query for")

123
124
125
_PEarlyRelease = ("early_release", False, ht.TBool,
                  "Whether to release locks as soon as possible")

126
127
_PIpCheckDoc = "Whether to ensure instance's IP address is inactive"

128
#: Do not remember instance state changes
129
130
_PNoRemember = ("no_remember", False, ht.TBool,
                "Do not remember the state change")
131

Michael Hanselmann's avatar
Michael Hanselmann committed
132
133
134
135
#: Target node for instance migration/failover
_PMigrationTargetNode = ("target_node", None, ht.TMaybeString,
                         "Target node for shared-storage instances")

136
137
138
_PStartupPaused = ("startup_paused", False, ht.TBool,
                   "Pause instance at startup")

139
140
141
142
143
144
145
146
147
_PVerbose = ("verbose", False, ht.TBool, "Verbose mode")

# Parameters for cluster verification
_PDebugSimulateErrors = ("debug_simulate_errors", False, ht.TBool,
                         "Whether to simulate errors (useful for debugging)")
_PErrorCodes = ("error_codes", False, ht.TBool, "Error codes")
_PSkipChecks = ("skip_checks", ht.EmptyList,
                ht.TListOf(ht.TElemOf(constants.VERIFY_OPTIONAL_CHECKS)),
                "Which checks to skip")
148
149
150
_PIgnoreErrors = ("ignore_errors", ht.EmptyList,
                  ht.TListOf(ht.TElemOf(constants.CV_ALL_ECODES_STRINGS)),
                  "List of error codes that should be treated as warnings")
151

152
# Disk parameters
153
154
_PDiskParams = \
  ("diskparams", None,
Iustin Pop's avatar
Iustin Pop committed
155
   ht.TMaybe(ht.TDictOf(ht.TElemOf(constants.DISK_TEMPLATES), ht.TDict)),
156
   "Disk templates' parameter defaults")
157

158
159
160
161
# Parameters for node resource model
_PHvState = ("hv_state", None, ht.TMaybeDict, "Set hypervisor states")
_PDiskState = ("disk_state", None, ht.TMaybeDict, "Set disk states")

162
163
164
165
166
167
#: Opportunistic locking
_POpportunisticLocking = \
  ("opportunistic_locking", False, ht.TBool,
   ("Whether to employ opportunistic locking for nodes, meaning nodes"
    " already locked by another opcode won't be considered for instance"
    " allocation (only when an iallocator is used)"))
168
169
170
171

_PIgnoreIpolicy = ("ignore_ipolicy", False, ht.TBool,
                   "Whether to ignore ipolicy violations")

172
173
174
175
# Allow runtime changes while migrating
_PAllowRuntimeChgs = ("allow_runtime_changes", True, ht.TBool,
                      "Allow runtime changes (eg. memory ballooning)")

176
177
178
#: IAllocator field builder
_PIAllocFromDesc = lambda desc: ("iallocator", None, ht.TMaybeString, desc)

179
180
181
#: a required network name
_PNetworkName = ("network_name", ht.NoDefault, ht.TNonEmptyString,
                 "Set network name")
182

183
184
185
186
_PTargetGroups = \
  ("target_groups", None, ht.TMaybeListOf(ht.TNonEmptyString),
   "Destination group names or UUIDs (defaults to \"all but current group\")")

187
188
189
190
191
192
193
194
# The reason for a state change of an instance
_PReason = \
  ("reason", (constants.INSTANCE_REASON_SOURCE_UNKNOWN, None),
   ht.TAnd(ht.TIsLength(2),
           ht.TItems([
             ht.TElemOf(constants.INSTANCE_REASON_SOURCES),
             ht.TMaybeString,
           ])),
195
   "The reason why the state of the instance is changing")
196

197
198
199
#: OP_ID conversion regular expression
_OPID_RE = re.compile("([a-z])([A-Z])")

200
#: Utility function for L{OpClusterSetParams}
201
202
203
204
205
_TestClusterOsListItem = \
  ht.TAnd(ht.TIsLength(2), ht.TItems([
    ht.TElemOf(constants.DDMS_VALUES),
    ht.TNonEmptyString,
    ]))
206

207
_TestClusterOsList = ht.TMaybeListOf(_TestClusterOsListItem)
208

209
210
# TODO: Generate check from constants.INIC_PARAMS_TYPES
#: Utility function for testing NIC definitions
211
212
_TestNicDef = \
  ht.Comment("NIC parameters")(ht.TDictOf(ht.TElemOf(constants.INIC_PARAMS),
Iustin Pop's avatar
Iustin Pop committed
213
                                          ht.TMaybeString))
214

215
216
_TSetParamsResultItemItems = [
  ht.Comment("name of changed parameter")(ht.TNonEmptyString),
217
  ht.Comment("new value")(ht.TAny),
218
219
220
221
222
223
  ]

_TSetParamsResult = \
  ht.TListOf(ht.TAnd(ht.TIsLength(len(_TSetParamsResultItemItems)),
                     ht.TItems(_TSetParamsResultItemItems)))

224
225
226
227
# In the disks option we can provide arbitrary parameters too, which
# we may not be able to validate at this level, so we just check the
# format of the dict here and the checks concerning IDISK_PARAMS will
# 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
233
234
235
236
_TQueryRow = \
  ht.TListOf(ht.TAnd(ht.TIsLength(2),
                     ht.TItems([ht.TElemOf(constants.RS_ALL),
                                ht.TAny])))

237
238
239
240
241
242
243
_TQueryResult = ht.TListOf(_TQueryRow)

_TOldQueryRow = ht.TListOf(ht.TAny)

_TOldQueryResult = ht.TListOf(_TOldQueryRow)


244
245
246
247
248
249
250
_SUMMARY_PREFIX = {
  "CLUSTER_": "C_",
  "GROUP_": "G_",
  "NODE_": "N_",
  "INSTANCE_": "I_",
  }

Michael Hanselmann's avatar
Michael Hanselmann committed
251
252
253
#: Attribute name for dependencies
DEPEND_ATTR = "depends"

254
255
256
#: Attribute name for comment
COMMENT_ATTR = "comment"

257

258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
def _NameToId(name):
  """Convert an opcode class name to an OP_ID.

  @type name: string
  @param name: the class name, as OpXxxYyy
  @rtype: string
  @return: the name in the OP_XXXX_YYYY format

  """
  if not name.startswith("Op"):
    return None
  # Note: (?<=[a-z])(?=[A-Z]) would be ideal, since it wouldn't
  # consume any input, and hence we would just have all the elements
  # in the list, one by one; but it seems that split doesn't work on
  # non-consuming input, hence we have to process the input string a
  # bit
  name = _OPID_RE.sub(r"\1,\2", name)
  elems = name.split(",")
  return "_".join(n.upper() for n in elems)

278

279
280
281
282
283
284
285
286
287
288
289
290
291
def _GenerateObjectTypeCheck(obj, fields_types):
  """Helper to generate type checks for objects.

  @param obj: The object to generate type checks
  @param fields_types: The fields and their types as a dict
  @return: A ht type check function

  """
  assert set(obj.GetAllSlots()) == set(fields_types.keys()), \
    "%s != %s" % (set(obj.GetAllSlots()), set(fields_types.keys()))
  return ht.TStrictDict(True, True, fields_types)


292
293
294
295
296
297
298
_TQueryFieldDef = \
  _GenerateObjectTypeCheck(objects.QueryFieldDefinition, {
    "name": ht.TNonEmptyString,
    "title": ht.TNonEmptyString,
    "kind": ht.TElemOf(constants.QFT_ALL),
    "doc": ht.TNonEmptyString,
    })
299
300


301
302
303
304
305
306
307
308
309
310
311
312
313
314
def RequireFileStorage():
  """Checks that file storage is enabled.

  While it doesn't really fit into this module, L{utils} was deemed too large
  of a dependency to be imported for just one or two functions.

  @raise errors.OpPrereqError: when file storage is disabled

  """
  if not constants.ENABLE_FILE_STORAGE:
    raise errors.OpPrereqError("File storage disabled at configure time",
                               errors.ECODE_INVAL)


315
316
317
318
319
320
321
322
323
324
325
326
327
328
def RequireSharedFileStorage():
  """Checks that shared file storage is enabled.

  While it doesn't really fit into this module, L{utils} was deemed too large
  of a dependency to be imported for just one or two functions.

  @raise errors.OpPrereqError: when shared file storage is disabled

  """
  if not constants.ENABLE_SHARED_FILE_STORAGE:
    raise errors.OpPrereqError("Shared file storage disabled at"
                               " configure time", errors.ECODE_INVAL)


329
330
331
@ht.WithDesc("CheckFileStorage")
def _CheckFileStorage(value):
  """Ensures file storage is enabled if used.
332
333

  """
334
  if value == constants.DT_FILE:
335
    RequireFileStorage()
336
337
  elif value == constants.DT_SHARED_FILE:
    RequireSharedFileStorage()
338
339
340
  return True


341
342
343
344
345
346
347
348
349
350
351
def _BuildDiskTemplateCheck(accept_none):
  """Builds check for disk template.

  @type accept_none: bool
  @param accept_none: whether to accept None as a correct value
  @rtype: callable

  """
  template_check = ht.TElemOf(constants.DISK_TEMPLATES)

  if accept_none:
Iustin Pop's avatar
Iustin Pop committed
352
    template_check = ht.TMaybe(template_check)
353
354

  return ht.TAnd(template_check, _CheckFileStorage)
355
356


357
358
359
360
361
362
363
364
def _CheckStorageType(storage_type):
  """Ensure a given storage type is valid.

  """
  if storage_type not in constants.VALID_STORAGE_TYPES:
    raise errors.OpPrereqError("Unknown storage type: %s" % storage_type,
                               errors.ECODE_INVAL)
  if storage_type == constants.ST_FILE:
365
    # TODO: What about shared file storage?
366
367
368
369
370
    RequireFileStorage()
  return True


#: Storage type parameter
371
372
_PStorageType = ("storage_type", ht.NoDefault, _CheckStorageType,
                 "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
381
382
383
384
385

  """
  try:
    ipaddr.IPv4Network(value)
  except ipaddr.AddressValueError:
    return False
  return True

386

387
@ht.WithDesc("IPv4 address")
388
def _CheckCIDRAddrNotation(value):
389
  """Ensure a given CIDR notation type is valid.
390
391
392
393
394
395
396
397

  """
  try:
    ipaddr.IPv4Address(value)
  except ipaddr.AddressValueError:
    return False
  return True

398

399
@ht.WithDesc("IPv6 address")
400
def _CheckCIDR6AddrNotation(value):
401
  """Ensure a given CIDR notation type is valid.
402
403
404
405
406
407
408
409

  """
  try:
    ipaddr.IPv6Address(value)
  except ipaddr.AddressValueError:
    return False
  return True

410

411
@ht.WithDesc("IPv6 network")
412
def _CheckCIDR6NetNotation(value):
413
  """Ensure a given CIDR notation type is valid.
414
415
416
417
418
419
420

  """
  try:
    ipaddr.IPv6Network(value)
  except ipaddr.AddressValueError:
    return False
  return True
421

422

Iustin Pop's avatar
Iustin Pop committed
423
424
425
426
427
_TIpAddress4 = ht.TAnd(ht.TString, _CheckCIDRAddrNotation)
_TIpAddress6 = ht.TAnd(ht.TString, _CheckCIDR6AddrNotation)
_TIpNetwork4 = ht.TAnd(ht.TString, _CheckCIDRNetNotation)
_TIpNetwork6 = ht.TAnd(ht.TString, _CheckCIDR6NetNotation)
_TMaybeAddr4List = ht.TMaybe(ht.TListOf(_TIpAddress4))
428
429


430
class _AutoOpParamSlots(outils.AutoSlots):
431
432
433
434
435
436
437
438
439
440
441
442
443
  """Meta class for opcode definitions.

  """
  def __new__(mcs, name, bases, attrs):
    """Called when a class should be created.

    @param mcs: The meta class
    @param name: Name of created class
    @param bases: Base classes
    @type attrs: dict
    @param attrs: Class attributes

    """
444
    assert "OP_ID" not in attrs, "Class '%s' defining OP_ID" % name
445

446
447
448
    slots = mcs._GetSlots(attrs)
    assert "OP_DSC_FIELD" not in attrs or attrs["OP_DSC_FIELD"] in slots, \
      "Class '%s' uses unknown field in OP_DSC_FIELD" % name
449
450
451
452
    assert ("OP_DSC_FORMATTER" not in attrs or
            callable(attrs["OP_DSC_FORMATTER"])), \
      ("Class '%s' uses non-callable in OP_DSC_FORMATTER (%s)" %
       (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
459
460
461
462

  @classmethod
  def _GetSlots(mcs, attrs):
    """Build the slots out of OP_PARAMS.

    """
463
464
465
466
    # Always set OP_PARAMS to avoid duplicates in BaseOpCode.GetAllParams
    params = attrs.setdefault("OP_PARAMS", [])

    # Use parameter names as slots
467
    return [pname for (pname, _, _, _) in params]
468

469

470
class BaseOpCode(outils.ValidatedSlots):
471
472
  """A simple serializable object.

Iustin Pop's avatar
Iustin Pop committed
473
474
475
  This object serves as a parent class for OpCode without any custom
  field handling.

476
  """
477
  # pylint: disable=E1101
478
  # as OP_ID is dynamically defined
479
480
  __metaclass__ = _AutoOpParamSlots

481
  def __getstate__(self):
Iustin Pop's avatar
Iustin Pop committed
482
483
484
485
486
487
488
489
490
    """Generic serializer.

    This method just returns the contents of the instance as a
    dictionary.

    @rtype:  C{dict}
    @return: the instance attributes and their values

    """
491
    state = {}
492
    for name in self.GetAllSlots():
493
494
495
496
497
      if hasattr(self, name):
        state[name] = getattr(self, name)
    return state

  def __setstate__(self, state):
Iustin Pop's avatar
Iustin Pop committed
498
499
500
501
502
503
504
505
506
    """Generic unserializer.

    This method just restores from the serialized state the attributes
    of the current instance.

    @param state: the serialized opcode data
    @type state:  C{dict}

    """
507
508
509
510
    if not isinstance(state, dict):
      raise ValueError("Invalid data to __setstate__: expected dict, got %s" %
                       type(state))

511
    for name in self.GetAllSlots():
Iustin Pop's avatar
Iustin Pop committed
512
      if name not in state and hasattr(self, name):
513
514
515
516
517
        delattr(self, name)

    for name in state:
      setattr(self, name, state[name])

518
519
520
521
522
523
524
525
526
527
  @classmethod
  def GetAllParams(cls):
    """Compute list of all parameters for an opcode.

    """
    slots = []
    for parent in cls.__mro__:
      slots.extend(getattr(parent, "OP_PARAMS", []))
    return slots

528
  def Validate(self, set_defaults): # pylint: disable=W0221
529
530
531
532
533
534
535
536
    """Validate opcode parameters, optionally setting default values.

    @type set_defaults: bool
    @param set_defaults: Whether to set default values
    @raise errors.OpPrereqError: When a parameter value doesn't match
                                 requirements

    """
537
    for (attr_name, default, test, _) in self.GetAllParams():
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
      assert test == ht.NoType or callable(test)

      if not hasattr(self, attr_name):
        if default == ht.NoDefault:
          raise errors.OpPrereqError("Required parameter '%s.%s' missing" %
                                     (self.OP_ID, attr_name),
                                     errors.ECODE_INVAL)
        elif set_defaults:
          if callable(default):
            dval = default()
          else:
            dval = default
          setattr(self, attr_name, dval)

      if test == ht.NoType:
        # no tests here
        continue

      if set_defaults or hasattr(self, attr_name):
        attr_val = getattr(self, attr_name)
        if not test(attr_val):
559
560
          logging.error("OpCode %s, parameter %s, has invalid type %s/value"
                        " '%s' expecting type %s",
561
                        self.OP_ID, attr_name, type(attr_val), attr_val, test)
562
563
564
565
          raise errors.OpPrereqError("Parameter '%s.%s' fails validation" %
                                     (self.OP_ID, attr_name),
                                     errors.ECODE_INVAL)

566

567
568
569
570
571
572
573
574
575
576
577
578
579
580
def _BuildJobDepCheck(relative):
  """Builds check for job dependencies (L{DEPEND_ATTR}).

  @type relative: bool
  @param relative: Whether to accept relative job IDs (negative)
  @rtype: callable

  """
  if relative:
    job_id = ht.TOr(ht.TJobId, ht.TRelativeJobId)
  else:
    job_id = ht.TJobId

  job_dep = \
581
582
    ht.TAnd(ht.TOr(ht.TList, ht.TTuple),
            ht.TIsLength(2),
583
584
585
            ht.TItems([job_id,
                       ht.TListOf(ht.TElemOf(constants.JOBS_FINALIZED))]))

586
  return ht.TMaybeListOf(job_dep)
587
588
589
590


TNoRelativeJobDependencies = _BuildJobDepCheck(False)

591
#: List of submission status and job ID as returned by C{SubmitManyJobs}
592
593
594
595
596
597
598
_TJobIdListItem = \
  ht.TAnd(ht.TIsLength(2),
          ht.TItems([ht.Comment("success")(ht.TBool),
                     ht.Comment("Job ID if successful, error message"
                                " otherwise")(ht.TOr(ht.TString,
                                                     ht.TJobId))]))
TJobIdList = ht.TListOf(_TJobIdListItem)
599

600
601
#: Result containing only list of submitted jobs
TJobIdListOnly = ht.TStrictDict(True, True, {
602
  constants.JOB_IDS_KEY: ht.Comment("List of submitted jobs")(TJobIdList),
603
604
  })

605

Iustin Pop's avatar
Iustin Pop committed
606
class OpCode(BaseOpCode):
Iustin Pop's avatar
Iustin Pop committed
607
608
609
610
611
612
  """Abstract OpCode.

  This is the root of the actual OpCode hierarchy. All clases derived
  from this class should override OP_ID.

  @cvar OP_ID: The ID of this opcode. This should be unique amongst all
613
               children of this class.
614
615
616
  @cvar OP_DSC_FIELD: The name of a field whose value will be included in the
                      string returned by Summary(); see the docstring of that
                      method for details).
617
618
619
  @cvar OP_DSC_FORMATTER: A callable that should format the OP_DSC_FIELD; if
                          not present, then the field will be simply converted
                          to string
620
621
  @cvar OP_PARAMS: List of opcode attributes, the default values they should
                   get if not already defined, and types they must match.
622
  @cvar OP_RESULT: Callable to verify opcode result
623
624
  @cvar WITH_LU: Boolean that specifies whether this should be included in
      mcpu's dispatch table
625
626
  @ivar dry_run: Whether the LU should be run in dry-run mode, i.e. just
                 the check steps
627
  @ivar priority: Opcode priority for queue
Iustin Pop's avatar
Iustin Pop committed
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"),
Iustin Pop's avatar
Iustin Pop committed
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
639
    (DEPEND_ATTR, None, _BuildJobDepCheck(True),
     "Job dependencies; if used through ``SubmitManyJobs`` relative (negative)"
640
641
     " job IDs can be used; see :doc:`design document <design-chained-jobs>`"
     " for details"),
642
643
    (COMMENT_ATTR, None, ht.TMaybeString,
     "Comment describing the purpose of the opcode"),
644
    ]
645
  OP_RESULT = None
646
647
648
649

  def __getstate__(self):
    """Specialized getstate for opcodes.

Iustin Pop's avatar
Iustin Pop committed
650
651
652
653
654
655
656
    This method adds to the state dictionary the OP_ID of the class,
    so that on unload we can identify the correct class for
    instantiating the opcode.

    @rtype:   C{dict}
    @return:  the state as a dictionary

657
    """
Iustin Pop's avatar
Iustin Pop committed
658
    data = BaseOpCode.__getstate__(self)
659
660
661
662
    data["OP_ID"] = self.OP_ID
    return data

  @classmethod
663
  def LoadOpCode(cls, data):
664
665
    """Generic load opcode method.

Iustin Pop's avatar
Iustin Pop committed
666
667
668
669
670
671
672
    The method identifies the correct opcode class from the dict-form
    by looking for a OP_ID key, if this is not found, or its value is
    not available in this module as a child of this class, we fail.

    @type data:  C{dict}
    @param data: the serialized opcode

673
674
675
676
677
678
679
    """
    if not isinstance(data, dict):
      raise ValueError("Invalid data to LoadOpCode (%s)" % type(data))
    if "OP_ID" not in data:
      raise ValueError("Invalid data to LoadOpcode, missing OP_ID")
    op_id = data["OP_ID"]
    op_class = None
Iustin Pop's avatar
Iustin Pop committed
680
681
682
    if op_id in OP_MAPPING:
      op_class = OP_MAPPING[op_id]
    else:
683
684
685
686
687
688
689
690
      raise ValueError("Invalid data to LoadOpCode: OP_ID %s unsupported" %
                       op_id)
    op = op_class()
    new_data = data.copy()
    del new_data["OP_ID"]
    op.__setstate__(new_data)
    return op

691
692
693
  def Summary(self):
    """Generates a summary description of this opcode.

694
695
696
697
698
    The summary is the value of the OP_ID attribute (without the "OP_"
    prefix), plus the value of the OP_DSC_FIELD attribute, if one was
    defined; this field should allow to easily identify the operation
    (for an instance creation job, e.g., it would be the instance
    name).
699

700
    """
701
    assert self.OP_ID is not None and len(self.OP_ID) > 3
702
703
704
705
706
    # all OP_ID start with OP_, we remove that
    txt = self.OP_ID[3:]
    field_name = getattr(self, "OP_DSC_FIELD", None)
    if field_name:
      field_value = getattr(self, field_name, None)
707
708
709
710
      field_formatter = getattr(self, "OP_DSC_FORMATTER", None)
      if callable(field_formatter):
        field_value = field_formatter(field_value)
      elif isinstance(field_value, (list, tuple)):
711
        field_value = ",".join(str(i) for i in field_value)
712
713
714
      txt = "%s(%s)" % (txt, field_value)
    return txt

715
716
717
718
719
720
721
722
723
724
725
726
727
728
  def TinySummary(self):
    """Generates a compact summary description of the opcode.

    """
    assert self.OP_ID.startswith("OP_")

    text = self.OP_ID[3:]

    for (prefix, supplement) in _SUMMARY_PREFIX.items():
      if text.startswith(prefix):
        return supplement + text[len(prefix):]

    return text

Iustin Pop's avatar
Iustin Pop committed
729

730
731
# cluster opcodes

732
class OpClusterPostInit(OpCode):
733
734
735
736
737
738
  """Post cluster initialization.

  This opcode does not touch the cluster at all. Its purpose is to run hooks
  after the cluster has been initialized.

  """
739
  OP_RESULT = ht.TBool
740
741


742
class OpClusterDestroy(OpCode):
Iustin Pop's avatar
Iustin Pop committed
743
744
745
746
747
748
  """Destroy the cluster.

  This opcode has no other parameters. All the state is irreversibly
  lost after the execution of this opcode.

  """
749
  OP_RESULT = ht.TNonEmptyString
Iustin Pop's avatar
Iustin Pop committed
750
751


752
class OpClusterQuery(OpCode):
Iustin Pop's avatar
Iustin Pop committed
753
  """Query cluster information."""
754
  OP_RESULT = ht.TDictOf(ht.TNonEmptyString, ht.TAny)
Iustin Pop's avatar
Iustin Pop committed
755
756


757
758
759
760
761
762
763
764
class OpClusterVerify(OpCode):
  """Submits all jobs necessary to verify the cluster.

  """
  OP_PARAMS = [
    _PDebugSimulateErrors,
    _PErrorCodes,
    _PSkipChecks,
765
    _PIgnoreErrors,
766
    _PVerbose,
767
    ("group_name", None, ht.TMaybeString, "Group to verify"),
768
    ]
769
  OP_RESULT = TJobIdListOnly
770
771


772
773
774
775
776
class OpClusterVerifyConfig(OpCode):
  """Verify the cluster config.

  """
  OP_PARAMS = [
777
778
    _PDebugSimulateErrors,
    _PErrorCodes,
779
    _PIgnoreErrors,
780
    _PVerbose,
781
    ]
782
  OP_RESULT = ht.TBool
783
784
785
786


class OpClusterVerifyGroup(OpCode):
  """Run verify on a node group from the cluster.
Iustin Pop's avatar
Iustin Pop committed
787
788
789
790
791
792
793
794

  @type skip_checks: C{list}
  @ivar skip_checks: steps to be skipped from the verify process; this
                     needs to be a subset of
                     L{constants.VERIFY_OPTIONAL_CHECKS}; currently
                     only L{constants.VERIFY_NPLUSONE_MEM} can be passed

  """
795
  OP_DSC_FIELD = "group_name"
796
  OP_PARAMS = [
797
798
799
800
    _PGroupName,
    _PDebugSimulateErrors,
    _PErrorCodes,
    _PSkipChecks,
801
    _PIgnoreErrors,
802
    _PVerbose,
803
    ]
804
  OP_RESULT = ht.TBool
Iustin Pop's avatar
Iustin Pop committed
805
806


807
class OpClusterVerifyDisks(OpCode):
Iustin Pop's avatar
Iustin Pop committed
808
809
  """Verify the cluster disks.

810
  """
811
  OP_RESULT = TJobIdListOnly
812
813
814
815


class OpGroupVerifyDisks(OpCode):
  """Verifies the status of all disks in a node group.
Iustin Pop's avatar
Iustin Pop committed
816

817
818
  Result: a tuple of three elements:
    - dict of node names with issues (values: error msg)
Iustin Pop's avatar
Iustin Pop committed
819
    - list of instances with degraded disks (that should be activated)
820
821
    - dict of instances with missing logical volumes (values: (node, vol)
      pairs with details about the missing volumes)
Iustin Pop's avatar
Iustin Pop committed
822

823
824
825
826
  In normal operation, all lists should be empty. A non-empty instance
  list (3rd element of the result) is still ok (errors were fixed) but
  non-empty node list means some node is down, and probably there are
  unfixable drbd errors.
Iustin Pop's avatar
Iustin Pop committed
827
828
829
830
831

  Note that only instances that are drbd-based are taken into
  consideration. This might need to be revisited in the future.

  """
832
833
834
835
  OP_DSC_FIELD = "group_name"
  OP_PARAMS = [
    _PGroupName,
    ]
836
837
838
839
  OP_RESULT = \
    ht.TAnd(ht.TIsLength(3),
            ht.TItems([ht.TDictOf(ht.TString, ht.TString),
                       ht.TListOf(ht.TString),
840
841
                       ht.TDictOf(ht.TString,
                                  ht.TListOf(ht.TListOf(ht.TString)))]))
Iustin Pop's avatar
Iustin Pop committed
842
843


844
class OpClusterRepairDiskSizes(OpCode):
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
  """Verify the disk sizes of the instances and fixes configuration
  mimatches.

  Parameters: optional instances list, in case we want to restrict the
  checks to only a subset of the instances.

  Result: a list of tuples, (instance, disk, new-size) for changed
  configurations.

  In normal operation, the list should be empty.

  @type instances: list
  @ivar instances: the list of instances to check, or empty for all instances

  """
860
  OP_PARAMS = [
861
    ("instances", ht.EmptyList, ht.TListOf(ht.TNonEmptyString), None),
862
    ]
863
864
  OP_RESULT = ht.TListOf(ht.TAnd(ht.TIsLength(3),
                                 ht.TItems([ht.TNonEmptyString,
865
866
                                            ht.TNonNegativeInt,
                                            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)
Iustin Pop's avatar
Iustin Pop committed
875
876


877
class OpClusterRename(OpCode):
Iustin Pop's avatar
Iustin Pop committed
878
879
880
881
882
883
884
885
  """Rename the cluster.

  @type name: C{str}
  @ivar name: The new name of the cluster. The name and/or the master IP
              address will be changed to match the new name and its IP
              address.

  """
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):
Iustin Pop's avatar
Iustin Pop committed
894
895
896
897
898
899
  """Change the parameters of the cluster.

  @type vg_name: C{str} or C{None}
  @ivar vg_name: The new volume group name or None to disable LVM usage.

  """
900
  OP_PARAMS = [
901
902
    _PHvState,
    _PDiskState,
Iustin Pop's avatar
Iustin Pop committed
903
    ("vg_name", None, ht.TMaybe(ht.TString), "Volume group name"),
904
    ("enabled_hypervisors", None,
Iustin Pop's avatar
Iustin Pop committed
905
906
     ht.TMaybe(ht.TAnd(ht.TListOf(ht.TElemOf(constants.HYPER_TYPES)),
                       ht.TTrue)),
907
     "List of enabled hypervisors"),
908
    ("hvparams", None,
Iustin Pop's avatar
Iustin Pop committed
909
     ht.TMaybe(ht.TDictOf(ht.TNonEmptyString, ht.TDict)),
910
     "Cluster-wide hypervisor parameter defaults, hypervisor-dependent"),
Iustin Pop's avatar
Iustin Pop committed
911
    ("beparams", None, ht.TMaybeDict,
912
     "Cluster-wide backend parameter defaults"),
Iustin Pop's avatar
Iustin Pop committed
913
    ("os_hvp", None, ht.TMaybe(ht.TDictOf(ht.TNonEmptyString, ht.TDict)),
914
     "Cluster-wide per-OS hypervisor parameter defaults"),
915
    ("osparams", None,
Iustin Pop's avatar
Iustin Pop committed
916
     ht.TMaybe(ht.TDictOf(ht.TNonEmptyString, ht.TDict)),
917
     "Cluster-wide OS parameter defaults"),
918
    _PDiskParams,
Iustin Pop's avatar
Iustin Pop committed
919
    ("candidate_pool_size", None, ht.TMaybe(ht.TPositiveInt),
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
     "Master candidate pool size"),
    ("uid_pool", None, ht.NoType,
     "Set UID pool, must be list of lists describing UID ranges (two items,"
     " start and end inclusive)"),
    ("add_uids", None, ht.NoType,
     "Extend UID pool, must be list of lists describing UID ranges (two"
     " items, start and end inclusive) to be added"),
    ("remove_uids", None, ht.NoType,
     "Shrink UID pool, must be list of lists describing UID ranges (two"
     " items, start and end inclusive) to be removed"),
    ("maintain_node_health", None, ht.TMaybeBool,
     "Whether to automatically maintain node health"),
    ("prealloc_wipe_disks", None, ht.TMaybeBool,
     "Whether to wipe disks before allocating them to instances"),
    ("nicparams", None, ht.TMaybeDict, "Cluster-wide NIC parameter defaults"),
    ("ndparams", None, ht.TMaybeDict, "Cluster-wide node parameter defaults"),
936
937
    ("ipolicy", None, ht.TMaybeDict,
     "Cluster-wide :ref:`instance policy <rapi-ipolicy>` specs"),
Iustin Pop's avatar
Iustin Pop committed
938
939
    ("drbd_helper", None, ht.TMaybe(ht.TString), "DRBD helper program"),
    ("default_iallocator", None, ht.TMaybe(ht.TString),
940
     "Default iallocator for cluster"),
Iustin Pop's avatar
Iustin Pop committed
941
    ("master_netdev", None, ht.TMaybe(ht.TString),
942
     "Master network device"),
Iustin Pop's avatar
Iustin Pop committed
943
    ("master_netmask", None, ht.TMaybe(ht.TNonNegativeInt),
944
     "Netmask of the master IP"),
945
    ("reserved_lvs", None, ht.TMaybeListOf(ht.TNonEmptyString),
946
947
     "List of reserved LVs"),
    ("hidden_os", None, _TestClusterOsList,
948
949
950
     "Modify list of hidden operating systems: each modification must have"
     " two items, the operation and the OS name; the operation can be"
     " ``%s`` or ``%s``" % (constants.DDM_ADD, constants.DDM_REMOVE)),
951
    ("blacklisted_os", None, _TestClusterOsList,
952
953
954
     "Modify list of blacklisted operating systems: each modification must"
     " have two items, the operation and the OS name; the operation can be"
     " ``%s`` or ``%s``" % (constants.DDM_ADD, constants.DDM_REMOVE)),
955
956
    ("use_external_mip_script", None, ht.TMaybeBool,
     "Whether to use an external master IP address setup script"),
957
958
959
960
    ("enabled_disk_templates", None,
     ht.TMaybe(ht.TAnd(ht.TListOf(ht.TElemOf(constants.DISK_TEMPLATES)),
                       ht.TTrue)),
     "List of enabled disk templates"),
961
    ]
962
  OP_RESULT = ht.TNone
963
964


965
class OpClusterRedistConf(OpCode):
966
967
968
  """Force a full push of the cluster configuration.

  """
969
  OP_RESULT = ht.TNone
970

Michael Hanselmann's avatar
Michael Hanselmann committed
971

972
973
974
975
class OpClusterActivateMasterIp(OpCode):
  """Activate the master IP on the master node.

  """
976
  OP_RESULT = ht.TNone
977
978
979
980
981
982


class OpClusterDeactivateMasterIp(OpCode):
  """Deactivate the master IP on the master node.

  """
983
  OP_RESULT = ht.TNone
984
985


Michael Hanselmann's avatar
Michael Hanselmann committed
986
987
988
class OpQuery(OpCode):
  """Query for resources/items.

989
  @ivar what: Resources to query for, must be one of L{constants.QR_VIA_OP}
Michael Hanselmann's avatar
Michael Hanselmann committed
990
  @ivar fields: List of fields to retrieve
991
  @ivar qfilter: Query filter
Michael Hanselmann's avatar
Michael Hanselmann committed
992
993

  """
994
  OP_DSC_FIELD = "what"
995
  OP_PARAMS = [
996
    _PQueryWhat,
997
    _PUseLocking,
998
999
    ("fields", ht.NoDefault, ht.TListOf(ht.TNonEmptyString),
     "Requested fields"),
Iustin Pop's avatar
Iustin Pop committed
1000
    ("qfilter", None, ht.TMaybe(ht.TList),
1001
     "Query filter"),
Michael Hanselmann's avatar
Michael Hanselmann committed
1002
    ]
1003
  OP_RESULT = \
1004
1005
1006
1007
    _GenerateObjectTypeCheck(objects.QueryResponse, {
      "fields": ht.TListOf(_TQueryFieldDef),
      "data": _TQueryResult,
      })
Michael Hanselmann's avatar
Michael Hanselmann committed
1008
1009
1010
1011
1012


class OpQueryFields(OpCode):
  """Query for available resource/item fields.

1013
  @ivar what: Resources to query for, must be one of L{constants.QR_VIA_OP}
Michael Hanselmann's avatar
Michael Hanselmann committed
1014
1015
1016
  @ivar fields: List of fields to retrieve

  """
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"),
Michael Hanselmann's avatar
Michael Hanselmann committed
1022
    ]
1023
  OP_RESULT = \
1024
1025
1026
    _GenerateObjectTypeCheck(objects.QueryFieldsResponse, {
      "fields": ht.TListOf(_TQueryFieldDef),
      })
Michael Hanselmann's avatar
Michael Hanselmann committed
1027
1028


1029
class OpOobCommand(OpCode):
René Nussbaumer's avatar
René Nussbaumer committed
1030
  """Interact with OOB."""
1031
  OP_PARAMS = [
1032
1033
    ("node_names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString),
     "List of nodes to run the OOB command against"),
1034
    ("command", ht.NoDefault, ht.TElemOf(constants.OOB_COMMANDS),
1035
1036
1037
1038
1039
     "OOB command to be run"),
    ("timeout", constants.OOB_TIMEOUT, ht.TInt,
     "Timeout before the OOB helper will be terminated"),
    ("ignore_status", False, ht.TBool,
     "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"),
René Nussbaumer's avatar
René Nussbaumer committed
1042
    ]
1043
  # Fixme: Make it more specific with all the special cases in LUOobCommand
1044
  OP_RESULT = _TQueryResult
René Nussbaumer's avatar
René Nussbaumer committed
1045
1046


1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
class OpRestrictedCommand(OpCode):
  """Runs a restricted command on node(s).

  """
  OP_PARAMS = [
    _PUseLocking,
    ("nodes", ht.NoDefault, ht.TListOf(ht.TNonEmptyString),
     "Nodes on which the command should be run (at least one)"),
    ("command", ht.NoDefault, ht.TNonEmptyString,
     "Command name (no parameters)"),
    ]

  _RESULT_ITEMS = [
    ht.Comment("success")(ht.TBool),
    ht.Comment("output or error message")(ht.TString),
    ]

  OP_RESULT = \
    ht.TListOf(ht.TAnd(ht.TIsLength(len(_RESULT_ITEMS)),
                       ht.TItems(_RESULT_ITEMS)))


1069
1070
# node opcodes

1071
class OpNodeRemove(OpCode):
Iustin Pop's avatar
Iustin Pop committed
1072
1073
1074
1075
1076
1077
1078
  """Remove a node.

  @type node_name: C{str}
  @ivar node_name: The name of the node to remove. If the node still has
                   instances on it, the operation will fail.

  """
1079
  OP_DSC_FIELD = "node_name"
1080
1081
1082
  OP_PARAMS = [
    _PNodeName,
    ]
1083
  OP_RESULT = ht.TNone
Iustin Pop's avatar
Iustin Pop committed
1084
1085


Iustin Pop's avatar
Iustin Pop committed
1086
class OpNodeAdd(OpCode):
Iustin Pop's avatar
Iustin Pop committed
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
  """Add a node to the cluster.

  @type node_name: C{str}
  @ivar node_name: The name of the node to add. This can be a short name,
                   but it will be expanded to the FQDN.
  @type primary_ip: IP address
  @ivar primary_ip: The primary IP of the node. This will be ignored when the
                    opcode is submitted, but will be filled during the node
                    add (so it will be visible in the job query).
  @type secondary_ip: IP address
  @ivar secondary_ip: The secondary IP of the node. This needs to be passed
                      if the cluster has been initialized in 'dual-network'
                      mode, otherwise it must not be given.
  @type readd: C{bool}
  @ivar readd: Whether to re-add an existing node to the cluster. If
               this is not passed, then the operation will abort if the node
               name is already in the cluster; use this parameter to 'repair'
               a node that had its configuration broken, or was reinstalled
               without removal from the cluster.
1106
1107
  @type group: C{str}
  @ivar group: The node group to which this node will belong.
1108
1109
1110
1111
  @type vm_capable: C{bool}
  @ivar vm_capable: The vm_capable node attribute
  @type master_capable: C{bool}
  @ivar master_capable: The master_capable node attribute
Iustin Pop's avatar
Iustin Pop committed
1112
1113

  """
1114
  OP_DSC_FIELD = "node_name"
1115
1116
  OP_PARAMS = [
    _PNodeName,
1117
1118
    _PHvState,
    _PDiskState,
1119
1120
1121
1122
1123
1124
1125
1126
1127
    ("primary_ip", None, ht.NoType, "Primary IP address"),
    ("secondary_ip", None, ht.TMaybeString, "Secondary IP address"),
    ("readd", False, ht.TBool, "Whether node is re-added to cluster"),
    ("group", None, ht.TMaybeString, "Initial node group"),
    ("master_capable", None, ht.TMaybeBool,
     "Whether node can become master or master candidate"),
    ("vm_capable", None, ht.TMaybeBool,
     "Whether node can host instances"),
    ("ndparams", None, ht.TMaybeDict, "Node parameters"),
1128
    ]
1129
  OP_RESULT = ht.TNone
Iustin Pop's avatar
Iustin Pop committed
1130
1131


1132
class OpNodeQuery(OpCode):
Iustin Pop's avatar
Iustin Pop committed
1133
  """Compute the list of nodes."""
1134
1135
  OP_PARAMS = [
    _POutputFields,
1136
1137
1138
    _PUseLocking,
    ("names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString),
     "Empty list to query all nodes, node names otherwise"),
1139
    ]
1140
  OP_RESULT = _TOldQueryResult
Iustin Pop's avatar
Iustin Pop committed
1141
1142