Add command to print ipolicy options
authorBernardo Dal Seno <bdalseno@google.com>
Thu, 4 Apr 2013 20:30:59 +0000 (22:30 +0200)
committerBernardo Dal Seno <bdalseno@google.com>
Wed, 24 Apr 2013 10:02:43 +0000 (12:02 +0200)
The output of this command can be used to create an exact copy of the
current instance policy specs. The command could be expanded to print all
the options used to create a group or the cluster.

Signed-off-by: Bernardo Dal Seno <bdalseno@google.com>
Reviewed-by: Helga Velroyen <helgav@google.com>
Reviewed-by: Guido Trotter <ultrotter@google.com>

lib/cli.py
lib/client/gnt_cluster.py
lib/client/gnt_group.py
man/gnt-cluster.rst
man/gnt-group.rst
test/py/ganeti.cli_unittest.py

index 9c83c41..2113308 100644 (file)
@@ -108,6 +108,7 @@ __all__ = [
   "IGNORE_REMOVE_FAILURES_OPT",
   "IGNORE_SECONDARIES_OPT",
   "IGNORE_SIZE_OPT",
+  "INCLUDEDEFAULTS_OPT",
   "INTERVAL_OPT",
   "MAC_PREFIX_OPT",
   "MAINTAIN_NODE_HEALTH_OPT",
@@ -237,6 +238,7 @@ __all__ = [
   "FormatQueryResult",
   "FormatParamsDictInfo",
   "FormatPolicyInfo",
+  "PrintIPolicyCommand",
   "PrintGenericInfo",
   "GenerateTable",
   "AskUser",
@@ -1621,6 +1623,10 @@ NOCONFLICTSCHECK_OPT = cli_option("--no-conflicts-check",
                                   action="store_false",
                                   help="Don't check for conflicting IPs")
 
+INCLUDEDEFAULTS_OPT = cli_option("--include-defaults", dest="include_defaults",
+                                 default=False, action="store_true",
+                                 help="Include default values")
+
 #: Options provided by all commands
 COMMON_OPTS = [DEBUG_OPT, REASON_OPT]
 
@@ -3751,6 +3757,41 @@ def FormatPolicyInfo(custom_ipolicy, eff_ipolicy, iscluster):
   return ret
 
 
+def _PrintSpecsParameters(buf, specs):
+  values = ("%s=%s" % (par, val) for (par, val) in sorted(specs.items()))
+  buf.write(",".join(values))
+
+
+def PrintIPolicyCommand(buf, ipolicy, isgroup):
+  """Print the command option used to generate the given instance policy.
+
+  Currently only the parts dealing with specs are supported.
+
+  @type buf: StringIO
+  @param buf: stream to write into
+  @type ipolicy: dict
+  @param ipolicy: instance policy
+  @type isgroup: bool
+  @param isgroup: whether the policy is at group level
+
+  """
+  if not isgroup:
+    stdspecs = ipolicy.get("std")
+    if stdspecs:
+      buf.write(" %s " % IPOLICY_STD_SPECS_STR)
+      _PrintSpecsParameters(buf, stdspecs)
+  minmax = ipolicy.get("minmax")
+  if minmax:
+    minspecs = minmax.get("min")
+    maxspecs = minmax.get("max")
+    if minspecs and maxspecs:
+      buf.write(" %s " % IPOLICY_BOUNDS_SPECS_STR)
+      buf.write("min:")
+      _PrintSpecsParameters(buf, minspecs)
+      buf.write("/max:")
+      _PrintSpecsParameters(buf, maxspecs)
+
+
 def ConfirmOperation(names, list_type, text, extra=""):
   """Ask the user to confirm an operation on a list of list_type.
 
index e0e2d7f..750d655 100644 (file)
@@ -26,6 +26,7 @@
 # W0614: Unused import %s from wildcard import (since we need cli)
 # C0103: Invalid name gnt-cluster
 
+from cStringIO import StringIO
 import os.path
 import time
 import OpenSSL
@@ -1491,6 +1492,26 @@ def Epo(opts, args, cl=None, _on_fn=_EpoOn, _off_fn=_EpoOff,
     return _off_fn(opts, node_list, inst_map)
 
 
+def _GetCreateCommand(info):
+  buf = StringIO()
+  buf.write("gnt-cluster init")
+  PrintIPolicyCommand(buf, info["ipolicy"], False)
+  buf.write(" ")
+  buf.write(info["name"])
+  return buf.getvalue()
+
+
+def ShowCreateCommand(opts, args):
+  """Shows the command that can be used to re-create the cluster.
+
+  Currently it works only for ipolicy specs.
+
+  """
+  cl = GetClient(query=True)
+  result = cl.QueryClusterInfo()
+  ToStdout(_GetCreateCommand(result))
+
+
 commands = {
   "init": (
     InitCluster, [ArgHost(min=1, max=1)],
@@ -1603,6 +1624,9 @@ commands = {
   "deactivate-master-ip": (
     DeactivateMasterIp, ARGS_NONE, [CONFIRM_OPT], "",
     "Deactivates the master IP"),
+  "show-ispecs-cmd": (
+    ShowCreateCommand, ARGS_NONE, [], "",
+    "Show the command line to re-create the cluster"),
   }
 
 
index 2f8dcc9..32e0e3a 100644 (file)
@@ -24,6 +24,8 @@
 # W0401: Wildcard import ganeti.cli
 # W0614: Unused import %s from wildcard import (since we need cli)
 
+from cStringIO import StringIO
+
 from ganeti.cli import *
 from ganeti import constants
 from ganeti import opcodes
@@ -313,6 +315,35 @@ def GroupInfo(_, args):
     ])
 
 
+def _GetCreateCommand(group):
+  (name, ipolicy) = group
+  buf = StringIO()
+  buf.write("gnt-group add")
+  PrintIPolicyCommand(buf, ipolicy, True)
+  buf.write(" ")
+  buf.write(name)
+  return buf.getvalue()
+
+
+def ShowCreateCommand(opts, args):
+  """Shows the command that can be used to re-create a node group.
+
+  Currently it works only for ipolicy specs.
+
+  """
+  cl = GetClient(query=True)
+  selected_fields = ["name"]
+  if opts.include_defaults:
+    selected_fields += ["ipolicy"]
+  else:
+    selected_fields += ["custom_ipolicy"]
+  result = cl.QueryGroups(names=args, fields=selected_fields,
+                          use_locking=False)
+
+  for group in result:
+    ToStdout(_GetCreateCommand(group))
+
+
 commands = {
   "add": (
     AddGroup, ARGS_ONE_GROUP,
@@ -366,6 +397,10 @@ commands = {
   "info": (
     GroupInfo, ARGS_MANY_GROUPS, [], "[<group_name>...]",
     "Show group information"),
+  "show-ispecs-cmd": (
+    ShowCreateCommand, ARGS_MANY_GROUPS, [INCLUDEDEFAULTS_OPT],
+    "[--include-defaults] [<group_name>...]",
+    "Show the command line to re-create a group"),
   }
 
 
index c42c5fc..1e837ae 100644 (file)
@@ -152,6 +152,14 @@ Passing the ``--roman`` option gnt-cluster info will try to print
 its integer fields in a latin friendly way. This allows further
 diffusion of Ganeti among ancient cultures.
 
+SHOW-ISPECS-CMD
+~~~~~~~~~~~~~~~
+
+**show-ispecs-cmd**
+
+Shows the command line that can be used to recreate the cluster with the
+same options relative to specs in the instance policies.
+
 INIT
 ~~~~
 
index a633dc9..6786e69 100644 (file)
@@ -257,10 +257,23 @@ be interpreted as stdin.
 INFO
 ~~~~
 
-**info** [group...]
+**info** [*group*...]
 
 Shows config information for all (or given) groups.
 
+SHOW-ISPECS-CMD
+~~~~~~~~~~~~~~~
+
+**show-ispecs-cmd** [\--include-defaults] [*group*...]
+
+Shows the command line that can be used to recreate the given groups (or
+all groups, if none is given) with the same options relative to specs in
+the instance policies.
+
+If ``--include-defaults`` is specified, include also the default values
+(i.e. the cluster-level settings), and not only the configuration items
+that a group overrides.
+
 
 .. vim: set textwidth=72 :
 .. Local Variables:
index 96f84d6..a90dd4a 100755 (executable)
@@ -1423,5 +1423,59 @@ class TestCreateIPolicyFromOpts(unittest.TestCase):
       self._TestFullISpecsInner(skel_ipolicy, exp_minmax1, None,
                                 False, fill_all)
 
+
+class TestPrintIPolicyCommand(unittest.TestCase):
+  """Test case for cli.PrintIPolicyCommand"""
+  _SPECS1 = {
+    "par1": 42,
+    "par2": "xyz",
+    }
+  _SPECS1_STR = "par1=42,par2=xyz"
+  _SPECS2 = {
+    "param": 10,
+    "another_param": 101,
+    }
+  _SPECS2_STR = "another_param=101,param=10"
+
+  def _CheckPrintIPolicyCommand(self, ipolicy, isgroup, expected):
+    buf = StringIO()
+    cli.PrintIPolicyCommand(buf, ipolicy, isgroup)
+    self.assertEqual(buf.getvalue(), expected)
+
+  def testIgnoreStdForGroup(self):
+    self._CheckPrintIPolicyCommand({"std": self._SPECS1}, True, "")
+
+  def testIgnoreEmpty(self):
+    policies = [
+      {},
+      {"std": {}},
+      {"minmax": {}},
+      {"minmax": {
+        "min": {},
+        "max": {},
+        }},
+      {"minmax": {
+        "min": self._SPECS1,
+        "max": {},
+        }},
+      ]
+    for pol in policies:
+      self._CheckPrintIPolicyCommand(pol, False, "")
+
+  def testFullPolicies(self):
+    cases = [
+      ({"std": self._SPECS1},
+       " %s %s" % (cli.IPOLICY_STD_SPECS_STR, self._SPECS1_STR)),
+      ({"minmax": {
+        "min": self._SPECS1,
+        "max": self._SPECS2,
+        }},
+       " %s min:%s/max:%s" % (cli.IPOLICY_BOUNDS_SPECS_STR,
+                              self._SPECS1_STR, self._SPECS2_STR)),
+      ]
+    for (pol, exp) in cases:
+      self._CheckPrintIPolicyCommand(pol, False, exp)
+
+
 if __name__ == "__main__":
   testutils.GanetiTestProgram()