Statistics
| Branch: | Tag: | Revision:

root / lib / objects.py @ f95c81bf

History | View | Annotate | Download (26.1 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"]
41

    
42

    
43
class ConfigObject(object):
44
  """A generic config object.
45

46
  It has the following properties:
47

48
    - provides somewhat safe recursive unpickling and pickling for its classes
49
    - unset attributes which are defined in slots are always returned
50
      as None instead of raising an error
51

52
  Classes derived from this must always declare __slots__ (we use many
53
  config objects and the memory reduction is useful)
54

55
  """
56
  __slots__ = []
57

    
58
  def __init__(self, **kwargs):
59
    for k, v in kwargs.iteritems():
60
      setattr(self, k, v)
61

    
62
  def __getattr__(self, name):
63
    if name not in self.__slots__:
64
      raise AttributeError("Invalid object attribute %s.%s" %
65
                           (type(self).__name__, name))
66
    return None
67

    
68
  def __setitem__(self, key, value):
69
    if key not in self.__slots__:
70
      raise KeyError(key)
71
    setattr(self, key, value)
72

    
73
  def __getstate__(self):
74
    state = {}
75
    for name in self.__slots__:
76
      if hasattr(self, name):
77
        state[name] = getattr(self, name)
78
    return state
79

    
80
  def __setstate__(self, state):
81
    for name in state:
82
      if name in self.__slots__:
83
        setattr(self, name, state[name])
84

    
85
  def ToDict(self):
86
    """Convert to a dict holding only standard python types.
87

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

94
    """
95
    return dict([(k, getattr(self, k, None)) for k in self.__slots__])
96

    
97
  @classmethod
98
  def FromDict(cls, val):
99
    """Create an object from a dictionary.
100

101
    This generic routine takes a dict, instantiates a new instance of
102
    the given class, and sets attributes based on the dict content.
103

104
    As for `ToDict`, this does not work if the class has children
105
    who are ConfigObjects themselves (e.g. the nics list in an
106
    Instance), in which case the object should subclass the function
107
    and alter the objects.
108

109
    """
110
    if not isinstance(val, dict):
111
      raise errors.ConfigurationError("Invalid object passed to FromDict:"
112
                                      " expected dict, got %s" % type(val))
113
    val_str = dict([(str(k), v) for k, v in val.iteritems()])
114
    obj = cls(**val_str)
115
    return obj
116

    
117
  @staticmethod
118
  def _ContainerToDicts(container):
119
    """Convert the elements of a container to standard python types.
120

121
    This method converts a container with elements derived from
122
    ConfigData to standard python types. If the container is a dict,
123
    we don't touch the keys, only the values.
124

125
    """
126
    if isinstance(container, dict):
127
      ret = dict([(k, v.ToDict()) for k, v in container.iteritems()])
128
    elif isinstance(container, (list, tuple, set, frozenset)):
129
      ret = [elem.ToDict() for elem in container]
130
    else:
131
      raise TypeError("Invalid type %s passed to _ContainerToDicts" %
132
                      type(container))
133
    return ret
134

    
135
  @staticmethod
136
  def _ContainerFromDicts(source, c_type, e_type):
137
    """Convert a container from standard python types.
138

139
    This method converts a container with standard python types to
140
    ConfigData objects. If the container is a dict, we don't touch the
141
    keys, only the values.
142

143
    """
144
    if not isinstance(c_type, type):
145
      raise TypeError("Container type %s passed to _ContainerFromDicts is"
146
                      " not a type" % type(c_type))
147
    if c_type is dict:
148
      ret = dict([(k, e_type.FromDict(v)) for k, v in source.iteritems()])
149
    elif c_type in (list, tuple, set, frozenset):
150
      ret = c_type([e_type.FromDict(elem) for elem in source])
151
    else:
152
      raise TypeError("Invalid container type %s passed to"
153
                      " _ContainerFromDicts" % c_type)
154
    return ret
155

    
156
  def Copy(self):
157
    """Makes a deep copy of the current object and its children.
158

159
    """
160
    dict_form = self.ToDict()
161
    clone_obj = self.__class__.FromDict(dict_form)
162
    return clone_obj
163

    
164
  def __repr__(self):
165
    """Implement __repr__ for ConfigObjects."""
166
    return repr(self.ToDict())
167

    
168
  def UpgradeConfig(self):
169
    """Fill defaults for missing configuration values.
170

171
    This method will be called at configuration load time, and its
172
    implementation will be object dependent.
173

174
    """
175
    pass
176

    
177

    
178
class TaggableObject(ConfigObject):
179
  """An generic class supporting tags.
180

181
  """
182
  __slots__ = ConfigObject.__slots__ + ["tags"]
183

    
184
  @staticmethod
185
  def ValidateTag(tag):
186
    """Check if a tag is valid.
187

188
    If the tag is invalid, an errors.TagError will be raised. The
189
    function has no return value.
190

191
    """
192
    if not isinstance(tag, basestring):
193
      raise errors.TagError("Invalid tag type (not a string)")
194
    if len(tag) > constants.MAX_TAG_LEN:
195
      raise errors.TagError("Tag too long (>%d characters)" %
196
                            constants.MAX_TAG_LEN)
197
    if not tag:
198
      raise errors.TagError("Tags cannot be empty")
199
    if not re.match("^[\w.+*/:-]+$", tag):
200
      raise errors.TagError("Tag contains invalid characters")
201

    
202
  def GetTags(self):
203
    """Return the tags list.
204

205
    """
206
    tags = getattr(self, "tags", None)
207
    if tags is None:
208
      tags = self.tags = set()
209
    return tags
210

    
211
  def AddTag(self, tag):
212
    """Add a new tag.
213

214
    """
215
    self.ValidateTag(tag)
216
    tags = self.GetTags()
217
    if len(tags) >= constants.MAX_TAGS_PER_OBJ:
218
      raise errors.TagError("Too many tags")
219
    self.GetTags().add(tag)
220

    
221
  def RemoveTag(self, tag):
222
    """Remove a tag.
223

224
    """
225
    self.ValidateTag(tag)
226
    tags = self.GetTags()
227
    try:
228
      tags.remove(tag)
229
    except KeyError:
230
      raise errors.TagError("Tag not found")
231

    
232
  def ToDict(self):
233
    """Taggable-object-specific conversion to standard python types.
234

235
    This replaces the tags set with a list.
236

237
    """
238
    bo = super(TaggableObject, self).ToDict()
239

    
240
    tags = bo.get("tags", None)
241
    if isinstance(tags, set):
242
      bo["tags"] = list(tags)
243
    return bo
244

    
245
  @classmethod
246
  def FromDict(cls, val):
247
    """Custom function for instances.
248

249
    """
250
    obj = super(TaggableObject, cls).FromDict(val)
251
    if hasattr(obj, "tags") and isinstance(obj.tags, list):
252
      obj.tags = set(obj.tags)
253
    return obj
254

    
255

    
256
class ConfigData(ConfigObject):
257
  """Top-level config object."""
258
  __slots__ = ["version", "cluster", "nodes", "instances", "serial_no"]
259

    
260
  def ToDict(self):
261
    """Custom function for top-level config data.
262

263
    This just replaces the list of instances, nodes and the cluster
264
    with standard python types.
265

266
    """
267
    mydict = super(ConfigData, self).ToDict()
268
    mydict["cluster"] = mydict["cluster"].ToDict()
269
    for key in "nodes", "instances":
270
      mydict[key] = self._ContainerToDicts(mydict[key])
271

    
272
    return mydict
273

    
274
  @classmethod
275
  def FromDict(cls, val):
276
    """Custom function for top-level config data
277

278
    """
279
    obj = super(ConfigData, cls).FromDict(val)
280
    obj.cluster = Cluster.FromDict(obj.cluster)
281
    obj.nodes = cls._ContainerFromDicts(obj.nodes, dict, Node)
282
    obj.instances = cls._ContainerFromDicts(obj.instances, dict, Instance)
283
    return obj
284

    
285
  def UpgradeConfig(self):
286
    """Fill defaults for missing configuration values.
287

288
    """
289
    self.cluster.UpgradeConfig()
290
    for node in self.nodes.values():
291
      node.UpgradeConfig()
292
    for instance in self.instances.values():
293
      instance.UpgradeConfig()
294

    
295

    
296
class NIC(ConfigObject):
297
  """Config object representing a network card."""
298
  __slots__ = ["mac", "ip", "bridge"]
299

    
300

    
301
class Disk(ConfigObject):
302
  """Config object representing a block device."""
303
  __slots__ = ["dev_type", "logical_id", "physical_id",
304
               "children", "iv_name", "size", "mode"]
305

    
306
  def CreateOnSecondary(self):
307
    """Test if this device needs to be created on a secondary node."""
308
    return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
309

    
310
  def AssembleOnSecondary(self):
311
    """Test if this device needs to be assembled on a secondary node."""
312
    return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
313

    
314
  def OpenOnSecondary(self):
315
    """Test if this device needs to be opened on a secondary node."""
316
    return self.dev_type in (constants.LD_LV,)
317

    
318
  def StaticDevPath(self):
319
    """Return the device path if this device type has a static one.
320

321
    Some devices (LVM for example) live always at the same /dev/ path,
322
    irrespective of their status. For such devices, we return this
323
    path, for others we return None.
324

325
    """
326
    if self.dev_type == constants.LD_LV:
327
      return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
328
    return None
329

    
330
  def ChildrenNeeded(self):
331
    """Compute the needed number of children for activation.
332

333
    This method will return either -1 (all children) or a positive
334
    number denoting the minimum number of children needed for
335
    activation (only mirrored devices will usually return >=0).
336

337
    Currently, only DRBD8 supports diskless activation (therefore we
338
    return 0), for all other we keep the previous semantics and return
339
    -1.
340

341
    """
342
    if self.dev_type == constants.LD_DRBD8:
343
      return 0
344
    return -1
345

    
346
  def GetNodes(self, node):
347
    """This function returns the nodes this device lives on.
348

349
    Given the node on which the parent of the device lives on (or, in
350
    case of a top-level device, the primary node of the devices'
351
    instance), this function will return a list of nodes on which this
352
    devices needs to (or can) be assembled.
353

354
    """
355
    if self.dev_type in [constants.LD_LV, constants.LD_FILE]:
356
      result = [node]
357
    elif self.dev_type in constants.LDS_DRBD:
358
      result = [self.logical_id[0], self.logical_id[1]]
359
      if node not in result:
360
        raise errors.ConfigurationError("DRBD device passed unknown node")
361
    else:
362
      raise errors.ProgrammerError("Unhandled device type %s" % self.dev_type)
363
    return result
364

    
365
  def ComputeNodeTree(self, parent_node):
366
    """Compute the node/disk tree for this disk and its children.
367

368
    This method, given the node on which the parent disk lives, will
369
    return the list of all (node, disk) pairs which describe the disk
370
    tree in the most compact way. For example, a drbd/lvm stack
371
    will be returned as (primary_node, drbd) and (secondary_node, drbd)
372
    which represents all the top-level devices on the nodes.
373

374
    """
375
    my_nodes = self.GetNodes(parent_node)
376
    result = [(node, self) for node in my_nodes]
377
    if not self.children:
378
      # leaf device
379
      return result
380
    for node in my_nodes:
381
      for child in self.children:
382
        child_result = child.ComputeNodeTree(node)
383
        if len(child_result) == 1:
384
          # child (and all its descendants) is simple, doesn't split
385
          # over multiple hosts, so we don't need to describe it, our
386
          # own entry for this node describes it completely
387
          continue
388
        else:
389
          # check if child nodes differ from my nodes; note that
390
          # subdisk can differ from the child itself, and be instead
391
          # one of its descendants
392
          for subnode, subdisk in child_result:
393
            if subnode not in my_nodes:
394
              result.append((subnode, subdisk))
395
            # otherwise child is under our own node, so we ignore this
396
            # entry (but probably the other results in the list will
397
            # be different)
398
    return result
399

    
400
  def RecordGrow(self, amount):
401
    """Update the size of this disk after growth.
402

403
    This method recurses over the disks's children and updates their
404
    size correspondigly. The method needs to be kept in sync with the
405
    actual algorithms from bdev.
406

407
    """
408
    if self.dev_type == constants.LD_LV:
409
      self.size += amount
410
    elif self.dev_type == constants.LD_DRBD8:
411
      if self.children:
412
        self.children[0].RecordGrow(amount)
413
      self.size += amount
414
    else:
415
      raise errors.ProgrammerError("Disk.RecordGrow called for unsupported"
416
                                   " disk type %s" % self.dev_type)
417

    
418
  def UnsetSize(self):
419
    """Sets recursively the size to zero for the disk and its children.
420

421
    """
422
    if self.children:
423
      for child in self.children:
424
        child.UnsetSize()
425
    self.size = 0
426

    
427
  def SetPhysicalID(self, target_node, nodes_ip):
428
    """Convert the logical ID to the physical ID.
429

430
    This is used only for drbd, which needs ip/port configuration.
431

432
    The routine descends down and updates its children also, because
433
    this helps when the only the top device is passed to the remote
434
    node.
435

436
    Arguments:
437
      - target_node: the node we wish to configure for
438
      - nodes_ip: a mapping of node name to ip
439

440
    The target_node must exist in in nodes_ip, and must be one of the
441
    nodes in the logical ID for each of the DRBD devices encountered
442
    in the disk tree.
443

444
    """
445
    if self.children:
446
      for child in self.children:
447
        child.SetPhysicalID(target_node, nodes_ip)
448

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

    
471
  def ToDict(self):
472
    """Disk-specific conversion to standard python types.
473

474
    This replaces the children lists of objects with lists of
475
    standard python types.
476

477
    """
478
    bo = super(Disk, self).ToDict()
479

    
480
    for attr in ("children",):
481
      alist = bo.get(attr, None)
482
      if alist:
483
        bo[attr] = self._ContainerToDicts(alist)
484
    return bo
485

    
486
  @classmethod
487
  def FromDict(cls, val):
488
    """Custom function for Disks
489

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

    
504
  def __str__(self):
505
    """Custom str() formatter for disks.
506

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

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

    
539
  def Verify(self):
540
    """Checks that this disk is correctly configured.
541

542
    """
543
    all_errors = []
544
    if self.mode not in constants.DISK_ACCESS_SET:
545
      all_errors.append("Disk access mode '%s' is invalid" % (self.mode, ))
546
    return all_errors
547

    
548
  def UpgradeConfig(self):
549
    """Fill defaults for missing configuration values.
550

551
    """
552
    if self.children:
553
      for child in self.children:
554
        child.UpgradeConfig()
555
    # add here config upgrade for this disk
556

    
557

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

    
575
  def _ComputeSecondaryNodes(self):
576
    """Compute the list of secondary nodes.
577

578
    This is a simple wrapper over _ComputeAllNodes.
579

580
    """
581
    all_nodes = set(self._ComputeAllNodes())
582
    all_nodes.discard(self.primary_node)
583
    return tuple(all_nodes)
584

    
585
  secondary_nodes = property(_ComputeSecondaryNodes, None, None,
586
                             "List of secondary nodes")
587

    
588
  def _ComputeAllNodes(self):
589
    """Compute the list of all nodes.
590

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

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

    
607
    all_nodes = set()
608
    all_nodes.add(self.primary_node)
609
    for device in self.disks:
610
      _Helper(all_nodes, device)
611
    return tuple(all_nodes)
612

    
613
  all_nodes = property(_ComputeAllNodes, None, None,
614
                       "List of all nodes of the instance")
615

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

619
    This function figures out what logical volumes should belong on
620
    which nodes, recursing through a device tree.
621

622
    @param lvmap: optional dictionary to receive the
623
        'node' : ['lv', ...] data.
624

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

628
    """
629
    if node == None:
630
      node = self.primary_node
631

    
632
    if lvmap is None:
633
      lvmap = { node : [] }
634
      ret = lvmap
635
    else:
636
      if not node in lvmap:
637
        lvmap[node] = []
638
      ret = None
639

    
640
    if not devs:
641
      devs = self.disks
642

    
643
    for dev in devs:
644
      if dev.dev_type == constants.LD_LV:
645
        lvmap[node].append(dev.logical_id[1])
646

    
647
      elif dev.dev_type in constants.LDS_DRBD:
648
        if dev.children:
649
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[0])
650
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[1])
651

    
652
      elif dev.children:
