cmdlib: Fix typo, s/nick/NIC/
[ganeti-local] / lib / objects.py
index 83ed133..c72bd4a 100644 (file)
@@ -1,7 +1,7 @@
 #
 #
 
 #
 #
 
-# Copyright (C) 2006, 2007 Google Inc.
+# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Google Inc.
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -36,18 +36,22 @@ pass to and from external parties.
 import ConfigParser
 import re
 import copy
 import ConfigParser
 import re
 import copy
+import time
 from cStringIO import StringIO
 
 from ganeti import errors
 from ganeti import constants
 
 from cStringIO import StringIO
 
 from ganeti import errors
 from ganeti import constants
 
+from socket import AF_INET
+
 
 __all__ = ["ConfigObject", "ConfigData", "NIC", "Disk", "Instance",
 
 __all__ = ["ConfigObject", "ConfigData", "NIC", "Disk", "Instance",
-           "OS", "Node", "Cluster", "FillDict"]
+           "OS", "Node", "NodeGroup", "Cluster", "FillDict"]
 
 _TIMESTAMPS = ["ctime", "mtime"]
 _UUID = ["uuid"]
 
 
 _TIMESTAMPS = ["ctime", "mtime"]
 _UUID = ["uuid"]
 
+
 def FillDict(defaults_dict, custom_dict, skip_keys=None):
   """Basic function to apply settings on top a default dict.
 
 def FillDict(defaults_dict, custom_dict, skip_keys=None):
   """Basic function to apply settings on top a default dict.
 
@@ -109,16 +113,27 @@ class ConfigObject(object):
       setattr(self, k, v)
 
   def __getattr__(self, name):
       setattr(self, k, v)
 
   def __getattr__(self, name):
-    if name not in self.__slots__:
+    if name not in self._all_slots():
       raise AttributeError("Invalid object attribute %s.%s" %
                            (type(self).__name__, name))
     return None
 
   def __setstate__(self, state):
       raise AttributeError("Invalid object attribute %s.%s" %
                            (type(self).__name__, name))
     return None
 
   def __setstate__(self, state):
+    slots = self._all_slots()
     for name in state:
     for name in state:
-      if name in self.__slots__:
+      if name in slots:
         setattr(self, name, state[name])
 
         setattr(self, name, state[name])
 
+  @classmethod
+  def _all_slots(cls):
+    """Compute the list of all declared slots for a class.
+
+    """
+    slots = []
+    for parent in cls.__mro__:
+      slots.extend(getattr(parent, "__slots__", []))
+    return slots
+
   def ToDict(self):
     """Convert to a dict holding only standard python types.
 
   def ToDict(self):
     """Convert to a dict holding only standard python types.
 
@@ -130,7 +145,7 @@ class ConfigObject(object):
 
     """
     result = {}
 
     """
     result = {}
-    for name in self.__slots__:
+    for name in self._all_slots():
       value = getattr(self, name, None)
       if value is not None:
         result[name] = value
       value = getattr(self, name, None)
       if value is not None:
         result[name] = value
@@ -188,6 +203,8 @@ class ConfigObject(object):
     if not isinstance(c_type, type):
       raise TypeError("Container type %s passed to _ContainerFromDicts is"
                       " not a type" % type(c_type))
     if not isinstance(c_type, type):
       raise TypeError("Container type %s passed to _ContainerFromDicts is"
                       " not a type" % type(c_type))
+    if source is None:
+      source = c_type()
     if c_type is dict:
       ret = dict([(k, e_type.FromDict(v)) for k, v in source.iteritems()])
     elif c_type in (list, tuple, set, frozenset):
     if c_type is dict:
       ret = dict([(k, e_type.FromDict(v)) for k, v in source.iteritems()])
     elif c_type in (list, tuple, set, frozenset):
@@ -223,7 +240,7 @@ class TaggableObject(ConfigObject):
   """An generic class supporting tags.
 
   """
   """An generic class supporting tags.
 
   """
-  __slots__ = ConfigObject.__slots__ + ["tags"]
+  __slots__ = ["tags"]
   VALID_TAG_RE = re.compile("^[\w.+*/:@-]+$")
 
   @classmethod
   VALID_TAG_RE = re.compile("^[\w.+*/:@-]+$")
 
   @classmethod
@@ -300,8 +317,14 @@ class TaggableObject(ConfigObject):
 
 class ConfigData(ConfigObject):
   """Top-level config object."""
 
 class ConfigData(ConfigObject):
   """Top-level config object."""
