Statistics
| Branch: | Tag: | Revision:

root / lib / objects.py @ b86a6bcd

History | View | Annotate | Download (24.5 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
    self.UpgradeConfig()
62

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
157
  def __repr__(self):
158
    """Implement __repr__ for ConfigObjects."""
159
    return repr(self.ToDict())
160

    
161
  def UpgradeConfig(self):
162
    """Fill defaults for missing configuration values.
163

164
    This method will be called at object init time, and its implementation will
165
    be object dependent.
166

167
    """
168
    pass
169

    
170

    
171
class TaggableObject(ConfigObject):
172
  """An generic class supporting tags.
173

174
  """
175
  __slots__ = ConfigObject.__slots__ + ["tags"]
176

    
177
  @staticmethod
178
  def ValidateTag(tag):
179
    """Check if a tag is valid.
180

181
    If the tag is invalid, an errors.TagError will be raised. The
182
    function has no return value.
183

184
    """
185
    if not isinstance(tag, basestring):
186
      raise errors.TagError("Invalid tag type (not a string)")
187
    if len(tag) > constants.MAX_TAG_LEN:
188
      raise errors.TagError("Tag too long (>%d characters)" %
189
                            constants.MAX_TAG_LEN)
190
    if not tag:
191
      raise errors.TagError("Tags cannot be empty")
192
    if not re.match("^[\w.+*/:-]+$", tag):
193
      raise errors.TagError("Tag contains invalid characters")
194

    
195
  def GetTags(self):
196
    """Return the tags list.
197

198
    """
199
    tags = getattr(self, "tags", None)
200
    if tags is None:
201
      tags = self.tags = set()
202
    return tags
203

    
204
  def AddTag(self, tag):
205
    """Add a new tag.
206

207
    """
208
    self.ValidateTag(tag)
209
    tags = self.GetTags()
210
    if len(tags) >= constants.MAX_TAGS_PER_OBJ:
211
      raise errors.TagError("Too many tags")
212
    self.GetTags().add(tag)
213

    
214
  def RemoveTag(self, tag):
215
    """Remove a tag.
216

217
    """
218
    self.ValidateTag(tag)
219
    tags = self.GetTags()
220
    try:
221
      tags.remove(tag)
222
    except KeyError:
223
      raise errors.TagError("Tag not found")
224

    
225
  def ToDict(self):
226
    """Taggable-object-specific conversion to standard python types.
227

228
    This replaces the tags set with a list.
229

230
    """
231
    bo = super(TaggableObject, self).ToDict()
232

    
233
    tags = bo.get("tags", None)
234
    if isinstance(tags, set):
235
      bo["tags"] = list(tags)
236
    return bo
237

    
238
  @classmethod
239
  def FromDict(cls, val):
240
    """Custom function for instances.
241

242
    """
243
    obj = super(TaggableObject, cls).FromDict(val)
244
    if hasattr(obj, "tags") and isinstance(obj.tags, list):
245
      obj.tags = set(obj.tags)
246
    return obj
247

    
248

    
249
class ConfigData(ConfigObject):
250
  """Top-level config object."""
251
  __slots__ = ["version", "cluster", "nodes", "instances", "serial_no"]
252

    
253
  def ToDict(self):
254
    """Custom function for top-level config data.
255

256
    This just replaces the list of instances, nodes and the cluster
257
    with standard python types.
258

259
    """
260
    mydict = super(ConfigData, self).ToDict()
261
    mydict["cluster"] = mydict["cluster"].ToDict()
262
    for key in "nodes", "instances":
263
      mydict[key] = self._ContainerToDicts(mydict[key])
264

    
265
    return mydict
266

    
267
  @classmethod
268
  def FromDict(cls, val):
269
    """Custom function for top-level config data
270

271
    """
272
    obj = super(ConfigData, cls).FromDict(val)
273
    obj.cluster = Cluster.FromDict(obj.cluster)
274
    obj.nodes = cls._ContainerFromDicts(obj.nodes, dict, Node)
275
    obj.instances = cls._ContainerFromDicts(obj.instances, dict, Instance)
276
    return obj
277

    
278

    
279
class NIC(ConfigObject):
280
  """Config object representing a network card."""
281
  __slots__ = ["mac", "ip", "bridge"]
282

    
283

    
284
class Disk(ConfigObject):
285
  """Config object representing a block device."""
286
  __slots__ = ["dev_type", "logical_id", "physical_id",
287
               "children", "iv_name", "size", "mode"]
288

    
289
  def CreateOnSecondary(self):
290
    """Test if this device needs to be created on a secondary node."""
291
    return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
292

    
293
  def AssembleOnSecondary(self):
294
    """Test if this device needs to be assembled on a secondary node."""
295
    return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
296

    
297
  def OpenOnSecondary(self):
298
    """Test if this device needs to be opened on a secondary node."""
299
    return self.dev_type in (constants.LD_LV,)
300

    
301
  def StaticDevPath(self):
302
    """Return the device path if this device type has a static one.
303

304
    Some devices (LVM for example) live always at the same /dev/ path,
305
    irrespective of their status. For such devices, we return this
306
    path, for others we return None.
307

308
    """
309
    if self.dev_type == constants.LD_LV:
310
      return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
311
    return None
312

    
313
  def ChildrenNeeded(self):
314
    """Compute the needed number of children for activation.
315

316
    This method will return either -1 (all children) or a positive
317
    number denoting the minimum number of children needed for
318
    activation (only mirrored devices will usually return >=0).
319

320
    Currently, only DRBD8 supports diskless activation (therefore we
321
    return 0), for all other we keep the previous semantics and return
322
    -1.
323

324
    """
325
    if self.dev_type == constants.LD_DRBD8:
326
      return 0
327
    return -1
328

    
329
  def GetNodes(self, node):
330
    """This function returns the nodes this device lives on.
331

332
    Given the node on which the parent of the device lives on (or, in
333
    case of a top-level device, the primary node of the devices'
334
    instance), this function will return a list of nodes on which this
335
    devices needs to (or can) be assembled.
336

337
    """
338
    if self.dev_type in [constants.LD_LV, constants.LD_FILE]:
339
      result = [node]
340
    elif self.dev_type in constants.LDS_DRBD:
341
      result = [self.logical_id[0], self.logical_id[1]]
342
      if node not in result:
343
        raise errors.ConfigurationError("DRBD device passed unknown node")
344
    else:
345
      raise errors.ProgrammerError("Unhandled device type %s" % self.dev_type)
346
    return result
347

    
348
  def ComputeNodeTree(self, parent_node):
349
    """Compute the node/disk tree for this disk and its children.
350

351
    This method, given the node on which the parent disk lives, will
352
    return the list of all (node, disk) pairs which describe the disk
353
    tree in the most compact way. For example, a drbd/lvm stack
354
    will be returned as (primary_node, drbd) and (secondary_node, drbd)
355
    which represents all the top-level devices on the nodes.
356

357
    """
358
    my_nodes = self.GetNodes(parent_node)
359
    result = [(node, self) for node in my_nodes]
360
    if not self.children:
361
      # leaf device
362
      return result
363
    for node in my_nodes:
364
      for child in self.children:
365
        child_result = child.ComputeNodeTree(node)
366
        if len(child_result) == 1:
367
          # child (and all its descendants) is simple, doesn't split
368
          # over multiple hosts, so we don't need to describe it, our
369
          # own entry for this node describes it completely
370
          continue
371
        else:
372
          # check if child nodes differ from my nodes; note that
373
          # subdisk can differ from the child itself, and be instead
374
          # one of its descendants
375
          for subnode, subdisk in child_result:
376
            if subnode not in my_nodes:
377
              result.append((subnode, subdisk))
378
            # otherwise child is under our own node, so we ignore this
379
            # entry (but probably the other results in the list will
380
            # be different)
381
    return result
382

    
383
  def RecordGrow(self, amount):
384
    """Update the size of this disk after growth.
385

386
    This method recurses over the disks's children and updates their
387
    size correspondigly. The method needs to be kept in sync with the
388
    actual algorithms from bdev.
389

390
    """
391
    if self.dev_type == constants.LD_LV:
392
      self.size += amount
393
    elif self.dev_type == constants.LD_DRBD8:
394
      if self.children:
395
        self.children[0].RecordGrow(amount)
396
      self.size += amount
397
    else:
398
      raise errors.ProgrammerError("Disk.RecordGrow called for unsupported"
399
                                   " disk type %s" % self.dev_type)
400

    
401
  def SetPhysicalID(self, target_node, nodes_ip):
402
    """Convert the logical ID to the physical ID.
403

404
    This is used only for drbd, which needs ip/port configuration.
405

406
    The routine descends down and updates its children also, because
407
    this helps when the only the top device is passed to the remote
408
    node.
409

410
    Arguments:
411
      - target_node: the node we wish to configure for
412
      - nodes_ip: a mapping of node name to ip
413

414
    The target_node must exist in in nodes_ip, and must be one of the
415
    nodes in the logical ID for each of the DRBD devices encountered
416
    in the disk tree.
417

418
    """
419
    if self.children:
420
      for child in self.children:
421
        child.SetPhysicalID(target_node, nodes_ip)
422

    
423
    if self.logical_id is None and self.physical_id is not None:
424
      return
425
    if self.dev_type in constants.LDS_DRBD:
426
      pnode, snode, port, pminor, sminor, secret = self.logical_id
427
      if target_node not in (pnode, snode):
428
        raise errors.ConfigurationError("DRBD device not knowing node %s" %
429
                                        target_node)
430
      pnode_ip = nodes_ip.get(pnode, None)
431
      snode_ip = nodes_ip.get(snode, None)
432
      if pnode_ip is None or snode_ip is None:
433
        raise errors.ConfigurationError("Can't find primary or secondary node"
434
                                        " for %s" % str(self))
435
      p_data = (pnode_ip, port)
436
      s_data = (snode_ip, port)
437
      if pnode == target_node:
438
        self.physical_id = p_data + s_data + (pminor, secret)
439
      else: # it must be secondary, we tested above
440
        self.physical_id = s_data + p_data + (sminor, secret)
441
    else:
442
      self.physical_id = self.logical_id
443
    return
444

    
445
  def ToDict(self):
446
    """Disk-specific conversion to standard python types.
447

448
    This replaces the children lists of objects with lists of
449
    standard python types.
450

451
    """
452
    bo = super(Disk, self).ToDict()
453

    
454
    for attr in ("children",):
455
      alist = bo.get(attr, None)
456
      if alist:
457
        bo[attr] = self._ContainerToDicts(alist)
458
    return bo
459

    
460
  @classmethod
461
  def FromDict(cls, val):
462
    """Custom function for Disks
463

464
    """
465
    obj = super(Disk, cls).FromDict(val)
466
    if obj.children:
467
      obj.children = cls._ContainerFromDicts(obj.children, list, Disk)
468
    if obj.logical_id and isinstance(obj.logical_id, list):
469
      obj.logical_id = tuple(obj.logical_id)
470
    if obj.physical_id and isinstance(obj.physical_id, list):
471
      obj.physical_id = tuple(obj.physical_id)
472
    if obj.dev_type in constants.LDS_DRBD:
473
      # we need a tuple of length six here
474
      if len(obj.logical_id) < 6:
475
        obj.logical_id += (None,) * (6 - len(obj.logical_id))
476
    return obj
477

    
478
  def __str__(self):
479
    """Custom str() formatter for disks.
480

481
    """
482
    if self.dev_type == constants.LD_LV:
483
      val =  "<LogicalVolume(/dev/%s/%s" % self.logical_id
484
    elif self.dev_type in constants.LDS_DRBD:
485
      node_a, node_b, port, minor_a, minor_b = self.logical_id[:5]
486
      val = "<DRBD8("
487
      if self.physical_id is None:
488
        phy = "unconfigured"
489
      else:
490
        phy = ("configured as %s:%s %s:%s" %
491
               (self.physical_id[0], self.physical_id[1],
492
                self.physical_id[2], self.physical_id[3]))
493

    
494
      val += ("hosts=%s/%d-%s/%d, port=%s, %s, " %
495
              (node_a, minor_a, node_b, minor_b, port, phy))
496
      if self.children and self.children.count(None) == 0:
497
        val += "backend=%s, metadev=%s" % (self.children[0], self.children[1])
498
      else:
499
        val += "no local storage"
500
    else:
501
      val = ("<Disk(type=%s, logical_id=%s, physical_id=%s, children=%s" %
502
             (self.dev_type, self.logical_id, self.physical_id, self.children))
503
    if self.iv_name is None:
504
      val += ", not visible"
505
    else:
506
      val += ", visible as /dev/%s" % self.iv_name
507
    if isinstance(self.size, int):
508
      val += ", size=%dm)>" % self.size
509
    else:
510
      val += ", size='%s')>" % (self.size,)
511
    return val
512

    
513
  def Verify(self):
514
    """Checks that this disk is correctly configured.
515

516
    """
517
    errors = []
518
    if self.mode not in constants.DISK_ACCESS_SET:
519
      errors.append("Disk access mode '%s' is invalid" % (self.mode, ))
520
    return errors
521

    
522

    
523
class Instance(TaggableObject):
524
  """Config object representing an instance."""
525
  __slots__ = TaggableObject.__slots__ + [
526
    "name",
527
    "primary_node",
528
    "os",
529
    "hypervisor",
530
    "hvparams",
531
    "beparams",
532
    "admin_up",
533
    "nics",
534
    "disks",
535
    "disk_template",
536
    "network_port",
537
    "serial_no",
538
    ]
539

    
540
  def _ComputeSecondaryNodes(self):
541
    """Compute the list of secondary nodes.
542

543
    This is a simple wrapper over _ComputeAllNodes.
544

545
    """
546
    all_nodes = set(self._ComputeAllNodes())
547
    all_nodes.discard(self.primary_node)
548
    return tuple(all_nodes)
549

    
550
  secondary_nodes = property(_ComputeSecondaryNodes, None, None,
551
                             "List of secondary nodes")
552

    
553
  def _ComputeAllNodes(self):
554
    """Compute the list of all nodes.
555

556
    Since the data is already there (in the drbd disks), keeping it as
557
    a separate normal attribute is redundant and if not properly
558
    synchronised can cause problems. Thus it's better to compute it
559
    dynamically.
560

561
    """
562
    def _Helper(nodes, device):
563
      """Recursively computes nodes given a top device."""
564
      if device.dev_type in constants.LDS_DRBD:
565
        nodea, nodeb = device.logical_id[:2]
566
        nodes.add(nodea)
567
        nodes.add(nodeb)
568
      if device.children:
569
        for child in device.children:
570
          _Helper(nodes, child)
571

    
572
    all_nodes = set()
573
    all_nodes.add(self.primary_node)
574
    for device in self.disks:
575
      _Helper(all_nodes, device)
576
    return tuple(all_nodes)
577

    
578
  all_nodes = property(_ComputeAllNodes, None, None,
579
                       "List of all nodes of the instance")
580

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

584
    This function figures out what logical volumes should belong on
585
    which nodes, recursing through a device tree.
586

587
    @param lvmap: optional dictionary to receive the
588
        'node' : ['lv', ...] data.
589

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

593
    """
594
    if node == None:
595
      node = self.primary_node
596

    
597
    if lvmap is None:
598
      lvmap = { node : [] }
599
      ret = lvmap
600
    else:
601
      if not node in lvmap:
602
        lvmap[node] = []
603
      ret = None
604

    
605
    if not devs:
606
      devs = self.disks
607

    
608
    for dev in devs:
609
      if dev.dev_type == constants.LD_LV:
610
        lvmap[node].append(dev.logical_id[1])
611

    
612
      elif dev.dev_type in constants.LDS_DRBD:
613
        if dev.children:
614
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[0])
615
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[1])
616

    
617
      elif dev.children:
618
        self.MapLVsByNode(lvmap, dev.children, node)
619

    
620
    return ret
621

    
622
  def FindDisk(self, idx):
623
    """Find a disk given having a specified index.
624

625
    This is just a wrapper that does validation of the index.
626

627
    @type idx: int
628
    @param idx: the disk index
629
    @rtype: L{Disk}
630
    @return: the corresponding disk
631
    @raise errors.OpPrereqError: when the given index is not valid
632

633
    """
634
    try:
635
      idx = int(idx)
636
      return self.disks[idx]
637
    except ValueError, err:
638
      raise errors.OpPrereqError("Invalid disk index: '%s'" % str(err))
639
    except IndexError:
640
      raise errors.OpPrereqError("Invalid disk index: %d (instace has disks"
641
                                 " 0 to %d" % (idx, len(self.disks)))
642

    
643
  def ToDict(self):
644
    """Instance-specific conversion to standard python types.
645

646
    This replaces the children lists of objects with lists of standard
647
    python types.
648

649
    """
650
    bo = super(Instance, self).ToDict()
651

    
652
    for attr in "nics", "disks":
653
      alist = bo.get(attr, None)
654
      if alist:
655
        nlist = self._ContainerToDicts(alist)
656
      else:
657
        nlist = []
658
      bo[attr] = nlist
659
    return bo
660

    
661
  @classmethod
662
  def FromDict(cls, val):
663
    """Custom function for instances.
664

665
    """
666
    obj = super(Instance, cls).FromDict(val)
667
    obj.nics = cls._ContainerFromDicts(obj.nics, list, NIC)
668
    obj.disks = cls._ContainerFromDicts(obj.disks, list, Disk)
669
    return obj
670

    
671

    
672
class OS(ConfigObject):
673
  """Config object representing an operating system."""
674
  __slots__ = [
675
    "name",
676
    "path",
677
    "status",
678
    "api_versions",
679
    "create_script",
680
    "export_script",
681
    "import_script",
682
    "rename_script",
683
    ]
684

    
685
  @classmethod
686
  def FromInvalidOS(cls, err):
687
    """Create an OS from an InvalidOS error.
688

689
    This routine knows how to convert an InvalidOS error to an OS
690
    object representing the broken OS with a meaningful error message.
691

692
    """
693
    if not isinstance(err, errors.InvalidOS):
694
      raise errors.ProgrammerError("Trying to initialize an OS from an"
695
                                   " invalid object of type %s" % type(err))
696

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

    
699
  def __nonzero__(self):
700
    return self.status == constants.OS_VALID_STATUS
701

    
702
  __bool__ = __nonzero__
703

    
704

    
705
class Node(TaggableObject):
706
  """Config object representing a node."""
707
  __slots__ = TaggableObject.__slots__ + [
708
    "name",
709
    "primary_ip",
710
    "secondary_ip",
711
    "serial_no",
712
    "master_candidate",
713
    "offline",
714
    "drained",
715
    ]
716

    
717

    
718
class Cluster(TaggableObject):
719
  """Config object representing the cluster."""
720
  __slots__ = TaggableObject.__slots__ + [
721
    "serial_no",
722
    "rsahostkeypub",
723
    "highest_used_port",
724
    "tcpudp_port_pool",
725
    "mac_prefix",
726
    "volume_group_name",
727
    "default_bridge",
728
    "default_hypervisor",
729
    "master_node",
730
    "master_ip",
731
    "master_netdev",
732
    "cluster_name",
733
    "file_storage_dir",
734
    "enabled_hypervisors",
735
    "hvparams",
736
    "beparams",
737
    "candidate_pool_size",
738
    "modify_etc_hosts",
739
    ]
740

    
741
  def UpgradeConfig(self):
742
    """Fill defaults for missing configuration values.
743

744
    """
745
    if self.modify_etc_hosts is None:
746
      self.modify_etc_hosts = True
747

    
748
  def ToDict(self):
749
    """Custom function for cluster.
750

751
    """
752
    mydict = super(Cluster, self).ToDict()
753
    mydict["tcpudp_port_pool"] = list(self.tcpudp_port_pool)
754
    return mydict
755

    
756
  @classmethod
757
  def FromDict(cls, val):
758
    """Custom function for cluster.
759

760
    """
761
    obj = super(Cluster, cls).FromDict(val)
762
    if not isinstance(obj.tcpudp_port_pool, set):
763
      obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
764
    return obj
765

    
766
  @staticmethod
767
  def FillDict(defaults_dict, custom_dict):
768
    """Basic function to apply settings on top a default dict.
769

770
    @type defaults_dict: dict
771
    @param defaults_dict: dictionary holding the default values
772
    @type custom_dict: dict
773
    @param custom_dict: dictionary holding customized value
774
    @rtype: dict
775
    @return: dict with the 'full' values
776

777
    """
778
    ret_dict = copy.deepcopy(defaults_dict)
779
    ret_dict.update(custom_dict)
780
    return ret_dict
781

    
782
  def FillHV(self, instance):
783
    """Fill an instance's hvparams dict.
784

785
    @type instance: object
786
    @param instance: the instance parameter to fill
787
    @rtype: dict
788
    @return: a copy of the instance's hvparams with missing keys filled from
789
        the cluster defaults
790

791
    """
792
    return self.FillDict(self.hvparams.get(instance.hypervisor, {}),
793
                         instance.hvparams)
794

    
795
  def FillBE(self, instance):
796
    """Fill an instance's beparams dict.
797

798
    @type instance: object
799
    @param instance: the instance parameter to fill
800
    @rtype: dict
801
    @return: a copy of the instance's beparams with missing keys filled from
802
        the cluster defaults
803

804
    """
805
    return self.FillDict(self.beparams.get(constants.BEGR_DEFAULT, {}),
806
                         instance.beparams)
807

    
808

    
809
class SerializableConfigParser(ConfigParser.SafeConfigParser):
810
  """Simple wrapper over ConfigParse that allows serialization.
811

812
  This class is basically ConfigParser.SafeConfigParser with two
813
  additional methods that allow it to serialize/unserialize to/from a
814
  buffer.
815

816
  """
817
  def Dumps(self):
818
    """Dump this instance and return the string representation."""
819
    buf = StringIO()
820
    self.write(buf)
821
    return buf.getvalue()
822

    
823
  @staticmethod
824
  def Loads(data):
825
    """Load data from a string."""
826
    buf = StringIO(data)
827
    cfp = SerializableConfigParser()
828
    cfp.readfp(buf)
829
    return cfp