653
        self.MapLVsByNode(lvmap, dev.children, node)
654

    
655
    return ret
656

    
657
  def FindDisk(self, idx):
658
    """Find a disk given having a specified index.
659

660
    This is just a wrapper that does validation of the index.
661

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

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

    
678
  def ToDict(self):
679
    """Instance-specific conversion to standard python types.
680

681
    This replaces the children lists of objects with lists of standard
682
    python types.
683

684
    """
685
    bo = super(Instance, self).ToDict()
686

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

    
696
  @classmethod
697
  def FromDict(cls, val):
698
    """Custom function for instances.
699

700
    """
701
    obj = super(Instance, cls).FromDict(val)
702
    obj.nics = cls._ContainerFromDicts(obj.nics, list, NIC)
703
    obj.disks = cls._ContainerFromDicts(obj.disks, list, Disk)
704
    return obj
705

    
706
  def UpgradeConfig(self):
707
    """Fill defaults for missing configuration values.
708

709
    """
710
    for nic in self.nics:
711
      nic.UpgradeConfig()
712
    for disk in self.disks:
713
      disk.UpgradeConfig()
714

    
715

    
716
class OS(ConfigObject):
717
  """Config object representing an operating system."""
718
  __slots__ = [
719
    "name",
720
    "path",
721
    "status",
722
    "api_versions",
723
    "create_script",
724
    "export_script",
725
    "import_script",
726
    "rename_script",
727
    ]
728

    
729
  @classmethod
730
  def FromInvalidOS(cls, err):
731
    """Create an OS from an InvalidOS error.