-  __slots__ = (["version", "cluster", "nodes", "instances", "serial_no"] +
-               _TIMESTAMPS)
+  __slots__ = [
+    "version",
+    "cluster",
+    "nodes",
+    "nodegroups",
+    "instances",
+    "serial_no",
+    ] + _TIMESTAMPS
 
   def ToDict(self):
     """Custom function for top-level config data.
 
   def ToDict(self):
     """Custom function for top-level config data.
@@ -312,7 +335,7 @@ class ConfigData(ConfigObject):
     """
     mydict = super(ConfigData, self).ToDict()
     mydict["cluster"] = mydict["cluster"].ToDict()
     """
     mydict = super(ConfigData, self).ToDict()
     mydict["cluster"] = mydict["cluster"].ToDict()
-    for key in "nodes", "instances":
+    for key in "nodes", "instances", "nodegroups":
       mydict[key] = self._ContainerToDicts(mydict[key])
 
     return mydict
       mydict[key] = self._ContainerToDicts(mydict[key])
 
     return mydict
@@ -326,8 +349,24 @@ class ConfigData(ConfigObject):
     obj.cluster = Cluster.FromDict(obj.cluster)
     obj.nodes = cls._ContainerFromDicts(obj.nodes, dict, Node)
     obj.instances = cls._ContainerFromDicts(obj.instances, dict, Instance)
     obj.cluster = Cluster.FromDict(obj.cluster)
     obj.nodes = cls._ContainerFromDicts(obj.nodes, dict, Node)
     obj.instances = cls._ContainerFromDicts(obj.instances, dict, Instance)
+    obj.nodegroups = cls._ContainerFromDicts(obj.nodegroups, dict, NodeGroup)
     return obj
 
     return obj
 
+  def HasAnyDiskOfType(self, dev_type):
+    """Check if in there is at disk of the given type in the configuration.
+
+    @type dev_type: L{constants.LDS_BLOCK}
+    @param dev_type: the type to look for
+    @rtype: boolean
+    @return: boolean indicating if a disk of the given type was found or not
+
+    """
+    for instance in self.instances.values():
+      for disk in instance.disks:
+        if disk.IsBasedOnDiskType(dev_type):
+          return True
+    return False
+
   def UpgradeConfig(self):
     """Fill defaults for missing configuration values.
 
   def UpgradeConfig(self):
     """Fill defaults for missing configuration values.
 
@@ -337,11 +376,21 @@ class ConfigData(ConfigObject):
       node.UpgradeConfig()
     for instance in self.instances.values():
       instance.UpgradeConfig()
       node.UpgradeConfig()
     for instance in self.instances.values():
       instance.UpgradeConfig()
+    if self.nodegroups is None:
+      self.nodegroups = {}
+    for nodegroup in self.nodegroups.values():
+      nodegroup.UpgradeConfig()
+    if self.cluster.drbd_usermode_helper is None:
+      # To decide if we set an helper let's check if at least one instance has
+      # a DRBD disk. This does not cover all the possible scenarios but it
+      # gives a good approximation.
+      if self.HasAnyDiskOfType(constants.LD_DRBD8):
+        self.cluster.drbd_usermode_helper = constants.DEFAULT_DRBD_HELPER
 
 
 class NIC(ConfigObject):
   """Config object representing a network card."""
 
 
 class NIC(ConfigObject):
   """Config object representing a network card."""
-  __slots__ = ["mac", "ip", "bridge", "nicparams"]
+  __slots__ = ["mac", "ip", "nicparams"]
 
   @classmethod
   def CheckParameterSyntax(cls, nicparams):
 
   @classmethod
   def CheckParameterSyntax(cls, nicparams):
@@ -361,20 +410,6 @@ class NIC(ConfigObject):
       err = "Missing bridged nic link"
       raise errors.ConfigurationError(err)
 
       err = "Missing bridged nic link"
       raise errors.ConfigurationError(err)
 
-  def UpgradeConfig(self):
-    """Fill defaults for missing configuration values.
-
-    """
-    if self.nicparams is None:
-      self.nicparams = {}
-      if self.bridge is not None:
-        self.nicparams[constants.NIC_MODE] = constants.NIC_MODE_BRIDGED
-        self.nicparams[constants.NIC_LINK] = self.bridge
-    # bridge is no longer used it 2.1. The slot is left there to support
-    # upgrading, but will be removed in 2.2
-    if self.bridge is not None:
-      self.bridge = None
-
 
 class Disk(ConfigObject):
   """Config object representing a block device."""
 
 class Disk(ConfigObject):
   """Config object representing a block device."""
