Statistics
| Branch: | Tag: | Revision:

root / lib / objects.py @ 686d7433

History | View | Annotate | Download (23.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 __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"]
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
      val = "<DRBD8("
476
      if self.physical_id is None:
477
        phy = "unconfigured"
478
      else:
479
        phy = ("configured as %s:%s %s:%s" %
480
               (self.physical_id[0], self.physical_id[1],
481
                self.physical_id[2], self.physical_id[3]))
482

    
483
      val += ("hosts=%s-%s, port=%s, %s, " %
484
              (self.logical_id[0], self.logical_id[1], self.logical_id[2],
485
               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
    val += ", size=%dm)>" % self.size
498
    return val
499

    
500

    
501
class Instance(TaggableObject):
502
  """Config object representing an instance."""
503
  __slots__ = TaggableObject.__slots__ + [
504
    "name",
505
    "primary_node",
506
    "os",
507
    "hypervisor",
508
    "hvparams",
509
    "beparams",
510
    "status",
511
    "nics",
512
    "disks",
513
    "disk_template",
514
    "network_port",
515
    "serial_no",
516
    ]
517

    
518
  def _ComputeSecondaryNodes(self):
519
    """Compute the list of secondary nodes.
520

521
    Since the data is already there (in the drbd disks), keeping it as
522
    a separate normal attribute is redundant and if not properly
523
    synchronised can cause problems. Thus it's better to compute it
524
    dynamically.
525

526
    """
527
    def _Helper(primary, sec_nodes, device):
528
      """Recursively computes secondary nodes given a top device."""
529
      if device.dev_type in constants.LDS_DRBD:
530
        nodea, nodeb, dummy = device.logical_id[:3]
531
        if nodea == primary:
532
          candidate = nodeb
533
        else:
534
          candidate = nodea
535
        if candidate not in sec_nodes:
536
          sec_nodes.append(candidate)
537
      if device.children:
538
        for child in device.children:
539
          _Helper(primary, sec_nodes, child)
540

    
541
    secondary_nodes = []
542
    for device in self.disks:
543
      _Helper(self.primary_node, secondary_nodes, device)
544
    return tuple(secondary_nodes)
545

    
546
  secondary_nodes = property(_ComputeSecondaryNodes, None, None,
547
                             "List of secondary nodes")
548

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

552
    This function figures out what logical volumes should belong on which
553
    nodes, recursing through a device tree.
554

555
    Args:
556
      lvmap: (optional) a dictionary to receive the 'node' : ['lv', ...] data.
557

558
    Returns:
559
      None if lvmap arg is given.
560
      Otherwise, { 'nodename' : ['volume1', 'volume2', ...], ... }
561

562
    """
563
    if node == None:
564
      node = self.primary_node
565

    
566
    if lvmap is None:
567
      lvmap = { node : [] }
568
      ret = lvmap
569
    else:
570
      if not node in lvmap:
571
        lvmap[node] = []
572
      ret = None
573

    
574
    if not devs:
575
      devs = self.disks
576

    
577
    for dev in devs:
578
      if dev.dev_type == constants.LD_LV:
579
        lvmap[node].append(dev.logical_id[1])
580

    
581
      elif dev.dev_type in constants.LDS_DRBD:
582
        if dev.logical_id[0] not in lvmap:
583
          lvmap[dev.logical_id[0]] = []
584

    
585
        if dev.logical_id[1] not in lvmap:
586
          lvmap[dev.logical_id[1]] = []
587

    
588
        if dev.children:
589
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[0])
590
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[1])
591

    
592
      elif dev.children:
593
        self.MapLVsByNode(lvmap, dev.children, node)
594

    
595
    return ret
596

    
597
  def FindDisk(self, name):
598
    """Find a disk given having a specified name.
599

600
    This will return the disk which has the given iv_name.
601

602
    """
603
    for disk in self.disks:
604
      if disk.iv_name == name:
605
        return disk
606

    
607
    return None
608

    
609
  def ToDict(self):
610
    """Instance-specific conversion to standard python types.
611

612
    This replaces the children lists of objects with lists of standard
613
    python types.
614

615
    """
616
    bo = super(Instance, self).ToDict()
617

    
618
    for attr in "nics", "disks":
619
      alist = bo.get(attr, None)
620
      if alist:
621
        nlist = self._ContainerToDicts(alist)
622
      else:
623
        nlist = []
624
      bo[attr] = nlist
625
    return bo
626

    
627
  @classmethod
628
  def FromDict(cls, val):
629
    """Custom function for instances.
630

631
    """
632
    obj = super(Instance, cls).FromDict(val)
633
    obj.nics = cls._ContainerFromDicts(obj.nics, list, NIC)
634
    obj.disks = cls._ContainerFromDicts(obj.disks, list, Disk)
635
    return obj
636

    
637

    
638
class OS(ConfigObject):
639
  """Config object representing an operating system."""
640
  __slots__ = [
641
    "name",
642
    "path",
643
    "status",
644
    "api_versions",
645
    "create_script",
646
    "export_script",
647
    "import_script",
648
    "rename_script",
649
    ]
650

    
651
  @classmethod
652
  def FromInvalidOS(cls, err):
653
    """Create an OS from an InvalidOS error.
654

655
    This routine knows how to convert an InvalidOS error to an OS
656
    object representing the broken OS with a meaningful error message.
657

658
    """
659
    if not isinstance(err, errors.InvalidOS):
660
      raise errors.ProgrammerError("Trying to initialize an OS from an"
661
                                   " invalid object of type %s" % type(err))
662

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

    
665
  def __nonzero__(self):
666
    return self.status == constants.OS_VALID_STATUS
667

    
668
  __bool__ = __nonzero__
669

    
670

    
671
class Node(TaggableObject):
672
  """Config object representing a node."""
673
  __slots__ = TaggableObject.__slots__ + [
674
    "name",
675
    "primary_ip",
676
    "secondary_ip",
677
    "serial_no",
678
    ]
679

    
680

    
681
class Cluster(TaggableObject):
682
  """Config object representing the cluster."""
683
  __slots__ = TaggableObject.__slots__ + [
684
    "serial_no",
685
    "rsahostkeypub",
686
    "highest_used_port",
687
    "tcpudp_port_pool",
688
    "mac_prefix",
689
    "volume_group_name",
690
    "default_bridge",
691
    "hypervisor",
692
    "master_node",
693
    "master_ip",
694
    "master_netdev",
695
    "cluster_name",
696
    "file_storage_dir",
697
    "enabled_hypervisors",
698
    "hvparams",
699
    "beparams",
700
    ]
701

    
702
  def ToDict(self):
703
    """Custom function for cluster.
704

705
    """
706
    mydict = super(Cluster, self).ToDict()
707
    mydict["tcpudp_port_pool"] = list(self.tcpudp_port_pool)
708
    return mydict
709

    
710
  @classmethod
711
  def FromDict(cls, val):
712
    """Custom function for cluster.
713

714
    """
715
    obj = super(Cluster, cls).FromDict(val)
716
    if not isinstance(obj.tcpudp_port_pool, set):
717
      obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
718
    return obj
719

    
720
  @staticmethod
721
  def FillDict(defaults_dict, custom_dict):
722
    """Basic function to apply settings on top a default dict.
723

724
    @type defaults_dict: dict
725
    @param defaults_dict: dictionary holding the default values
726
    @type custom_dict: dict
727
    @param custom_dict: dictionary holding customized value
728
    @rtype: dict
729
    @return: dict with the 'full' values
730

731
    """
732
    ret_dict = copy.deepcopy(defaults_dict)
733
    ret_dict.update(custom_dict)
734
    return ret_dict
735

    
736
  def FillHV(self, instance):
737
    """Fill an instance's hvparams dict.
738

739
    @type instance: object
740
    @param instance: the instance parameter to fill
741
    @rtype: dict
742
    @return: a copy of the instance's hvparams with missing keys filled from
743
        the cluster defaults
744

745
    """
746
    return self.FillDict(self.hvparams.get(instance.hypervisor, {}),
747
                         instance.hvparams)
748

    
749
  def FillBE(self, instance):
750
    """Fill an instance's beparams dict.
751

752
    @type instance: object
753
    @param instance: the instance parameter to fill
754
    @rtype: dict
755
    @return: a copy of the instance's beparams with missing keys filled from
756
        the cluster defaults
757

758
    """
759
    return self.FillDict(self.beparams.get(constants.BEGR_DEFAULT, {}),
760
                         instance.beparams)
761

    
762

    
763
class SerializableConfigParser(ConfigParser.SafeConfigParser):
764
  """Simple wrapper over ConfigParse that allows serialization.
765

766
  This class is basically ConfigParser.SafeConfigParser with two
767
  additional methods that allow it to serialize/unserialize to/from a
768
  buffer.
769

770
  """
771
  def Dumps(self):
772
    """Dump this instance and return the string representation."""
773
    buf = StringIO()
774
    self.write(buf)
775
    return buf.getvalue()
776

    
777
  @staticmethod
778
  def Loads(data):
779
    """Load data from a string."""
780
    buf = StringIO(data)
781
    cfp = SerializableConfigParser()
782
    cfp.readfp(buf)
783
    return cfp