732

733
    This routine knows how to convert an InvalidOS error to an OS
734
    object representing the broken OS with a meaningful error message.
735

736
    """
737
    if not isinstance(err, errors.InvalidOS):
738
      raise errors.ProgrammerError("Trying to initialize an OS from an"
739
                                   " invalid object of type %s" % type(err))
740

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

    
743
  def __nonzero__(self):
744
    return self.status == constants.OS_VALID_STATUS
745

    
746
  __bool__ = __nonzero__
747

    
748

    
749
class Node(TaggableObject):
750
  """Config object representing a node."""
751
  __slots__ = TaggableObject.__slots__ + [
752
    "name",
753
    "primary_ip",
754
    "secondary_ip",
755
    "serial_no",
756
    "master_candidate",
757
    "offline",
758
    "drained",
759
    ]
760

    
761

    
762
class Cluster(TaggableObject):
763
  """Config object representing the cluster."""
764
  __slots__ = TaggableObject.__slots__ + [
765
    "serial_no",
766
    "rsahostkeypub",
767
    "highest_used_port",
768
    "tcpudp_port_pool",
769
    "mac_prefix",
770
    "volume_group_name",
771
    "default_bridge",
772
    "default_hypervisor",
773
    "master_node",
774
    "master_ip",
775
    "master_netdev",
776
    "cluster_name",
777
    "file_storage_dir",
778
    "enabled_hypervisors",
779
    "hvparams",
780
    "beparams",
781
    "candidate_pool_size",
782
    "modify_etc_hosts",
783
    ]
784

    
785
  def UpgradeConfig(self):
786
    """Fill defaults for missing configuration values.
