Statistics
| Branch: | Tag: | Revision:

root / lib / objects.py @ 2971c913

History | View | Annotate | Download (24 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 __repr__(self):
157
    """Implement __repr__ for ConfigObjects."""
158
    return repr(self.ToDict())
159

    
160

    
161
class TaggableObject(ConfigObject):
162
  """An generic class supporting tags.
163

164
  """
165
  __slots__ = ConfigObject.__slots__ + ["tags"]
166

    
167
  @staticmethod
168
  def ValidateTag(tag):
169
    """Check if a tag is valid.
170

171
    If the tag is invalid, an errors.TagError will be raised. The
172
    function has no return value.
173

174
    """
175
    if not isinstance(tag, basestring):
176
      raise errors.TagError("Invalid tag type (not a string)")
177
    if len(tag) > constants.MAX_TAG_LEN:
178
      raise errors.TagError("Tag too long (>%d characters)" %
179
                            constants.MAX_TAG_LEN)
180
    if not tag:
181
      raise errors.TagError("Tags cannot be empty")
182
    if not re.match("^[\w.+*/:-]+$", tag):
183
      raise errors.TagError("Tag contains invalid characters")
184

    
185
  def GetTags(self):
186
    """Return the tags list.
187

188
    """
189
    tags = getattr(self, "tags", None)
190
    if tags is None:
191
      tags = self.tags = set()
192
    return tags
193

    
194
  def AddTag(self, tag):
195
    """Add a new tag.
196

197
    """
198
    self.ValidateTag(tag)
199
    tags = self.GetTags()
200
    if len(tags) >= constants.MAX_TAGS_PER_OBJ:
201
      raise errors.TagError("Too many tags")
202
    self.GetTags().add(tag)
203

    
204
  def RemoveTag(self, tag):
205
    """Remove a tag.
206

207
    """
208
    self.ValidateTag(tag)
209
    tags = self.GetTags()
210
    try:
211
      tags.remove(tag)
212
    except KeyError:
213
      raise errors.TagError("Tag not found")
214

    
215
  def ToDict(self):
216
    """Taggable-object-specific conversion to standard python types.
217

218
    This replaces the tags set with a list.
219

220
    """
221
    bo = super(TaggableObject, self).ToDict()
222

    
223
    tags = bo.get("tags", None)
224
    if isinstance(tags, set):
225
      bo["tags"] = list(tags)
226
    return bo
227

    
228
  @classmethod
229
  def FromDict(cls, val):
230
    """Custom function for instances.
231

232
    """
233
    obj = super(TaggableObject, cls).FromDict(val)
234
    if hasattr(obj, "tags") and isinstance(obj.tags, list):
235
      obj.tags = set(obj.tags)
236
    return obj
237

    
238

    
239
class ConfigData(ConfigObject):
240
  """Top-level config object."""
241
  __slots__ = ["version", "cluster", "nodes", "instances", "serial_no"]
242

    
243
  def ToDict(self):
244
    """Custom function for top-level config data.
245

246
    This just replaces the list of instances, nodes and the cluster
247
    with standard python types.
248

249
    """
250
    mydict = super(ConfigData, self).ToDict()
251
    mydict["cluster"] = mydict["cluster"].ToDict()
252
    for key in "nodes", "instances":
253
      mydict[key] = self._ContainerToDicts(mydict[key])
254

    
255
    return mydict
256

    
257
  @classmethod
258
  def FromDict(cls, val):
259
    """Custom function for top-level config data
260

261
    """
262
    obj = super(ConfigData, cls).FromDict(val)
263
    obj.cluster = Cluster.FromDict(obj.cluster)
264
    obj.nodes = cls._ContainerFromDicts(obj.nodes, dict, Node)
265
    obj.instances = cls._ContainerFromDicts(obj.instances, dict, Instance)
266
    return obj
267

    
268

    
269
class NIC(ConfigObject):
270
  """Config object representing a network card."""
271
  __slots__ = ["mac", "ip", "bridge"]
272

    
273

    
274
class Disk(ConfigObject):
275
  """Config object representing a block device."""
276
  __slots__ = ["dev_type", "logical_id", "physical_id",
277
               "children", "iv_name", "size", "mode"]
278

    
279
  def CreateOnSecondary(self):
280
    """Test if this device needs to be created on a secondary node."""
281
    return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
282

    
283
  def AssembleOnSecondary(self):
284
    """Test if this device needs to be assembled on a secondary node."""
285
    return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
286

    
287
  def OpenOnSecondary(self):
288
    """Test if this device needs to be opened on a secondary node."""
289
    return self.dev_type in (constants.LD_LV,)
290

    
291
  def StaticDevPath(self):
292
    """Return the device path if this device type has a static one.
293

294
    Some devices (LVM for example) live always at the same /dev/ path,
295
    irrespective of their status. For such devices, we return this
296
    path, for others we return None.
297

298
    """
299
    if self.dev_type == constants.LD_LV:
300
      return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
301
    return None
302

    
303
  def ChildrenNeeded(self):
304
    """Compute the needed number of children for activation.
305

306
    This method will return either -1 (all children) or a positive
307
    number denoting the minimum number of children needed for
308
    activation (only mirrored devices will usually return >=0).
309

310
    Currently, only DRBD8 supports diskless activation (therefore we
311
    return 0), for all other we keep the previous semantics and return
312
    -1.
313

314
    """
315
    if self.dev_type == constants.LD_DRBD8:
316
      return 0
317
    return -1
318

    
319
  def GetNodes(self, node):
320
    """This function returns the nodes this device lives on.
321

322
    Given the node on which the parent of the device lives on (or, in
323
    case of a top-level device, the primary node of the devices'
324
    instance), this function will return a list of nodes on which this
325
    devices needs to (or can) be assembled.
326

327
    """
328
    if self.dev_type in [constants.LD_LV, constants.LD_FILE]:
329
      result = [node]
330
    elif self.dev_type in constants.LDS_DRBD:
331
      result = [self.logical_id[0], self.logical_id[1]]
332
      if node not in result:
333
        raise errors.ConfigurationError("DRBD device passed unknown node")
334
    else:
335
      raise errors.ProgrammerError("Unhandled device type %s" % self.dev_type)
336
    return result
337

    
338
  def ComputeNodeTree(self, parent_node):
339
    """Compute the node/disk tree for this disk and its children.
340

341
    This method, given the node on which the parent disk lives, will
342
    return the list of all (node, disk) pairs which describe the disk
343
    tree in the most compact way. For example, a drbd/lvm stack
344
    will be returned as (primary_node, drbd) and (secondary_node, drbd)
345
    which represents all the top-level devices on the nodes.
346

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

    
373
  def RecordGrow(self, amount):
374
    """Update the size of this disk after growth.
375

376
    This method recurses over the disks's children and updates their
377
    size correspondigly. The method needs to be kept in sync with the
378
    actual algorithms from bdev.
379

380
    """
381
    if self.dev_type == constants.LD_LV:
382
      self.size += amount
383
    elif self.dev_type == constants.LD_DRBD8:
384
      if self.children:
385
        self.children[0].RecordGrow(amount)
386
      self.size += amount
387
    else:
388
      raise errors.ProgrammerError("Disk.RecordGrow called for unsupported"
389
                                   " disk type %s" % self.dev_type)
390

    
391
  def SetPhysicalID(self, target_node, nodes_ip):
392
    """Convert the logical ID to the physical ID.
393

394
    This is used only for drbd, which needs ip/port configuration.
395

396
    The routine descends down and updates its children also, because
397
    this helps when the only the top device is passed to the remote
398
    node.
399

400
    Arguments:
401
      - target_node: the node we wish to configure for
402
      - nodes_ip: a mapping of node name to ip
403

404
    The target_node must exist in in nodes_ip, and must be one of the
405
    nodes in the logical ID for each of the DRBD devices encountered
406
    in the disk tree.
407

408
    """
409
    if self.children:
410
      for child in self.children:
411
        child.SetPhysicalID(target_node, nodes_ip)
412

    
413
    if self.logical_id is None and self.physical_id is not None:
414
      return
415
    if self.dev_type in constants.LDS_DRBD:
416
      pnode, snode, port, pminor, sminor, secret = self.logical_id
417
      if target_node not in (pnode, snode):
418
        raise errors.ConfigurationError("DRBD device not knowing node %s" %
419
                                        target_node)
420
      pnode_ip = nodes_ip.get(pnode, None)
421
      snode_ip = nodes_ip.get(snode, None)
422
      if pnode_ip is None or snode_ip is None:
423
        raise errors.ConfigurationError("Can't find primary or secondary node"
424
                                        " for %s" % str(self))
425
      p_data = (pnode_ip, port)
426
      s_data = (snode_ip, port)
427
      if pnode == target_node:
428
        self.physical_id = p_data + s_data + (pminor, secret)
429
      else: # it must be secondary, we tested above
430
        self.physical_id = s_data + p_data + (sminor, secret)
431
    else:
432
      self.physical_id = self.logical_id
433
    return
434

    
435
  def ToDict(self):
436
    """Disk-specific conversion to standard python types.
437

438
    This replaces the children lists of objects with lists of
439
    standard python types.
440

441
    """
442
    bo = super(Disk, self).ToDict()
443

    
444
    for attr in ("children",):
445
      alist = bo.get(attr, None)
446
      if alist:
447
        bo[attr] = self._ContainerToDicts(alist)
448
    return bo
449

    
450
  @classmethod
451
  def FromDict(cls, val):
452
    """Custom function for Disks
453

454
    """
455
    obj = super(Disk, cls).FromDict(val)
456
    if obj.children:
457
      obj.children = cls._ContainerFromDicts(obj.children, list, Disk)
458
    if obj.logical_id and isinstance(obj.logical_id, list):
459
      obj.logical_id = tuple(obj.logical_id)
460
    if obj.physical_id and isinstance(obj.physical_id, list):
461
      obj.physical_id = tuple(obj.physical_id)
462
    if obj.dev_type in constants.LDS_DRBD:
463
      # we need a tuple of length six here
464
      if len(obj.logical_id) < 6:
465
        obj.logical_id += (None,) * (6 - len(obj.logical_id))
466
    return obj
467

    
468
  def __str__(self):
469
    """Custom str() formatter for disks.
470

471
    """
472
    if self.dev_type == constants.LD_LV:
473
      val =  "<LogicalVolume(/dev/%s/%s" % self.logical_id
474
    elif self.dev_type in constants.LDS_DRBD:
475
      node_a, node_b, port, minor_a, minor_b = self.logical_id[:5]
476
      val = "<DRBD8("
477
      if self.physical_id is None:
478
        phy = "unconfigured"
479
      else:
480
        phy = ("configured as %s:%s %s:%s" %
481
               (self.physical_id[0], self.physical_id[1],
482
                self.physical_id[2], self.physical_id[3]))
483

    
484
      val += ("hosts=%s/%d-%s/%d, port=%s, %s, " %
485
              (node_a, minor_a, node_b, minor_b, port, phy))
486
      if self.children and self.children.count(None) == 0:
487
        val += "backend=%s, metadev=%s" % (self.children[0], self.children[1])
488
      else:
489
        val += "no local storage"
490
    else:
491
      val = ("<Disk(type=%s, logical_id=%s, physical_id=%s, children=%s" %
492
             (self.dev_type, self.logical_id, self.physical_id, self.children))
493
    if self.iv_name is None:
494
      val += ", not visible"
495
    else:
496
      val += ", visible as /dev/%s" % self.iv_name
497
    if isinstance(self.size, int):
498
      val += ", size=%dm)>" % self.size
499
    else:
500
      val += ", size='%s')>" % (self.size,)
501
    return val
502

    
503
  def Verify(self):
504
    """Checks that this disk is correctly configured.
505

506
    """
507
    errors = []
508
    if self.mode not in constants.DISK_ACCESS_SET:
509
      errors.append("Disk access mode '%s' is invalid" % (self.mode, ))
510
    return errors
511

    
512

    
513
class Instance(TaggableObject):
514
  """Config object representing an instance."""
515
  __slots__ = TaggableObject.__slots__ + [
516
    "name",
517
    "primary_node",
518
    "os",
519
    "hypervisor",
520
    "hvparams",
521
    "beparams",
522
    "admin_up",
523
    "nics",
524
    "disks",
525
    "disk_template",
526
    "network_port",
527
    "serial_no",
528
    ]
529

    
530
  def _ComputeSecondaryNodes(self):
531
    """Compute the list of secondary nodes.
532

533
    This is a simple wrapper over _ComputeAllNodes.
534

535
    """
536
    all_nodes = set(self._ComputeAllNodes())
537
    all_nodes.discard(self.primary_node)
538
    return tuple(all_nodes)
539

    
540
  secondary_nodes = property(_ComputeSecondaryNodes, None, None,
541
                             "List of secondary nodes")
542

    
543
  def _ComputeAllNodes(self):
544
    """Compute the list of all nodes.
545

546
    Since the data is already there (in the drbd disks), keeping it as
547
    a separate normal attribute is redundant and if not properly
548
    synchronised can cause problems. Thus it's better to compute it
549
    dynamically.
550

551
    """
552
    def _Helper(nodes, device):
553
      """Recursively computes nodes given a top device."""
554
      if device.dev_type in constants.LDS_DRBD:
555
        nodea, nodeb = device.logical_id[:2]
556
        nodes.add(nodea)
557
        nodes.add(nodeb)
558
      if device.children:
559
        for child in device.children:
560
          _Helper(nodes, child)
561

    
562
    all_nodes = set()
563
    all_nodes.add(self.primary_node)
564
    for device in self.disks:
565
      _Helper(all_nodes, device)
566
    return tuple(all_nodes)
567

    
568
  all_nodes = property(_ComputeAllNodes, None, None,
569
                       "List of all nodes of the instance")
570

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

574
    This function figures out what logical volumes should belong on
575
    which nodes, recursing through a device tree.
576

577
    @param lvmap: optional dictionary to receive the
578
        'node' : ['lv', ...] data.
579

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

583
    """
584
    if node == None:
585
      node = self.primary_node
586

    
587
    if lvmap is None:
588
      lvmap = { node : [] }
589
      ret = lvmap
590
    else:
591
      if not node in lvmap:
592
        lvmap[node] = []
593
      ret = None
594

    
595
    if not devs:
596
      devs = self.disks
597

    
598
    for dev in devs:
599
      if dev.dev_type == constants.LD_LV:
600
        lvmap[node].append(dev.logical_id[1])
601

    
602
      elif dev.dev_type in constants.LDS_DRBD:
603
        if dev.children:
604
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[0])
605
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[1])
606

    
607
      elif dev.children:
608
        self.MapLVsByNode(lvmap, dev.children, node)
609

    
610
    return ret
611

    
612
  def FindDisk(self, idx):
613
    """Find a disk given having a specified index.
614

615
    This is just a wrapper that does validation of the index.
616

617
    @type idx: int
618
    @param idx: the disk index
619
    @rtype: L{Disk}
620
    @return: the corresponding disk
621
    @raise errors.OpPrereqError: when the given index is not valid
622

623
    """
624
    try:
625
      idx = int(idx)
626
      return self.disks[idx]
627
    except ValueError, err:
628
      raise errors.OpPrereqError("Invalid disk index: '%s'" % str(err))
629
    except IndexError:
630
      raise errors.OpPrereqError("Invalid disk index: %d (instace has disks"
631
                                 " 0 to %d" % (idx, len(self.disks)))
632

    
633
  def ToDict(self):
634
    """Instance-specific conversion to standard python types.
635

636
    This replaces the children lists of objects with lists of standard
637
    python types.
638

639
    """
640
    bo = super(Instance, self).ToDict()
641

    
642
    for attr in "nics", "disks":
643
      alist = bo.get(attr, None)
644
      if alist:
645
        nlist = self._ContainerToDicts(alist)
646
      else:
647
        nlist = []
648
      bo[attr] = nlist
