Statistics
| Branch: | Tag: | Revision:

root / lib / objects.py @ daa55b04

History | View | Annotate | Download (42.6 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2008, 2009, 2010 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
"""Transportable objects for Ganeti.
23

24
This module provides small, mostly data-only objects which are safe to
25
pass to and from external parties.
26

27
"""
28

    
29
# pylint: disable-msg=E0203,W0201
30

    
31
# E0203: Access to member %r before its definition, since we use
32
# objects.py which doesn't explicitely initialise its members
33

    
34
# W0201: Attribute '%s' defined outside __init__
35

    
36
import ConfigParser
37
import re
38
import copy
39
import time
40
from cStringIO import StringIO
41

    
42
from ganeti import errors
43
from ganeti import constants
44

    
45
from socket import AF_INET
46

    
47

    
48
__all__ = ["ConfigObject", "ConfigData", "NIC", "Disk", "Instance",
49
           "OS", "Node", "NodeGroup", "Cluster", "FillDict"]
50

    
51
_TIMESTAMPS = ["ctime", "mtime"]
52
_UUID = ["uuid"]
53

    
54

    
55
def FillDict(defaults_dict, custom_dict, skip_keys=None):
56
  """Basic function to apply settings on top a default dict.
57

58
  @type defaults_dict: dict
59
  @param defaults_dict: dictionary holding the default values
60
  @type custom_dict: dict
61
  @param custom_dict: dictionary holding customized value
62
  @type skip_keys: list
63
  @param skip_keys: which keys not to fill
64
  @rtype: dict
65
  @return: dict with the 'full' values
66

67
  """
68
  ret_dict = copy.deepcopy(defaults_dict)
69
  ret_dict.update(custom_dict)
70
  if skip_keys:
71
    for k in skip_keys:
72
      try:
73
        del ret_dict[k]
74
      except KeyError:
75
        pass
76
  return ret_dict
77

    
78

    
79
def UpgradeGroupedParams(target, defaults):
80
  """Update all groups for the target parameter.
81

82
  @type target: dict of dicts
83
  @param target: {group: {parameter: value}}
84
  @type defaults: dict
85
  @param defaults: default parameter values
86

87
  """
88
  if target is None:
89
    target = {constants.PP_DEFAULT: defaults}
90
  else:
91
    for group in target:
92
      target[group] = FillDict(defaults, target[group])
93
  return target
94

    
95

    
96
class ConfigObject(object):
97
  """A generic config object.
98

99
  It has the following properties:
100

101
    - provides somewhat safe recursive unpickling and pickling for its classes
102
    - unset attributes which are defined in slots are always returned
103
      as None instead of raising an error
104

105
  Classes derived from this must always declare __slots__ (we use many
106
  config objects and the memory reduction is useful)
107

108
  """
109
  __slots__ = []
110

    
111
  def __init__(self, **kwargs):
112
    for k, v in kwargs.iteritems():
113
      setattr(self, k, v)
114

    
115
  def __getattr__(self, name):
116
    if name not in self._all_slots():
117
      raise AttributeError("Invalid object attribute %s.%s" %
118
                           (type(self).__name__, name))
119
    return None
120

    
121
  def __setstate__(self, state):
122
    slots = self._all_slots()
123
    for name in state:
124
      if name in slots:
125
        setattr(self, name, state[name])
126

    
127
  @classmethod
128
  def _all_slots(cls):
129
    """Compute the list of all declared slots for a class.
130

131
    """
132
    slots = []
133
    for parent in cls.__mro__:
134
      slots.extend(getattr(parent, "__slots__", []))
135
    return slots
136

    
137
  def ToDict(self):
138
    """Convert to a dict holding only standard python types.
139

140
    The generic routine just dumps all of this object's attributes in
141
    a dict. It does not work if the class has children who are
142
    ConfigObjects themselves (e.g. the nics list in an Instance), in
143
    which case the object should subclass the function in order to
144
    make sure all objects returned are only standard python types.
145

146
    """
147
    result = {}
148
    for name in self._all_slots():
149
      value = getattr(self, name, None)
150
      if value is not None:
151
        result[name] = value
152
    return result
153

    
154
  __getstate__ = ToDict
155

    
156
  @classmethod
157
  def FromDict(cls, val):
158
    """Create an object from a dictionary.
159

160
    This generic routine takes a dict, instantiates a new instance of
161
    the given class, and sets attributes based on the dict content.
162

163
    As for `ToDict`, this does not work if the class has children
164
    who are ConfigObjects themselves (e.g. the nics list in an
165
    Instance), in which case the object should subclass the function
166
    and alter the objects.
167

168
    """
169
    if not isinstance(val, dict):
170
      raise errors.ConfigurationError("Invalid object passed to FromDict:"
171
                                      " expected dict, got %s" % type(val))
172
    val_str = dict([(str(k), v) for k, v in val.iteritems()])
173
    obj = cls(**val_str) # pylint: disable-msg=W0142
174
    return obj
175

    
176
  @staticmethod
177
  def _ContainerToDicts(container):
178
    """Convert the elements of a container to standard python types.
179

180
    This method converts a container with elements derived from
181
    ConfigData to standard python types. If the container is a dict,
182
    we don't touch the keys, only the values.
183

184
    """
185
    if isinstance(container, dict):
186
      ret = dict([(k, v.ToDict()) for k, v in container.iteritems()])
187
    elif isinstance(container, (list, tuple, set, frozenset)):
188
      ret = [elem.ToDict() for elem in container]
189
    else:
190
      raise TypeError("Invalid type %s passed to _ContainerToDicts" %
191
                      type(container))
192
    return ret
193

    
194
  @staticmethod
195
  def _ContainerFromDicts(source, c_type, e_type):
196
    """Convert a container from standard python types.
197

198
    This method converts a container with standard python types to
199
    ConfigData objects. If the container is a dict, we don't touch the
200
    keys, only the values.
201

202
    """
203
    if not isinstance(c_type, type):
204
      raise TypeError("Container type %s passed to _ContainerFromDicts is"
205
                      " not a type" % type(c_type))
206
    if source is None:
207
      source = c_type()
208
    if c_type is dict:
209
      ret = dict([(k, e_type.FromDict(v)) for k, v in source.iteritems()])
210
    elif c_type in (list, tuple, set, frozenset):
211
      ret = c_type([e_type.FromDict(elem) for elem in source])
212
    else:
213
      raise TypeError("Invalid container type %s passed to"
214
                      " _ContainerFromDicts" % c_type)
215
    return ret
216

    
217
  def Copy(self):
218
    """Makes a deep copy of the current object and its children.
219

220
    """
221
    dict_form = self.ToDict()
222
    clone_obj = self.__class__.FromDict(dict_form)
223
    return clone_obj
224

    
225
  def __repr__(self):
226
    """Implement __repr__ for ConfigObjects."""
227
    return repr(self.ToDict())
228

    
229
  def UpgradeConfig(self):
230
    """Fill defaults for missing configuration values.
231

232
    This method will be called at configuration load time, and its
233
    implementation will be object dependent.
234

235
    """
236
    pass
237

    
238

    
239
class TaggableObject(ConfigObject):
240
  """An generic class supporting tags.
241

242
  """
243
  __slots__ = ["tags"]
244
  VALID_TAG_RE = re.compile("^[\w.+*/:@-]+$")
245

    
246
  @classmethod
247
  def ValidateTag(cls, tag):
248
    """Check if a tag is valid.
249

250
    If the tag is invalid, an errors.TagError will be raised. The
251
    function has no return value.
252

253
    """
254
    if not isinstance(tag, basestring):
255
      raise errors.TagError("Invalid tag type (not a string)")
256
    if len(tag) > constants.MAX_TAG_LEN:
257
      raise errors.TagError("Tag too long (>%d characters)" %
258
                            constants.MAX_TAG_LEN)
259
    if not tag:
260
      raise errors.TagError("Tags cannot be empty")
261
    if not cls.VALID_TAG_RE.match(tag):
262
      raise errors.TagError("Tag contains invalid characters")
263

    
264
  def GetTags(self):
265
    """Return the tags list.
266

267
    """
268
    tags = getattr(self, "tags", None)
269
    if tags is None:
270
      tags = self.tags = set()
271
    return tags
272

    
273
  def AddTag(self, tag):
274
    """Add a new tag.
275

276
    """
277
    self.ValidateTag(tag)
278
    tags = self.GetTags()
279
    if len(tags) >= constants.MAX_TAGS_PER_OBJ:
280
      raise errors.TagError("Too many tags")
281
    self.GetTags().add(tag)
282

    
283
  def RemoveTag(self, tag):
284
    """Remove a tag.
285

286
    """
287
    self.ValidateTag(tag)
288
    tags = self.GetTags()
289
    try:
290
      tags.remove(tag)
291
    except KeyError:
292
      raise errors.TagError("Tag not found")
293

    
294
  def ToDict(self):
295
    """Taggable-object-specific conversion to standard python types.
296

297
    This replaces the tags set with a list.
298

299
    """
300
    bo = super(TaggableObject, self).ToDict()
301

    
302
    tags = bo.get("tags", None)
303
    if isinstance(tags, set):
304
      bo["tags"] = list(tags)
305
    return bo
306

    
307
  @classmethod
308
  def FromDict(cls, val):
309
    """Custom function for instances.
310

311
    """
312
    obj = super(TaggableObject, cls).FromDict(val)
313
    if hasattr(obj, "tags") and isinstance(obj.tags, list):
314
      obj.tags = set(obj.tags)
315
    return obj
316

    
317

    
318
class ConfigData(ConfigObject):
319
  """Top-level config object."""
320
  __slots__ = [
321
    "version",
322
    "cluster",
323
    "nodes",
324
    "nodegroups",
325
    "instances",
326
    "serial_no",
327
    ] + _TIMESTAMPS
328

    
329
  def ToDict(self):
330
    """Custom function for top-level config data.
331

332
    This just replaces the list of instances, nodes and the cluster
333
    with standard python types.
334

335
    """
336
    mydict = super(ConfigData, self).ToDict()
337
    mydict["cluster"] = mydict["cluster"].ToDict()
338
    for key in "nodes", "instances", "nodegroups":
339
      mydict[key] = self._ContainerToDicts(mydict[key])
340

    
341
    return mydict
342

    
343
  @classmethod
344
  def FromDict(cls, val):
345
    """Custom function for top-level config data
346

347
    """
348
    obj = super(ConfigData, cls).FromDict(val)
349
    obj.cluster = Cluster.FromDict(obj.cluster)
350
    obj.nodes = cls._ContainerFromDicts(obj.nodes, dict, Node)
351
    obj.instances = cls._ContainerFromDicts(obj.instances, dict, Instance)
352
    obj.nodegroups = cls._ContainerFromDicts(obj.nodegroups, dict, NodeGroup)
353
    return obj
354

    
355
  def HasAnyDiskOfType(self, dev_type):
356
    """Check if in there is at disk of the given type in the configuration.
357

358
    @type dev_type: L{constants.LDS_BLOCK}
359
    @param dev_type: the type to look for
360
    @rtype: boolean
361
    @return: boolean indicating if a disk of the given type was found or not
362

363
    """
364
    for instance in self.instances.values():
365
      for disk in instance.disks:
366
        if disk.IsBasedOnDiskType(dev_type):
367
          return True
368
    return False
369

    
370
  def UpgradeConfig(self):
371
    """Fill defaults for missing configuration values.
372

373
    """
374
    self.cluster.UpgradeConfig()
375
    for node in self.nodes.values():
376
      node.UpgradeConfig()
377
    for instance in self.instances.values():
378
      instance.UpgradeConfig()
379
    if self.nodegroups is None:
380
      self.nodegroups = {}
381
    for nodegroup in self.nodegroups.values():
382
      nodegroup.UpgradeConfig()
383
    if self.cluster.drbd_usermode_helper is None:
384
      # To decide if we set an helper let's check if at least one instance has
385
      # a DRBD disk. This does not cover all the possible scenarios but it
386
      # gives a good approximation.
387
      if self.HasAnyDiskOfType(constants.LD_DRBD8):
388
        self.cluster.drbd_usermode_helper = constants.DEFAULT_DRBD_HELPER
389

    
390

    
391
class NIC(ConfigObject):
392
  """Config object representing a network card."""
393
  __slots__ = ["mac", "ip", "nicparams"]
394

    
395
  @classmethod
396
  def CheckParameterSyntax(cls, nicparams):
397
    """Check the given parameters for validity.
398

399
    @type nicparams:  dict
400
    @param nicparams: dictionary with parameter names/value
401
    @raise errors.ConfigurationError: when a parameter is not valid
402

403
    """
404
    if nicparams[constants.NIC_MODE] not in constants.NIC_VALID_MODES:
405
      err = "Invalid nic mode: %s" % nicparams[constants.NIC_MODE]
406
      raise errors.ConfigurationError(err)
407

    
408
    if (nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED and
409
        not nicparams[constants.NIC_LINK]):
410
      err = "Missing bridged nic link"
411
      raise errors.ConfigurationError(err)
412

    
413

    
414
class Disk(ConfigObject):
415
  """Config object representing a block device."""
416
  __slots__ = ["dev_type", "logical_id", "physical_id",
417
               "children", "iv_name", "size", "mode"]
418

    
419
  def CreateOnSecondary(self):
420
    """Test if this device needs to be created on a secondary node."""
421
    return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
422

    
423
  def AssembleOnSecondary(self):
424
    """Test if this device needs to be assembled on a secondary node."""
425
    return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
426

    
427
  def OpenOnSecondary(self):
428
    """Test if this device needs to be opened on a secondary node."""
429
    return self.dev_type in (constants.LD_LV,)
430

    
431
  def StaticDevPath(self):
432
    """Return the device path if this device type has a static one.
433

434
    Some devices (LVM for example) live always at the same /dev/ path,
435
    irrespective of their status. For such devices, we return this
436
    path, for others we return None.
437

438
    @warning: The path returned is not a normalized pathname; callers
439
        should check that it is a valid path.
440

441
    """
442
    if self.dev_type == constants.LD_LV:
443
      return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
444
    return None
445

    
446
  def ChildrenNeeded(self):
447
    """Compute the needed number of children for activation.
448

449
    This method will return either -1 (all children) or a positive
450
    number denoting the minimum number of children needed for
451
    activation (only mirrored devices will usually return >=0).
452

453
    Currently, only DRBD8 supports diskless activation (therefore we
454
    return 0), for all other we keep the previous semantics and return
455
    -1.
456

457
    """
458
    if self.dev_type == constants.LD_DRBD8:
459
      return 0
460
    return -1
461

    
462
  def IsBasedOnDiskType(self, dev_type):
463
    """Check if the disk or its children are based on the given type.
464

465
    @type dev_type: L{constants.LDS_BLOCK}
466
    @param dev_type: the type to look for
467
    @rtype: boolean
468
    @return: boolean indicating if a device of the given type was found or not
469

470
    """
471
    if self.children:
472
      for child in self.children:
473
        if child.IsBasedOnDiskType(dev_type):
474
          return True
475
    return self.dev_type == dev_type
476

    
477
  def GetNodes(self, node):
478
    """This function returns the nodes this device lives on.
479

480
    Given the node on which the parent of the device lives on (or, in
481
    case of a top-level device, the primary node of the devices'
482
    instance), this function will return a list of nodes on which this
483
    devices needs to (or can) be assembled.
484

485
    """
486
    if self.dev_type in [constants.LD_LV, constants.LD_FILE]:
487
      result = [node]
488
    elif self.dev_type in constants.LDS_DRBD:
489
      result = [self.logical_id[0], self.logical_id[1]]
490
      if node not in result:
491
        raise errors.ConfigurationError("DRBD device passed unknown node")
492
    else:
493
      raise errors.ProgrammerError("Unhandled device type %s" % self.dev_type)
494
    return result
495

    
496
  def ComputeNodeTree(self, parent_node):
497
    """Compute the node/disk tree for this disk and its children.
498

499
    This method, given the node on which the parent disk lives, will
500
    return the list of all (node, disk) pairs which describe the disk
501
    tree in the most compact way. For example, a drbd/lvm stack
502
    will be returned as (primary_node, drbd) and (secondary_node, drbd)
503
    which represents all the top-level devices on the nodes.
504

505
    """
506
    my_nodes = self.GetNodes(parent_node)
507
    result = [(node, self) for node in my_nodes]
508
    if not self.children:
509
      # leaf device
510
      return result
511
    for node in my_nodes:
512
      for child in self.children:
513
        child_result = child.ComputeNodeTree(node)
514
        if len(child_result) == 1:
515
          # child (and all its descendants) is simple, doesn't split
516
          # over multiple hosts, so we don't need to describe it, our
517
          # own entry for this node describes it completely
518
          continue
519
        else:
520
          # check if child nodes differ from my nodes; note that
521
          # subdisk can differ from the child itself, and be instead
522
          # one of its descendants
523
          for subnode, subdisk in child_result:
524
            if subnode not in my_nodes:
525
              result.append((subnode, subdisk))
526
            # otherwise child is under our own node, so we ignore this
527
            # entry (but probably the other results in the list will
528
            # be different)
529
    return result
530

    
531
  def RecordGrow(self, amount):
532
    """Update the size of this disk after growth.
533

534
    This method recurses over the disks's children and updates their
535
    size correspondigly. The method needs to be kept in sync with the
536
    actual algorithms from bdev.
537

538
    """
539
    if self.dev_type == constants.LD_LV or self.dev_type == constants.LD_FILE:
540
      self.size += amount
541
    elif self.dev_type == constants.LD_DRBD8:
542
      if self.children:
543
        self.children[0].RecordGrow(amount)
544
      self.size += amount
545
    else:
546
      raise errors.ProgrammerError("Disk.RecordGrow called for unsupported"
547
                                   " disk type %s" % self.dev_type)
548

    
549
  def UnsetSize(self):
550
    """Sets recursively the size to zero for the disk and its children.
551

552
    """
553
    if self.children:
554
      for child in self.children:
555
        child.UnsetSize()
556
    self.size = 0
557

    
558
  def SetPhysicalID(self, target_node, nodes_ip):
559
    """Convert the logical ID to the physical ID.
560

561
    This is used only for drbd, which needs ip/port configuration.
562

563
    The routine descends down and updates its children also, because
564
    this helps when the only the top device is passed to the remote
565
    node.
566

567
    Arguments:
568
      - target_node: the node we wish to configure for
569
      - nodes_ip: a mapping of node name to ip
570

571
    The target_node must exist in in nodes_ip, and must be one of the
572
    nodes in the logical ID for each of the DRBD devices encountered
573
    in the disk tree.
574

575
    """
576
    if self.children:
577
      for child in self.children:
578
        child.SetPhysicalID(target_node, nodes_ip)
579

    
580
    if self.logical_id is None and self.physical_id is not None:
581
      return
582
    if self.dev_type in constants.LDS_DRBD:
583
      pnode, snode, port, pminor, sminor, secret = self.logical_id
584
      if target_node not in (pnode, snode):
585
        raise errors.ConfigurationError("DRBD device not knowing node %s" %
586
                                        target_node)
587
      pnode_ip = nodes_ip.get(pnode, None)
588
      snode_ip = nodes_ip.get(snode, None)
589
      if pnode_ip is None or snode_ip is None:
590
        raise errors.ConfigurationError("Can't find primary or secondary node"
591
                                        " for %s" % str(self))
592
      p_data = (pnode_ip, port)
593
      s_data = (snode_ip, port)
594
      if pnode == target_node:
595
        self.physical_id = p_data + s_data + (pminor, secret)
596
      else: # it must be secondary, we tested above
597
        self.physical_id = s_data + p_data + (sminor, secret)
598
    else:
599
      self.physical_id = self.logical_id
600
    return
601

    
602
  def ToDict(self):
603
    """Disk-specific conversion to standard python types.
604

605
    This replaces the children lists of objects with lists of
606
    standard python types.
607

608
    """
609
    bo = super(Disk, self).ToDict()
610

    
611
    for attr in ("children",):
612
      alist = bo.get(attr, None)
613
      if alist:
614
        bo[attr] = self._ContainerToDicts(alist)
615
    return bo
616

    
617
  @classmethod
618
  def FromDict(cls, val):
619
    """Custom function for Disks
620

621
    """
622
    obj = super(Disk, cls).FromDict(val)
623
    if obj.children:
624
      obj.children = cls._ContainerFromDicts(obj.children, list, Disk)
625
    if obj.logical_id and isinstance(obj.logical_id, list):
626
      obj.logical_id = tuple(obj.logical_id)
627
    if obj.physical_id and isinstance(obj.physical_id, list):
628
      obj.physical_id = tuple(obj.physical_id)
629
    if obj.dev_type in constants.LDS_DRBD:
630
      # we need a tuple of length six here
631
      if len(obj.logical_id) < 6:
632
        obj.logical_id += (None,) * (6 - len(obj.logical_id))
633
    return obj
634

    
635
  def __str__(self):
636
    """Custom str() formatter for disks.
637

638
    """
639
    if self.dev_type == constants.LD_LV:
640
      val =  "<LogicalVolume(/dev/%s/%s" % self.logical_id
641
    elif self.dev_type in constants.LDS_DRBD:
642
      node_a, node_b, port, minor_a, minor_b = self.logical_id[:5]
643
      val = "<DRBD8("
644
      if self.physical_id is None:
645
        phy = "unconfigured"
646
      else:
647
        phy = ("configured as %s:%s %s:%s" %
648
               (self.physical_id[0], self.physical_id[1],
649
                self.physical_id[2], self.physical_id[3]))
650

    
651
      val += ("hosts=%s/%d-%s/%d, port=%s, %s, " %
652
              (node_a, minor_a, node_b, minor_b, port, phy))
653
      if self.children and self.children.count(None) == 0:
654
        val += "backend=%s, metadev=%s" % (self.children[0], self.children[1])
655
      else:
656
        val += "no local storage"
657
    else:
658
      val = ("<Disk(type=%s, logical_id=%s, physical_id=%s, children=%s" %
659
             (self.dev_type, self.logical_id, self.physical_id, self.children))
660
    if self.iv_name is None:
661
      val += ", not visible"
662
    else:
663
      val += ", visible as /dev/%s" % self.iv_name
664
    if isinstance(self.size, int):
665
      val += ", size=%dm)>" % self.size
666
    else:
667
      val += ", size='%s')>" % (self.size,)
668
    return val
669

    
670
  def Verify(self):
671
    """Checks that this disk is correctly configured.
672

673
    """
674
    all_errors = []
675
    if self.mode not in constants.DISK_ACCESS_SET:
676
      all_errors.append("Disk access mode '%s' is invalid" % (self.mode, ))
677
    return all_errors
678

    
679
  def UpgradeConfig(self):
680
    """Fill defaults for missing configuration values.
681

682
    """
683
    if self.children:
684
      for child in self.children:
685
        child.UpgradeConfig()
686
    # add here config upgrade for this disk
687

    
688

    
689
class Instance(TaggableObject):
690
  """Config object representing an instance."""
691
  __slots__ = [
692
    "name",
693
    "primary_node",
694
    "os",
695
    "hypervisor",
696
    "hvparams",
697
    "beparams",
698
    "osparams",
699
    "admin_up",
700
    "nics",
701
    "disks",
702
    "disk_template",
703
    "network_port",
704
    "serial_no",
705
    ] + _TIMESTAMPS + _UUID
706

    
707
  def _ComputeSecondaryNodes(self):
708
    """Compute the list of secondary nodes.
709

710
    This is a simple wrapper over _ComputeAllNodes.
711

712
    """
713
    all_nodes = set(self._ComputeAllNodes())
714
    all_nodes.discard(self.primary_node)
715
    return tuple(all_nodes)
716

    
717
  secondary_nodes = property(_ComputeSecondaryNodes, None, None,
718
                             "List of secondary nodes")
719

    
720
  def _ComputeAllNodes(self):
721
    """Compute the list of all nodes.
722

723
    Since the data is already there (in the drbd disks), keeping it as
724
    a separate normal attribute is redundant and if not properly
725
    synchronised can cause problems. Thus it's better to compute it
726
    dynamically.
727

728
    """
729
    def _Helper(nodes, device):
730
      """Recursively computes nodes given a top device."""
731
      if device.dev_type in constants.LDS_DRBD:
732
        nodea, nodeb = device.logical_id[:2]
733
        nodes.add(nodea)
734
        nodes.add(nodeb)
735
      if device.children:
736
        for child in device.children:
737
          _Helper(nodes, child)
738

    
739
    all_nodes = set()
740
    all_nodes.add(self.primary_node)
741
    for device in self.disks:
742
      _Helper(all_nodes, device)
743
    return tuple(all_nodes)
744

    
745
  all_nodes = property(_ComputeAllNodes, None, None,
746
                       "List of all nodes of the instance")
747

    
748
  def MapLVsByNode(self, lvmap=None, devs=None, node=None):
749
    """Provide a mapping of nodes to LVs this instance owns.
750

751
    This function figures out what logical volumes should belong on
752
    which nodes, recursing through a device tree.
753

754
    @param lvmap: optional dictionary to receive the
755
        'node' : ['lv', ...] data.
756

757
    @return: None if lvmap arg is given, otherwise, a dictionary of
758
        the form { 'nodename' : ['volume1', 'volume2', ...], ... };
759
        volumeN is of the form "vg_name/lv_name", compatible with
760
        GetVolumeList()
761

762
    """
763
    if node == None:
764
      node = self.primary_node
765

    
766
    if lvmap is None:
767
      lvmap = { node : [] }
768
      ret = lvmap
769
    else:
770
      if not node in lvmap:
771
        lvmap[node] = []
772
      ret = None
773

    
774
    if not devs:
775
      devs = self.disks
776

    
777
    for dev in devs:
778
      if dev.dev_type == constants.LD_LV:
779
        lvmap[node].append(dev.logical_id[0]+"/"+dev.logical_id[1])
780

    
781
      elif dev.dev_type in constants.LDS_DRBD:
782
        if dev.children:
783
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[0])
784
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[1])
785

    
786
      elif dev.children:
787
        self.MapLVsByNode(lvmap, dev.children, node)
788

    
789
    return ret
790

    
791
  def FindDisk(self, idx):
792
    """Find a disk given having a specified index.
793

794
    This is just a wrapper that does validation of the index.
795

796
    @type idx: int
797
    @param idx: the disk index
798
    @rtype: L{Disk}
799
    @return: the corresponding disk
800
    @raise errors.OpPrereqError: when the given index is not valid
801

802
    """
803
    try:
804
      idx = int(idx)
805
      return self.disks[idx]
806
    except (TypeError, ValueError), err:
807
      raise errors.OpPrereqError("Invalid disk index: '%s'" % str(err),
808
                                 errors.ECODE_INVAL)
809
    except IndexError:
810
      raise errors.OpPrereqError("Invalid disk index: %d (instace has disks"
811
                                 " 0 to %d" % (idx, len(self.disks) - 1),
812
                                 errors.ECODE_INVAL)
813

    
814
  def ToDict(self):
815
    """Instance-specific conversion to standard python types.
816

817
    This replaces the children lists of objects with lists of standard
818
    python types.
819

820
    """
821
    bo = super(Instance, self).ToDict()
822

    
823
    for attr in "nics", "disks":
824
      alist = bo.get(attr, None)
825
      if alist:
826
        nlist = self._ContainerToDicts(alist)
827
      else:
828
        nlist = []
829
      bo[attr] = nlist
830
    return bo
831

    
832
  @classmethod
833
  def FromDict(cls, val):
834
    """Custom function for instances.
835

836
    """
837
    obj = super(Instance, cls).FromDict(val)
838
    obj.nics = cls._ContainerFromDicts(obj.nics, list, NIC)
839
    obj.disks = cls._ContainerFromDicts(obj.disks, list, Disk)
840
    return obj
841

    
842
  def UpgradeConfig(self):
843
    """Fill defaults for missing configuration values.
844

845
    """
846
    for nic in self.nics:
847
      nic.UpgradeConfig()
848
    for disk in self.disks:
849
      disk.UpgradeConfig()
850
    if self.hvparams:
851
      for key in constants.HVC_GLOBALS:
852
        try:
853
          del self.hvparams[key]
854
        except KeyError:
855
          pass
856
    if self.osparams is None:
857
      self.osparams = {}
858

    
859

    
860
class OS(ConfigObject):
861
  """Config object representing an operating system.
862

863
  @type supported_parameters: list
864
  @ivar supported_parameters: a list of tuples, name and description,
865
      containing the supported parameters by this OS
866

867
  @type VARIANT_DELIM: string
868
  @cvar VARIANT_DELIM: the variant delimiter
869

870
  """