787

788
    """
789
    if self.hvparams is None:
790
      self.hvparams = constants.HVC_DEFAULTS
791
    else:
792
      for hypervisor in self.hvparams:
793
        self.hvparams[hypervisor] = self.FillDict(
794
            constants.HVC_DEFAULTS[hypervisor], self.hvparams[hypervisor])
795

    
796
    if self.beparams is None:
797
      self.beparams = {constants.BEGR_DEFAULT: constants.BEC_DEFAULTS}
798
    else:
799
      for begroup in self.beparams:
800
        self.beparams[begroup] = self.FillDict(constants.BEC_DEFAULTS,
801
                                               self.beparams[begroup])
802

    
803
    if self.modify_etc_hosts is None:
804
      self.modify_etc_hosts = True
805

    
806
  def ToDict(self):
807
    """Custom function for cluster.
808

809
    """
810
    mydict = super(Cluster, self).ToDict()
811
    mydict["tcpudp_port_pool"] = list(self.tcpudp_port_pool)
812
    return mydict
813

    
814
  @classmethod
815
  def FromDict(cls, val):
816
    """Custom function for cluster.
817

818
    """
819
    obj = super(Cluster, cls).FromDict(val)
820
    if not isinstance(obj.tcpudp_port_pool, set):
821
      obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
822
    return obj
823

    
824
  @staticmethod
825
  def FillDict(defaults_dict, custom_dict):
826
    """Basic function to apply settings on top a default dict.
