Merge branch 'stable-2.6'
[ganeti-local] / lib / objects.py
index f0bd2bb..5ba4a1b 100644 (file)
@@ -1,7 +1,7 @@
 #
 #
 
-# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Google Inc.
+# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 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
@@ -44,6 +44,7 @@ from cStringIO import StringIO
 from ganeti import errors
 from ganeti import constants
 from ganeti import netutils
+from ganeti import utils
 
 from socket import AF_INET
 
@@ -79,18 +80,39 @@ def FillDict(defaults_dict, custom_dict, skip_keys=None):
   return ret_dict
 
 
-def FillDictOfDicts(defaults_dict, custom_dict, skip_keys=None):
-  """Run FillDict for each key in dictionary.
+def FillIPolicy(default_ipolicy, custom_ipolicy, skip_keys=None):
+  """Fills an instance policy with defaults.
 
   """
+  assert frozenset(default_ipolicy.keys()) == constants.IPOLICY_ALL_KEYS
   ret_dict = {}
-  for key in defaults_dict.keys():
-    ret_dict[key] = FillDict(defaults_dict[key],
-                             custom_dict.get(key, {}),
+  for key in constants.IPOLICY_ISPECS:
+    ret_dict[key] = FillDict(default_ipolicy[key],
+                             custom_ipolicy.get(key, {}),
                              skip_keys=skip_keys)
+  # list items
+  for key in [constants.IPOLICY_DTS]:
+    ret_dict[key] = list(custom_ipolicy.get(key, default_ipolicy[key]))
+  # other items which we know we can directly copy (immutables)
+  for key in constants.IPOLICY_PARAMETERS:
+    ret_dict[key] = custom_ipolicy.get(key, default_ipolicy[key])
+
   return ret_dict
 
 
+def FillDiskParams(default_dparams, custom_dparams, skip_keys=None):
+  """Fills the disk parameter defaults.
+
+  @see: L{FillDict} for parameters and return value
+
+  """
+  assert frozenset(default_dparams.keys()) == constants.DISK_TEMPLATES
+
+  return dict((dt, FillDict(default_dparams[dt], custom_dparams.get(dt, {}),
+                             skip_keys=skip_keys))
+              for dt in constants.DISK_TEMPLATES)
+
+
 def UpgradeGroupedParams(target, defaults):
   """Update all groups for the target parameter.
 
@@ -128,34 +150,40 @@ def UpgradeDiskParams(diskparams):
   @type diskparams: dict
   @param diskparams: disk parameters to upgrade
   @rtype: dict
-  @return: the upgraded disk parameters dit
+  @return: the upgraded disk parameters dict
 
   """
-  result = dict()
-  if diskparams is None:
-    result = constants.DISK_DT_DEFAULTS.copy()
+  if not diskparams:
+    result = {}
   else:
-    # Update the disk parameter values for each disk template.
-    # The code iterates over constants.DISK_TEMPLATES because new templates
-    # might have been added.
-    for template in constants.DISK_TEMPLATES:
-      if template not in diskparams:
-        result[template] = constants.DISK_DT_DEFAULTS[template].copy()
-      else:
-        result[template] = FillDict(constants.DISK_DT_DEFAULTS[template],
-                                    diskparams[template])
+    result = FillDiskParams(constants.DISK_DT_DEFAULTS, diskparams)
 
   return result
 
 
+def UpgradeNDParams(ndparams):
+  """Upgrade ndparams structure.
+
+  @type ndparams: dict
+  @param ndparams: disk parameters to upgrade
+  @rtype: dict
+  @return: the upgraded node parameters dict
+
+  """
+  if ndparams is None:
+    ndparams = {}
+
+  return FillDict(constants.NDC_DEFAULTS, ndparams)
+
+
 def MakeEmptyIPolicy():
   """Create empty IPolicy dictionary.
 
   """
   return dict([
-    (constants.MIN_ISPECS, dict()),
-    (constants.MAX_ISPECS, dict()),
-    (constants.STD_ISPECS, dict()),
+    (constants.ISPECS_MIN, {}),
+    (constants.ISPECS_MAX, {}),
+    (constants.ISPECS_STD, {}),
     ])
 
 
@@ -200,6 +228,9 @@ class ConfigObject(object):
       slots.extend(getattr(parent, "__slots__", []))
     return slots
 
+  #: Public getter for the defined slots
+  GetAllSlots = _all_slots
+
   def ToDict(self):
     """Convert to a dict holding only standard python types.
 