871
  __slots__ = [
872
    "name",
873
    "path",
874
    "api_versions",
875
    "create_script",
876
    "export_script",
877
    "import_script",
878
    "rename_script",
879
    "verify_script",
880
    "supported_variants",
881
    "supported_parameters",
882
    ]
883

    
884
  VARIANT_DELIM = "+"
885

    
886
  @classmethod
887
  def SplitNameVariant(cls, name):
888
    """Splits the name into the proper name and variant.
889

890
    @param name: the OS (unprocessed) name
891
    @rtype: list
892
    @return: a list of two elements; if the original name didn't
893
        contain a variant, it's returned as an empty string
894

895
    """
896
    nv = name.split(cls.VARIANT_DELIM, 1)
897
    if len(nv) == 1:
898
      nv.append("")
899
    return nv
900

    
901
  @classmethod
902
  def GetName(cls, name):
903
    """Returns the proper name of the os (without the variant).
904

905
    @param name: the OS (unprocessed) name
906

907
    """
908
    return cls.SplitNameVariant(name)[0]
909

    
910
  @classmethod
911
  def GetVariant(cls, name):
912
    """Returns the variant the os (without the base name).
913

914
    @param name: the OS (unprocessed) name
915

916
    """
917
    return cls.SplitNameVariant(name)[1]
