beparams: add min/max memory values
[ganeti-local] / lib / objects.py
index 5ee6ddf..c23068e 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
@@ -26,28 +26,35 @@ pass to and from external parties.
 
 """
 
-# pylint: disable-msg=E0203,W0201
+# pylint: disable=E0203,W0201,R0902
 
 # E0203: Access to member %r before its definition, since we use
 # objects.py which doesn't explicitely initialise its members
 
 # W0201: Attribute '%s' defined outside __init__
 
+# R0902: Allow instances of these objects to have more than 20 attributes
+
 import ConfigParser
 import re
 import copy
+import time
 from cStringIO import StringIO
 
 from ganeti import errors
 from ganeti import constants
+from ganeti import netutils
+
+from socket import AF_INET
 
 
 __all__ = ["ConfigObject", "ConfigData", "NIC", "Disk", "Instance",
-           "OS", "Node", "Cluster", "FillDict"]
+           "OS", "Node", "NodeGroup", "Cluster", "FillDict"]
 
 _TIMESTAMPS = ["ctime", "mtime"]
 _UUID = ["uuid"]
 
+
 def FillDict(defaults_dict, custom_dict, skip_keys=None):
   """Basic function to apply settings on top a default dict.
 
@@ -89,6 +96,21 @@ def UpgradeGroupedParams(target, defaults):
   return target
 
 
+def UpgradeBeParams(target):
+  """Update the be parameters dict to the new format.
+
+  @type target: dict
+  @param target: "be" parameters dict
+
+  """
+  if constants.BE_MEMORY in target:
+    memory = target[constants.BE_MEMORY]
+    target[constants.BE_MAXMEM] = memory
+    target[constants.BE_MINMEM] = memory
+    #FIXME(dynmem): delete old value
+    #del target[constants.BE_MEMORY]
+
+
 class ConfigObject(object):
   """A generic config object.
 
@@ -166,7 +188,7 @@ class ConfigObject(object):
       raise errors.ConfigurationError("Invalid object passed to FromDict:"
                                       " expected dict, got %s" % type(val))
     val_str = dict([(str(k), v) for k, v in val.iteritems()])
-    obj = cls(**val_str) # pylint: disable-msg=W0142
+    obj = cls(**val_str) # pylint: disable=W0142
     return obj
 
   @staticmethod
@@ -199,6 +221,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 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):
@@ -309,10 +333,35 @@ class TaggableObject(ConfigObject):
     return obj
 
 
+class MasterNetworkParameters(ConfigObject):
+  """Network configuration parameters for the master
+
+  @ivar name: master name
+  @ivar ip: master IP
+  @ivar netmask: master netmask
+  @ivar netdev: master network device
+  @ivar ip_family: master IP family
+
+  """
+  __slots__ = [
+    "name",
+    "ip",
+    "netmask",
+    "netdev",
+    "ip_family"
+    ]
+
+
 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.
@@ -323,7 +372,7 @@ class ConfigData(ConfigObject):
     """
     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
@@ -337,8 +386,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.nodegroups = cls._ContainerFromDicts(obj.nodegroups, dict, NodeGroup)
     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.
 
@@ -348,11 +413,21 @@ class ConfigData(ConfigObject):
       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."""
-  __slots__ = ["mac", "ip", "bridge", "nicparams"]
+  __slots__ = ["mac", "ip", "nicparams"]
 
   @classmethod
   def CheckParameterSyntax(cls, nicparams):
@@ -363,7 +438,8 @@ class NIC(ConfigObject):
     @raise errors.ConfigurationError: when a parameter is not valid
 
     """
-    if nicparams[constants.NIC_MODE] not in constants.NIC_VALID_MODES:
+    if (nicparams[constants.NIC_MODE] not in constants.NIC_VALID_MODES and
+        nicparams[constants.NIC_MODE] != constants.VALUE_AUTO):
       err = "Invalid nic mode: %s" % nicparams[constants.NIC_MODE]
       raise errors.ConfigurationError(err)
 
@@ -372,20 +448,6 @@ class NIC(ConfigObject):
       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."""
@@ -417,6 +479,8 @@ class Disk(ConfigObject):
     """
     if self.dev_type == constants.LD_LV:
       return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