@@ -400,6 +435,9 @@ class Disk(ConfigObject):
     irrespective of their status. For such devices, we return this
     path, for others we return None.
 
     irrespective of their status. For such devices, we return this
     path, for others we return None.
 
+    @warning: The path returned is not a normalized pathname; callers
+        should check that it is a valid path.
+
     """
     if self.dev_type == constants.LD_LV:
       return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
     """
     if self.dev_type == constants.LD_LV:
       return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
@@ -421,6 +459,21 @@ class Disk(ConfigObject):
       return 0
     return -1
 
       return 0
     return -1
 
+  def IsBasedOnDiskType(self, dev_type):
+    """Check if the disk or its children are based on the given type.
+
+    @type dev_type: L{constants.LDS_BLOCK}
+    @param dev_type: the type to look for
+    @rtype: boolean
+    @return: boolean indicating if a device of the given type was found or not
+
+    """
+    if self.children:
+      for child in self.children:
+        if child.IsBasedOnDiskType(dev_type):
+          return True
+    return self.dev_type == dev_type
+
   def GetNodes(self, node):
     """This function returns the nodes this device lives on.
 
   def GetNodes(self, node):
     """This function returns the nodes this device lives on.
 
@@ -475,6 +528,28 @@ class Disk(ConfigObject):
             # be different)
     return result
 
             # be different)
     return result
 
+  def ComputeGrowth(self, amount):
+    """Compute the per-VG growth requirements.
+
+    This only works for VG-based disks.
+
+    @type amount: integer
+    @param amount: the desired increase in (user-visible) disk space
+    @rtype: dict
+    @return: a dictionary of volume-groups and the required size
+
+    """
+    if self.dev_type == constants.LD_LV:
+      return {self.logical_id[0]: amount}
+    elif self.dev_type == constants.LD_DRBD8:
+      if self.children:
+        return self.children[0].ComputeGrowth(amount)
+      else:
+        return {}
+    else:
+      # Other disk types do not require VG space
+      return {}
+
   def RecordGrow(self, amount):
     """Update the size of this disk after growth.
 
   def RecordGrow(self, amount):
     """Update the size of this disk after growth.
 
@@ -483,7 +558,7 @@ class Disk(ConfigObject):
     actual algorithms from bdev.
 
     """
     actual algorithms from bdev.
 
     """
-    if self.dev_type == constants.LD_LV:
+    if self.dev_type == constants.LD_LV or self.dev_type == constants.LD_FILE:
       self.size += amount
     elif self.dev_type == constants.LD_DRBD8:
       if self.children:
       self.size += amount
     elif self.dev_type == constants.LD_DRBD8:
       if self.children:
@@ -635,13 +710,14 @@ class Disk(ConfigObject):
 
 class Instance(TaggableObject):
   """Config object representing an instance."""
 
 class Instance(TaggableObject):
   """Config object representing an instance."""
-  __slots__ = TaggableObject.__slots__ + [
+  __slots__ = [
     "name",
     "primary_node",
     "os",
     "hypervisor",
     "hvparams",
     "beparams",
     "name",
     "primary_node",
     "os",
     "hypervisor",
     "hvparams",
     "beparams",
+    "osparams",
     "admin_up",
     "nics",
     "disks",
     "admin_up",
     "nics",
     "disks",
@@ -700,8 +776,10 @@ class Instance(TaggableObject):
     @param lvmap: optional dictionary to receive the
         'node' : ['lv', ...] data.
 
     @param lvmap: optional dictionary to receive the
         'node' : ['lv', ...] data.
 
-    @return: None if lvmap arg is given, otherwise, a dictionary
-        of the form { 'nodename' : ['volume1', 'volume2', ...], ... }
+    @return: None if lvmap arg is given, otherwise, a dictionary of
+        the form { 'nodename' : ['volume1', 'volume2', ...], ... };
+        volumeN is of the form "vg_name/lv_name", compatible with
+        GetVolumeList()
 
     """
     if node == None:
 
     """
     if node == None:
@@ -720,7 +798,7 @@ class Instance(TaggableObject):
 
     for dev in devs:
       if dev.dev_type == constants.LD_LV:
 
     for dev in devs:
       if dev.dev_type == constants.LD_LV:
-        lvmap[node].append(dev.logical_id[1])
+        lvmap[node].append(dev.logical_id[0]+"/"+dev.logical_id[1])
 
       elif dev.dev_type in constants.LDS_DRBD:
         if dev.children:
 
       elif dev.dev_type in constants.LDS_DRBD:
         if dev.children:
@@ -752,7 +830,7 @@ class Instance(TaggableObject):
                                  errors.ECODE_INVAL)
     except IndexError:
       raise errors.OpPrereqError("Invalid disk index: %d (instace has disks"
                                  errors.ECODE_INVAL)
     except IndexError:
       raise errors.OpPrereqError("Invalid disk index: %d (instace has disks"
-                                 " 0 to %d" % (idx, len(self.disks)),
+                                 " 0 to %d" % (idx, len(self.disks) - 1),
                                  errors.ECODE_INVAL)
 
   def ToDict(self):
                                  errors.ECODE_INVAL)
 
   def ToDict(self):