918

    
919

    
920
class Node(TaggableObject):
921
  """Config object representing a node."""
922
  __slots__ = [
923
    "name",
924
    "primary_ip",
925
    "secondary_ip",
926
    "serial_no",
927
    "master_candidate",
928
    "offline",
929
    "drained",
930
    "group",
931
    "master_capable",
932
    "vm_capable",
933
    "ndparams",
934
    "powered",
935
    ] + _TIMESTAMPS + _UUID
936

    
937
  def UpgradeConfig(self):
938
    """Fill defaults for missing configuration values.
939

940
    """
941
    # pylint: disable-msg=E0203
942
    # because these are "defined" via slots, not manually
943
    if self.master_capable is None:
944
      self.master_capable = True
945

    
946
    if self.vm_capable is None:
947
      self.vm_capable = True
948

    
949
    if self.ndparams is None:
950
      self.ndparams = {}
951

    
952
    if self.powered is None:
953
      self.powered = True
954

    
955

    
956
class NodeGroup(ConfigObject):
957
  """Config object representing a node group."""
958
  __slots__ = [
959
    "name",
960
    "members",
961
    "ndparams",
962
    "serial_no",
963
    "alloc_policy",
964
    ] + _TIMESTAMPS + _UUID
965

    
966
  def ToDict(self):
967
    """Custom function for nodegroup.
968

969
    This discards the members object, which gets recalculated and is only kept
970
    in memory.
971

972
    """
