This can be set at group creation time and via OpSetGroupParams. The default
is "preferred", and existing node groups from previous Ganeti version will
get the attribute set to this value.

Signed-off-by: Adeodato Simo <[email protected]>
---
 doc/hooks.rst           |   10 ++++++++++
 lib/cli.py              |    5 +++++
 lib/client/gnt_group.py |   15 +++++++++------
 lib/cmdlib.py           |   27 ++++++++++++++++++++++++---
 lib/config.py           |    4 ++++
 lib/constants.py        |   10 ++++++++++
 lib/objects.py          |    4 ++++
 lib/opcodes.py          |    2 ++
 lib/rapi/client.py      |    5 ++++-
 lib/rapi/rlib2.py       |   19 +++++++++++++++++--
 man/gnt-group.rst       |   27 +++++++++++++++++++++++++--
 qa/qa_rapi.py           |    1 +
 12 files changed, 115 insertions(+), 14 deletions(-)

diff --git a/doc/hooks.rst b/doc/hooks.rst
index 282ed93..5d8bbc2 100644
--- a/doc/hooks.rst
+++ b/doc/hooks.rst
@@ -181,6 +181,16 @@ Adds a node group to the cluster.
 :pre-execution: master node
 :post-execution: master node
 
+OP_GROUP_SET_PARAMS
++++++++++++++++++++
+
+Changes a node group's parameters.
+
+:directory: group-modify
+:env. vars: GROUP_NAME, NEW_ALLOC_POLICY
+:pre-execution: master node
+:post-execution: master node
+
 OP_REMOVE_GROUP
 +++++++++++++++
 