+    elif self.dev_type == constants.LD_BLOCKDEV:
+      return self.logical_id[1]
     return None
 
   def ChildrenNeeded(self):
@@ -435,6 +499,21 @@ class Disk(ConfigObject):
       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.
 
@@ -444,7 +523,8 @@ class Disk(ConfigObject):
     devices needs to (or can) be assembled.
 
     """
-    if self.dev_type in [constants.LD_LV, constants.LD_FILE]:
+    if self.dev_type in [constants.LD_LV, constants.LD_FILE,
+                         constants.LD_BLOCKDEV]:
       result = [node]
     elif self.dev_type in constants.LDS_DRBD:
       result = [self.logical_id[0], self.logical_id[1]]
@@ -489,6 +569,28 @@ class Disk(ConfigObject):
             # 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.
 
@@ -497,7 +599,7 @@ class Disk(ConfigObject):
     actual algorithms from bdev.
 
     """
-    if self.dev_type == constants.LD_LV or self.dev_type == constants.LD_FILE:
+    if self.dev_type in (constants.LD_LV, constants.LD_FILE):
       self.size += amount
     elif self.dev_type == constants.LD_DRBD8:
       if self.children:
@@ -598,7 +700,7 @@ class Disk(ConfigObject):
 
     """
     if self.dev_type == constants.LD_LV:
-      val =  "<LogicalVolume(/dev/%s/%s" % self.logical_id
+      val = "<LogicalVolume(/dev/%s/%s" % self.logical_id
     elif self.dev_type in constants.LDS_DRBD:
       node_a, node_b, port, minor_a, minor_b = self.logical_id[:5]
       val = "<DRBD8("
@@ -656,7 +758,8 @@ class Instance(TaggableObject):
     "hypervisor",
     "hvparams",
     "beparams",
-    "admin_up",
+    "osparams",
+    "admin_state",
     "nics",
     "disks",
     "disk_template",
@@ -714,15 +817,19 @@ class Instance(TaggableObject):
     @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:
       node = self.primary_node
 
     if lvmap is None:
-      lvmap = { node : [] }
+      lvmap = {
+        node: [],
+        }
       ret = lvmap
     else:
       if not node in lvmap:
@@ -734,7 +841,7 @@ class Instance(TaggableObject):
 
     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:
@@ -766,7 +873,7 @@ class Instance(TaggableObject):
                                  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):
@@ -792,6 +899,13 @@ class Instance(TaggableObject):
     """Custom function for instances.
 
     """
+    if "admin_state" not in val:
+      if val.get("admin_up", False):
+        val["admin_state"] = constants.ADMINST_UP
+      else:
+        val["admin_state"] = constants.ADMINST_DOWN
+    if "admin_up" in val:
+      del val["admin_up"]
     obj = super(Instance, cls).FromDict(val)
     obj.nics = cls._ContainerFromDicts(obj.nics, list, NIC)
     obj.disks = cls._ContainerFromDicts(obj.disks, list, Disk)
@@ -811,10 +925,22 @@ class Instance(TaggableObject):
           del self.hvparams[key]
         except KeyError:
           pass
+    if self.osparams is None:
+      self.osparams = {}
+    UpgradeBeParams(self.beparams)
 
 
 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",
@@ -823,9 +949,46 @@ class OS(ConfigObject):
     "export_script",
     "import_script",
     "rename_script",
+    "verify_script",
     "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."""
@@ -837,8 +1000,106 @@ class Node(TaggableObject):
     "master_candidate",
     "offline",
     "drained",
+    "group",
+    "master_capable",
+    "vm_capable",
+    "ndparams",
+    "powered",
+    "hv_state",
+    "disk_state",
+    ] + _TIMESTAMPS + _UUID
+
+  def UpgradeConfig(self):
+    """Fill defaults for missing configuration values.
+
+    """
+    # pylint: disable=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(TaggableObject):
+  """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{objects.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."""
@@ -849,28 +1110,42 @@ class Cluster(TaggableObject):
     "tcpudp_port_pool",
     "mac_prefix",
     "volume_group_name",
