From 918eb80bea02013b7a474e3ef0e9d5fb4a3ac8ea Mon Sep 17 00:00:00 2001
From: Agata Murawska <agatamurawska@google.com>
Date: Tue, 6 Dec 2011 16:07:50 +0100
Subject: [PATCH] Introduce instance policy on cluster level

Signed-off-by: Agata Murawska <agatamurawska@google.com>
Reviewed-by: Iustin Pop <iustin@google.com>
---
 lib/config.py    | 13 ++++++++
 lib/constants.py | 51 +++++++++++++++++++++++++++++++
 lib/objects.py   | 78 ++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 142 insertions(+)

diff --git a/lib/config.py b/lib/config.py
index c7c3cc6e7..13e00ad11 100644
--- a/lib/config.py
+++ b/lib/config.py
@@ -426,6 +426,17 @@ class ConfigWriter:
       except errors.ConfigurationError, err:
         result.append("%s has invalid nicparams: %s" % (owner, err))
 
+    def _helper_ipolicy(owner, params):
+      try:
+        objects.InstancePolicy.CheckParameterSyntax(params)
+      except errors.ConfigurationError, err:
+        result.append("%s has invalid instance policy: %s" % (owner, err))
+
+    def _helper_ispecs(owner, params):
+      for key, value in params.iteritems():
+        fullkey = "ipolicy/" + key
+        _helper(owner, fullkey, value, constants.ISPECS_PARAMETER_TYPES)
+
     # check cluster parameters
     _helper("cluster", "beparams", cluster.SimpleFillBE({}),
             constants.BES_PARAMETER_TYPES)
@@ -434,6 +445,8 @@ class ConfigWriter:
     _helper_nic("cluster", cluster.SimpleFillNIC({}))
     _helper("cluster", "ndparams", cluster.SimpleFillND({}),
             constants.NDS_PARAMETER_TYPES)
+    _helper_ipolicy("cluster", cluster.SimpleFillIPolicy({}))
+    _helper_ispecs("cluster", cluster.SimpleFillIPolicy({}))
 
     # per-instance checks
     for instance_name in data.instances:
diff --git a/lib/constants.py b/lib/constants.py
index d27fefe97..27a993cfe 100644
--- a/lib/constants.py
+++ b/lib/constants.py
@@ -921,6 +921,33 @@ BES_PARAMETER_COMPAT.update(BES_PARAMETER_TYPES)
 
 BES_PARAMETERS = frozenset(BES_PARAMETER_TYPES.keys())
 
+# instance specs
+MEM_SIZE_SPEC = "memory-size"
+CPU_COUNT_SPEC = "cpu-count"
+DISK_COUNT_SPEC = "disk-count"
+DISK_SIZE_SPEC = "disk-size"
+NIC_COUNT_SPEC = "nic-count"
+
+ISPECS_PARAMETER_TYPES = {
+  MEM_SIZE_SPEC: VTYPE_INT,
+  CPU_COUNT_SPEC: VTYPE_INT,
+  DISK_COUNT_SPEC: VTYPE_INT,
+  DISK_SIZE_SPEC: VTYPE_INT,
+  NIC_COUNT_SPEC: VTYPE_INT,
+  }
+
+ISPECS_PARAMETERS = frozenset(ISPECS_PARAMETER_TYPES.keys())
+
+MIN_ISPECS = "min"
+MAX_ISPECS = "max"
+STD_ISPECS = "std"
+
+IPOLICY_PARAMETERS = frozenset([
+  MIN_ISPECS,
+  MAX_ISPECS,
+  STD_ISPECS
+  ])
+
 # Node parameter names
 ND_OOB_PROGRAM = "oob_program"
 
@@ -1783,6 +1810,30 @@ NICC_DEFAULTS = {
   NIC_LINK: DEFAULT_BRIDGE,
   }
 
+IPOLICY_DEFAULTS = {
+  MIN_ISPECS: {
+    MEM_SIZE_SPEC: 128,
+    CPU_COUNT_SPEC: 1,
+    DISK_COUNT_SPEC: 1,
+    DISK_SIZE_SPEC: 1024,
+    NIC_COUNT_SPEC: 1,
+    },
+  MAX_ISPECS: {
+    MEM_SIZE_SPEC: 128,
+    CPU_COUNT_SPEC: 1,
+    DISK_COUNT_SPEC: 1,
+    DISK_SIZE_SPEC: 1024,
+    NIC_COUNT_SPEC: 1,
+    },
+  STD_ISPECS: {
+    MEM_SIZE_SPEC: 128,
+    CPU_COUNT_SPEC: 1,
+    DISK_COUNT_SPEC: 1,
+    DISK_SIZE_SPEC: 1024,
+    NIC_COUNT_SPEC: 1,
+    }
+  }
+
 MASTER_POOL_SIZE_DEFAULT = 10
 
 CONFD_PROTOCOL_VERSION = 1
diff --git a/lib/objects.py b/lib/objects.py
index 7cedc12d2..f0bd2bb13 100644
--- a/lib/objects.py
+++ b/lib/objects.py
@@ -79,6 +79,18 @@ 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.
+
+  """
+  ret_dict = {}
+  for key in defaults_dict.keys():
+    ret_dict[key] = FillDict(defaults_dict[key],
+                             custom_dict.get(key, {}),
+                             skip_keys=skip_keys)
+  return ret_dict
+
+
 def UpgradeGroupedParams(target, defaults):
   """Update all groups for the target parameter.
 
@@ -136,6 +148,17 @@ def UpgradeDiskParams(diskparams):
   return result
 
 
+def MakeEmptyIPolicy():
+  """Create empty IPolicy dictionary.
+
+  """
+  return dict([
+    (constants.MIN_ISPECS, dict()),
+    (constants.MAX_ISPECS, dict()),
+    (constants.STD_ISPECS, dict()),
+    ])
+
+
 class ConfigObject(object):
   """A generic config object.
 
@@ -780,6 +803,44 @@ class Disk(ConfigObject):
     # add here config upgrade for this disk
 
 
+class InstancePolicy(ConfigObject):
+  """Config object representing instance policy limits dictionary."""
+  __slots__ = ["min", "max", "std"]
+
+  @classmethod
+  def CheckParameterSyntax(cls, ipolicy):
+    """ Check the instance policy for validity.
+
+    """
+    for param in constants.ISPECS_PARAMETERS:
+      InstancePolicy.CheckISpecSyntax(ipolicy, param)
+
+  @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.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)
+    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, "-")))
+    if min_v > std_v or std_v > max_v:
+      raise errors.ConfigurationError(err)
+
+
 class Instance(TaggableObject):
   """Config object representing an instance."""
   __slots__ = [
@@ -1238,6 +1299,7 @@ class Cluster(TaggableObject):
     "shared_file_storage_dir",
     "enabled_hypervisors",
     "hvparams",
+    "ipolicy",
     "os_hvp",
     "beparams",
     "osparams",
@@ -1354,6 +1416,10 @@ class Cluster(TaggableObject):
 
     self.diskparams = UpgradeDiskParams(self.diskparams)
 
+    # instance policy added before 2.6
+    if self.ipolicy is None:
+      self.ipolicy = MakeEmptyIPolicy()
+
   @property
   def primary_hypervisor(self):
     """The first hypervisor is the primary.
@@ -1537,6 +1603,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 FillDictOfDicts(self.ipolicy, ipolicy)
+
 
 class BlockDevStatus(ConfigObject):
   """Config object representing the status of a block device."""
-- 
GitLab