Statistics
| Branch: | Tag: | Revision:

root / lib / opcodes.py @ 6daf26a0

History | View | Annotate | Download (17.7 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007 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-msg=R0903
35

    
36

    
37
class BaseOpCode(object):
38
  """A simple serializable object.
39

40
  This object serves as a parent class for OpCode without any custom
41
  field handling.
42

43
  """
44
  __slots__ = []
45

    
46
  def __init__(self, **kwargs):
47
    """Constructor for BaseOpCode.
48

49
    The constructor takes only keyword arguments and will set
50
    attributes on this object based on the passed arguments. As such,
51
    it means that you should not pass arguments which are not in the
52
    __slots__ attribute for this class.
53

54
    """
55
    for key in kwargs:
56
      if key not in self.__slots__:
57
        raise TypeError("Object %s doesn't support the parameter '%s'" %
58
                        (self.__class__.__name__, key))
59
      setattr(self, key, kwargs[key])
60

    
61
  def __getstate__(self):
62
    """Generic serializer.
63

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

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

70
    """
71
    state = {}
72
    for name in self.__slots__:
73
      if hasattr(self, name):
74
        state[name] = getattr(self, name)
75
    return state
76

    
77
  def __setstate__(self, state):
78
    """Generic unserializer.
79

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

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

86
    """
87
    if not isinstance(state, dict):
88
      raise ValueError("Invalid data to __setstate__: expected dict, got %s" %
89
                       type(state))
90

    
91
    for name in self.__slots__:
92
      if name not in state:
93
        delattr(self, name)
94

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

    
98

    
99
class OpCode(BaseOpCode):
100
  """Abstract OpCode.
101

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

105
  @cvar OP_ID: The ID of this opcode. This should be unique amongst all
106
               children of this class.
107
  @ivar dry_run: Whether the LU should be run in dry-run mode, i.e. just
108
                 the check steps
109

110
  """
111
  OP_ID = "OP_ABSTRACT"
112
  __slots__ = BaseOpCode.__slots__ + ["dry_run"]
113

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

117
    This method adds to the state dictionary the OP_ID of the class,
118
    so that on unload we can identify the correct class for
119
    instantiating the opcode.
120

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

124
    """
125
    data = BaseOpCode.__getstate__(self)
126
    data["OP_ID"] = self.OP_ID
127
    return data
128

    
129
  @classmethod
130
  def LoadOpCode(cls, data):
131
    """Generic load opcode method.
132

133
    The method identifies the correct opcode class from the dict-form
134
    by looking for a OP_ID key, if this is not found, or its value is
135
    not available in this module as a child of this class, we fail.
136

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

140
    """
141
    if not isinstance(data, dict):
142
      raise ValueError("Invalid data to LoadOpCode (%s)" % type(data))
143
    if "OP_ID" not in data:
144
      raise ValueError("Invalid data to LoadOpcode, missing OP_ID")
145
    op_id = data["OP_ID"]
146
    op_class = None
147
    if op_id in OP_MAPPING:
148
      op_class = OP_MAPPING[op_id]
149
    else:
150
      raise ValueError("Invalid data to LoadOpCode: OP_ID %s unsupported" %
151
                       op_id)
152
    op = op_class()
153
    new_data = data.copy()
154
    del new_data["OP_ID"]
155
    op.__setstate__(new_data)
156
    return op
157

    
158
  def Summary(self):
159
    """Generates a summary description of this opcode.
160

161
    """
162
    # all OP_ID start with OP_, we remove that
163
    txt = self.OP_ID[3:]
164
    field_name = getattr(self, "OP_DSC_FIELD", None)
165
    if field_name:
166
      field_value = getattr(self, field_name, None)
167
      txt = "%s(%s)" % (txt, field_value)
168
    return txt
169

    
170

    
171
# cluster opcodes
172

    
173
class OpPostInitCluster(OpCode):
174
  """Post cluster initialization.
175

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

179
  """
180
  OP_ID = "OP_CLUSTER_POST_INIT"
181
  __slots__ = OpCode.__slots__ + []
182

    
183

    
184
class OpDestroyCluster(OpCode):
185
  """Destroy the cluster.
186

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

190
  """
191
  OP_ID = "OP_CLUSTER_DESTROY"
192
  __slots__ = OpCode.__slots__ + []
193

    
194

    
195
class OpQueryClusterInfo(OpCode):
196
  """Query cluster information."""
197
  OP_ID = "OP_CLUSTER_QUERY"
198
  __slots__ = OpCode.__slots__ + []
199

    
200

    
201
class OpVerifyCluster(OpCode):
202
  """Verify the cluster state.
203

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

210
  """
211
  OP_ID = "OP_CLUSTER_VERIFY"
212
  __slots__ = OpCode.__slots__ + ["skip_checks"]
213

    
214

    
215
class OpVerifyDisks(OpCode):
216
  """Verify the cluster disks.
217

218
  Parameters: none
219

220
  Result: a tuple of four elements:
221
    - list of node names with bad data returned (unreachable, etc.)
222
    - dict of node names with broken volume groups (values: error msg)
223
    - list of instances with degraded disks (that should be activated)
224
    - dict of instances with missing logical volumes (values: (node, vol)
225
      pairs with details about the missing volumes)
226

227
  In normal operation, all lists should be empty. A non-empty instance
228
  list (3rd element of the result) is still ok (errors were fixed) but
229
  non-empty node list means some node is down, and probably there are
230
  unfixable drbd errors.
231

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

235
  """
236
  OP_ID = "OP_CLUSTER_VERIFY_DISKS"
237
  __slots__ = OpCode.__slots__ + []
238

    
239

    
240
class OpQueryConfigValues(OpCode):
241
  """Query cluster configuration values."""
242
  OP_ID = "OP_CLUSTER_CONFIG_QUERY"
243
  __slots__ = OpCode.__slots__ + ["output_fields"]
244

    
245

    
246
class OpRenameCluster(OpCode):
247
  """Rename the cluster.
248

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

254
  """
255
  OP_ID = "OP_CLUSTER_RENAME"
256
  OP_DSC_FIELD = "name"
257
  __slots__ = OpCode.__slots__ + ["name"]
258

    
259

    
260
class OpSetClusterParams(OpCode):
261
  """Change the parameters of the cluster.
262

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

266
  """
267
  OP_ID = "OP_CLUSTER_SET_PARAMS"
268
  __slots__ = OpCode.__slots__ + [
269
    "vg_name",
270
    "enabled_hypervisors",
271
    "hvparams",
272
    "beparams",
273
    "nicparams",
274
    "candidate_pool_size",
275
    ]
276

    
277

    
278
class OpRedistributeConfig(OpCode):
279
  """Force a full push of the cluster configuration.
280

281
  """
282
  OP_ID = "OP_CLUSTER_REDIST_CONF"
283
  __slots__ = OpCode.__slots__ + [
284
    ]
285

    
286
# node opcodes
287

    
288
class OpRemoveNode(OpCode):
289
  """Remove a node.
290

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

295
  """
296
  OP_ID = "OP_NODE_REMOVE"
297
  OP_DSC_FIELD = "node_name"
298
  __slots__ = OpCode.__slots__ + ["node_name"]
299

    
300

    
301
class OpAddNode(OpCode):
302
  """Add a node to the cluster.
303

304
  @type node_name: C{str}
305
  @ivar node_name: The name of the node to add. This can be a short name,
306
                   but it will be expanded to the FQDN.
307
  @type primary_ip: IP address
308
  @ivar primary_ip: The primary IP of the node. This will be ignored when the
309
                    opcode is submitted, but will be filled during the node
310
                    add (so it will be visible in the job query).
311
  @type secondary_ip: IP address
312
  @ivar secondary_ip: The secondary IP of the node. This needs to be passed
313
                      if the cluster has been initialized in 'dual-network'
314
                      mode, otherwise it must not be given.
315
  @type readd: C{bool}
316
  @ivar readd: Whether to re-add an existing node to the cluster. If
317
               this is not passed, then the operation will abort if the node
318
               name is already in the cluster; use this parameter to 'repair'
319
               a node that had its configuration broken, or was reinstalled
320
               without removal from the cluster.
321

322
  """
323
  OP_ID = "OP_NODE_ADD"
324
  OP_DSC_FIELD = "node_name"
325
  __slots__ = OpCode.__slots__ + [
326
    "node_name", "primary_ip", "secondary_ip", "readd",
327
    ]
328

    
329

    
330
class OpQueryNodes(OpCode):
331
  """Compute the list of nodes."""
332
  OP_ID = "OP_NODE_QUERY"
333
  __slots__ = OpCode.__slots__ + ["output_fields", "names", "use_locking"]
334

    
335

    
336
class OpQueryNodeVolumes(OpCode):
337
  """Get list of volumes on node."""
338
  OP_ID = "OP_NODE_QUERYVOLS"
339
  __slots__ = OpCode.__slots__ + ["nodes", "output_fields"]
340

    
341

    
342
class OpQueryNodeStorage(OpCode):
343
  """Get information on storage for node(s)."""
344
  OP_ID = "OP_NODE_QUERY_STORAGE"
345
  __slots__ = OpCode.__slots__ + [
346
    "nodes",
347
    "storage_type",
348
    "name",
349
    "output_fields",
350
    ]
351

    
352

    
353
class OpModifyNodeStorage(OpCode):
354
  """"""
355
  OP_ID = "OP_NODE_MODIFY_STORAGE"
356
  __slots__ = OpCode.__slots__ + [
357
    "node_name",
358
    "storage_type",
359
    "name",
360
    "changes",
361
    ]
362

    
363

    
364
class OpSetNodeParams(OpCode):
365
  """Change the parameters of a node."""
366
  OP_ID = "OP_NODE_SET_PARAMS"
367
  OP_DSC_FIELD = "node_name"
368
  __slots__ = OpCode.__slots__ + [
369
    "node_name",
370
    "force",
371
    "master_candidate",
372
    "offline",
373
    "drained",
374
    ]
375

    
376

    
377
class OpPowercycleNode(OpCode):
378
  """Tries to powercycle a node."""
379
  OP_ID = "OP_NODE_POWERCYCLE"
380
  OP_DSC_FIELD = "node_name"
381
  __slots__ = OpCode.__slots__ + [
382
    "node_name",
383
    "force",
384
    ]
385

    
386

    
387
class OpEvacuateNode(OpCode):
388
  """Relocate secondary instances from a node."""
389
  OP_ID = "OP_NODE_EVACUATE"
390
  OP_DSC_FIELD = "node_name"
391
  __slots__ = OpCode.__slots__ + [
392
    "node_name", "remote_node", "iallocator",
393
    ]
394

    
395

    
396
class OpMigrateNode(OpCode):
397
  """Migrate all instances from a node."""
398
  OP_ID = "OP_NODE_MIGRATE"
399
  OP_DSC_FIELD = "node_name"
400
  __slots__ = OpCode.__slots__ + [
401
    "node_name",
402
    "live",
403
    ]
404

    
405

    
406
# instance opcodes
407

    
408
class OpCreateInstance(OpCode):
409
  """Create an instance."""
410
  OP_ID = "OP_INSTANCE_CREATE"
411
  OP_DSC_FIELD = "instance_name"
412
  __slots__ = OpCode.__slots__ + [
413
    "instance_name", "os_type", "pnode",
414
    "disk_template", "snode", "mode",
415
    "disks", "nics",
416
    "src_node", "src_path", "start",
417
    "wait_for_sync", "ip_check",
418
    "file_storage_dir", "file_driver",
419
    "iallocator",
420
    "hypervisor", "hvparams", "beparams",
421
    "dry_run",
422
    ]
423

    
424

    
425
class OpReinstallInstance(OpCode):
426
  """Reinstall an instance's OS."""
427
  OP_ID = "OP_INSTANCE_REINSTALL"
428
  OP_DSC_FIELD = "instance_name"
429
  __slots__ = OpCode.__slots__ + ["instance_name", "os_type"]
430

    
431

    
432
class OpRemoveInstance(OpCode):
433
  """Remove an instance."""
434
  OP_ID = "OP_INSTANCE_REMOVE"
435
  OP_DSC_FIELD = "instance_name"
436
  __slots__ = OpCode.__slots__ + ["instance_name", "ignore_failures"]
437

    
438

    
439
class OpRenameInstance(OpCode):
440
  """Rename an instance."""
441
  OP_ID = "OP_INSTANCE_RENAME"
442
  __slots__ = OpCode.__slots__ + [
443
    "instance_name", "ignore_ip", "new_name",
444
    ]
445

    
446

    
447
class OpStartupInstance(OpCode):
448
  """Startup an instance."""
449
  OP_ID = "OP_INSTANCE_STARTUP"
450
  OP_DSC_FIELD = "instance_name"
451
  __slots__ = OpCode.__slots__ + [
452
    "instance_name", "force", "hvparams", "beparams",
453
    ]
454

    
455

    
456
class OpShutdownInstance(OpCode):
457
  """Shutdown an instance."""
458
  OP_ID = "OP_INSTANCE_SHUTDOWN"
459
  OP_DSC_FIELD = "instance_name"
460
  __slots__ = OpCode.__slots__ + ["instance_name"]
461

    
462

    
463
class OpRebootInstance(OpCode):
464
  """Reboot an instance."""
465
  OP_ID = "OP_INSTANCE_REBOOT"
466
  OP_DSC_FIELD = "instance_name"
467
  __slots__ = OpCode.__slots__ + [
468
    "instance_name", "reboot_type", "ignore_secondaries",
469
    ]
470

    
471

    
472
class OpReplaceDisks(OpCode):
473
  """Replace the disks of an instance."""
474
  OP_ID = "OP_INSTANCE_REPLACE_DISKS"
475
  OP_DSC_FIELD = "instance_name"
476
  __slots__ = OpCode.__slots__ + [
477
    "instance_name", "remote_node", "mode", "disks", "iallocator",
478
    ]
479

    
480

    
481
class OpFailoverInstance(OpCode):
482
  """Failover an instance."""
483
  OP_ID = "OP_INSTANCE_FAILOVER"
484
  OP_DSC_FIELD = "instance_name"
485
  __slots__ = OpCode.__slots__ + ["instance_name", "ignore_consistency"]
486

    
487

    
488
class OpMigrateInstance(OpCode):
489
  """Migrate an instance.
490

491
  This migrates (without shutting down an instance) to its secondary
492
  node.
493

494
  @ivar instance_name: the name of the instance
495

496
  """
497
  OP_ID = "OP_INSTANCE_MIGRATE"
498
  OP_DSC_FIELD = "instance_name"
499
  __slots__ = OpCode.__slots__ + ["instance_name", "live", "cleanup"]
500

    
501

    
502
class OpConnectConsole(OpCode):
503
  """Connect to an instance's console."""
504
  OP_ID = "OP_INSTANCE_CONSOLE"
505
  OP_DSC_FIELD = "instance_name"
506
  __slots__ = OpCode.__slots__ + ["instance_name"]
507

    
508

    
509
class OpActivateInstanceDisks(OpCode):
510
  """Activate an instance's disks."""
511
  OP_ID = "OP_INSTANCE_ACTIVATE_DISKS"
512
  OP_DSC_FIELD = "instance_name"
513
  __slots__ = OpCode.__slots__ + ["instance_name"]
514

    
515

    
516
class OpDeactivateInstanceDisks(OpCode):
517
  """Deactivate an instance's disks."""
518
  OP_ID = "OP_INSTANCE_DEACTIVATE_DISKS"
519
  OP_DSC_FIELD = "instance_name"
520
  __slots__ = OpCode.__slots__ + ["instance_name"]
521

    
522

    
523
class OpQueryInstances(OpCode):
524
  """Compute the list of instances."""
525
  OP_ID = "OP_INSTANCE_QUERY"
526
  __slots__ = OpCode.__slots__ + ["output_fields", "names", "use_locking"]
527

    
528

    
529
class OpQueryInstanceData(OpCode):
530
  """Compute the run-time status of instances."""
531
  OP_ID = "OP_INSTANCE_QUERY_DATA"
532
  __slots__ = OpCode.__slots__ + ["instances", "static"]
533

    
534

    
535
class OpSetInstanceParams(OpCode):
536
  """Change the parameters of an instance."""
537
  OP_ID = "OP_INSTANCE_SET_PARAMS"
538
  OP_DSC_FIELD = "instance_name"
539
  __slots__ = OpCode.__slots__ + [
540
    "instance_name",
541
    "hvparams", "beparams", "force",
542
    "nics", "disks",
543
    ]
544

    
545

    
546
class OpGrowDisk(OpCode):
547
  """Grow a disk of an instance."""
548
  OP_ID = "OP_INSTANCE_GROW_DISK"
549
  OP_DSC_FIELD = "instance_name"
550
  __slots__ = OpCode.__slots__ + [
551
    "instance_name", "disk", "amount", "wait_for_sync",
552
    ]
553

    
554

    
555
# OS opcodes
556
class OpDiagnoseOS(OpCode):
557
  """Compute the list of guest operating systems."""
558
  OP_ID = "OP_OS_DIAGNOSE"
559
  __slots__ = OpCode.__slots__ + ["output_fields", "names"]
560

    
561

    
562
# Exports opcodes
563
class OpQueryExports(OpCode):
564
  """Compute the list of exported images."""
565
  OP_ID = "OP_BACKUP_QUERY"
566
  __slots__ = OpCode.__slots__ + ["nodes", "use_locking"]
567

    
568

    
569
class OpExportInstance(OpCode):
570
  """Export an instance."""
571
  OP_ID = "OP_BACKUP_EXPORT"
572
  OP_DSC_FIELD = "instance_name"
573
  __slots__ = OpCode.__slots__ + ["instance_name", "target_node", "shutdown"]
574

    
575

    
576
class OpRemoveExport(OpCode):
577
  """Remove an instance's export."""
578
  OP_ID = "OP_BACKUP_REMOVE"
579
  OP_DSC_FIELD = "instance_name"
580
  __slots__ = OpCode.__slots__ + ["instance_name"]
581

    
582

    
583
# Tags opcodes
584
class OpGetTags(OpCode):
585
  """Returns the tags of the given object."""
586
  OP_ID = "OP_TAGS_GET"
587
  OP_DSC_FIELD = "name"
588
  __slots__ = OpCode.__slots__ + ["kind", "name"]
589

    
590

    
591
class OpSearchTags(OpCode):
592
  """Searches the tags in the cluster for a given pattern."""
593
  OP_ID = "OP_TAGS_SEARCH"
594
  OP_DSC_FIELD = "pattern"
595
  __slots__ = OpCode.__slots__ + ["pattern"]
596

    
597

    
598
class OpAddTags(OpCode):
599
  """Add a list of tags on a given object."""
600
  OP_ID = "OP_TAGS_SET"
601
  __slots__ = OpCode.__slots__ + ["kind", "name", "tags"]
602

    
603

    
604
class OpDelTags(OpCode):
605
  """Remove a list of tags from a given object."""
606
  OP_ID = "OP_TAGS_DEL"
607
  __slots__ = OpCode.__slots__ + ["kind", "name", "tags"]
608

    
609

    
610
# Test opcodes
611
class OpTestDelay(OpCode):
612
  """Sleeps for a configured amount of time.
613

614
  This is used just for debugging and testing.
615

616
  Parameters:
617
    - duration: the time to sleep
618
    - on_master: if true, sleep on the master
619
    - on_nodes: list of nodes in which to sleep
620

621
  If the on_master parameter is true, it will execute a sleep on the
622
  master (before any node sleep).
623

624
  If the on_nodes list is not empty, it will sleep on those nodes
625
  (after the sleep on the master, if that is enabled).
626

627
  As an additional feature, the case of duration < 0 will be reported
628
  as an execution error, so this opcode can be used as a failure
629
  generator. The case of duration == 0 will not be treated specially.
630

631
  """
632
  OP_ID = "OP_TEST_DELAY"
633
  OP_DSC_FIELD = "duration"
634
  __slots__ = OpCode.__slots__ + ["duration", "on_master", "on_nodes"]
635

    
636

    
637
class OpTestAllocator(OpCode):
638
  """Allocator framework testing.
639

640
  This opcode has two modes:
641
    - gather and return allocator input for a given mode (allocate new
642
      or replace secondary) and a given instance definition (direction
643
      'in')
644
    - run a selected allocator for a given operation (as above) and
645
      return the allocator output (direction 'out')
646

647
  """
648
  OP_ID = "OP_TEST_ALLOCATOR"
649
  OP_DSC_FIELD = "allocator"
650
  __slots__ = OpCode.__slots__ + [
651
    "direction", "mode", "allocator", "name",
652
    "mem_size", "disks", "disk_template",
653
    "os", "tags", "nics", "vcpus", "hypervisor",
654
    ]
655

    
656
OP_MAPPING = dict([(v.OP_ID, v) for v in globals().values()
657
                   if (isinstance(v, type) and issubclass(v, OpCode) and
658
                       hasattr(v, "OP_ID"))])