@@ -797,10 +875,21 @@ class Instance(TaggableObject):
           del self.hvparams[key]
         except KeyError:
           pass
           del self.hvparams[key]
         except KeyError:
           pass
+    if self.osparams is None:
+      self.osparams = {}
 
 
 class OS(ConfigObject):
 
 
 class OS(ConfigObject):
-  """Config object representing an operating system."""
+  """Config object representing an operating system.
+
+  @type supported_parameters: list
+  @ivar supported_parameters: a list of tuples, name and description,
+      containing the supported parameters by this OS
+
+  @type VARIANT_DELIM: string
+  @cvar VARIANT_DELIM: the variant delimiter
+
+  """
   __slots__ = [
     "name",
     "path",
   __slots__ = [
     "name",
     "path",
@@ -809,13 +898,50 @@ class OS(ConfigObject):
     "export_script",
     "import_script",
     "rename_script",
     "export_script",
     "import_script",
     "rename_script",
+    "verify_script",
     "supported_variants",
     "supported_variants",
+    "supported_parameters",
     ]
 
     ]
 
+  VARIANT_DELIM = "+"
+
+  @classmethod
+  def SplitNameVariant(cls, name):
+    """Splits the name into the proper name and variant.
+
+    @param name: the OS (unprocessed) name
+    @rtype: list
+    @return: a list of two elements; if the original name didn't
+        contain a variant, it's returned as an empty string
+
+    """
+    nv = name.split(cls.VARIANT_DELIM, 1)
+    if len(nv) == 1:
+      nv.append("")
+    return nv
+
+  @classmethod
+  def GetName(cls, name):
+    """Returns the proper name of the os (without the variant).
+
+    @param name: the OS (unprocessed) name
+
+    """
+    return cls.SplitNameVariant(name)[0]
+
+  @classmethod
+  def GetVariant(cls, name):
+    """Returns the variant the os (without the base name).
+
+    @param name: the OS (unprocessed) name
+
+    """
+    return cls.SplitNameVariant(name)[1]
+
 
 class Node(TaggableObject):
   """Config object representing a node."""
 
 class Node(TaggableObject):
   """Config object representing a node."""
-  __slots__ = TaggableObject.__slots__ + [
+  __slots__ = [
     "name",
     "primary_ip",
     "secondary_ip",
     "name",
     "primary_ip",
     "secondary_ip",
@@ -823,18 +949,116 @@ class Node(TaggableObject):
     "master_candidate",
     "offline",
     "drained",
     "master_candidate",
     "offline",
     "drained",
+    "group",
+    "master_capable",
+    "vm_capable",
+    "ndparams",
+    "powered",
     ] + _TIMESTAMPS + _UUID
 
     ] + _TIMESTAMPS + _UUID
 
