Front-end and doc to use allocator in recreate-disks
[ganeti-local] / lib / client / gnt_instance.py
index d417c83..4e1eb57 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
@@ -25,6 +25,7 @@
 # W0614: Unused import %s from wildcard import (since we need cli)
 # C0103: Invalid name gnt-instance
 
+import copy
 import itertools
 import simplejson
 import logging
@@ -65,6 +66,7 @@ _LIST_DEF_FIELDS = [
   ]
 
 
+_MISSING = object()
 _ENV_OVERRIDE = frozenset(["list"])
 
 
@@ -564,7 +566,8 @@ def ActivateDisks(opts, args):
   """
   instance_name = args[0]
   op = opcodes.OpInstanceActivateDisks(instance_name=instance_name,
-                                       ignore_size=opts.ignore_size)
+                                       ignore_size=opts.ignore_size,
+                                       wait_for_sync=opts.wait_for_sync)
   disks_info = SubmitOrSend(op, opts)
   for host, iname, nname in disks_info:
     ToStdout("%s:%s:%s", host, iname, nname)
@@ -627,6 +630,9 @@ def RecreateDisks(opts, args):
     # LUInstanceRecreateDisks, but it'd be nice to have in the client)
 
   if opts.node:
+    if opts.iallocator:
+      msg = "At most one of either --nodes or --iallocator can be passed"
+      raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
     pnode, snode = SplitNodeOption(opts.node)
     nodes = [pnode]
     if snode is not None:
@@ -635,7 +641,8 @@ def RecreateDisks(opts, args):
     nodes = []
 
   op = opcodes.OpInstanceRecreateDisks(instance_name=instance_name,
-                                       disks=disks, nodes=nodes)
+                                       disks=disks, nodes=nodes,
+                                       iallocator=opts.iallocator)
   SubmitOrSend(op, opts)
 
   return 0
@@ -659,10 +666,15 @@ def GrowDisk(opts, args):
   except (TypeError, ValueError), err:
     raise errors.OpPrereqError("Invalid disk index: %s" % str(err),
                                errors.ECODE_INVAL)
-  amount = utils.ParseUnit(args[2])
+  try:
+    amount = utils.ParseUnit(args[2])
+  except errors.UnitParseError:
+    raise errors.OpPrereqError("Can't parse the given amount '%s'" % args[2],
+                               errors.ECODE_INVAL)
   op = opcodes.OpInstanceGrowDisk(instance_name=instance,
                                   disk=disk, amount=amount,
-                                  wait_for_sync=opts.wait_for_sync)
+                                  wait_for_sync=opts.wait_for_sync,
+                                  absolute=opts.absolute)
   SubmitOrSend(op, opts)
   return 0
 
@@ -865,8 +877,9 @@ def MigrateInstance(opts, args):
                                  cleanup=opts.cleanup, iallocator=iallocator,
                                  target_node=target_node,
                                  allow_failover=opts.allow_failover,
+                                 allow_runtime_changes=opts.allow_runtime_chgs,
                                  ignore_ipolicy=opts.ignore_ipolicy)
-  SubmitOpCode(op, cl=cl, opts=opts)
+  SubmitOrSend(op, cl=cl, opts=opts)
   return 0
 
 
@@ -1201,7 +1214,15 @@ def ShowInstanceConfig(opts, args):
     ##          instance["auto_balance"])
     buf.write("  Nodes:\n")
     buf.write("    - primary: %s\n" % instance["pnode"])
-    buf.write("    - secondaries: %s\n" % utils.CommaJoin(instance["snodes"]))
+    buf.write("      group: %s (UUID %s)\n" %
+              (instance["pnode_group_name"], instance["pnode_group_uuid"]))
+    buf.write("    - secondaries: %s\n" %
+              utils.CommaJoin("%s (group %s, group UUID %s)" %
+                                (name, group_name, group_uuid)
+                              for (name, group_name, group_uuid) in
+                                zip(instance["snodes"],
+                                    instance["snodes_group_names"],
+                                    instance["snodes_group_uuids"])))
     buf.write("  Operating system: %s\n" % instance["os"])
     FormatParameterDict(buf, instance["os_instance"], instance["os_actual"],
                         level=2)
@@ -1234,23 +1255,12 @@ def ShowInstanceConfig(opts, args):
     FormatParameterDict(buf, instance["hv_instance"], instance["hv_actual"],
                         level=2)
     buf.write("  Hardware:\n")
-    buf.write("    - VCPUs: %s\n" %
-              compat.TryToRoman(instance["be_actual"][constants.BE_VCPUS],
-                                convert=opts.roman_integers))
-    buf.write("    - maxmem: %sMiB\n" %
-              compat.TryToRoman(instance["be_actual"][constants.BE_MAXMEM],
-                                convert=opts.roman_integers))
-    buf.write("    - minmem: %sMiB\n" %
-              compat.TryToRoman(instance["be_actual"][constants.BE_MINMEM],
-                                convert=opts.roman_integers))
     # deprecated "memory" value, kept for one version for compatibility
     # TODO(ganeti 2.7) remove.
-    buf.write("    - memory: %sMiB\n" %
-              compat.TryToRoman(instance["be_actual"][constants.BE_MAXMEM],
-                                convert=opts.roman_integers))
-    buf.write("    - %s: %s\n" %
-              (constants.BE_ALWAYS_FAILOVER,
-               instance["be_actual"][constants.BE_ALWAYS_FAILOVER]))
+    be_actual = copy.deepcopy(instance["be_actual"])
+    be_actual["memory"] = be_actual[constants.BE_MAXMEM]
+    FormatParameterDict(buf, instance["be_instance"], be_actual, level=2)
+    # TODO(ganeti 2.7) rework the NICs as well
     buf.write("    - NICs:\n")
     for idx, (ip, mac, mode, link) in enumerate(instance["nics"]):
       buf.write("      - nic/%d: MAC: %s, IP: %s, mode: %s, link: %s\n" %
@@ -1266,6 +1276,87 @@ def ShowInstanceConfig(opts, args):
   return retcode
 
 
+def _ConvertNicDiskModifications(mods):
+  """Converts NIC/disk modifications from CLI to opcode.
+
+  When L{opcodes.OpInstanceSetParams} was changed to support adding/removing
+  disks at arbitrary indices, its parameter format changed. This function
+  converts legacy requests (e.g. "--net add" or "--disk add:size=4G") to the
+  newer format and adds support for new-style requests (e.g. "--new 4:add").
+
+  @type mods: list of tuples
+  @param mods: Modifications as given by command line parser
+  @rtype: list of tuples
+  @return: Modifications as understood by L{opcodes.OpInstanceSetParams}
+
+  """
+  result = []
+
+  for (idx, params) in mods:
+    if idx == constants.DDM_ADD:
+      # Add item as last item (legacy interface)
+      action = constants.DDM_ADD
+      idxno = -1
+    elif idx == constants.DDM_REMOVE:
+      # Remove last item (legacy interface)
+      action = constants.DDM_REMOVE
+      idxno = -1
+    else:
+      # Modifications and adding/removing at arbitrary indices
+      try:
+        idxno = int(idx)
+      except (TypeError, ValueError):
+        raise errors.OpPrereqError("Non-numeric index '%s'" % idx,
+                                   errors.ECODE_INVAL)
+
+      add = params.pop(constants.DDM_ADD, _MISSING)
+      remove = params.pop(constants.DDM_REMOVE, _MISSING)
+      modify = params.pop(constants.DDM_MODIFY, _MISSING)
+
+      if modify is _MISSING:
+        if not (add is _MISSING or remove is _MISSING):
+          raise errors.OpPrereqError("Cannot add and remove at the same time",
+                                     errors.ECODE_INVAL)
+        elif add is not _MISSING:
+          action = constants.DDM_ADD
+        elif remove is not _MISSING:
+          action = constants.DDM_REMOVE
+        else:
+          action = constants.DDM_MODIFY
+
+      else:
+        if add is _MISSING and remove is _MISSING:
+          action = constants.DDM_MODIFY
+        else:
+          raise errors.OpPrereqError("Cannot modify and add/remove at the"
+                                     " same time", errors.ECODE_INVAL)
+
+      assert not (constants.DDMS_VALUES_WITH_MODIFY & set(params.keys()))
+
+    if action == constants.DDM_REMOVE and params:
+      raise errors.OpPrereqError("Not accepting parameters on removal",
+                                 errors.ECODE_INVAL)
+
+    result.append((action, idxno, params))
+
+  return result
+
+
+def _ParseDiskSizes(mods):
+  """Parses disk sizes in parameters.
+
+  """
+  for (action, _, params) in mods:
+    if params and constants.IDISK_SIZE in params:
+      params[constants.IDISK_SIZE] = \
+        utils.ParseUnit(params[constants.IDISK_SIZE])
+    elif action == constants.DDM_ADD:
+      raise errors.OpPrereqError("Missing required parameter 'size'",
+                                 errors.ECODE_INVAL)
+
+  return mods
+
+
 def SetInstanceParams(opts, args):
   """Modifies an instance.
 