649
    return bo
650

    
651
  @classmethod
652
  def FromDict(cls, val):
653
    """Custom function for instances.
654

655
    """
656
    obj = super(Instance, cls).FromDict(val)
657
    obj.nics = cls._ContainerFromDicts(obj.nics, list, NIC)
658
    obj.disks = cls._ContainerFromDicts(obj.disks, list, Disk)
659
    return obj
660

    
661

    
662
class OS(ConfigObject):
663
  """Config object representing an operating system."""
664
  __slots__ = [
665
    "name",
666
    "path",
667
    "status",
668
    "api_versions",
669
    "create_script",
670
    "export_script",
671
    "import_script",
672
    "rename_script",
673
    ]
674

    
675
  @classmethod
676
  def FromInvalidOS(cls, err):
677
    """Create an OS from an InvalidOS error.
678

679
    This routine knows how to convert an InvalidOS error to an OS
680
    object representing the broken OS with a meaningful error message.
681

682
    """
683
    if not isinstance(err, errors.InvalidOS):
684
      raise errors.ProgrammerError("Trying to initialize an OS from an"
685
                                   " invalid object of type %s" % type(err))
686

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

    
689
  def __nonzero__(self):
690
    return self.status == constants.OS_VALID_STATUS
691

    
692
  __bool__ = __nonzero__