+  def UpgradeConfig(self):
+    """Fill defaults for missing configuration values.
+
+    """
+    # pylint: disable-msg=E0203
+    # because these are "defined" via slots, not manually
+    if self.master_capable is None:
+      self.master_capable = True
+
+    if self.vm_capable is None:
+      self.vm_capable = True
+
+    if self.ndparams is None:
+      self.ndparams = {}
+
+    if self.powered is None:
+      self.powered = True
+
+
+class NodeGroup(ConfigObject):
+  """Config object representing a node group."""
+  __slots__ = [
+    "name",
+    "members",
+    "ndparams",
+    "serial_no",
+    "alloc_policy",
+    ] + _TIMESTAMPS + _UUID
+
+  def ToDict(self):
+    """Custom function for nodegroup.
+
+    This discards the members object, which gets recalculated and is only kept
+    in memory.
+
+    """
+    mydict = super(NodeGroup, self).ToDict()
+    del mydict["members"]
+    return mydict
+
+  @classmethod
+  def FromDict(cls, val):
+    """Custom function for nodegroup.
+
+    The members slot is initialized to an empty list, upon deserialization.
+
+    """
+    obj = super(NodeGroup, cls).FromDict(val)
+    obj.members = []
+    return obj
+
+  def UpgradeConfig(self):
+    """Fill defaults for missing configuration values.
+
+    """
+    if self.ndparams is None:
+      self.ndparams = {}
+
+    if self.serial_no is None:
+      self.serial_no = 1
+
+    if self.alloc_policy is None:
+      self.alloc_policy = constants.ALLOC_POLICY_PREFERRED
+
+    # We only update mtime, and not ctime, since we would not be able to provide
+    # a correct value for creation time.
+    if self.mtime is None:
+      self.mtime = time.time()
+
+  def FillND(self, node):
+    """Return filled out ndparams for L{object.Node}
+
+    @type node: L{objects.Node}
+    @param node: A Node object to fill
+    @return a copy of the node's ndparams with defaults filled
+
+    """
+    return self.SimpleFillND(node.ndparams)
+
+  def SimpleFillND(self, ndparams):
+    """Fill a given ndparams dict with defaults.
+
+    @type ndparams: dict
+    @param ndparams: the dict to fill
+    @rtype: dict
+    @return: a copy of the passed in ndparams with missing keys filled
+        from the node group defaults
+
+    """
+    return FillDict(self.ndparams, ndparams)
+
 
 class Cluster(TaggableObject):
   """Config object representing the cluster."""
 
 class Cluster(TaggableObject):
   """Config object representing the cluster."""
-  __slots__ = TaggableObject.__slots__ + [
+  __slots__ = [
     "serial_no",
     "rsahostkeypub",
     "highest_used_port",
     "tcpudp_port_pool",
     "mac_prefix",
     "volume_group_name",
     "serial_no",
     "rsahostkeypub",
     "highest_used_port",
     "tcpudp_port_pool",
     "mac_prefix",
     "volume_group_name",
+    "reserved_lvs",
+    "drbd_usermode_helper",
     "default_bridge",
     "default_hypervisor",
     "master_node",
     "default_bridge",
     "default_hypervisor",
     "master_node",
@@ -844,11 +1068,21 @@ class Cluster(TaggableObject):
     "file_storage_dir",
     "enabled_hypervisors",
     "hvparams",
     "file_storage_dir",
     "enabled_hypervisors",
     "hvparams",
+    "os_hvp",
     "beparams",
     "beparams",
+    "osparams",
     "nicparams",
     "nicparams",
+    "ndparams",
     "candidate_pool_size",
     "modify_etc_hosts",
     "modify_ssh_setup",
     "candidate_pool_size",
     "modify_etc_hosts",
     "modify_ssh_setup",
+    "maintain_node_health",
+    "uid_pool",
+    "default_iallocator",
+    "hidden_os",
+    "blacklisted_os",
+    "primary_ip_family",
+    "prealloc_wipe_disks",
     ] + _TIMESTAMPS + _UUID
 
   def UpgradeConfig(self):
     ] + _TIMESTAMPS + _UUID
 
   def UpgradeConfig(self):
@@ -864,6 +1098,16 @@ class Cluster(TaggableObject):
         self.hvparams[hypervisor] = FillDict(
             constants.HVC_DEFAULTS[hypervisor], self.hvparams[hypervisor])
 
         self.hvparams[hypervisor] = FillDict(
             constants.HVC_DEFAULTS[hypervisor], self.hvparams[hypervisor])
 
+    if self.os_hvp is None:
+      self.os_hvp = {}
+
+    # osparams added before 2.2
+    if self.osparams is None:
+      self.osparams = {}
+
+    if self.ndparams is None:
+      self.ndparams = constants.NDC_DEFAULTS
+
     self.beparams = UpgradeGroupedParams(self.beparams,
                                          constants.BEC_DEFAULTS)
     migrate_default_bridge = not self.nicparams
     self.beparams = UpgradeGroupedParams(self.beparams,
                                          constants.BEC_DEFAULTS)
     migrate_default_bridge = not self.nicparams
@@ -879,18 +1123,48 @@ class Cluster(TaggableObject):
     if self.modify_ssh_setup is None:
       self.modify_ssh_setup = True
 
     if self.modify_ssh_setup is None:
       self.modify_ssh_setup = True
 