@@ -1300,24 +1391,8 @@ def SetInstanceParams(opts, args):
   utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
                       allowed_values=[constants.VALUE_DEFAULT])
 
-  for idx, (nic_op, nic_dict) in enumerate(opts.nics):
-    try:
-      nic_op = int(nic_op)
-      opts.nics[idx] = (nic_op, nic_dict)
-    except (TypeError, ValueError):
-      pass
-
-  for idx, (disk_op, disk_dict) in enumerate(opts.disks):
-    try:
-      disk_op = int(disk_op)
-      opts.disks[idx] = (disk_op, disk_dict)
-    except (TypeError, ValueError):
-      pass
-    if disk_op == constants.DDM_ADD:
-      if "size" not in disk_dict:
-        raise errors.OpPrereqError("Missing required parameter 'size'",
-                                   errors.ECODE_INVAL)
-      disk_dict["size"] = utils.ParseUnit(disk_dict["size"])
+  nics = _ConvertNicDiskModifications(opts.nics)
+  disks = _ParseDiskSizes(_ConvertNicDiskModifications(opts.disks))
 
   if (opts.disk_template and
       opts.disk_template in constants.DTS_INT_MIRROR and
@@ -1326,9 +1401,16 @@ def SetInstanceParams(opts, args):
              " specifying a secondary node")
     return 1
 