693

    
694

    
695
class Node(TaggableObject):
696
  """Config object representing a node."""
697
  __slots__ = TaggableObject.__slots__ + [
698
    "name",
699
    "primary_ip",
700
    "secondary_ip",
701
    "serial_no",
702
    "master_candidate",
703
    "offline",
704
    "drained",
705
    ]
706

    
707

    
708
class Cluster(TaggableObject):
709
  """Config object representing the cluster."""
710
  __slots__ = TaggableObject.__slots__ + [
711
    "serial_no",
712
    "rsahostkeypub",
713
    "highest_used_port",
714
    "tcpudp_port_pool",
715
    "mac_prefix",
716
    "volume_group_name",
717
    "default_bridge",
718
    "default_hypervisor",
719
    "master_node",
720
    "master_ip",
721
    "master_netdev",
722
    "cluster_name",
723
    "file_storage_dir",
724
    "enabled_hypervisors",
725
    "hvparams",
726
    "beparams",
727
    "candidate_pool_size",
728
    ]
729

    
730
  def ToDict(self):
731
    """Custom function for cluster.
732

733
    """
734
    mydict = super(Cluster, self).ToDict()
735
    mydict["tcpudp_port_pool"] = list(self.tcpudp_port_pool)
736
    return mydict
737

    
738
  @classmethod