-    # default_bridge is no longer used it 2.1. The slot is left there to
-    # support auto-upgrading, but will be removed in 2.2
+    # default_bridge is no longer used in 2.1. The slot is left there to
+    # support auto-upgrading. It can be removed once we decide to deprecate
+    # upgrading straight from 2.0.
     if self.default_bridge is not None:
       self.default_bridge = None
 
     if self.default_bridge is not None:
       self.default_bridge = None
 
-    # default_hypervisor is just the first enabled one in 2.1
+    # default_hypervisor is just the first enabled one in 2.1. This slot and
+    # code can be removed once upgrading straight from 2.0 is deprecated.
     if self.default_hypervisor is not None:
       self.enabled_hypervisors = ([self.default_hypervisor] +
         [hvname for hvname in self.enabled_hypervisors
          if hvname != self.default_hypervisor])
       self.default_hypervisor = None
 
     if self.default_hypervisor is not None:
       self.enabled_hypervisors = ([self.default_hypervisor] +
         [hvname for hvname in self.enabled_hypervisors
          if hvname != self.default_hypervisor])
       self.default_hypervisor = None
 
+    # maintain_node_health added after 2.1.1
+    if self.maintain_node_health is None:
+      self.maintain_node_health = False
+
+    if self.uid_pool is None:
+      self.uid_pool = []
+
+    if self.default_iallocator is None:
+      self.default_iallocator = ""
+
+    # reserved_lvs added before 2.2
+    if self.reserved_lvs is None:
+      self.reserved_lvs = []
+
+    # hidden and blacklisted operating systems added before 2.2.1
+    if self.hidden_os is None:
+      self.hidden_os = []
+
+    if self.blacklisted_os is None:
+      self.blacklisted_os = []
+
+    # primary_ip_family added before 2.3
+    if self.primary_ip_family is None:
+      self.primary_ip_family = AF_INET
+
+    if self.prealloc_wipe_disks is None:
+      self.prealloc_wipe_disks = False
+
   def ToDict(self):
     """Custom function for cluster.
 
   def ToDict(self):
     """Custom function for cluster.
 
@@ -909,16 +1183,41 @@ class Cluster(TaggableObject):
       obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
     return obj
 
       obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
     return obj
 
-  def FillHV(self, instance, skip_globals=False):
-    """Fill an instance's hvparams dict.
+  def GetHVDefaults(self, hypervisor, os_name=None, skip_keys=None):
+    """Get the default hypervisor parameters for the cluster.
 
 
-    @type instance: L{objects.Instance}
-    @param instance: the instance parameter to fill
+    @param hypervisor: the hypervisor name
+    @param os_name: if specified, we'll also update the defaults for this OS
+    @param skip_keys: if passed, list of keys not to use
+    @return: the defaults dict
+
+    """
+    if skip_keys is None:
+      skip_keys = []
+
+    fill_stack = [self.hvparams.get(hypervisor, {})]
+    if os_name is not None:
+      os_hvp = self.os_hvp.get(os_name, {}).get(hypervisor, {})
+      fill_stack.append(os_hvp)
+
+    ret_dict = {}
+    for o_dict in fill_stack:
+      ret_dict = FillDict(ret_dict, o_dict, skip_keys=skip_keys)
+
+    return ret_dict
+
+  def SimpleFillHV(self, hv_name, os_name, hvparams, skip_globals=False):
+    """Fill a given hvparams dict with cluster defaults.
+
+    @type hv_name: string
+    @param hv_name: the hypervisor to use
+    @type os_name: string
+    @param os_name: the OS to use for overriding the hypervisor defaults
     @type skip_globals: boolean
     @param skip_globals: if True, the global hypervisor parameters will
         not be filled
     @rtype: dict
     @type skip_globals: boolean
     @param skip_globals: if True, the global hypervisor parameters will
         not be filled
     @rtype: dict
-    @return: a copy of the instance's hvparams with missing keys filled from
+    @return: a copy of the given hvparams with missing keys filled from
         the cluster defaults
 
     """
         the cluster defaults
 
     """
@@ -926,11 +1225,40 @@ class Cluster(TaggableObject):
       skip_keys = constants.HVC_GLOBALS
     else:
       skip_keys = []
       skip_keys = constants.HVC_GLOBALS
     else:
       skip_keys = []