diff --git a/lib/cli.py b/lib/cli.py
index 069c6e9..e5e31a3 100644
--- a/lib/cli.py
+++ b/lib/cli.py
@@ -48,6 +48,7 @@ __all__ = [
   # Command line options
   "ADD_UIDS_OPT",
   "ALLOCATABLE_OPT",
+  "ALLOC_POLICY_OPT",
   "ALL_OPT",
   "AUTO_PROMOTE_OPT",
   "AUTO_REPLACE_OPT",
@@ -1117,6 +1118,10 @@ NODE_PARAMS_OPT = cli_option("--node-parameters", 
dest="ndparams",
                              type="keyval", default=None,
                              help="Node parameters")
 
+ALLOC_POLICY_OPT = cli_option("--alloc-policy", dest="alloc_policy",
+                              action="store", metavar="POLICY", default=None,
+                              help="Allocation policy for the node group")
+
 
 #: Options provided by all commands
 COMMON_OPTS = [DEBUG_OPT]
diff --git a/lib/client/gnt_group.py b/lib/client/gnt_group.py
index b427647..d3b477b 100644
--- a/lib/client/gnt_group.py
+++ b/lib/client/gnt_group.py
@@ -31,12 +31,12 @@ from ganeti import utils
 
 
 #: default list of fields for L{ListGroups}
-_LIST_DEF_FIELDS = ["name", "node_cnt", "pinst_cnt"]
+_LIST_DEF_FIELDS = ["name", "node_cnt", "pinst_cnt", "alloc_policy"]
 
 
 #: headers (and full field list) for L{ListGroups}
 _LIST_HEADERS = {
-  "name": "Group", "uuid": "UUID",
+  "name": "Group", "uuid": "UUID", "alloc_policy": "AllocPolicy",
   "node_cnt": "Nodes", "node_list": "NodeList",
   "pinst_cnt": "Instances", "pinst_list": "InstanceList",
   "ctime": "CTime", "mtime": "MTime", "serial_no": "SerialNo",
@@ -54,7 +54,8 @@ def AddGroup(opts, args):
 
   """
   (group_name,) = args
-  op = opcodes.OpAddGroup(group_name=group_name, ndparams=opts.ndparams)
+  op = opcodes.OpAddGroup(group_name=group_name, ndparams=opts.ndparams,
+                          alloc_policy=opts.alloc_policy)
   SubmitOpCode(op, opts=opts)
 
 
@@ -118,13 +119,15 @@ def SetGroupParams(opts, args):
   """
   all_changes = {
     "ndparams": opts.ndparams,
+    "alloc_policy": opts.alloc_policy,
   }
 
   if all_changes.values().count(None) == len(all_changes):
     ToStderr("Please give at least one of the parameters.")
     return 1
 
-  op = opcodes.OpSetGroupParams(group_name=args[0], **all_changes)
+  op = opcodes.OpSetGroupParams(group_name=args[0], # pylint: disable-msg=W0142
+                                **all_changes)
   result = SubmitOrSend(op, opts)
 
   if result:
@@ -167,7 +170,7 @@ def RenameGroup(opts, args):
 
 commands = {
   "add": (
-    AddGroup, ARGS_ONE_GROUP, [DRY_RUN_OPT, NODE_PARAMS_OPT],
+    AddGroup, ARGS_ONE_GROUP, [DRY_RUN_OPT, ALLOC_POLICY_OPT, NODE_PARAMS_OPT],
     "<group_name>", "Add a new node group to the cluster"),
   "list": (
     ListGroups, ARGS_MANY_GROUPS,
@@ -178,7 +181,7 @@ commands = {
     (utils.CommaJoin(_LIST_HEADERS), utils.CommaJoin(_LIST_DEF_FIELDS))),
   "modify": (
     SetGroupParams, ARGS_ONE_GROUP,
-    [DRY_RUN_OPT, SUBMIT_OPT, NODE_PARAMS_OPT],
+    [DRY_RUN_OPT, SUBMIT_OPT, ALLOC_POLICY_OPT, NODE_PARAMS_OPT],
     "<group_name>", "Alters the parameters of a node group"),
   "remove": (
     RemoveGroup, ARGS_ONE_GROUP, [DRY_RUN_OPT],
diff --git a/lib/cmdlib.py b/lib/cmdlib.py
index 60d0bc7..9eb283c 100644
--- a/lib/cmdlib.py
+++ b/lib/cmdlib.py
@@ -10369,6 +10369,8 @@ class LUAddGroup(LogicalUnit):
   _OP_PARAMS = [
     _PGroupName,
     ("ndparams", None, ht.TOr(ht.TDict, ht.TNone)),
+    ("alloc_policy", None, ht.TOr(ht.TNone,
+                                  ht.TElemOf(constants.VALID_ALLOC_POLICIES))),
     ]
 
   REQ_BGL = False
@@ -10417,6 +10419,7 @@ class LUAddGroup(LogicalUnit):
     """
     group_obj = objects.NodeGroup(name=self.op.group_name, members=[],
                                   uuid=self.group_uuid,
+                                  alloc_policy=self.op.alloc_policy,
                                   ndparams=self.op.ndparams)
 
     self.cfg.AddNodeGroup(group_obj, self.proc.GetECId(), check_uuid=False)
@@ -10437,7 +10440,8 @@ class LUQueryGroups(NoHooksLU):
 
   _FIELDS_DYNAMIC = utils.FieldSet()
 
-  _SIMPLE_FIELDS = ["name", "uuid", "ctime", "mtime", "serial_no"]
+  _SIMPLE_FIELDS = ["name", "uuid", "alloc_policy",
+                    "ctime", "mtime", "serial_no"]
 
   _FIELDS_STATIC = utils.FieldSet(
       "node_cnt", "node_list", "pinst_cnt", "pinst_list", *_SIMPLE_FIELDS)
@@ -10535,12 +10539,14 @@ class LUSetGroupParams(LogicalUnit):
   """Modifies the parameters of a node group.
 
   """
-  HPATH = None
-  HTYPE = None
+  HPATH = "group-modify"
+  HTYPE = constants.HTYPE_GROUP
 
   _OP_PARAMS = [
     _PGroupName,
     ("ndparams", None, ht.TOr(ht.TDict, ht.TNone)),
+    ("alloc_policy", None, ht.TOr(ht.TNone,
+                                  ht.TElemOf(constants.VALID_ALLOC_POLICIES))),
     ]
 
   REQ_BGL = False
@@ -10548,6 +10554,7 @@ class LUSetGroupParams(LogicalUnit):
   def CheckArguments(self):
     all_changes = [
       self.op.ndparams,
+      self.op.alloc_policy,
       ]
 
     if all_changes.count(None) == len(all_changes):
@@ -10576,6 +10583,17 @@ class LUSetGroupParams(LogicalUnit):
       utils.ForceDictType(self.op.ndparams, constants.NDS_PARAMETER_TYPES)
       self.new_ndparams = self.group.SimpleFillND(self.op.ndparams)
 
+  def BuildHooksEnv(self):
+    """Build hooks env.
+
+    """
+    env = {
+      "GROUP_NAME": self.op.group_name,
+      "NEW_ALLOC_POLICY": self.op.alloc_policy,
+      }
+    mn = self.cfg.GetMasterNode()
+    return env, [mn], [mn]
+
   def Exec(self, feedback_fn):
     """Modifies the node group.
 
@@ -10586,6 +10604,9 @@ class LUSetGroupParams(LogicalUnit):
       self.group.ndparams = self.new_ndparams
       result.append(("ndparams", str(self.group.ndparams)))
 
+    if self.op.alloc_policy:
+      self.group.alloc_policy = self.op.alloc_policy
+
     self.cfg.Update(self.group, feedback_fn)
     return result
 
diff --git a/lib/config.py b/lib/config.py
index 97a4f64..6116b8d 100644
--- a/lib/config.py
+++ b/lib/config.py
@@ -868,6 +868,9 @@ class ConfigWriter:
   def AddNodeGroup(self, group, ec_id, check_uuid=True):
     """Add a node group to the configuration.
 
+    This method calls group.UpgradeConfig() to fill any missing attributes
+    according to their default values.
+
     @type group: L{objects.NodeGroup}
     @param group: the NodeGroup object to add
     @type ec_id: string
@@ -895,6 +898,7 @@ class ConfigWriter:
 
     group.serial_no = 1
     group.ctime = group.mtime = time.time()
+    group.UpgradeConfig()
 
     self._config_data.nodegroups[group.uuid] = group
     self._config_data.cluster.serial_no += 1
diff --git a/lib/constants.py b/lib/constants.py
index eae9ce6..d5ea409 100644
--- a/lib/constants.py
+++ b/lib/constants.py
@@ -1198,3 +1198,13 @@ PGREP = "pgrep"
 
 # Name of the node group that gets created at cluster init or upgrade
 INITIAL_NODE_GROUP_NAME = "default"
+
+# Possible values for NodeGroup.alloc_policy
+ALLOC_POLICY_PREFERRED = "preferred"
+ALLOC_POLICY_LAST_RESORT = "last_resort"
+ALLOC_POLICY_UNALLOCABLE = "unallocable"
+VALID_ALLOC_POLICIES = [
+  ALLOC_POLICY_PREFERRED,
+  ALLOC_POLICY_LAST_RESORT,
+  ALLOC_POLICY_UNALLOCABLE,
+  ]
diff --git a/lib/objects.py b/lib/objects.py
index 343c218..b40e027 100644
--- a/lib/objects.py
+++ b/lib/objects.py
@@ -956,6 +956,7 @@ class NodeGroup(ConfigObject):
     "members",
     "ndparams",
     "serial_no",
+    "alloc_policy",
     ] + _TIMESTAMPS + _UUID
 
   def ToDict(self):
@@ -990,6 +991,9 @@ class NodeGroup(ConfigObject):
     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:
diff --git a/lib/opcodes.py b/lib/opcodes.py
index 00c7cab..3524813 100644
--- a/lib/opcodes.py
+++ b/lib/opcodes.py
@@ -727,6 +727,7 @@ class OpAddGroup(OpCode):
   __slots__ = [
     "group_name",
     "ndparams",
+    "alloc_policy",
     ]
 
 
@@ -743,6 +744,7 @@ class OpSetGroupParams(OpCode):
   __slots__ = [
     "group_name",
     "ndparams",
+    "alloc_policy",
     ]
 
 
diff --git a/lib/rapi/client.py b/lib/rapi/client.py
index 47ac422..096463e 100644
--- a/lib/rapi/client.py
+++ b/lib/rapi/client.py
@@ -1408,11 +1408,13 @@ class GanetiRapiClient(object):
                              "/%s/groups/%s" % (GANETI_RAPI_VERSION, group),
                              None, None)
 
-  def CreateGroup(self, name, dry_run=False):
+  def CreateGroup(self, name, alloc_policy=None, dry_run=False):
     """Creates a new node group.
 
     @type name: str
     @param name: the name of node group to create
+    @type alloc_policy: str
+    @param alloc_policy: the desired allocation policy for the group, if any
     @type dry_run: bool
     @param dry_run: whether to peform a dry run
 
@@ -1426,6 +1428,7 @@ class GanetiRapiClient(object):
 
     body = {
       "name": name,
+      "alloc_policy": alloc_policy
       }
 
     return self._SendRequest(HTTP_POST, "/%s/groups" % GANETI_RAPI_VERSION,
diff --git a/lib/rapi/rlib2.py b/lib/rapi/rlib2.py
index 6c265ec..418c003 100644
--- a/lib/rapi/rlib2.py
+++ b/lib/rapi/rlib2.py
@@ -74,6 +74,7 @@ N_FIELDS = ["name", "offline", "master_candidate", "drained",
             ] + _COMMON_FIELDS
 
 G_FIELDS = ["name", "uuid",
+            "alloc_policy",
             "node_cnt", "node_list",
             "ctime", "mtime", "serial_no",
             ]  # "tags" is missing to be able to use _COMMON_FIELDS here.
@@ -531,6 +532,21 @@ class R_2_nodes_name_storage_repair(baserlib.R_Generic):
     return baserlib.SubmitJob([op])
 
 
+def _ParseCreateGroupRequest(data, dry_run):
+  """Parses a request for creating a node group.
+
+  @rtype: L{opcodes.OpAddGroup}
+  @return: Group creation opcode
+
+  """
+  group_name = baserlib.CheckParameter(data, "name")
+  alloc_policy = baserlib.CheckParameter(data, "alloc_policy", default=None)
+
+  return opcodes.OpAddGroup(group_name=group_name,
+                            alloc_policy=alloc_policy,
+                            dry_run=dry_run)
+
+
 class R_2_groups(baserlib.R_Generic):
   """/2/groups resource.
 
@@ -557,8 +573,7 @@ class R_2_groups(baserlib.R_Generic):
 
     """
     baserlib.CheckType(self.request_body, dict, "Body contents")
-    group_name = baserlib.CheckParameter(self.request_body, "name")
-    op = opcodes.OpAddGroup(group_name=group_name, dry_run=self.dryRun())
+    op = _ParseCreateGroupRequest(self.request_body, self.dryRun())
     return baserlib.SubmitJob([op])
 
 
diff --git a/man/gnt-group.rst b/man/gnt-group.rst
index 61be0a7..6d71f4b 100644
--- a/man/gnt-group.rst
+++ b/man/gnt-group.rst
@@ -25,6 +25,7 @@ ADD
 
 | **add**
 | [--node-parameters=*NDPARAMS*]
+| [--alloc-policy=*POLICY*]
 | {*group*}
 
 Creates a new group with the given name. The node group will be
@@ -34,17 +35,36 @@ The ``--node-parameters`` option allows you to set default 
node
 parameters for nodes in the group. Please see **ganeti**(7) for more
 information about supported key=value pairs.
 
+The ``--alloc-policy`` option allows you to set an allocation policy for
+the group at creation time. Possible values are:
+
+unallocable
+    nodes in the group should not be candidates for instance allocation,
+    and the operation (e.g., instance creation) should fail if only
+    groups in this state could be found to satisfy the requirements.
+
+last_resort
+    nodes in the group should not be used for instance allocations,
+    unless this would be the only way to have the operation succeed.
+
+preferred
+    nodes in the group can be used freely for allocation of instances
+    (this is the default). Note that prioritization among groups in this
+    state will be deferred to the iallocator plugin that's being used.
+
+
 MODIFY
 ~~~~~~
 
 | **modify**
 | [--node-parameters=*NDPARAMS*]
+| [--alloc-policy=*POLICY*]
 | {*group*}
 
 Modifies some parameters from the node group.
 
-The ``--node-parameters`` option is documented in the **add** command
-above.
+The ``--node-parameters`` and ``--alloc-policy`` optiosn are documented
+in the **add** command above.
 
 REMOVE
 ~~~~~~
@@ -93,6 +113,9 @@ pinst_cnt
 pinst_list
     the list of primary instances in the group
 
+alloc_policy
+    the current allocation policy for the group
+
 ctime
     the creation time of the group; note that this field contains spaces
     and as such it's harder to parse
diff --git a/qa/qa_rapi.py b/qa/qa_rapi.py
index 86374e5..ea74bb5 100644
--- a/qa/qa_rapi.py
+++ b/qa/qa_rapi.py
@@ -96,6 +96,7 @@ NODE_FIELDS = ("name", "dtotal", "dfree",
 
 GROUP_FIELDS = frozenset([
   "name", "uuid",
+  "alloc_policy",
   "node_cnt", "node_list",
   ])
 
-- 
1.7.3.1

Reply via email to