Rename IPOLICY_PARAMETERS to IPOLICY_ISPECS
[ganeti-local] / lib / objects.py
index 956c0ed..96c5440 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
 
@@ -54,6 +55,18 @@ __all__ = ["ConfigObject", "ConfigData", "NIC", "Disk", "Instance",
 _TIMESTAMPS = ["ctime", "mtime"]
 _UUID = ["uuid"]
 
+# constants used to create InstancePolicy dictionary
+TISPECS_GROUP_TYPES = {
+  constants.ISPECS_MIN: constants.VTYPE_INT,
+  constants.ISPECS_MAX: constants.VTYPE_INT,
+  }
+
+TISPECS_CLUSTER_TYPES = {
+  constants.ISPECS_MIN: constants.VTYPE_INT,
+  constants.ISPECS_MAX: constants.VTYPE_INT,
+  constants.ISPECS_STD: constants.VTYPE_INT,
+  }
+
 
 def FillDict(defaults_dict, custom_dict, skip_keys=None):
   """Basic function to apply settings on top a default dict.
@@ -79,6 +92,23 @@ def FillDict(defaults_dict, custom_dict, skip_keys=None):
   return ret_dict
 
 
+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 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]))
+
+  return ret_dict
+
+
 def UpgradeGroupedParams(target, defaults):
   """Update all groups for the target parameter.
 
@@ -136,6 +166,69 @@ def UpgradeDiskParams(diskparams):
   return result
 
 
+def MakeEmptyIPolicy():
+  """Create empty IPolicy dictionary.
+
+  """
+  return dict([
+    (constants.ISPECS_MIN, {}),
+    (constants.ISPECS_MAX, {}),
+    (constants.ISPECS_STD, {}),
+    ])
+
+
+def CreateIPolicyFromOpts(ispecs_mem_size=None,
+                          ispecs_cpu_count=None,
+                          ispecs_disk_count=None,
+                          ispecs_disk_size=None,
+                          ispecs_nic_count=None,
+                          ipolicy_disk_templates=None,
+                          group_ipolicy=False,
+                          allowed_values=None,
+                          fill_all=False):
+  """Creation of instance policy based on command line options.
+
+  @param fill_all: whether for cluster policies we should ensure that
+    all values are filled
+
+
+  """
+  # prepare ipolicy dict
+  ipolicy_transposed = {
+    constants.ISPEC_MEM_SIZE: ispecs_mem_size,
+    constants.ISPEC_CPU_COUNT: ispecs_cpu_count,
+    constants.ISPEC_DISK_COUNT: ispecs_disk_count,
+    constants.ISPEC_DISK_SIZE: ispecs_disk_size,
+    constants.ISPEC_NIC_COUNT: ispecs_nic_count,
+    }
+
+  # first, check that the values given are correct
+  if group_ipolicy:
+    forced_type = TISPECS_GROUP_TYPES
+  else:
+    forced_type = TISPECS_CLUSTER_TYPES
+
+  for specs in ipolicy_transposed.values():
+    utils.ForceDictType(specs, forced_type, allowed_values=allowed_values)
+
+  # then transpose
+  ipolicy_out = MakeEmptyIPolicy()
+  for name, specs in ipolicy_transposed.iteritems():
+    assert name in constants.ISPECS_PARAMETERS
+    for key, val in specs.items(): # {min: .. ,max: .., std: ..}
+      ipolicy_out[key][name] = val
+
+  # no filldict for lists
+  if not group_ipolicy and fill_all and ipolicy_disk_templates is None:
+    ipolicy_disk_templates = constants.DISK_TEMPLATES
+  if ipolicy_disk_templates is not None:
+    ipolicy_out[constants.IPOLICY_DTS] = list(ipolicy_disk_templates)
+
+  assert not (frozenset(ipolicy_out.keys()) - constants.IPOLICY_ALL_KEYS)
+
+  return ipolicy_out
+
+
 class ConfigObject(object):
   """A generic config object.
 
@@ -506,6 +599,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):
@@ -549,7 +644,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]]
@@ -624,7 +719,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:
@@ -634,6 +730,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.
 
@@ -780,6 +891,60 @@ class Disk(ConfigObject):
     # add here config upgrade for this disk
 
 
+class InstancePolicy(ConfigObject):
+  """Config object representing instance policy limits dictionary."""
+  __slots__ = ["min", "max", "std", "disk_templates"]
+
+  @classmethod
+  def CheckParameterSyntax(cls, ipolicy):
+    """ Check the instance policy for validity.
+
+    """
+    for param in constants.ISPECS_PARAMETERS:
+      InstancePolicy.CheckISpecSyntax(ipolicy, param)
+    if constants.IPOLICY_DTS in ipolicy:
+      InstancePolicy.CheckDiskTemplates(ipolicy[constants.IPOLICY_DTS])
+    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):
+    """Check the instance policy for validity on a given key.
+
+    We check if the instance policy makes sense for a given key, that is
+    if ipolicy[min][name] <= ipolicy[std][name] <= ipolicy[max][name].
+
+    @type ipolicy: dict
+    @param ipolicy: dictionary with min, max, std specs
+    @type name: string
+    @param name: what are the limits for
+    @raise errors.ConfigureError: when specs for given name are not valid
+
+    """
+    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.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))
+
+
 class Instance(TaggableObject):
   """Config object representing an instance."""
   __slots__ = [
@@ -1144,6 +1309,7 @@ class NodeGroup(TaggableObject):
     "members",
     "ndparams",
     "diskparams",
+    "ipolicy",
     "serial_no",
     "hv_state_static",
     "disk_state_static",
@@ -1185,12 +1351,14 @@ 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.ipolicy is None:
+      self.ipolicy = MakeEmptyIPolicy()
 
   def FillND(self, node):
     """Return filled out ndparams for L{objects.Node}
@@ -1238,6 +1406,7 @@ class Cluster(TaggableObject):
     "shared_file_storage_dir",
     "enabled_hypervisors",
     "hvparams",
+    "ipolicy",
     "os_hvp",
     "beparams",
     "osparams",
@@ -1254,6 +1423,8 @@ class Cluster(TaggableObject):
     "blacklisted_os",
     "primary_ip_family",
     "prealloc_wipe_disks",
+    "hv_state_static",
+    "disk_state_static",
     ] + _TIMESTAMPS + _UUID
 
   def UpgradeConfig(self):