-    return FillDict(self.hvparams.get(instance.hypervisor, {}),
-                    instance.hvparams, skip_keys=skip_keys)
+
+    def_dict = self.GetHVDefaults(hv_name, os_name, skip_keys=skip_keys)
+    return FillDict(def_dict, hvparams, skip_keys=skip_keys)
+
+  def FillHV(self, instance, skip_globals=False):
+    """Fill an instance's hvparams dict with cluster defaults.
+
+    @type instance: L{objects.Instance}
+    @param instance: the instance parameter to fill
+    @type skip_globals: boolean
+    @param skip_globals: if True, the global hypervisor parameters will
+        not be filled
+    @rtype: dict
+    @return: a copy of the instance's hvparams with missing keys filled from
+        the cluster defaults
+
+    """
+    return self.SimpleFillHV(instance.hypervisor, instance.os,
+                             instance.hvparams, skip_globals)
+
+  def SimpleFillBE(self, beparams):
+    """Fill a given beparams dict with cluster defaults.
+
+    @type beparams: dict
+    @param beparams: the dict to fill
+    @rtype: dict
+    @return: a copy of the passed in beparams with missing keys filled
+        from the cluster defaults
+
+    """
+    return FillDict(self.beparams.get(constants.PP_DEFAULT, {}), beparams)
 
   def FillBE(self, instance):
 
   def FillBE(self, instance):
-    """Fill an instance's beparams dict.
+    """Fill an instance's beparams dict with cluster defaults.
 
     @type instance: L{objects.Instance}
     @param instance: the instance parameter to fill
 
     @type instance: L{objects.Instance}
     @param instance: the instance parameter to fill
@@ -939,8 +1267,63 @@ class Cluster(TaggableObject):
         the cluster defaults
 
     """
         the cluster defaults
 
     """
-    return FillDict(self.beparams.get(constants.PP_DEFAULT, {}),
-                    instance.beparams)
+    return self.SimpleFillBE(instance.beparams)
+
+  def SimpleFillNIC(self, nicparams):
+    """Fill a given nicparams dict with cluster defaults.
+
+    @type nicparams: dict
+    @param nicparams: the dict to fill
+    @rtype: dict
+    @return: a copy of the passed in nicparams with missing keys filled
+        from the cluster defaults
+
+    """
+    return FillDict(self.nicparams.get(constants.PP_DEFAULT, {}), nicparams)
+
+  def SimpleFillOS(self, os_name, os_params):
+    """Fill an instance's osparams dict with cluster defaults.
+
+    @type os_name: string
+    @param os_name: the OS name to use
+    @type os_params: dict
+    @param os_params: the dict to fill with default values
+    @rtype: dict
+    @return: a copy of the instance's osparams with missing keys filled from
+        the cluster defaults
+
+    """
+    name_only = os_name.split("+", 1)[0]
+    # base OS
+    result = self.osparams.get(name_only, {})
+    # OS with variant
+    result = FillDict(result, self.osparams.get(os_name, {}))
+    # specified params
+    return FillDict(result, os_params)
+
+  def FillND(self, node, nodegroup):
+    """Return filled out ndparams for L{objects.NodeGroup} and L{object.Node}
+
+    @type node: L{objects.Node}
+    @param node: A Node object to fill
+    @type nodegroup: L{objects.NodeGroup}
+    @param nodegroup: A Node object to fill
+    @return a copy of the node's ndparams with defaults filled
+
+    """
+    return self.SimpleFillND(nodegroup.FillND(node))
+
+  def SimpleFillND(self, ndparams):
+    """Fill a given ndparams dict with defaults.
+
+    @type ndparams: dict
+    @param ndparams: the dict to fill
+    @rtype: dict
+    @return: a copy of the passed in ndparams with missing keys filled
+        from the cluster defaults
+
+    """
+    return FillDict(self.ndparams, ndparams)
 
 
 class BlockDevStatus(ConfigObject):
 
 
 class BlockDevStatus(ConfigObject):
@@ -956,6 +1339,42 @@ class BlockDevStatus(ConfigObject):
     ]
 
 
     ]
 
 
+class ImportExportStatus(ConfigObject):
+  """Config object representing the status of an import or export."""
+  __slots__ = [
+    "recent_output",
+    "listen_port",
+    "connected",
+    "progress_mbytes",
+    "progress_throughput",
+    "progress_eta",
+    "progress_percent",
+    "exit_status",
+    "error_message",
+    ] + _TIMESTAMPS
+
+
+class ImportExportOptions(ConfigObject):
+  """Options for import/export daemon
+
+  @ivar key_name: X509 key name (None for cluster certificate)
+  @ivar ca_pem: Remote peer CA in PEM format (None for cluster certificate)
+  @ivar compress: Compression method (one of L{constants.IEC_ALL})
+  @ivar magic: Used to ensure the connection goes to the right disk
+  @ivar ipv6: Whether to use IPv6
+  @ivar connect_timeout: Number of seconds for establishing connection
+
+  """
+  __slots__ = [
+    "key_name",
+    "ca_pem",
+    "compress",
+    "magic",
+    "ipv6",
+    "connect_timeout",
+    ]
+
+
 class ConfdRequest(ConfigObject):
   """Object holding a confd request.
 
 class ConfdRequest(ConfigObject):
   """Object holding a confd request.
 