827

828
    @type defaults_dict: dict
829
    @param defaults_dict: dictionary holding the default values
830
    @type custom_dict: dict
831
    @param custom_dict: dictionary holding customized value
832
    @rtype: dict
833
    @return: dict with the 'full' values
834

835
    """
836
    ret_dict = copy.deepcopy(defaults_dict)
837
    ret_dict.update(custom_dict)
838
    return ret_dict
839

    
840
  def FillHV(self, instance):
841
    """Fill an instance's hvparams dict.
842

843
    @type instance: L{objects.Instance}
844
    @param instance: the instance parameter to fill
845
    @rtype: dict
846
    @return: a copy of the instance's hvparams with missing keys filled from
847
        the cluster defaults
848

849
    """
850
    return self.FillDict(self.hvparams.get(instance.hypervisor, {}),
851
                         instance.hvparams)
852

    
853
  def FillBE(self, instance):
854
    """Fill an instance's beparams dict.
855

856
    @type instance: L{objects.Instance}
857
    @param instance: the instance parameter to fill
858
    @rtype: dict
859
    @return: a copy of the instance's beparams with missing keys filled from
860
        the cluster defaults
861

862
    """
863
    return self.FillDict(self.beparams.get(constants.BEGR_DEFAULT, {}),
864
                         instance.beparams)
865

    
866

    
867
class SerializableConfigParser(ConfigParser.SafeConfigParser):
868
  """Simple wrapper over ConfigParse that allows serialization.
869

870
  This class is basically ConfigParser.SafeConfigParser with two
871
  additional methods that allow it to serialize/unserialize to/from a
872
  buffer.
873

874
  """
875
  def Dumps(self):
876
    """Dump this instance and return the string representation."""
877
    buf = StringIO()
878
    self.write(buf)
879
    return buf.getvalue()
880

    
881
  @staticmethod
882
  def Loads(data):
883
    """Load data from a string."""
884
    buf = StringIO(data)
885
    cfp = SerializableConfigParser()
886
    cfp.readfp(buf)
887
    return cfp