+    "reserved_lvs",
+    "drbd_usermode_helper",
     "default_bridge",
     "default_hypervisor",
     "master_node",
     "master_ip",
     "master_netdev",
+    "master_netmask",
+    "use_external_mip_script",
     "cluster_name",
     "file_storage_dir",
+    "shared_file_storage_dir",
     "enabled_hypervisors",
     "hvparams",
     "os_hvp",
     "beparams",
+    "osparams",
     "nicparams",
+    "ndparams",
     "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):
     """Fill defaults for missing configuration values.
 
     """
-    # pylint: disable-msg=E0203
+    # pylint: disable=E0203
     # because these are "defined" via slots, not manually
     if self.hvparams is None:
       self.hvparams = constants.HVC_DEFAULTS
@@ -879,12 +1154,21 @@ class Cluster(TaggableObject):
         self.hvparams[hypervisor] = FillDict(
             constants.HVC_DEFAULTS[hypervisor], self.hvparams[hypervisor])
 
-    # TODO: Figure out if it's better to put this into OS than Cluster
     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)
+    for beparams_group in self.beparams:
+      UpgradeBeParams(self.beparams[beparams_group])
+
     migrate_default_bridge = not self.nicparams
     self.nicparams = UpgradeGroupedParams(self.nicparams,
                                           constants.NICC_DEFAULTS)
@@ -898,18 +1182,59 @@ class Cluster(TaggableObject):
     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
 
-    # 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
 
+    # 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.master_netmask is None:
+      ipcls = netutils.IPAddress.GetClassFromIpFamily(self.primary_ip_family)
+      self.master_netmask = ipcls.iplen
+
+    if self.prealloc_wipe_disks is None:
+      self.prealloc_wipe_disks = False
+
+    # shared_file_storage_dir added before 2.5
+    if self.shared_file_storage_dir is None:
+      self.shared_file_storage_dir = ""
+
+    if self.use_external_mip_script is None:
+      self.use_external_mip_script = False
+
   def ToDict(self):
     """Custom function for cluster.
 
@@ -928,16 +1253,41 @@ class Cluster(TaggableObject):
       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
-    @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
 
     """
@@ -946,21 +1296,39 @@ class Cluster(TaggableObject):
     else:
       skip_keys = []
 
-    # We fill the list from least to most important override
-    fill_stack = [
-      self.hvparams.get(instance.hypervisor, {}),
-      self.os_hvp.get(instance.os, {}).get(instance.hypervisor, {}),
-      instance.hvparams,
-      ]
+    def_dict = self.GetHVDefaults(hv_name, os_name, skip_keys=skip_keys)
+    return FillDict(def_dict, hvparams, skip_keys=skip_keys)
 
-    ret_dict = {}
-    for o_dict in fill_stack:
-      ret_dict = FillDict(ret_dict, o_dict, skip_keys=skip_keys)
+  def FillHV(self, instance, skip_globals=False):
+    """Fill an instance's hvparams dict with cluster defaults.
 
-    return ret_dict
+    @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):
-    """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
@@ -969,8 +1337,63 @@ class Cluster(TaggableObject):
         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{objects.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):
@@ -986,6 +1409,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.
 
@@ -1020,6 +1479,139 @@ 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
+  @ivar doc: Human-readable description
+
+  """
+  __slots__ = [
+    "name",
+    "title",
+    "kind",
+    "doc",
+    ]
+
+
+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",
+    "qfilter",
+    ]
+
+
+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 MigrationStatus(ConfigObject):
+  """Object holding the status of a migration.
+
+  """
+  __slots__ = [
+    "status",
+    "transferred_ram",
+    "total_ram",
+    ]
+
+
+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_SPICE,
+                                         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_SPICE,
+                                      constants.CONS_VNC]
+    assert self.command or self.kind in [constants.CONS_MESSAGE,
+                                         constants.CONS_SPICE,
+                                         constants.CONS_VNC]
+    assert self.display or self.kind in [constants.CONS_MESSAGE,
+                                         constants.CONS_SPICE,
+                                         constants.CONS_SSH]
+    return True
+
+
 class SerializableConfigParser(ConfigParser.SafeConfigParser):
   """Simple wrapper over ConfigParse that allows serialization.