@@ -990,6 +1409,121 @@ class ConfdReply(ConfigObject):
     ]
 
 
     ]
 
 
+class QueryFieldDefinition(ConfigObject):
+  """Object holding a query field definition.
+
+  @ivar name: Field name
+  @ivar title: Human-readable title
+  @ivar kind: Field type
+
+  """
+  __slots__ = [
+    "name",
+    "title",
+    "kind",
+    ]
+
+
+class _QueryResponseBase(ConfigObject):
+  __slots__ = [
+    "fields",
+    ]
+
+  def ToDict(self):
+    """Custom function for serializing.
+
+    """
+    mydict = super(_QueryResponseBase, self).ToDict()
+    mydict["fields"] = self._ContainerToDicts(mydict["fields"])
+    return mydict
+
+  @classmethod
+  def FromDict(cls, val):
+    """Custom function for de-serializing.
+
+    """
+    obj = super(_QueryResponseBase, cls).FromDict(val)
+    obj.fields = cls._ContainerFromDicts(obj.fields, list, QueryFieldDefinition)
+    return obj
+
+
+class QueryRequest(ConfigObject):
+  """Object holding a query request.
+
+  """
+  __slots__ = [
+    "what",
+    "fields",
+    "filter",
+    ]
+
+
+class QueryResponse(_QueryResponseBase):
+  """Object holding the response to a query.
+
+  @ivar fields: List of L{QueryFieldDefinition} objects
+  @ivar data: Requested data
+
+  """
+  __slots__ = [
+    "data",
+    ]
+
+
+class QueryFieldsRequest(ConfigObject):
+  """Object holding a request for querying available fields.
+
+  """
+  __slots__ = [
+    "what",
+    "fields",
+    ]
+
+
+class QueryFieldsResponse(_QueryResponseBase):
+  """Object holding the response to a query for fields.
+
+  @ivar fields: List of L{QueryFieldDefinition} objects
+
+  """
+  __slots__ = [
+    ]
+
+
+class InstanceConsole(ConfigObject):
+  """Object describing how to access the console of an instance.
+
+  """
+  __slots__ = [
+    "instance",
+    "kind",
+    "message",
+    "host",
+    "port",
+    "user",
+    "command",
+    "display",
+    ]
+
+  def Validate(self):
+    """Validates contents of this object.
+
+    """
+    assert self.kind in constants.CONS_ALL, "Unknown console type"
+    assert self.instance, "Missing instance name"
+    assert self.message or self.kind in [constants.CONS_SSH, constants.CONS_VNC]
+    assert self.host or self.kind == constants.CONS_MESSAGE
+    assert self.port or self.kind in [constants.CONS_MESSAGE,
+                                      constants.CONS_SSH]
+    assert self.user or self.kind in [constants.CONS_MESSAGE,
+                                      constants.CONS_VNC]
+    assert self.command or self.kind in [constants.CONS_MESSAGE,
+                                         constants.CONS_VNC]
+    assert self.display or self.kind in [constants.CONS_MESSAGE,
+                                         constants.CONS_SSH]
+    return True
+
+
 class SerializableConfigParser(ConfigParser.SafeConfigParser):
   """Simple wrapper over ConfigParse that allows serialization.
 
 class SerializableConfigParser(ConfigParser.SafeConfigParser):
   """Simple wrapper over ConfigParse that allows serialization.
 
@@ -1004,10 +1538,10 @@ class SerializableConfigParser(ConfigParser.SafeConfigParser):
     self.write(buf)
     return buf.getvalue()
 
     self.write(buf)
     return buf.getvalue()
 
-  @staticmethod
-  def Loads(data):
+  @classmethod
+  def Loads(cls, data):
     """Load data from a string."""
     buf = StringIO(data)
     """Load data from a string."""
     buf = StringIO(data)
-    cfp = SerializableConfigParser()
+    cfp = cls()
     cfp.readfp(buf)
     return cfp
     cfp.readfp(buf)
     return cfp