@@ -529,6 +560,8 @@ class Disk(ConfigObject):
       return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
     elif self.dev_type == constants.LD_BLOCKDEV:
       return self.logical_id[1]
+    elif self.dev_type == constants.LD_RBD:
+      return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
     return None
 
   def ChildrenNeeded(self):
@@ -572,7 +605,7 @@ class Disk(ConfigObject):
 
     """
     if self.dev_type in [constants.LD_LV, constants.LD_FILE,
-                         constants.LD_BLOCKDEV]:
+                         constants.LD_BLOCKDEV, constants.LD_RBD]:
       result = [node]
     elif self.dev_type in constants.LDS_DRBD:
       result = [self.logical_id[0], self.logical_id[1]]
@@ -647,7 +680,8 @@ class Disk(ConfigObject):
     actual algorithms from bdev.
 
     """
-    if self.dev_type in (constants.LD_LV, constants.LD_FILE):
+    if self.dev_type in (constants.LD_LV, constants.LD_FILE,
+                         constants.LD_RBD):
       self.size += amount
     elif self.dev_type == constants.LD_DRBD8:
       if self.children:
@@ -657,6 +691,21 @@ class Disk(ConfigObject):
       raise errors.ProgrammerError("Disk.RecordGrow called for unsupported"
                                    " disk type %s" % self.dev_type)
 
