Statistics
| Branch: | Tag: | Revision:

root / lib / objects.py @ 6e34b628

History | View | Annotate | Download (25.2 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
"""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

    
30
import ConfigParser
31
import re
32
import copy
33
from cStringIO import StringIO
34

    
35
from ganeti import errors
36
from ganeti import constants
37

    
38

    
39
__all__ = ["ConfigObject", "ConfigData", "NIC", "Disk", "Instance",
40
           "OS", "Node", "Cluster", "FillDict"]
41

    
42
def FillDict(defaults_dict, custom_dict):
43
    """Basic function to apply settings on top a default dict.
44

45
    @type defaults_dict: dict
46
    @param defaults_dict: dictionary holding the default values
47
    @type custom_dict: dict
48
    @param custom_dict: dictionary holding customized value
49
    @rtype: dict
50
    @return: dict with the 'full' values
51

52
    """
53
    ret_dict = copy.deepcopy(defaults_dict)
54
    ret_dict.update(custom_dict)
55
    return ret_dict
56

    
57

    
58
def UpgradeGroupedParams(target, defaults):
59
  """Update all groups for the target parameter.
60

61
  @type target: dict of dicts
62
  @param target: {group: {parameter: value}}
63
  @type defaults: dict
64
  @param defaults: default parameter values
65

66
  """
67
  if target is None:
68
    target = {constants.PP_DEFAULT: defaults}
69
  else:
70
    for group in target:
71
      target[group] = FillDict(defaults, target[group])
72
  return target
73

    
74

    
75
class ConfigObject(object):
76
  """A generic config object.
77

78
  It has the following properties:
79

80
    - provides somewhat safe recursive unpickling and pickling for its classes
81
    - unset attributes which are defined in slots are always returned
82
      as None instead of raising an error
83

84
  Classes derived from this must always declare __slots__ (we use many
85
  config objects and the memory reduction is useful)
86

87
  """
88
  __slots__ = []
89

    
90
  def __init__(self, **kwargs):
91
    for k, v in kwargs.iteritems():
92
      setattr(self, k, v)
93
    self.UpgradeConfig()
94

    
95
  def __getattr__(self, name):
96
    if name not in self.__slots__:
97
      raise AttributeError("Invalid object attribute %s.%s" %
98
                           (type(self).__name__, name))
99
    return None
100

    
101
  def __setitem__(self, key, value):
102
    if key not in self.__slots__:
103
      raise KeyError(key)
104
    setattr(self, key, value)
105

    
106
  def __getstate__(self):
107
    state = {}
108
    for name in self.__slots__:
109
      if hasattr(self, name):
110
        state[name] = getattr(self, name)
111
    return state
112

    
113
  def __setstate__(self, state):
114
    for name in state:
115
      if name in self.__slots__:
116
        setattr(self, name, state[name])
117

    
118
  def ToDict(self):
119
    """Convert to a dict holding only standard python types.
120

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

127
    """
128
    return dict([(k, getattr(self, k, None)) for k in self.__slots__])
129

    
130
  @classmethod
131
  def FromDict(cls, val):
132
    """Create an object from a dictionary.
133

134
    This generic routine takes a dict, instantiates a new instance of
135
    the given class, and sets attributes based on the dict content.
136

137
    As for `ToDict`, this does not work if the class has children
138
    who are ConfigObjects themselves (e.g. the nics list in an
139
    Instance), in which case the object should subclass the function
140
    and alter the objects.
141

142
    """
143
    if not isinstance(val, dict):
144
      raise errors.ConfigurationError("Invalid object passed to FromDict:"
145
                                      " expected dict, got %s" % type(val))
146
    val_str = dict([(str(k), v) for k, v in val.iteritems()])
147
    obj = cls(**val_str)
148
    return obj
149

    
150
  @staticmethod
151
  def _ContainerToDicts(container):
152
    """Convert the elements of a container to standard python types.
153

154
    This method converts a container with elements derived from
155
    ConfigData to standard python types. If the container is a dict,
156
    we don't touch the keys, only the values.
157

158
    """
159
    if isinstance(container, dict):
160
      ret = dict([(k, v.ToDict()) for k, v in container.iteritems()])
161
    elif isinstance(container, (list, tuple, set, frozenset)):
162
      ret = [elem.ToDict() for elem in container]
163
    else:
164
      raise TypeError("Invalid type %s passed to _ContainerToDicts" %
165
                      type(container))
166
    return ret
167

    
168
  @staticmethod
169
  def _ContainerFromDicts(source, c_type, e_type):
170
    """Convert a container from standard python types.
171

172
    This method converts a container with standard python types to
173
    ConfigData objects. If the container is a dict, we don't touch the
174
    keys, only the values.
175

176
    """
177
    if not isinstance(c_type, type):
178
      raise TypeError("Container type %s passed to _ContainerFromDicts is"
179
                      " not a type" % type(c_type))
180
    if c_type is dict:
181
      ret = dict([(k, e_type.FromDict(v)) for k, v in source.iteritems()])
182
    elif c_type in (list, tuple, set, frozenset):
183
      ret = c_type([e_type.FromDict(elem) for elem in source])
184
    else:
185
      raise TypeError("Invalid container type %s passed to"
186
                      " _ContainerFromDicts" % c_type)
187
    return ret
188

    
189
  def __repr__(self):
190
    """Implement __repr__ for ConfigObjects."""
191
    return repr(self.ToDict())
192

    
193
  def UpgradeConfig(self):
194
    """Fill defaults for missing configuration values.
195

196
    This method will be called at object init time, and its implementation will
197
    be object dependent.
198

199
    """
200
    pass
201

    
202

    
203
class TaggableObject(ConfigObject):
204
  """An generic class supporting tags.
205

206
  """
207
  __slots__ = ConfigObject.__slots__ + ["tags"]
208

    
209
  @staticmethod
210
  def ValidateTag(tag):
211
    """Check if a tag is valid.
212

213
    If the tag is invalid, an errors.TagError will be raised. The
214
    function has no return value.
215

216
    """
217
    if not isinstance(tag, basestring):
218
      raise errors.TagError("Invalid tag type (not a string)")
219
    if len(tag) > constants.MAX_TAG_LEN:
220
      raise errors.TagError("Tag too long (>%d characters)" %
221
                            constants.MAX_TAG_LEN)
222
    if not tag:
223
      raise errors.TagError("Tags cannot be empty")
224
    if not re.match("^[\w.+*/:-]+$", tag):
225
      raise errors.TagError("Tag contains invalid characters")
226

    
227
  def GetTags(self):
228
    """Return the tags list.
229

230
    """
231
    tags = getattr(self, "tags", None)
232
    if tags is None:
233
      tags = self.tags = set()
234
    return tags
235

    
236
  def AddTag(self, tag):
237
    """Add a new tag.
238

239
    """
240
    self.ValidateTag(tag)
241
    tags = self.GetTags()
242
    if len(tags) >= constants.MAX_TAGS_PER_OBJ:
243
      raise errors.TagError("Too many tags")
244
    self.GetTags().add(tag)
245

    
246
  def RemoveTag(self, tag):
247
    """Remove a tag.
248

249
    """
250
    self.ValidateTag(tag)
251
    tags = self.GetTags()
252
    try:
253
      tags.remove(tag)
254
    except KeyError:
255
      raise errors.TagError("Tag not found")
256

    
257
  def ToDict(self):
258
    """Taggable-object-specific conversion to standard python types.
259

260
    This replaces the tags set with a list.
261

262
    """
263
    bo = super(TaggableObject, self).ToDict()
264

    
265
    tags = bo.get("tags", None)
266
    if isinstance(tags, set):
267
      bo["tags"] = list(tags)
268
    return bo
269

    
270
  @classmethod
271
  def FromDict(cls, val):
272
    """Custom function for instances.
273

274
    """
275
    obj = super(TaggableObject, cls).FromDict(val)
276
    if hasattr(obj, "tags") and isinstance(obj.tags, list):
277
      obj.tags = set(obj.tags)
278
    return obj
279

    
280

    
281
class ConfigData(ConfigObject):
282
  """Top-level config object."""
283
  __slots__ = ["version", "cluster", "nodes", "instances", "serial_no"]
284

    
285
  def ToDict(self):
286
    """Custom function for top-level config data.
287

288
    This just replaces the list of instances, nodes and the cluster
289
    with standard python types.
290

291
    """
292
    mydict = super(ConfigData, self).ToDict()
293
    mydict["cluster"] = mydict["cluster"].ToDict()
294
    for key in "nodes", "instances":
295
      mydict[key] = self._ContainerToDicts(mydict[key])
296

    
297
    return mydict
298

    
299
  @classmethod
300
  def FromDict(cls, val):
301
    """Custom function for top-level config data
302

303
    """
304
    obj = super(ConfigData, cls).FromDict(val)
305
    obj.cluster = Cluster.FromDict(obj.cluster)
306
    obj.nodes = cls._ContainerFromDicts(obj.nodes, dict, Node)
307
    obj.instances = cls._ContainerFromDicts(obj.instances, dict, Instance)
308
    return obj
309

    
310

    
311
class NIC(ConfigObject):
312
  """Config object representing a network card."""
313
  __slots__ = ["mac", "ip", "bridge"]
314

    
315

    
316
class Disk(ConfigObject):
317
  """Config object representing a block device."""
318
  __slots__ = ["dev_type", "logical_id", "physical_id",
319
               "children", "iv_name", "size", "mode"]
320

    
321
  def CreateOnSecondary(self):
322
    """Test if this device needs to be created on a secondary node."""
323
    return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
324

    
325
  def AssembleOnSecondary(self):
326
    """Test if this device needs to be assembled on a secondary node."""
327
    return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
328

    
329
  def OpenOnSecondary(self):
330
    """Test if this device needs to be opened on a secondary node."""
331
    return self.dev_type in (constants.LD_LV,)
332

    
333
  def StaticDevPath(self):
334
    """Return the device path if this device type has a static one.
335

336
    Some devices (LVM for example) live always at the same /dev/ path,
337
    irrespective of their status. For such devices, we return this
338
    path, for others we return None.
339

340
    """
341
    if self.dev_type == constants.LD_LV:
342
      return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
343
    return None
344

    
345
  def ChildrenNeeded(self):
346
    """Compute the needed number of children for activation.
347

348
    This method will return either -1 (all children) or a positive
349
    number denoting the minimum number of children needed for
350
    activation (only mirrored devices will usually return >=0).
351

352
    Currently, only DRBD8 supports diskless activation (therefore we
353
    return 0), for all other we keep the previous semantics and return
354
    -1.
355

356
    """
357
    if self.dev_type == constants.LD_DRBD8:
358
      return 0
359
    return -1
360

    
361
  def GetNodes(self, node):
362
    """This function returns the nodes this device lives on.
363

364
    Given the node on which the parent of the device lives on (or, in
365
    case of a top-level device, the primary node of the devices'
366
    instance), this function will return a list of nodes on which this
367
    devices needs to (or can) be assembled.
368

369
    """
370
    if self.dev_type in [constants.LD_LV, constants.LD_FILE]:
371
      result = [node]
372
    elif self.dev_type in constants.LDS_DRBD:
373
      result = [self.logical_id[0], self.logical_id[1]]
374
      if node not in result:
375
        raise errors.ConfigurationError("DRBD device passed unknown node")
376
    else:
377
      raise errors.ProgrammerError("Unhandled device type %s" % self.dev_type)
378
    return result
379

    
380
  def ComputeNodeTree(self, parent_node):
381
    """Compute the node/disk tree for this disk and its children.
382

383
    This method, given the node on which the parent disk lives, will
384
    return the list of all (node, disk) pairs which describe the disk
385
    tree in the most compact way. For example, a drbd/lvm stack
386
    will be returned as (primary_node, drbd) and (secondary_node, drbd)
387
    which represents all the top-level devices on the nodes.
388

389
    """
390
    my_nodes = self.GetNodes(parent_node)
391
    result = [(node, self) for node in my_nodes]
392
    if not self.children:
393
      # leaf device
394
      return result
395
    for node in my_nodes:
396
      for child in self.children:
397
        child_result = child.ComputeNodeTree(node)
398
        if len(child_result) == 1:
399
          # child (and all its descendants) is simple, doesn't split
400
          # over multiple hosts, so we don't need to describe it, our
401
          # own entry for this node describes it completely
402
          continue
403
        else:
404
          # check if child nodes differ from my nodes; note that
405
          # subdisk can differ from the child itself, and be instead
406
          # one of its descendants
407
          for subnode, subdisk in child_result:
408
            if subnode not in my_nodes:
409
              result.append((subnode, subdisk))
410
            # otherwise child is under our own node, so we ignore this
411
            # entry (but probably the other results in the list will
412
            # be different)
413
    return result
414

    
415
  def RecordGrow(self, amount):
416
    """Update the size of this disk after growth.
417

418
    This method recurses over the disks's children and updates their
419
    size correspondigly. The method needs to be kept in sync with the
420
    actual algorithms from bdev.
421

422
    """
423
    if self.dev_type == constants.LD_LV:
424
      self.size += amount
425
    elif self.dev_type == constants.LD_DRBD8:
426
      if self.children:
427
        self.children[0].RecordGrow(amount)
428
      self.size += amount
429
    else:
430
      raise errors.ProgrammerError("Disk.RecordGrow called for unsupported"
431
                                   " disk type %s" % self.dev_type)
432

    
433
  def SetPhysicalID(self, target_node, nodes_ip):
434
    """Convert the logical ID to the physical ID.
435

436
    This is used only for drbd, which needs ip/port configuration.
437

438
    The routine descends down and updates its children also, because
439
    this helps when the only the top device is passed to the remote
440
    node.
441

442
    Arguments:
443
      - target_node: the node we wish to configure for
444
      - nodes_ip: a mapping of node name to ip
445

446
    The target_node must exist in in nodes_ip, and must be one of the
447
    nodes in the logical ID for each of the DRBD devices encountered
448
    in the disk tree.
449

450
    """
451
    if self.children:
452
      for child in self.children:
453
        child.SetPhysicalID(target_node, nodes_ip)
454

    
455
    if self.logical_id is None and self.physical_id is not None:
456
      return
457
    if self.dev_type in constants.LDS_DRBD:
458
      pnode, snode, port, pminor, sminor, secret = self.logical_id
459
      if target_node not in (pnode, snode):
460
        raise errors.ConfigurationError("DRBD device not knowing node %s" %
461
                                        target_node)
462
      pnode_ip = nodes_ip.get(pnode, None)
463
      snode_ip = nodes_ip.get(snode, None)
464
      if pnode_ip is None or snode_ip is None:
465
        raise errors.ConfigurationError("Can't find primary or secondary node"
466
                                        " for %s" % str(self))
467
      p_data = (pnode_ip, port)
468
      s_data = (snode_ip, port)
469
      if pnode == target_node:
470
        self.physical_id = p_data + s_data + (pminor, secret)
471
      else: # it must be secondary, we tested above
472
        self.physical_id = s_data + p_data + (sminor, secret)
473
    else:
474
      self.physical_id = self.logical_id
475
    return
476

    
477
  def ToDict(self):
478
    """Disk-specific conversion to standard python types.
479

480
    This replaces the children lists of objects with lists of
481
    standard python types.
482

483
    """
484
    bo = super(Disk, self).ToDict()
485

    
486
    for attr in ("children",):
487
      alist = bo.get(attr, None)
488
      if alist:
489
        bo[attr] = self._ContainerToDicts(alist)
490
    return bo
491

    
492
  @classmethod
493
  def FromDict(cls, val):
494
    """Custom function for Disks
495

496
    """
497
    obj = super(Disk, cls).FromDict(val)
498
    if obj.children:
499
      obj.children = cls._ContainerFromDicts(obj.children, list, Disk)
500
    if obj.logical_id and isinstance(obj.logical_id, list):
501
      obj.logical_id = tuple(obj.logical_id)
502
    if obj.physical_id and isinstance(obj.physical_id, list):
503
      obj.physical_id = tuple(obj.physical_id)
504
    if obj.dev_type in constants.LDS_DRBD:
505
      # we need a tuple of length six here
506
      if len(obj.logical_id) < 6:
507
        obj.logical_id += (None,) * (6 - len(obj.logical_id))
508
    return obj
509

    
510
  def __str__(self):
511
    """Custom str() formatter for disks.
512

513
    """
514
    if self.dev_type == constants.LD_LV:
515
      val =  "<LogicalVolume(/dev/%s/%s" % self.logical_id
516
    elif self.dev_type in constants.LDS_DRBD:
517
      node_a, node_b, port, minor_a, minor_b = self.logical_id[:5]
518
      val = "<DRBD8("
519
      if self.physical_id is None:
520
        phy = "unconfigured"
521
      else:
522
        phy = ("configured as %s:%s %s:%s" %
523
               (self.physical_id[0], self.physical_id[1],
524
                self.physical_id[2], self.physical_id[3]))
525

    
526
      val += ("hosts=%s/%d-%s/%d, port=%s, %s, " %
527
              (node_a, minor_a, node_b, minor_b, port, phy))
528
      if self.children and self.children.count(None) == 0:
529
        val += "backend=%s, metadev=%s" % (self.children[0], self.children[1])
530
      else:
531
        val += "no local storage"
532
    else:
533
      val = ("<Disk(type=%s, logical_id=%s, physical_id=%s, children=%s" %
534
             (self.dev_type, self.logical_id, self.physical_id, self.children))
535
    if self.iv_name is None:
536
      val += ", not visible"
537
    else:
538
      val += ", visible as /dev/%s" % self.iv_name
539
    if isinstance(self.size, int):
540
      val += ", size=%dm)>" % self.size
541
    else:
542
      val += ", size='%s')>" % (self.size,)
543
    return val
544

    
545
  def Verify(self):
546
    """Checks that this disk is correctly configured.
547

548
    """
549
    errors = []
550
    if self.mode not in constants.DISK_ACCESS_SET:
551
      errors.append("Disk access mode '%s' is invalid" % (self.mode, ))
552
    return errors
553

    
554

    
555
class Instance(TaggableObject):
556
  """Config object representing an instance."""
557
  __slots__ = TaggableObject.__slots__ + [
558
    "name",
559
    "primary_node",
560
    "os",
561
    "hypervisor",
562
    "hvparams",
563
    "beparams",
564
    "admin_up",
565
    "nics",
566
    "disks",
567
    "disk_template",
568
    "network_port",
569
    "serial_no",
570
    ]
571

    
572
  def _ComputeSecondaryNodes(self):
573
    """Compute the list of secondary nodes.
574

575
    This is a simple wrapper over _ComputeAllNodes.
576

577
    """
578
    all_nodes = set(self._ComputeAllNodes())
579
    all_nodes.discard(self.primary_node)
580
    return tuple(all_nodes)
581

    
582
  secondary_nodes = property(_ComputeSecondaryNodes, None, None,
583
                             "List of secondary nodes")
584

    
585
  def _ComputeAllNodes(self):
586
    """Compute the list of all nodes.
587

588
    Since the data is already there (in the drbd disks), keeping it as
589
    a separate normal attribute is redundant and if not properly
590
    synchronised can cause problems. Thus it's better to compute it
591
    dynamically.
592

593
    """
594
    def _Helper(nodes, device):
595
      """Recursively computes nodes given a top device."""
596
      if device.dev_type in constants.LDS_DRBD:
597
        nodea, nodeb = device.logical_id[:2]
598
        nodes.add(nodea)
599
        nodes.add(nodeb)
600
      if device.children:
601
        for child in device.children:
602
          _Helper(nodes, child)
603

    
604
    all_nodes = set()
605
    all_nodes.add(self.primary_node)
606
    for device in self.disks:
607
      _Helper(all_nodes, device)
608
    return tuple(all_nodes)
609

    
610
  all_nodes = property(_ComputeAllNodes, None, None,
611
                       "List of all nodes of the instance")
612

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

616
    This function figures out what logical volumes should belong on
617
    which nodes, recursing through a device tree.
618

619
    @param lvmap: optional dictionary to receive the
620
        'node' : ['lv', ...] data.
621

622
    @return: None if lvmap arg is given, otherwise, a dictionary
623
        of the form { 'nodename' : ['volume1', 'volume2', ...], ... }
624

625
    """
626
    if node == None:
627
      node = self.primary_node
628

    
629
    if lvmap is None:
630
      lvmap = { node : [] }
631
      ret = lvmap
632
    else:
633
      if not node in lvmap:
634
        lvmap[node] = []
635
      ret = None
636

    
637
    if not devs:
638
      devs = self.disks
639

    
640
    for dev in devs:
641
      if dev.dev_type == constants.LD_LV:
642
        lvmap[node].append(dev.logical_id[1])
643

    
644
      elif dev.dev_type in constants.LDS_DRBD:
645
        if dev.children:
646
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[0])
647
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[1])
648

    
649
      elif dev.children:
650
        self.MapLVsByNode(lvmap, dev.children, node)
651

    
652
    return ret
653

    
654
  def FindDisk(self, idx):
655
    """Find a disk given having a specified index.
656

657
    This is just a wrapper that does validation of the index.
658

659
    @type idx: int
660
    @param idx: the disk index
661
    @rtype: L{Disk}
662
    @return: the corresponding disk
663
    @raise errors.OpPrereqError: when the given index is not valid
664

665
    """
666
    try:
667
      idx = int(idx)
668
      return self.disks[idx]
669
    except ValueError, err:
670
      raise errors.OpPrereqError("Invalid disk index: '%s'" % str(err))
671
    except IndexError:
672
      raise errors.OpPrereqError("Invalid disk index: %d (instace has disks"
673
                                 " 0 to %d" % (idx, len(self.disks)))
674

    
675
  def ToDict(self):
676
    """Instance-specific conversion to standard python types.
677

678
    This replaces the children lists of objects with lists of standard
679
    python types.
680

681
    """
682
    bo = super(Instance, self).ToDict()
683

    
684
    for attr in "nics", "disks":
685
      alist = bo.get(attr, None)
686
      if alist:
687
        nlist = self._ContainerToDicts(alist)
688
      else:
689
        nlist = []
690
      bo[attr] = nlist