973
    mydict = super(NodeGroup, self).ToDict()
974
    del mydict["members"]
975
    return mydict
976

    
977
  @classmethod
978
  def FromDict(cls, val):
979
    """Custom function for nodegroup.
980

981
    The members slot is initialized to an empty list, upon deserialization.
982

983
    """
984
    obj = super(NodeGroup, cls).FromDict(val)
985
    obj.members = []
986
    return obj
987

    
988
  def UpgradeConfig(self):
989
    """Fill defaults for missing configuration values.
990

991
    """
992
    if self.ndparams is None:
993
      self.ndparams = {}
994

    
995
    if self.serial_no is None:
996
      self.serial_no = 1
997

    
998
    if self.alloc_policy is None:
999
      self.alloc_policy = constants.ALLOC_POLICY_PREFERRED
1000

    
1001
    # We only update mtime, and not ctime, since we would not be able to provide
1002
    # a correct value for creation time.
1003
    if self.mtime is None:
1004
      self.mtime = time.time()
1005

    
1006
  def FillND(self, node):
1007
    """Return filled out ndparams for L{object.Node}
1008

1009
    @type node: L{objects.Node}
1010
    @param node: A Node object to fill
1011
    @return a copy of the node's ndparams with defaults filled
1012

1013
    """
1014
    return self.SimpleFillND(node.ndparams)
1015

    
1016
  def SimpleFillND(self, ndparams):
1017
    """Fill a given ndparams dict with defaults.
1018

1019
    @type ndparams: dict
1020
    @param ndparams: the dict to fill
1021
    @rtype: dict
1022
    @return: a copy of the passed in ndparams with missing keys filled
1023
        from the node group defaults
1024

1025
    """
1026
    return FillDict(self.ndparams, ndparams)
1027

    
1028

    
1029
class Cluster(TaggableObject):
1030
  """Config object representing the cluster."""
1031
  __slots__ = [
1032
    "serial_no",
1033
    "rsahostkeypub",
1034
    "highest_used_port",
1035
    "tcpudp_port_pool",
1036
    "mac_prefix",
1037
    "volume_group_name",
1038
    "reserved_lvs",
1039
    "drbd_usermode_helper",
1040
    "default_bridge",
1041
    "default_hypervisor",
1042
    "master_node",
1043
    "master_ip",
1044
    "master_netdev",
1045
    "cluster_name",
1046
    "file_storage_dir",
1047
    "enabled_hypervisors",
1048
    "hvparams",
1049
    "os_hvp",
1050
    "beparams",
1051
    "osparams",
1052
    "nicparams",
1053
    "ndparams",
1054
    "candidate_pool_size",
1055
    "modify_etc_hosts",
1056
    "modify_ssh_setup",
1057
    "maintain_node_health",
1058
    "uid_pool",
1059
    "default_iallocator",
1060
    "hidden_os",
1061
    "blacklisted_os",
1062
    "primary_ip_family",
1063
    "prealloc_wipe_disks",
1064
    ] + _TIMESTAMPS + _UUID
1065

    
1066
  def UpgradeConfig(self):
1067
    """Fill defaults for missing configuration values.
1068

1069
    """
1070
    # pylint: disable-msg=E0203
1071
    # because these are "defined" via slots, not manually
1072
    if self.hvparams is None:
1073
      self.hvparams = constants.HVC_DEFAULTS
1074
    else:
1075
      for hypervisor in self.hvparams:
1076
        self.hvparams[hypervisor] = FillDict(
1077
            constants.HVC_DEFAULTS[hypervisor], self.hvparams[hypervisor])
1078

    
1079
    if self.os_hvp is None:
1080
      self.os_hvp = {}
1081

    
1082
    # osparams added before 2.2
1083
    if self.osparams is None:
1084
      self.osparams = {}
1085

    
1086
    if self.ndparams is None:
1087
      self.ndparams = constants.NDC_DEFAULTS
1088

    
1089
    self.beparams = UpgradeGroupedParams(self.beparams,
1090
                                         constants.BEC_DEFAULTS)
1091
    migrate_default_bridge = not self.nicparams
1092
    self.nicparams = UpgradeGroupedParams(self.nicparams,
1093
                                          constants.NICC_DEFAULTS)
1094
    if migrate_default_bridge:
1095
      self.nicparams[constants.PP_DEFAULT][constants.NIC_LINK] = \
1096
        self.default_bridge
1097

    
1098
    if self.modify_etc_hosts is None:
1099
      self.modify_etc_hosts = True
1100

    
1101
    if self.modify_ssh_setup is None:
1102
      self.modify_ssh_setup = True
1103

    
1104
    # default_bridge is no longer used it 2.1. The slot is left there to
1105
    # support auto-upgrading. It can be removed once we decide to deprecate
1106
    # upgrading straight from 2.0.
1107
    if self.default_bridge is not None:
1108
      self.default_bridge = None
1109

    
1110
    # default_hypervisor is just the first enabled one in 2.1. This slot and
1111
    # code can be removed once upgrading straight from 2.0 is deprecated.
1112
    if self.default_hypervisor is not None:
1113
      self.enabled_hypervisors = ([self.default_hypervisor] +
1114
        [hvname for hvname in self.enabled_hypervisors
1115
         if hvname != self.default_hypervisor])
1116
      self.default_hypervisor = None
1117

    
1118
    # maintain_node_health added after 2.1.1
1119
    if self.maintain_node_health is None:
1120
      self.maintain_node_health = False
1121

    
1122
    if self.uid_pool is None:
1123
      self.uid_pool = []
1124

    
1125
    if self.default_iallocator is None:
1126
      self.default_iallocator = ""
1127

    
1128
    # reserved_lvs added before 2.2
1129
    if self.reserved_lvs is None:
1130
      self.reserved_lvs = []
1131

    
1132
    # hidden and blacklisted operating systems added before 2.2.1
1133
    if self.hidden_os is None:
1134
      self.hidden_os = []
1135

    
1136
    if self.blacklisted_os is None:
1137
      self.blacklisted_os = []
1138

    
1139
    # primary_ip_family added before 2.3
1140
    if self.primary_ip_family is None:
1141
      self.primary_ip_family = AF_INET
1142

    
1143
    if self.prealloc_wipe_disks is None:
1144
      self.prealloc_wipe_disks = False
1145

    
1146
  def ToDict(self):
1147
    """Custom function for cluster.
1148

1149
    """