@@ -1352,6 +1523,15 @@ class Cluster(TaggableObject):
 
     self.diskparams = UpgradeDiskParams(self.diskparams)
 
+    # instance policy added before 2.6
+    if self.ipolicy is None:
+      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):
     """The first hypervisor is the primary.
@@ -1497,6 +1677,20 @@ class Cluster(TaggableObject):
     # specified params
     return FillDict(result, os_params)
 
+  @staticmethod
+  def SimpleFillHvState(hv_state):
+    """Fill an hv_state sub dict with cluster defaults.
+
+    """
+    return FillDict(constants.HVST_DEFAULTS, hv_state)
+
+  @staticmethod
+  def SimpleFillDiskState(disk_state):
+    """Fill an disk_state sub dict with cluster defaults.
+
+    """
+    return FillDict(constants.DS_DEFAULTS, disk_state)
+
   def FillND(self, node, nodegroup):
     """Return filled out ndparams for L{objects.NodeGroup} and L{objects.Node}
 
@@ -1521,6 +1715,18 @@ class Cluster(TaggableObject):
     """
     return FillDict(self.ndparams, ndparams)
 
+  def SimpleFillIPolicy(self, ipolicy):
+    """ Fill instance policy dict with defaults.
+
+    @type ipolicy: dict
+    @param ipolicy: the dict to fill
+    @rtype: dict
+    @return: a copy of passed ipolicy with missing keys filled from
+      the cluster defaults
+
+    """
+    return FillIPolicy(self.ipolicy, ipolicy)
+
 
 class BlockDevStatus(ConfigObject):
   """Config object representing the status of a block device."""