739
  def FromDict(cls, val):
740
    """Custom function for cluster.
741

742
    """
743
    obj = super(Cluster, cls).FromDict(val)
744
    if not isinstance(obj.tcpudp_port_pool, set):
745
      obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
746
    return obj
747

    
748
  @staticmethod
749
  def FillDict(defaults_dict, custom_dict):
750
    """Basic function to apply settings on top a default dict.
751

752
    @type defaults_dict: dict
753
    @param defaults_dict: dictionary holding the default values
754
    @type custom_dict: dict
755
    @param custom_dict: dictionary holding customized value
756
    @rtype: dict
757
    @return: dict with the 'full' values
758

759
    """
760
    ret_dict = copy.deepcopy(defaults_dict)
761
    ret_dict.update(custom_dict)
762
    return ret_dict
763

    
764
  def FillHV(self, instance):
765
    """Fill an instance's hvparams dict.
766

767
    @type instance: object
768
    @param instance: the instance parameter to fill
769
    @rtype: dict
770
    @return: a copy of the instance's hvparams with missing keys filled from
771
        the cluster defaults
772

773
    """
774
    return self.FillDict(self.hvparams.get(instance.hypervisor, {}),
775
                         instance.hvparams)
776

    
777
  def FillBE(self, instance):
778
    """Fill an instance's beparams dict.
779

780
    @type instance: object
781
    @param instance: the instance parameter to fill
782
    @rtype: dict
783
    @return: a copy of the instance's beparams with missing keys filled from
784
        the cluster defaults
785

786
    """
787
    return self.FillDict(self.beparams.get(constants.BEGR_DEFAULT, {}),
788
                         instance.beparams)
789

    
790

    
791
class SerializableConfigParser(ConfigParser.SafeConfigParser):
792
  """Simple wrapper over ConfigParse that allows serialization.
793

794
  This class is basically ConfigParser.SafeConfigParser with two
795
  additional methods that allow it to serialize/unserialize to/from a
796
  buffer.
797

798
  """
799
  def Dumps(self):
800
    """Dump this instance and return the string representation."""
801
    buf = StringIO()
802
    self.write(buf)
803
    return buf.getvalue()
804

    
805
  @staticmethod
806
  def Loads(data):
807
    """Load data from a string."""
808
    buf = StringIO(data)
809
    cfp = SerializableConfigParser()
810
    cfp.readfp(buf)
811
    return cfp