1150
    mydict = super(Cluster, self).ToDict()
1151
    mydict["tcpudp_port_pool"] = list(self.tcpudp_port_pool)
1152
    return mydict
1153

    
1154
  @classmethod
1155
  def FromDict(cls, val):
1156
    """Custom function for cluster.
1157

1158
    """
1159
    obj = super(Cluster, cls).FromDict(val)
1160
    if not isinstance(obj.tcpudp_port_pool, set):
1161
      obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
1162
    return obj
1163

    
1164
  def GetHVDefaults(self, hypervisor, os_name=None, skip_keys=None):
1165
    """Get the default hypervisor parameters for the cluster.
1166

1167
    @param hypervisor: the hypervisor name
1168
    @param os_name: if specified, we'll also update the defaults for this OS
1169
    @param skip_keys: if passed, list of keys not to use
1170
    @return: the defaults dict
1171

1172
    """
1173
    if skip_keys is None:
1174
      skip_keys = []
1175

    
1176
    fill_stack = [self.hvparams.get(hypervisor, {})]
1177
    if os_name is not None:
1178
      os_hvp = self.os_hvp.get(os_name, {}).get(hypervisor, {})
1179
      fill_stack.append(os_hvp)
1180

    
1181
    ret_dict = {}
1182
    for o_dict in fill_stack:
1183
      ret_dict = FillDict(ret_dict, o_dict, skip_keys=skip_keys)
1184

    
1185
    return ret_dict
1186

    
1187
  def SimpleFillHV(self, hv_name, os_name, hvparams, skip_globals=False):
1188
    """Fill a given hvparams dict with cluster defaults.
1189

1190
    @type hv_name: string
1191
    @param hv_name: the hypervisor to use
1192
    @type os_name: string
1193
    @param os_name: the OS to use for overriding the hypervisor defaults
1194
    @type skip_globals: boolean
1195
    @param skip_globals: if True, the global hypervisor parameters will
1196
        not be filled
1197
    @rtype: dict
1198
    @return: a copy of the given hvparams with missing keys filled from
1199
        the cluster defaults
1200

1201
    """
1202
    if skip_globals:
1203
      skip_keys = constants.HVC_GLOBALS
1204
    else:
1205
      skip_keys = []
1206

    
1207
    def_dict = self.GetHVDefaults(hv_name, os_name, skip_keys=skip_keys)
1208
    return FillDict(def_dict, hvparams, skip_keys=skip_keys)
1209

    
1210
  def FillHV(self, instance, skip_globals=False):
1211
    """Fill an instance's hvparams dict with cluster defaults.
1212

1213
    @type instance: L{objects.Instance}
1214
    @param instance: the instance parameter to fill
1215
    @type skip_globals: boolean
1216
    @param skip_globals: if True, the global hypervisor parameters will
1217
        not be filled
1218
    @rtype: dict
1219
    @return: a copy of the instance's hvparams with missing keys filled from
1220
        the cluster defaults
1221

1222
    """
1223
    return self.SimpleFillHV(instance.hypervisor, instance.os,
1224
                             instance.hvparams, skip_globals)
1225

    
1226
  def SimpleFillBE(self, beparams):
1227
    """Fill a given beparams dict with cluster defaults.
1228

1229
    @type beparams: dict
1230
    @param beparams: the dict to fill
1231
    @rtype: dict
1232
    @return: a copy of the passed in beparams with missing keys filled
1233
        from the cluster defaults
1234

1235
    """
1236
    return FillDict(self.beparams.get(constants.PP_DEFAULT, {}), beparams)
1237

    
1238
  def FillBE(self, instance):
1239
    """Fill an instance's beparams dict with cluster defaults.
1240

1241
    @type instance: L{objects.Instance}
1242
    @param instance: the instance parameter to fill
1243
    @rtype: dict
1244
    @return: a copy of the instance's beparams with missing keys filled from
1245
        the cluster defaults
1246

1247
    """
1248
    return self.SimpleFillBE(instance.beparams)
1249

    
1250
  def SimpleFillNIC(self, nicparams):
1251
    """Fill a given nicparams dict with cluster defaults.
1252

1253
    @type nicparams: dict
1254
    @param nicparams: the dict to fill
1255
    @rtype: dict
1256
    @return: a copy of the passed in nicparams with missing keys filled
1257
        from the cluster defaults
1258

1259
    """
1260
    return FillDict(self.nicparams.get(constants.PP_DEFAULT, {}), nicparams)
1261

    
1262
  def SimpleFillOS(self, os_name, os_params):
1263
    """Fill an instance's osparams dict with cluster defaults.
1264

1265
    @type os_name: string
1266
    @param os_name: the OS name to use
1267
    @type os_params: dict
1268
    @param os_params: the dict to fill with default values
1269
    @rtype: dict
1270
    @return: a copy of the instance's osparams with missing keys filled from
1271
        the cluster defaults
1272

1273
    """
1274
    name_only = os_name.split("+", 1)[0]
1275
    # base OS
1276
    result = self.osparams.get(name_only, {})
1277
    # OS with variant
1278
    result = FillDict(result, self.osparams.get(os_name, {}))
1279
    # specified params
1280
    return FillDict(result, os_params)
1281

    
1282
  def FillND(self, node, nodegroup):
1283
    """Return filled out ndparams for L{objects.NodeGroup} and L{object.Node}
1284

1285
    @type node: L{objects.Node}
1286
    @param node: A Node object to fill
1287
    @type nodegroup: L{objects.NodeGroup}
1288
    @param nodegroup: A Node object to fill
1289
    @return a copy of the node's ndparams with defaults filled
1290

1291
    """
1292
    return self.SimpleFillND(nodegroup.FillND(node))
1293

    
1294
  def SimpleFillND(self, ndparams):
1295
    """Fill a given ndparams dict with defaults.
1296

1297
    @type ndparams: dict
1298
    @param ndparams: the dict to fill
1299
    @rtype: dict
1300
    @return: a copy of the passed in ndparams with missing keys filled
1301
        from the cluster defaults
1302

1303
    """
1304
    return FillDict(self.ndparams, ndparams)
1305

    
1306

    
1307
class BlockDevStatus(ConfigObject):
1308
  """Config object representing the status of a block device."""
1309
  __slots__ = [
1310
    "dev_path",
1311
    "major",
1312
    "minor",
1313
    "sync_percent",
1314
    "estimated_time",
1315
    "is_degraded",
1316
    "ldisk_status",
1317
    ]