+  def Update(self, size=None, mode=None):
+    """Apply changes to size and mode.
+
+    """
+    if self.dev_type == constants.LD_DRBD8:
+      if self.children:
+        self.children[0].Update(size=size, mode=mode)
+    else:
+      assert not self.children
+
+    if size is not None:
+      self.size = size
+    if mode is not None:
+      self.mode = mode
+
   def UnsetSize(self):
     """Sets recursively the size to zero for the disk and its children.
 
@@ -802,11 +851,103 @@ class Disk(ConfigObject):
                              self.params)
     # add here config upgrade for this disk
 
+  @staticmethod
+  def ComputeLDParams(disk_template, disk_params):
+    """Computes Logical Disk parameters from Disk Template parameters.
+
+    @type disk_template: string
+    @param disk_template: disk template, one of L{constants.DISK_TEMPLATES}
+    @type disk_params: dict
+    @param disk_params: disk template parameters;
+                        dict(template_name -> parameters
+    @rtype: list(dict)
+    @return: a list of dicts, one for each node of the disk hierarchy. Each dict
+      contains the LD parameters of the node. The tree is flattened in-order.
+
+    """
+    if disk_template not in constants.DISK_TEMPLATES:
+      raise errors.ProgrammerError("Unknown disk template %s" % disk_template)
+
+    assert disk_template in disk_params
+
+    result = list()
+    dt_params = disk_params[disk_template]
+    if disk_template == constants.DT_DRBD8:
+      drbd_params = {
+        constants.LDP_RESYNC_RATE: dt_params[constants.DRBD_RESYNC_RATE],
+        constants.LDP_BARRIERS: dt_params[constants.DRBD_DISK_BARRIERS],
+        constants.LDP_NO_META_FLUSH: dt_params[constants.DRBD_META_BARRIERS],
+        constants.LDP_DEFAULT_METAVG: dt_params[constants.DRBD_DEFAULT_METAVG],
+        constants.LDP_DISK_CUSTOM: dt_params[constants.DRBD_DISK_CUSTOM],
+        constants.LDP_NET_CUSTOM: dt_params[constants.DRBD_NET_CUSTOM],
+        constants.LDP_DYNAMIC_RESYNC: dt_params[constants.DRBD_DYNAMIC_RESYNC],
+        constants.LDP_PLAN_AHEAD: dt_params[constants.DRBD_PLAN_AHEAD],
+        constants.LDP_FILL_TARGET: dt_params[constants.DRBD_FILL_TARGET],
+        constants.LDP_DELAY_TARGET: dt_params[constants.DRBD_DELAY_TARGET],
+        constants.LDP_MAX_RATE: dt_params[constants.DRBD_MAX_RATE],
+        constants.LDP_MIN_RATE: dt_params[constants.DRBD_MIN_RATE],
+        }
+
+      drbd_params = \
+        FillDict(constants.DISK_LD_DEFAULTS[constants.LD_DRBD8],
+                 drbd_params)
+
+      result.append(drbd_params)
+
+      # data LV
+      data_params = {
+        constants.LDP_STRIPES: dt_params[constants.DRBD_DATA_STRIPES],
+        }
+      data_params = \
+        FillDict(constants.DISK_LD_DEFAULTS[constants.LD_LV],
+                 data_params)
+      result.append(data_params)
+
+      # metadata LV
+      meta_params = {
+        constants.LDP_STRIPES: dt_params[constants.DRBD_META_STRIPES],
+        }
+      meta_params = \
+        FillDict(constants.DISK_LD_DEFAULTS[constants.LD_LV],
+                 meta_params)
+      result.append(meta_params)
+
+    elif (disk_template == constants.DT_FILE or
+          disk_template == constants.DT_SHARED_FILE):
+      result.append(constants.DISK_LD_DEFAULTS[constants.LD_FILE])
+
+    elif disk_template == constants.DT_PLAIN:
+      params = {
+        constants.LDP_STRIPES: dt_params[constants.LV_STRIPES],
+        }
+      params = \
+        FillDict(constants.DISK_LD_DEFAULTS[constants.LD_LV],
+                 params)
+      result.append(params)
+
+    elif disk_template == constants.DT_BLOCK:
+      result.append(constants.DISK_LD_DEFAULTS[constants.LD_BLOCKDEV])
+
+    elif disk_template == constants.DT_RBD:
+      params = {
+        constants.LDP_POOL: dt_params[constants.RBD_POOL]
+        }
+      params = \
+        FillDict(constants.DISK_LD_DEFAULTS[constants.LD_RBD],
+                 params)
+      result.append(params)
+
+    return result
+
 
 class InstancePolicy(ConfigObject):
-  """Config object representing instance policy limits dictionary."""
-  __slots__ = ["min", "max", "std"]
+  """Config object representing instance policy limits dictionary.
+
+
+  Note that this object is not actually used in the config, it's just
+  used as a placeholder for a few functions.
 
+  """
   @classmethod
   def CheckParameterSyntax(cls, ipolicy):
     """ Check the instance policy for validity.
@@ -814,6 +955,15 @@ class InstancePolicy(ConfigObject):
     """
     for param in constants.ISPECS_PARAMETERS:
       InstancePolicy.CheckISpecSyntax(ipolicy, param)
+    if constants.IPOLICY_DTS in ipolicy:
+      InstancePolicy.CheckDiskTemplates(ipolicy[constants.IPOLICY_DTS])
+    for key in constants.IPOLICY_PARAMETERS:
+      if key in ipolicy:
+        InstancePolicy.CheckParameter(key, ipolicy[key])
+    wrong_keys = frozenset(ipolicy.keys()) - constants.IPOLICY_ALL_KEYS
+    if wrong_keys:
+      raise errors.ConfigurationError("Invalid keys in ipolicy: %s" %
+                                      utils.CommaJoin(wrong_keys))
 
   @classmethod
   def CheckISpecSyntax(cls, ipolicy, name):
@@ -829,17 +979,40 @@ class InstancePolicy(ConfigObject):
     @raise errors.ConfigureError: when specs for given name are not valid
 
     """
-    min_v = ipolicy[constants.MIN_ISPECS].get(name, 0)
-    std_v = ipolicy[constants.STD_ISPECS].get(name, min_v)
-    max_v = ipolicy[constants.MAX_ISPECS].get(name, std_v)
+    min_v = ipolicy[constants.ISPECS_MIN].get(name, 0)
+    std_v = ipolicy[constants.ISPECS_STD].get(name, min_v)
+    max_v = ipolicy[constants.ISPECS_MAX].get(name, std_v)
     err = ("Invalid specification of min/max/std values for %s: %s/%s/%s" %
            (name,
-            ipolicy[constants.MIN_ISPECS].get(name, "-"),
-            ipolicy[constants.MAX_ISPECS].get(name, "-"),
-            ipolicy[constants.STD_ISPECS].get(name, "-")))
+            ipolicy[constants.ISPECS_MIN].get(name, "-"),
+            ipolicy[constants.ISPECS_MAX].get(name, "-"),
+            ipolicy[constants.ISPECS_STD].get(name, "-")))
     if min_v > std_v or std_v > max_v:
       raise errors.ConfigurationError(err)
 