691
    return bo
692

    
693
  @classmethod
694
  def FromDict(cls, val):
695
    """Custom function for instances.
696

697
    """
698
    obj = super(Instance, cls).FromDict(val)
699
    obj.nics = cls._ContainerFromDicts(obj.nics, list, NIC)
700
    obj.disks = cls._ContainerFromDicts(obj.disks, list, Disk)
701
    return obj
702

    
703

    
704
class OS(ConfigObject):
705
  """Config object representing an operating system."""
706
  __slots__ = [
707
    "name",
708
    "path",
709
    "status",
710
    "api_versions",
711
    "create_script",
712
    "export_script",
713
    "import_script",
714
    "rename_script",
715
    ]
716

    
717
  @classmethod
718
  def FromInvalidOS(cls, err):
719
    """Create an OS from an InvalidOS error.
720

721
    This routine knows how to convert an InvalidOS error to an OS
722
    object representing the broken OS with a meaningful error message.
723

724
    """
725
    if not isinstance(err, errors.InvalidOS):
726
      raise errors.ProgrammerError("Trying to initialize an OS from an"
727
                                   " invalid object of type %s" % type(err))
728

    
729
    return cls(name=err.args[0], path=err.args[1], status=err.args[2])
730

    
731
  def __nonzero__(self):