1318

    
1319

    
1320
class ImportExportStatus(ConfigObject):
1321
  """Config object representing the status of an import or export."""
1322
  __slots__ = [
1323
    "recent_output",
1324
    "listen_port",
1325
    "connected",
1326
    "progress_mbytes",
1327
    "progress_throughput",
1328
    "progress_eta",
1329
    "progress_percent",
1330
    "exit_status",
1331
    "error_message",
1332
    ] + _TIMESTAMPS
1333

    
1334

    
1335
class ImportExportOptions(ConfigObject):
1336
  """Options for import/export daemon
1337

1338
  @ivar key_name: X509 key name (None for cluster certificate)
1339
  @ivar ca_pem: Remote peer CA in PEM format (None for cluster certificate)
1340
  @ivar compress: Compression method (one of L{constants.IEC_ALL})
1341
  @ivar magic: Used to ensure the connection goes to the right disk
1342
  @ivar ipv6: Whether to use IPv6
1343

1344
  """
1345
  __slots__ = [
1346
    "key_name",
1347
    "ca_pem",
1348
    "compress",
1349
    "magic",
1350
    "ipv6",
1351
    ]
1352

    
1353

    
1354
class ConfdRequest(ConfigObject):
1355
  """Object holding a confd request.
1356

1357
  @ivar protocol: confd protocol version
1358
  @ivar type: confd query type
1359
  @ivar query: query request
1360
  @ivar rsalt: requested reply salt
1361

1362
  """
1363
  __slots__ = [
1364
    "protocol",
1365
    "type",
1366
    "query",
1367
    "rsalt",
1368
    ]
1369

    
1370

    
1371
class ConfdReply(ConfigObject):
1372
  """Object holding a confd reply.
1373

1374
  @ivar protocol: confd protocol version
1375
  @ivar status: reply status code (ok, error)
1376
  @ivar answer: confd query reply
1377
  @ivar serial: configuration serial number
1378

1379
  """
1380
  __slots__ = [
1381
    "protocol",
1382
    "status",
1383
    "answer",
1384
    "serial",
1385
    ]
1386

    
1387

    
1388
class QueryFieldDefinition(ConfigObject):
1389
  """Object holding a query field definition.
1390

1391
  @ivar name: Field name
1392
  @ivar title: Human-readable title
1393
  @ivar kind: Field type
1394

1395
  """
1396
  __slots__ = [
1397
    "name",
1398
    "title",
1399
    "kind",
1400
    ]
1401

    
1402

    
1403
class _QueryResponseBase(ConfigObject):
1404
  __slots__ = [
1405
    "fields",
1406
    ]
1407

    
1408
  def ToDict(self):
1409
    """Custom function for serializing.
1410

1411
    """
1412
    mydict = super(_QueryResponseBase, self).ToDict()
1413
    mydict["fields"] = self._ContainerToDicts(mydict["fields"])
1414
    return mydict
1415

    
1416
  @classmethod
1417
  def FromDict(cls, val):
1418
    """Custom function for de-serializing.
1419

1420
    """
1421
    obj = super(_QueryResponseBase, cls).FromDict(val)
1422
    obj.fields = cls._ContainerFromDicts(obj.fields, list, QueryFieldDefinition)
1423
    return obj
1424

    
1425

    
1426
class QueryRequest(ConfigObject):
1427
  """Object holding a query request.
1428

1429
  """
1430
  __slots__ = [
1431
    "what",
1432
    "fields",
1433
    "filter",
1434
    ]
1435

    
1436

    
1437
class QueryResponse(_QueryResponseBase):
1438
  """Object holding the response to a query.
1439

1440
  @ivar fields: List of L{QueryFieldDefinition} objects
1441
  @ivar data: Requested data
1442

1443
  """
1444
  __slots__ = [
1445
    "data",
1446
    ]
1447

    
1448

    
1449
class QueryFieldsRequest(ConfigObject):
1450
  """Object holding a request for querying available fields.
1451

1452
  """
1453
  __slots__ = [
1454
    "what",
1455
    "fields",
1456
    ]
1457

    
1458

    
1459
class QueryFieldsResponse(_QueryResponseBase):
1460
  """Object holding the response to a query for fields.
1461

1462
  @ivar fields: List of L{QueryFieldDefinition} objects
1463

1464
  """
1465
  __slots__ = [
1466
    ]
1467

    
1468

    
1469
class InstanceConsole(ConfigObject):
1470
  """Object describing how to access the console of an instance.
1471

1472
  """
1473
  __slots__ = [
1474
    "instance",
1475
    "kind",
1476
    "message",
1477
    "host",
1478
    "port",
1479
    "user",
1480
    "command",
1481
    "display",
1482
    ]
1483

    
1484
  def Validate(self):
1485
    """Validates contents of this object.
1486

1487
    """
1488
    assert self.kind in constants.CONS_ALL, "Unknown console type"
1489
    assert self.instance, "Missing instance name"
1490
    assert self.message or self.kind in [constants.CONS_SSH, constants.CONS_VNC]
1491
    assert self.host or self.kind == constants.CONS_MESSAGE
1492
    assert self.port or self.kind in [constants.CONS_MESSAGE,
1493
                                      constants.CONS_SSH]
1494
    assert self.user or self.kind in [constants.CONS_MESSAGE,
1495
                                      constants.CONS_VNC]
1496
    assert self.command or self.kind in [constants.CONS_MESSAGE,
1497
                                         constants.CONS_VNC]
1498
    assert self.display or self.kind in [constants.CONS_MESSAGE,
1499
                                         constants.CONS_SSH]
1500
    return True
1501

    
1502

    
1503
class SerializableConfigParser(ConfigParser.SafeConfigParser):
1504
  """Simple wrapper over ConfigParse that allows serialization.
1505

1506
  This class is basically ConfigParser.SafeConfigParser with two
1507
  additional methods that allow it to serialize/unserialize to/from a
1508
  buffer.
1509

1510
  """
1511
  def Dumps(self):
1512
    """Dump this instance and return the string representation."""
1513
    buf = StringIO()
1514
    self.write(buf)
1515
    return buf.getvalue()
1516

    
1517
  @classmethod
1518
  def Loads(cls, data):
1519
    """Load data from a string."""
1520
    buf = StringIO(data)
1521
    cfp = cls()
1522
    cfp.readfp(buf)
1523
    return cfp