+  @classmethod
+  def CheckDiskTemplates(cls, disk_templates):
+    """Checks the disk templates for validity.
+
+    """
+    wrong = frozenset(disk_templates).difference(constants.DISK_TEMPLATES)
+    if wrong:
+      raise errors.ConfigurationError("Invalid disk template(s) %s" %
+                                      utils.CommaJoin(wrong))
+
+  @classmethod
+  def CheckParameter(cls, key, value):
+    """Checks a parameter.
+
+    Currently we expect all parameters to be float values.
+
+    """
+    try:
+      float(value)
+    except (TypeError, ValueError), err:
+      raise errors.ConfigurationError("Invalid value for key" " '%s':"
+                                      " '%s', error: %s" % (key, value, err))
+
 
 class Instance(TaggableObject):
   """Config object representing an instance."""
@@ -1205,6 +1378,7 @@ class NodeGroup(TaggableObject):
     "members",
     "ndparams",
     "diskparams",
+    "ipolicy",
     "serial_no",
     "hv_state_static",
     "disk_state_static",
@@ -1246,12 +1420,15 @@ class NodeGroup(TaggableObject):
     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.
+    # 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()
 
-    self.diskparams = UpgradeDiskParams(self.diskparams)
+    if self.diskparams is None:
+      self.diskparams = {}
+    if self.ipolicy is None:
+      self.ipolicy = MakeEmptyIPolicy()
 
   def FillND(self, node):
     """Return filled out ndparams for L{objects.Node}
@@ -1340,8 +1517,7 @@ class Cluster(TaggableObject):
     if self.osparams is None:
       self.osparams = {}
 
-    if self.ndparams is None:
-      self.ndparams = constants.NDC_DEFAULTS
+    self.ndparams = UpgradeNDParams(self.ndparams)
 
     self.beparams = UpgradeGroupedParams(self.beparams,
                                          constants.BEC_DEFAULTS)
@@ -1414,11 +1590,19 @@ class Cluster(TaggableObject):
     if self.use_external_mip_script is None:
       self.use_external_mip_script = False
 
-    self.diskparams = UpgradeDiskParams(self.diskparams)
+    if self.diskparams:
+      self.diskparams = UpgradeDiskParams(self.diskparams)
+    else:
+      self.diskparams = constants.DISK_DT_DEFAULTS.copy()
 
     # instance policy added before 2.6
     if self.ipolicy is None:
-      self.ipolicy = MakeEmptyIPolicy()
+      self.ipolicy = FillIPolicy(constants.IPOLICY_DEFAULTS, {})
+    else:
+      # we can either make sure to upgrade the ipolicy always, or only
+      # do it in some corner cases (e.g. missing keys); note that this
+      # will break any removal of keys from the ipolicy dict
+      self.ipolicy = FillIPolicy(constants.IPOLICY_DEFAULTS, self.ipolicy)
 
   @property
   def primary_hypervisor(self):
@@ -1447,6 +1631,15 @@ class Cluster(TaggableObject):
       obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
     return obj
 
+  def SimpleFillDP(self, diskparams):
+    """Fill a given diskparams dict with cluster defaults.
+
+    @param diskparams: The diskparams
+    @return: The defaults dict
+
+    """
+    return FillDiskParams(self.diskparams, diskparams)
+
   def GetHVDefaults(self, hypervisor, os_name=None, skip_keys=None):
     """Get the default hypervisor parameters for the cluster.
 
@@ -1613,7 +1806,7 @@ class Cluster(TaggableObject):
       the cluster defaults
 
     """
-    return FillDictOfDicts(self.ipolicy, ipolicy)
+    return FillIPolicy(self.ipolicy, ipolicy)
 
 
 class BlockDevStatus(ConfigObject):
@@ -1739,17 +1932,6 @@ class _QueryResponseBase(ConfigObject):
     return obj
 
 
-class QueryRequest(ConfigObject):
-  """Object holding a query request.
-
-  """
-  __slots__ = [
-    "what",
-    "fields",
-    "qfilter",
-    ]
-
-
 class QueryResponse(_QueryResponseBase):
   """Object holding the response to a query.