+  if opts.offline_inst:
+    offline = True
+  elif opts.online_inst:
+    offline = False
+  else:
+    offline = None
+
   op = opcodes.OpInstanceSetParams(instance_name=args[0],
-                                   nics=opts.nics,
-                                   disks=opts.disks,
+                                   nics=nics,
+                                   disks=disks,
                                    disk_template=opts.disk_template,
                                    remote_node=opts.node,
                                    hvparams=opts.hvparams,
@@ -1339,8 +1421,7 @@ def SetInstanceParams(opts, args):
                                    force_variant=opts.force_variant,
                                    force=opts.force,
                                    wait_for_sync=opts.wait_for_sync,
-                                   offline_inst=opts.offline_inst,
-                                   online_inst=opts.online_inst,
+                                   offline=offline,
                                    ignore_ipolicy=opts.ignore_ipolicy)
 
   # even if here we process the result, we allow submit only
@@ -1367,7 +1448,7 @@ def ChangeGroup(opts, args):
                                      iallocator=opts.iallocator,
                                      target_groups=opts.to,
                                      early_release=opts.early_release)
-  result = SubmitOpCode(op, cl=cl, opts=opts)
+  result = SubmitOrSend(op, opts, cl=cl)
 
   # Keep track of submitted jobs
   jex = JobExecutor(cl=cl, opts=opts)
@@ -1469,7 +1550,7 @@ commands = {
     MigrateInstance, ARGS_ONE_INSTANCE,
     [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, CLEANUP_OPT, DRY_RUN_OPT,
      PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT, ALLOW_FAILOVER_OPT,
-     IGNORE_IPOLICY_OPT],
+     IGNORE_IPOLICY_OPT, NORUNTIME_CHGS_OPT, SUBMIT_OPT],
     "[-f] <instance>", "Migrate instance to its secondary node"
     " (only for mirrored instances)"),
   "move": (
@@ -1553,7 +1634,7 @@ commands = {
     "<instance>", "Reboots an instance"),
   "activate-disks": (
     ActivateDisks, ARGS_ONE_INSTANCE,
-    [SUBMIT_OPT, IGNORE_SIZE_OPT, PRIORITY_OPT],
+    [SUBMIT_OPT, IGNORE_SIZE_OPT, PRIORITY_OPT, WFSYNC_OPT],
     "<instance>", "Activate an instance's disks"),
   "deactivate-disks": (
     DeactivateDisks, ARGS_ONE_INSTANCE,
@@ -1561,28 +1642,29 @@ commands = {
     "[-f] <instance>", "Deactivate an instance's disks"),
   "recreate-disks": (
     RecreateDisks, ARGS_ONE_INSTANCE,
-    [SUBMIT_OPT, DISK_OPT, NODE_PLACEMENT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
+    [SUBMIT_OPT, DISK_OPT, NODE_PLACEMENT_OPT, DRY_RUN_OPT, PRIORITY_OPT,
+     IALLOCATOR_OPT],
     "<instance>", "Recreate an instance's disks"),
   "grow-disk": (
     GrowDisk,
     [ArgInstance(min=1, max=1), ArgUnknown(min=1, max=1),
      ArgUnknown(min=1, max=1)],
-    [SUBMIT_OPT, NWSYNC_OPT, DRY_RUN_OPT, PRIORITY_OPT],
+    [SUBMIT_OPT, NWSYNC_OPT, DRY_RUN_OPT, PRIORITY_OPT, ABSOLUTE_OPT],
     "<instance> <disk> <size>", "Grow an instance's disk"),
   "change-group": (
     ChangeGroup, ARGS_ONE_INSTANCE,
-    [TO_GROUP_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT],
+    [TO_GROUP_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT, PRIORITY_OPT, SUBMIT_OPT],
     "[-I <iallocator>] [--to <group>]", "Change group of instance"),
   "list-tags": (
-    ListTags, ARGS_ONE_INSTANCE, [PRIORITY_OPT],
+    ListTags, ARGS_ONE_INSTANCE, [],
     "<instance_name>", "List the tags of the given instance"),
   "add-tags": (
     AddTags, [ArgInstance(min=1, max=1), ArgUnknown()],
-    [TAG_SRC_OPT, PRIORITY_OPT],
+    [TAG_SRC_OPT, PRIORITY_OPT, SUBMIT_OPT],
     "<instance_name> tag...", "Add tags to the given instance"),
   "remove-tags": (
     RemoveTags, [ArgInstance(min=1, max=1), ArgUnknown()],
-    [TAG_SRC_OPT, PRIORITY_OPT],
+    [TAG_SRC_OPT, PRIORITY_OPT, SUBMIT_OPT],
     "<instance_name> tag...", "Remove tags from given instance"),
   }
 
@@ -1590,6 +1672,7 @@ commands = {
 aliases = {
   "start": "startup",
   "stop": "shutdown",
+  "show": "info",
   }