732
    return self.status == constants.OS_VALID_STATUS
733

    
734
  __bool__ = __nonzero__
735

    
736

    
737
class Node(TaggableObject):
738
  """Config object representing a node."""
739
  __slots__ = TaggableObject.__slots__ + [
740
    "name",
741
    "primary_ip",
742
    "secondary_ip",
743
    "serial_no",
744
    "master_candidate",
745
    "offline",
746
    "drained",
747
    ]
748

    
749

    
750
class Cluster(TaggableObject):
751
  """Config object representing the cluster."""
752
  __slots__ = TaggableObject.__slots__ + [
753
    "serial_no",
754
    "rsahostkeypub",
755
    "highest_used_port",
756
    "tcpudp_port_pool",
757
    "mac_prefix",
758
    "volume_group_name",
759
    "default_bridge",
760
    "default_hypervisor",
761
    "master_node",
762
    "master_ip",
763
    "master_netdev",
764
    "cluster_name",
765
    "file_storage_dir",
766
    "enabled_hypervisors",
767
    "hvparams",
768
    "beparams",
769
    "candidate_pool_size",
770
    "modify_etc_hosts",
771
    ]
772

    
773
  def UpgradeConfig(self):
774
    """Fill defaults for missing configuration values.
775

776
    """
777
    if self.hvparams is None:
778
      self.hvparams = constants.HVC_DEFAULTS
779
    else:
780
      for hypervisor in self.hvparams:
781
        self.hvparams[hypervisor] = FillDict(
782
            constants.HVC_DEFAULTS[hypervisor], self.hvparams[hypervisor])
783

    
784
    self.beparams = UpgradeGroupedParams(self.beparams,
785
                                         constants.BEC_DEFAULTS)
786

    
787
    if self.modify_etc_hosts is None:
788
      self.modify_etc_hosts = True
789

    
790
  def ToDict(self):
791
    """Custom function for cluster.
792

793
    """
794
    mydict = super(Cluster, self).ToDict()
795
    mydict["tcpudp_port_pool"] = list(self.tcpudp_port_pool)
796
    return mydict
797

    
798
  @classmethod
799
  def FromDict(cls, val):
800
    """Custom function for cluster.
801

802
    """
803
    obj = super(Cluster, cls).FromDict(val)
804
    if not isinstance(obj.tcpudp_port_pool, set):
805
      obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
806
    return obj
807

    
808
  def FillHV(self, instance):
809
    """Fill an instance's hvparams dict.
810

811
    @type instance: object
812
    @param instance: the instance parameter to fill
813
    @rtype: dict
814
    @return: a copy of the instance's hvparams with missing keys filled from
815
        the cluster defaults
816

817
    """
818
    return FillDict(self.hvparams.get(instance.hypervisor, {}),
819
                         instance.hvparams)
820

    
821
  def FillBE(self, instance):
822
    """Fill an instance's beparams dict.
823

824
    @type instance: object
825
    @param instance: the instance parameter to fill
826
    @rtype: dict
827
    @return: a copy of the instance's beparams with missing keys filled from
828
        the cluster defaults
829

830
    """
831
    return FillDict(self.beparams.get(constants.PP_DEFAULT, {}),
832
                          instance.beparams)
833

    
834

    
835
class SerializableConfigParser(ConfigParser.SafeConfigParser):
836
  """Simple wrapper over ConfigParse that allows serialization.
837

838
  This class is basically ConfigParser.SafeConfigParser with two
839
  additional methods that allow it to serialize/unserialize to/from a
840
  buffer.
841

842
  """
843
  def Dumps(self):
844
    """Dump this instance and return the string representation."""
845
    buf = StringIO()
846
    self.write(buf)
847
    return buf.getvalue()
848

    
849
  @staticmethod
850
  def Loads(data):
851
    """Load data from a string."""
852
    buf = StringIO(data)
853
    cfp = SerializableConfigParser()
854
    cfp.readfp(buf)
855
    return cfp