Add utils.IsNormAbsPath function
[ganeti-local] / scripts / gnt-instance
index a9fc2f8..3bd8cfe 100755 (executable)
 # 02110-1301, USA.
 
 
 # 02110-1301, USA.
 
 
+# pylint: disable-msg=W0401,W0614
+# W0401: Wildcard import ganeti.cli
+# W0614: Unused import %s from wildcard import (since we need cli)
+
 import sys
 import os
 import sys
 import os
+import itertools
+import simplejson
 from optparse import make_option
 from optparse import make_option
-import textwrap
 from cStringIO import StringIO
 
 from ganeti.cli import *
 from cStringIO import StringIO
 
 from ganeti.cli import *
+from ganeti import cli
 from ganeti import opcodes
 from ganeti import opcodes
-from ganeti import logger
 from ganeti import constants
 from ganeti import utils
 from ganeti import constants
 from ganeti import utils
+from ganeti import errors
+
+
+_SHUTDOWN_CLUSTER = "cluster"
+_SHUTDOWN_NODES_BOTH = "nodes"
+_SHUTDOWN_NODES_PRI = "nodes-pri"
+_SHUTDOWN_NODES_SEC = "nodes-sec"
+_SHUTDOWN_INSTANCES = "instances"
+
+
+_VALUE_TRUE = "true"
+
+#: default list of options for L{ListInstances}
+_LIST_DEF_FIELDS = [
+  "name", "hypervisor", "os", "pnode", "status", "oper_ram",
+  ]
+
+
+def _ExpandMultiNames(mode, names, client=None):
+  """Expand the given names using the passed mode.
+
+  For _SHUTDOWN_CLUSTER, all instances will be returned. For
+  _SHUTDOWN_NODES_PRI/SEC, all instances having those nodes as
+  primary/secondary will be returned. For _SHUTDOWN_NODES_BOTH, all
+  instances having those nodes as either primary or secondary will be
+  returned. For _SHUTDOWN_INSTANCES, the given instances will be
+  returned.
+
+  @param mode: one of L{_SHUTDOWN_CLUSTER}, L{_SHUTDOWN_NODES_BOTH},
+      L{_SHUTDOWN_NODES_PRI}, L{_SHUTDOWN_NODES_SEC} or
+      L{_SHUTDOWN_INSTANCES}
+  @param names: a list of names; for cluster, it must be empty,
+      and for node and instance it must be a list of valid item
+      names (short names are valid as usual, e.g. node1 instead of
+      node1.example.com)
+  @rtype: list
+  @return: the list of names after the expansion
+  @raise errors.ProgrammerError: for unknown selection type
+  @raise errors.OpPrereqError: for invalid input parameters
+
+  """
+  if client is None:
+    client = GetClient()
+  if mode == _SHUTDOWN_CLUSTER:
+    if names:
+      raise errors.OpPrereqError("Cluster filter mode takes no arguments")
+    idata = client.QueryInstances([], ["name"], False)
+    inames = [row[0] for row in idata]
+
+  elif mode in (_SHUTDOWN_NODES_BOTH,
+                _SHUTDOWN_NODES_PRI,
+                _SHUTDOWN_NODES_SEC):
+    if not names:
+      raise errors.OpPrereqError("No node names passed")
+    ndata = client.QueryNodes(names, ["name", "pinst_list", "sinst_list"],
+                              False)
+    ipri = [row[1] for row in ndata]
+    pri_names = list(itertools.chain(*ipri))
+    isec = [row[2] for row in ndata]
+    sec_names = list(itertools.chain(*isec))
+    if mode == _SHUTDOWN_NODES_BOTH:
+      inames = pri_names + sec_names
+    elif mode == _SHUTDOWN_NODES_PRI:
+      inames = pri_names
+    elif mode == _SHUTDOWN_NODES_SEC:
+      inames = sec_names
+    else:
+      raise errors.ProgrammerError("Unhandled shutdown type")
+
+  elif mode == _SHUTDOWN_INSTANCES:
+    if not names:
+      raise errors.OpPrereqError("No instance names passed")
+    idata = client.QueryInstances(names, ["name"], False)
+    inames = [row[0] for row in idata]
+
+  else:
+    raise errors.OpPrereqError("Unknown mode '%s'" % mode)
+
+  return inames
+
+
+def _ConfirmOperation(inames, text, extra=""):
+  """Ask the user to confirm an operation on a list of instances.
+
+  This function is used to request confirmation for doing an operation
+  on a given list of instances.
+
+  @type inames: list
+  @param inames: the list of names that we display when
+      we ask for confirmation
+  @type text: str
+  @param text: the operation that the user should confirm
+      (e.g. I{shutdown} or I{startup})
+  @rtype: boolean
+  @return: True or False depending on user's confirmation.
+
+  """
+  count = len(inames)
+  msg = ("The %s will operate on %d instances.\n%s"
+         "Do you want to continue?" % (text, count, extra))
+  affected = ("\nAffected instances:\n" +
+              "\n".join(["  %s" % name for name in inames]))
+
+  choices = [('y', True, 'Yes, execute the %s' % text),
+             ('n', False, 'No, abort the %s' % text)]
+
+  if count > 20:
+    choices.insert(1, ('v', 'v', 'View the list of affected instances'))
+    ask = msg
+  else:
+    ask = msg + affected
+
+  choice = AskUser(ask, choices)
+  if choice == 'v':
+    choices.pop(1)
+    choice = AskUser(msg + affected, choices)
+  return choice
+
+
+def _EnsureInstancesExist(client, names):
+  """Check for and ensure the given instance names exist.
+
+  This function will raise an OpPrereqError in case they don't
+  exist. Otherwise it will exit cleanly.
+
+  @type client: L{luxi.Client}
+  @param client: the client to use for the query
+  @type names: list
+  @param names: the list of instance names to query
+  @raise errors.OpPrereqError: in case any instance is missing
+
+  """
+  # TODO: change LUQueryInstances to that it actually returns None
+  # instead of raising an exception, or devise a better mechanism
+  result = client.QueryInstances(names, ["name"], False)
+  for orig_name, row in zip(names, result):
+    if row[0] is None:
+      raise errors.OpPrereqError("Instance '%s' does not exist" % orig_name)
 
 
 def ListInstances(opts, args):
 
 
 def ListInstances(opts, args):
-  """List nodes and their properties.
+  """List instances and their properties.
+
+  @param opts: the command line options selected by the user
+  @type args: list
+  @param args: should be an empty list
+  @rtype: int
+  @return: the desired exit code
 
   """
   if opts.output is None:
 
   """
   if opts.output is None:
-    selected_fields = ["name", "os", "pnode", "admin_state",
-                       "oper_state", "oper_ram"]
+    selected_fields = _LIST_DEF_FIELDS
+  elif opts.output.startswith("+"):
+    selected_fields = _LIST_DEF_FIELDS + opts.output[1:].split(",")
   else:
     selected_fields = opts.output.split(",")
 
   else:
     selected_fields = opts.output.split(",")
 
-  op = opcodes.OpQueryInstances(output_fields=selected_fields)
-  output = SubmitOpCode(op)
+  output = GetClient().QueryInstances(args, selected_fields, opts.do_locking)
 
 
-  mlens = [0 for name in selected_fields]
+  if not opts.no_headers:
+    headers = {
+      "name": "Instance", "os": "OS", "pnode": "Primary_node",
+      "snodes": "Secondary_Nodes", "admin_state": "Autostart",
+      "oper_state": "Running",
+      "oper_ram": "Memory", "disk_template": "Disk_template",
+      "ip": "IP_address", "mac": "MAC_address",
+      "bridge": "Bridge",
+      "sda_size": "Disk/0", "sdb_size": "Disk/1",
+      "disk_usage": "DiskUsage",
+      "status": "Status", "tags": "Tags",
+      "network_port": "Network_port",
+      "hv/kernel_path": "Kernel_path",
+      "hv/initrd_path": "Initrd_path",
+      "hv/boot_order": "HVM_boot_order",
+      "hv/acpi": "HVM_ACPI",
+      "hv/pae": "HVM_PAE",
+      "hv/cdrom_image_path": "HVM_CDROM_image_path",
+      "hv/nic_type": "HVM_NIC_type",
+      "hv/disk_type": "HVM_Disk_type",
+      "hv/vnc_bind_address": "VNC_bind_address",
+      "serial_no": "SerialNo", "hypervisor": "Hypervisor",
+      "hvparams": "Hypervisor_parameters",
+      "be/memory": "Configured_memory",
+      "be/vcpus": "VCPUs",
+      "be/auto_balance": "Auto_balance",
+      "disk.count": "Disks", "disk.sizes": "Disk_sizes",
+      "nic.count": "NICs", "nic.ips": "NIC_IPs",
+      "nic.bridges": "NIC_bridges", "nic.macs": "NIC_MACs",
+      }
+  else:
+    headers = None
 
 
-  format_fields = []
-  unitformat_fields = ("admin_ram", "oper_ram")
-  for field in selected_fields:
-    if field in ("admin_ram", "oper_ram"):
-      format_fields.append("%*s")
-    else:
-      format_fields.append("%-*s")
-  separator = opts.separator
-  if "%" in separator:
-    separator = separator.replace("%", "%%")
-  format = separator.join(format_fields)
+  unitfields = ["be/memory", "oper_ram", "sd(a|b)_size", "disk\.size/.*"]
+  numfields = ["be/memory", "oper_ram", "sd(a|b)_size", "be/vcpus",
+               "serial_no", "(disk|nic)\.count", "disk\.size/.*"]
 
 
+  list_type_fields = ("tags", "disk.sizes",
+                      "nic.macs", "nic.ips", "nic.bridges")
+  # change raw values to nicer strings
   for row in output:
   for row in output:
-    for idx, val in enumerate(row):
-      if opts.human_readable and selected_fields[idx] in unitformat_fields:
-        try:
-          val = int(val)
-        except ValueError:
-          pass
+    for idx, field in enumerate(selected_fields):
+      val = row[idx]
+      if field == "snodes":
+        val = ",".join(val) or "-"
+      elif field == "admin_state":
+        if val:
+          val = "yes"
         else:
         else:
-          val = row[idx] = utils.FormatUnit(val)
-      mlens[idx] = max(mlens[idx], len(val))
-
-  if not opts.no_headers:
-    header_list = {"name": "Instance", "os": "OS", "pnode": "Primary_node",
-                   "snodes": "Secondary_Nodes", "admin_state": "Autostart",
-                   "oper_state": "Status", "admin_ram": "Configured_memory",
-                   "oper_ram": "Memory", "disk_template": "Disk_template",
-                   "ip": "IP Address", "mac": "MAC Address",
-                   "bridge": "Bridge"}
-    args = []
-    for idx, name in enumerate(selected_fields):
-      hdr = header_list[name]
-      mlens[idx] = max(mlens[idx], len(hdr))
-      args.append(mlens[idx])
-      args.append(hdr)
-    logger.ToStdout(format % tuple(args))
-
-  for line in output:
-    args = []
-    for idx in range(len(selected_fields)):
-      args.append(mlens[idx])
-      args.append(line[idx])
-    logger.ToStdout(format % tuple(args))
+          val = "no"
+      elif field == "oper_state":
+        if val is None:
+          val = "(node down)"
+        elif val: # True
+          val = "running"
+        else:
+          val = "stopped"
+      elif field == "oper_ram":
+        if val is None:
+          val = "(node down)"
+      elif field == "sda_size" or field == "sdb_size":
+        if val is None:
+          val = "N/A"
+      elif field in list_type_fields:
+        val = ",".join(str(item) for item in val)
+      elif val is None:
+        val = "-"
+      row[idx] = str(val)
+
+  data = GenerateTable(separator=opts.separator, headers=headers,
+                       fields=selected_fields, unitfields=unitfields,
+                       numfields=numfields, data=output, units=opts.units)
+
+  for line in data:
+    ToStdout(line)
 
   return 0
 
 
   return 0
 
@@ -98,51 +276,342 @@ def ListInstances(opts, args):
 def AddInstance(opts, args):
   """Add an instance to the cluster.
 
 def AddInstance(opts, args):
   """Add an instance to the cluster.
 
-  Args:
-    opts - class with options as members
-    args - list with a single element, the instance name
-  Opts used:
-    mem - amount of memory to allocate to instance (MiB)
-    size - amount of disk space to allocate to instance (MiB)
-    os - which OS to run on instance
-    node - node to run new instance on
+  @param opts: the command line options selected by the user
+  @type args: list
+  @param args: should contain only one element, the new instance name
+  @rtype: int
+  @return: the desired exit code
 
   """
 
   """
-
   instance = args[0]
 
   instance = args[0]
 
-  op = opcodes.OpCreateInstance(instance_name=instance, mem_size=opts.mem,
-                                disk_size=opts.size, swap_size=opts.swap,
+  (pnode, snode) = SplitNodeOption(opts.node)
+
+  hypervisor = None
+  hvparams = {}
+  if opts.hypervisor:
+    hypervisor, hvparams = opts.hypervisor
+
+  if opts.nics:
+    try:
+      nic_max = max(int(nidx[0])+1 for nidx in opts.nics)
+    except ValueError, err:
+      raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
+    nics = [{}] * nic_max
+    for nidx, ndict in opts.nics:
+      nidx = int(nidx)
+      nics[nidx] = ndict
+  elif opts.no_nics:
+    # no nics
+    nics = []
+  else:
+    # default of one nic, all auto
+    nics = [{}]
+
+  if opts.disk_template == constants.DT_DISKLESS:
+    if opts.disks or opts.sd_size is not None:
+      raise errors.OpPrereqError("Diskless instance but disk"
+                                 " information passed")
+    disks = []
+  else:
+    if not opts.disks and not opts.sd_size:
+      raise errors.OpPrereqError("No disk information specified")
+    if opts.disks and opts.sd_size is not None:
+      raise errors.OpPrereqError("Please use either the '--disk' or"
+                                 " '-s' option")
+    if opts.sd_size is not None:
+      opts.disks = [(0, {"size": opts.sd_size})]
+    try:
+      disk_max = max(int(didx[0])+1 for didx in opts.disks)
+    except ValueError, err:
+      raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
+    disks = [{}] * disk_max
+    for didx, ddict in opts.disks:
+      didx = int(didx)
+      if "size" not in ddict:
+        raise errors.OpPrereqError("Missing size for disk %d" % didx)
+      try:
+        ddict["size"] = utils.ParseUnit(ddict["size"])
+      except ValueError, err:
+        raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
+                                   (didx, err))
+      disks[didx] = ddict
+
+  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
+  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
+
+  op = opcodes.OpCreateInstance(instance_name=instance,
+                                disks=disks,
                                 disk_template=opts.disk_template,
                                 disk_template=opts.disk_template,
+                                nics=nics,
                                 mode=constants.INSTANCE_CREATE,
                                 mode=constants.INSTANCE_CREATE,
-                                os_type=opts.os, pnode=opts.node,
-                                snode=opts.snode, vcpus=opts.vcpus,
-                                ip=opts.ip, bridge=opts.bridge, start=True,
-                                wait_for_sync=opts.wait_for_sync)
-  SubmitOpCode(op)
+                                os_type=opts.os, pnode=pnode,
+                                snode=snode,
+                                start=opts.start, ip_check=opts.ip_check,
+                                wait_for_sync=opts.wait_for_sync,
+                                hypervisor=hypervisor,
+                                hvparams=hvparams,
+                                beparams=opts.beparams,
+                                iallocator=opts.iallocator,
+                                file_storage_dir=opts.file_storage_dir,
+                                file_driver=opts.file_driver,
+                                )
+
+  SubmitOrSend(op, opts)
+  return 0
+
+
+def BatchCreate(opts, args):
+  """Create instances using a definition file.
+
+  This function reads a json file with instances defined
+  in the form::
+
+    {"instance-name":{
+      "disk_size": [20480],
+      "template": "drbd",
+      "backend": {
+        "memory": 512,
+        "vcpus": 1 },
+      "os": "debootstrap",
+      "primary_node": "firstnode",
+      "secondary_node": "secondnode",
+      "iallocator": "dumb"}
+    }
+
+  Note that I{primary_node} and I{secondary_node} have precedence over
+  I{iallocator}.
+
+  @param opts: the command line options selected by the user
+  @type args: list
+  @param args: should contain one element, the json filename
+  @rtype: int
+  @return: the desired exit code
+
+  """
+  _DEFAULT_SPECS = {"disk_size": [20 * 1024],
+                    "backend": {},
+                    "iallocator": None,
+                    "primary_node": None,
+                    "secondary_node": None,
+                    "ip": 'none',
+                    "mac": 'auto',
+                    "bridge": None,
+                    "start": True,
+                    "ip_check": True,
+                    "hypervisor": None,
+                    "hvparams": {},
+                    "file_storage_dir": None,
+                    "file_driver": 'loop'}
+
+  def _PopulateWithDefaults(spec):
+    """Returns a new hash combined with default values."""
+    mydict = _DEFAULT_SPECS.copy()
+    mydict.update(spec)
+    return mydict
+
+  def _Validate(spec):
+    """Validate the instance specs."""
+    # Validate fields required under any circumstances
+    for required_field in ('os', 'template'):
+      if required_field not in spec:
+        raise errors.OpPrereqError('Required field "%s" is missing.' %
+                                   required_field)
+    # Validate special fields
+    if spec['primary_node'] is not None:
+      if (spec['template'] in constants.DTS_NET_MIRROR and
+          spec['secondary_node'] is None):
+        raise errors.OpPrereqError('Template requires secondary node, but'
+                                   ' there was no secondary provided.')
+    elif spec['iallocator'] is None:
+      raise errors.OpPrereqError('You have to provide at least a primary_node'
+                                 ' or an iallocator.')
+
+    if (spec['hvparams'] and
+        not isinstance(spec['hvparams'], dict)):
+      raise errors.OpPrereqError('Hypervisor parameters must be a dict.')
+
+  json_filename = args[0]
+  try:
+    fd = open(json_filename, 'r')
+    instance_data = simplejson.load(fd)
+    fd.close()
+  except Exception, err:
+    ToStderr("Can't parse the instance definition file: %s" % str(err))
+    return 1
+
+  jex = JobExecutor()
+
+  # Iterate over the instances and do:
+  #  * Populate the specs with default value
+  #  * Validate the instance specs
+  i_names = utils.NiceSort(instance_data.keys())
+  for name in i_names:
+    specs = instance_data[name]
+    specs = _PopulateWithDefaults(specs)
+    _Validate(specs)
+
+    hypervisor = specs['hypervisor']
+    hvparams = specs['hvparams']
+
+    disks = []
+    for elem in specs['disk_size']:
+      try:
+        size = utils.ParseUnit(elem)
+      except ValueError, err:
+        raise errors.OpPrereqError("Invalid disk size '%s' for"
+                                   " instance %s: %s" %
+                                   (elem, name, err))
+      disks.append({"size": size})
+
+    nic0 = {'ip': specs['ip'], 'bridge': specs['bridge'], 'mac': specs['mac']}
+
+    utils.ForceDictType(specs['backend'], constants.BES_PARAMETER_TYPES)
+    utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
+
+    op = opcodes.OpCreateInstance(instance_name=name,
+                                  disks=disks,
+                                  disk_template=specs['template'],
+                                  mode=constants.INSTANCE_CREATE,
+                                  os_type=specs['os'],
+                                  pnode=specs['primary_node'],
+                                  snode=specs['secondary_node'],
+                                  nics=[nic0],
+                                  start=specs['start'],
+                                  ip_check=specs['ip_check'],
+                                  wait_for_sync=True,
+                                  iallocator=specs['iallocator'],
+                                  hypervisor=hypervisor,
+                                  hvparams=hvparams,
+                                  beparams=specs['backend'],
+                                  file_storage_dir=specs['file_storage_dir'],
+                                  file_driver=specs['file_driver'])
+
+    jex.QueueJob(name, op)
+  # we never want to wait, just show the submitted job IDs
+  jex.WaitOrShow(False)
+
+  return 0
+
+
+def ReinstallInstance(opts, args):
+  """Reinstall an instance.
+
+  @param opts: the command line options selected by the user
+  @type args: list
+  @param args: should contain only one element, the name of the
+      instance to be reinstalled
+  @rtype: int
+  @return: the desired exit code
+
+  """
+  # first, compute the desired name list
+  if opts.multi_mode is None:
+    opts.multi_mode = _SHUTDOWN_INSTANCES
+
+  inames = _ExpandMultiNames(opts.multi_mode, args)
+  if not inames:
+    raise errors.OpPrereqError("Selection filter does not match any instances")
+
+  # second, if requested, ask for an OS
+  if opts.select_os is True:
+    op = opcodes.OpDiagnoseOS(output_fields=["name", "valid"], names=[])
+    result = SubmitOpCode(op)
+
+    if not result:
+      ToStdout("Can't get the OS list")
+      return 1
+
+    ToStdout("Available OS templates:")
+    number = 0
+    choices = []
+    for entry in result:
+      ToStdout("%3s: %s", number, entry[0])
+      choices.append(("%s" % number, entry[0], entry[0]))
+      number = number + 1
+
+    choices.append(('x', 'exit', 'Exit gnt-instance reinstall'))
+    selected = AskUser("Enter OS template number (or x to abort):",
+                       choices)
+
+    if selected == 'exit':
+      ToStderr("User aborted reinstall, exiting")
+      return 1
+
+    os_name = selected
+  else:
+    os_name = opts.os
+
+  # third, get confirmation: multi-reinstall requires --force-multi
+  # *and* --force, single-reinstall just --force
+  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
+  if multi_on:
+    warn_msg = "Note: this will remove *all* data for the below instances!\n"
+    if not ((opts.force_multi and opts.force) or
+            _ConfirmOperation(inames, "reinstall", extra=warn_msg)):
+      return 1
+  else:
+    if not opts.force:
+      usertext = ("This will reinstall the instance %s and remove"
+                  " all data. Continue?") % instance_name
+      if not AskUser(usertext):
+        return 1
+
+  jex = JobExecutor(verbose=multi_on)
+  for instance_name in inames:
+    op = opcodes.OpReinstallInstance(instance_name=instance_name,
+                                     os_type=os_name)
+    jex.QueueJob(instance_name, op)
+
+  jex.WaitOrShow(not opts.submit_only)
   return 0
 
 
 def RemoveInstance(opts, args):
   """Remove an instance.
 
   return 0
 
 
 def RemoveInstance(opts, args):
   """Remove an instance.
 
-  Args:
-    opts - class with options as members
-    args - list containing a single element, the instance name
+  @param opts: the command line options selected by the user
+  @type args: list
+  @param args: should contain only one element, the name of
+      the instance to be removed
+  @rtype: int
+  @return: the desired exit code
 
   """
   instance_name = args[0]
   force = opts.force
 
   """
   instance_name = args[0]
   force = opts.force
+  cl = GetClient()
 
   if not force:
 
   if not force:
+    _EnsureInstancesExist(cl, [instance_name])
+
     usertext = ("This will remove the volumes of the instance %s"
                 " (including mirrors), thus removing all the data"
                 " of the instance. Continue?") % instance_name
     usertext = ("This will remove the volumes of the instance %s"
                 " (including mirrors), thus removing all the data"
                 " of the instance. Continue?") % instance_name
-    if not opts._ask_user(usertext):
+    if not AskUser(usertext):
       return 1
 
       return 1
 
-  op = opcodes.OpRemoveInstance(instance_name=instance_name)
-  SubmitOpCode(op)
+  op = opcodes.OpRemoveInstance(instance_name=instance_name,
+                                ignore_failures=opts.ignore_failures)
+  SubmitOrSend(op, opts, cl=cl)
+  return 0
+
+
+def RenameInstance(opts, args):
+  """Rename an instance.
+
+  @param opts: the command line options selected by the user
+  @type args: list
+  @param args: should contain two elements, the old and the
+      new instance names
+  @rtype: int
+  @return: the desired exit code
+
+  """
+  op = opcodes.OpRenameInstance(instance_name=args[0],
+                                new_name=args[1],
+                                ignore_ip=opts.ignore_ip)
+  SubmitOrSend(op, opts)
   return 0
 
 
   return 0
 
 
@@ -150,104 +619,210 @@ def ActivateDisks(opts, args):
   """Activate an instance's disks.
 
   This serves two purposes:
   """Activate an instance's disks.
 
   This serves two purposes:
-    - it allows one (as long as the instance is not running) to mount
-    the disks and modify them from the node
+    - it allows (as long as the instance is not running)
+      mounting the disks and modifying them from the node
     - it repairs inactive secondary drbds
 
     - it repairs inactive secondary drbds
 
+  @param opts: the command line options selected by the user
+  @type args: list
+  @param args: should contain only one element, the instance name
+  @rtype: int
+  @return: the desired exit code
+
   """
   instance_name = args[0]
   op = opcodes.OpActivateInstanceDisks(instance_name=instance_name)
   """
   instance_name = args[0]
   op = opcodes.OpActivateInstanceDisks(instance_name=instance_name)
-  disks_info = SubmitOpCode(op)
+  disks_info = SubmitOrSend(op, opts)
   for host, iname, nname in disks_info:
   for host, iname, nname in disks_info:
-    print "%s:%s:%s" % (host, iname, nname)
+    ToStdout("%s:%s:%s", host, iname, nname)
   return 0
 
 
 def DeactivateDisks(opts, args):
   return 0
 
 
 def DeactivateDisks(opts, args):
-  """Command-line interface for _ShutdownInstanceBlockDevices.
+  """Deactivate an instance's disks..
 
   This function takes the instance name, looks for its primary node
   and the tries to shutdown its block devices on that node.
 
 
   This function takes the instance name, looks for its primary node
   and the tries to shutdown its block devices on that node.
 
+  @param opts: the command line options selected by the user
+  @type args: list
+  @param args: should contain only one element, the instance name
+  @rtype: int
+  @return: the desired exit code
+
   """
   instance_name = args[0]
   op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
   """
   instance_name = args[0]
   op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
-  SubmitOpCode(op)
+  SubmitOrSend(op, opts)
   return 0
 
 
   return 0
 
 
-def StartupInstance(opts, args):
-  """Shutdown an instance.
+def GrowDisk(opts, args):
+  """Grow an instance's disks.
 
 
-  Args:
-    opts - class with options as members
-    args - list containing a single element, the instance name
+  @param opts: the command line options selected by the user
+  @type args: list
+  @param args: should contain two elements, the instance name
+      whose disks we grow and the disk name, e.g. I{sda}
+  @rtype: int
+  @return: the desired exit code
 
   """
 
   """
-  instance_name = args[0]
-  op = opcodes.OpStartupInstance(instance_name=instance_name, force=opts.force,
-                                 extra_args=opts.extra_args)
-  SubmitOpCode(op)
+  instance = args[0]
+  disk = args[1]
+  try:
+    disk = int(disk)
+  except ValueError, err:
+    raise errors.OpPrereqError("Invalid disk index: %s" % str(err))
+  amount = utils.ParseUnit(args[2])
+  op = opcodes.OpGrowDisk(instance_name=instance, disk=disk, amount=amount,
+                          wait_for_sync=opts.wait_for_sync)
+  SubmitOrSend(op, opts)
   return 0
 
 
   return 0
 
 
-def ShutdownInstance(opts, args):
-  """Shutdown an instance.
+def StartupInstance(opts, args):
+  """Startup instances.
 
 
-  Args:
-    opts - class with options as members
-    args - list containing a single element, the instance name
+  Depending on the options given, this will start one or more
+  instances.
+
+  @param opts: the command line options selected by the user
+  @type args: list
+  @param args: the instance or node names based on which we
+      create the final selection (in conjunction with the
+      opts argument)
+  @rtype: int
+  @return: the desired exit code
 
   """
 
   """
-  instance_name = args[0]
-  op = opcodes.OpShutdownInstance(instance_name=instance_name)
-  SubmitOpCode(op)
+  cl = GetClient()
+  if opts.multi_mode is None:
+    opts.multi_mode = _SHUTDOWN_INSTANCES
+  inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
+  if not inames:
+    raise errors.OpPrereqError("Selection filter does not match any instances")
+  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
+  if not (opts.force_multi or not multi_on
+          or _ConfirmOperation(inames, "startup")):
+    return 1
+  jex = cli.JobExecutor(verbose=multi_on, cl=cl)
+  for name in inames:
+    op = opcodes.OpStartupInstance(instance_name=name,
+                                   force=opts.force)
+    # do not add these parameters to the opcode unless they're defined
+    if opts.hvparams:
+      op.hvparams = opts.hvparams
+    if opts.beparams:
+      op.beparams = opts.beparams
+    jex.QueueJob(name, op)
+  jex.WaitOrShow(not opts.submit_only)
   return 0
 
 
   return 0
 
 
-def AddMDDRBDComponent(opts, args):
-  """Add a new component to a remote_raid1 disk.
+def RebootInstance(opts, args):
+  """Reboot instance(s).
 
 
-  Args:
-    opts - class with options as members
-    args - list with a single element, the instance name
+  Depending on the parameters given, this will reboot one or more
+  instances.
+
+  @param opts: the command line options selected by the user
+  @type args: list
+  @param args: the instance or node names based on which we
+      create the final selection (in conjunction with the
+      opts argument)
+  @rtype: int
+  @return: the desired exit code
 
   """
 
   """
-  op = opcodes.OpAddMDDRBDComponent(instance_name=args[0],
-                                    disk_name=opts.disk,
-                                    remote_node=opts.node)
-  SubmitOpCode(op)
+  cl = GetClient()
+  if opts.multi_mode is None:
+    opts.multi_mode = _SHUTDOWN_INSTANCES
+  inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
+  if not inames:
+    raise errors.OpPrereqError("Selection filter does not match any instances")
+  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
+  if not (opts.force_multi or not multi_on
+          or _ConfirmOperation(inames, "reboot")):
+    return 1
+  jex = JobExecutor(verbose=multi_on, cl=cl)
+  for name in inames:
+    op = opcodes.OpRebootInstance(instance_name=name,
+                                  reboot_type=opts.reboot_type,
+                                  ignore_secondaries=opts.ignore_secondaries)
+    jex.QueueJob(name, op)
+  jex.WaitOrShow(not opts.submit_only)
   return 0
 
 
   return 0
 
 
-def RemoveMDDRBDComponent(opts, args):
-  """Connect to the console of an instance
+def ShutdownInstance(opts, args):
+  """Shutdown an instance.
 
 
-  Args:
-    opts - class with options as members
-    args - list with a single element, the instance name
+  @param opts: the command line options selected by the user
+  @type args: list
+  @param args: the instance or node names based on which we
+      create the final selection (in conjunction with the
+      opts argument)
+  @rtype: int
+  @return: the desired exit code
 
   """
 
   """
-  op = opcodes.OpRemoveMDDRBDComponent(instance_name=args[0],
-                                       disk_name=opts.disk,
-                                       disk_id=opts.port)
-  SubmitOpCode(op)
+  cl = GetClient()
+  if opts.multi_mode is None:
+    opts.multi_mode = _SHUTDOWN_INSTANCES
+  inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
+  if not inames:
+    raise errors.OpPrereqError("Selection filter does not match any instances")
+  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
+  if not (opts.force_multi or not multi_on
+          or _ConfirmOperation(inames, "shutdown")):
+    return 1
+
+  jex = cli.JobExecutor(verbose=multi_on, cl=cl)
+  for name in inames:
+    op = opcodes.OpShutdownInstance(instance_name=name)
+    jex.QueueJob(name, op)
+  jex.WaitOrShow(not opts.submit_only)
   return 0
 
 
 def ReplaceDisks(opts, args):
   """Replace the disks of an instance
 
   return 0
 
 
 def ReplaceDisks(opts, args):
   """Replace the disks of an instance
 
-  Args:
-    opts - class with options as members
-    args - list with a single element, the instance name
+  @param opts: the command line options selected by the user
+  @type args: list
+  @param args: should contain only one element, the instance name
+  @rtype: int
+  @return: the desired exit code
 
   """
   instance_name = args[0]
 
   """
   instance_name = args[0]
-  new_secondary = opts.new_secondary
-  op = opcodes.OpReplaceDisks(instance_name=args[0],
-                              remote_node=opts.new_secondary)
-  SubmitOpCode(op)
+  new_2ndary = opts.new_secondary
+  iallocator = opts.iallocator
+  if opts.disks is None:
+    disks = []
+  else:
+    try:
+      disks = [int(i) for i in opts.disks.split(",")]
+    except ValueError, err:
+      raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
+  cnt = [opts.on_primary, opts.on_secondary,
+         new_2ndary is not None, iallocator is not None].count(True)
+  if cnt != 1:
+    raise errors.OpPrereqError("One and only one of the -p, -s, -n and -i"
+                               " options must be passed")
+  elif opts.on_primary:
+    mode = constants.REPLACE_DISK_PRI
+  elif opts.on_secondary:
+    mode = constants.REPLACE_DISK_SEC
+  elif new_2ndary is not None or iallocator is not None:
+    # replace secondary
+    mode = constants.REPLACE_DISK_CHG
+
+  op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
+                              remote_node=new_2ndary, mode=mode,
+                              iallocator=iallocator)
+  SubmitOrSend(op, opts)
   return 0
 
 
   return 0
 
 
@@ -257,115 +832,277 @@ def FailoverInstance(opts, args):
   The failover is done by shutting it down on its present node and
   starting it on the secondary.
 
   The failover is done by shutting it down on its present node and
   starting it on the secondary.
 
-  Args:
-    opts - class with options as members
-    args - list with a single element, the instance name
-  Opts used:
-    force - whether to failover without asking questions.
+  @param opts: the command line options selected by the user
+  @type args: list
+  @param args: should contain only one element, the instance name
+  @rtype: int
+  @return: the desired exit code
 
   """
 
   """
+  cl = GetClient()
   instance_name = args[0]
   force = opts.force
 
   if not force:
   instance_name = args[0]
   force = opts.force
 
   if not force:
+    _EnsureInstancesExist(cl, [instance_name])
+
     usertext = ("Failover will happen to image %s."
                 " This requires a shutdown of the instance. Continue?" %
                 (instance_name,))
     usertext = ("Failover will happen to image %s."
                 " This requires a shutdown of the instance. Continue?" %
                 (instance_name,))
-    usertext = textwrap.fill(usertext)
-    if not opts._ask_user(usertext):
+    if not AskUser(usertext):
       return 1
 
   op = opcodes.OpFailoverInstance(instance_name=instance_name,
                                   ignore_consistency=opts.ignore_consistency)
       return 1
 
   op = opcodes.OpFailoverInstance(instance_name=instance_name,
                                   ignore_consistency=opts.ignore_consistency)
-  SubmitOpCode(op)
+  SubmitOrSend(op, opts, cl=cl)
+  return 0
+
+
+def MigrateInstance(opts, args):
+  """Migrate an instance.
+
+  The migrate is done without shutdown.
+
+  @param opts: the command line options selected by the user
+  @type args: list
+  @param args: should contain only one element, the instance name
+  @rtype: int
+  @return: the desired exit code
+
+  """
+  cl = GetClient()
+  instance_name = args[0]
+  force = opts.force
+
+  if not force:
+    _EnsureInstancesExist(cl, [instance_name])
+
+    if opts.cleanup:
+      usertext = ("Instance %s will be recovered from a failed migration."
+                  " Note that the migration procedure (including cleanup)" %
+                  (instance_name,))
+    else:
+      usertext = ("Instance %s will be migrated. Note that migration" %
+                  (instance_name,))
+    usertext += (" is **experimental** in this version."
+                " This might impact the instance if anything goes wrong."
+                " Continue?")
+    if not AskUser(usertext):
+      return 1
+
+  op = opcodes.OpMigrateInstance(instance_name=instance_name, live=opts.live,
+                                 cleanup=opts.cleanup)
+  SubmitOpCode(op, cl=cl)
   return 0
 
 
 def ConnectToInstanceConsole(opts, args):
   """Connect to the console of an instance.
 
   return 0
 
 
 def ConnectToInstanceConsole(opts, args):
   """Connect to the console of an instance.
 
-  Args:
-    opts - class with options as members
-    args - list with a single element, the instance name
+  @param opts: the command line options selected by the user
+  @type args: list
+  @param args: should contain only one element, the instance name
+  @rtype: int
+  @return: the desired exit code
 
   """
   instance_name = args[0]
 
   op = opcodes.OpConnectConsole(instance_name=instance_name)
 
   """
   instance_name = args[0]
 
   op = opcodes.OpConnectConsole(instance_name=instance_name)
-  node, console_cmd = SubmitOpCode(op)
-  # drop lock and exec so other commands can run while we have console
-  utils.Unlock("cmd")
-  try:
-    os.execv("/usr/bin/ssh", ["ssh", "-qt", node, console_cmd])
-  finally:
-    sys.stderr.write("Can't run console command %s on node %s" %
-                     (console_cmd, node))
-    os._exit(1)
+  cmd = SubmitOpCode(op)
+
+  if opts.show_command:
+    ToStdout("%s", utils.ShellQuoteArgs(cmd))
+  else:
+    try:
+      os.execvp(cmd[0], cmd)
+    finally:
+      ToStderr("Can't run console command %s with arguments:\n'%s'",
+               cmd[0], " ".join(cmd))
+      os._exit(1)
+
+
+def _FormatLogicalID(dev_type, logical_id):
+  """Formats the logical_id of a disk.
+
+  """
+  if dev_type == constants.LD_DRBD8:
+    node_a, node_b, port, minor_a, minor_b, key = logical_id
+    data = [
+      ("nodeA", "%s, minor=%s" % (node_a, minor_a)),
+      ("nodeB", "%s, minor=%s" % (node_b, minor_b)),
+      ("port", port),
+      ("auth key", key),
+      ]
+  elif dev_type == constants.LD_LV:
+    vg_name, lv_name = logical_id
+    data = ["%s/%s" % (vg_name, lv_name)]
+  else:
+    data = [str(logical_id)]
+
+  return data
 
 
 
 
-def _FormatBlockDevInfo(buf, dev, indent_level):
+def _FormatBlockDevInfo(idx, top_level, dev, static):
   """Show block device information.
 
   """Show block device information.
 
-  This is only used by ShowInstanceConfig(), but it's too big to be
+  This is only used by L{ShowInstanceConfig}, but it's too big to be
   left for an inline definition.
 
   left for an inline definition.
 
+  @type idx: int
+  @param idx: the index of the current disk
+  @type top_level: boolean
+  @param top_level: if this a top-level disk?
+  @type dev: dict
+  @param dev: dictionary with disk information
+  @type static: boolean
+  @param static: wheter the device information doesn't contain
+      runtime information but only static data
+  @return: a list of either strings, tuples or lists
+      (which should be formatted at a higher indent level)
+
   """
   """
-  def helper(buf, dtype, status):
-    """Format one line for phsyical device status."""
+  def helper(dtype, status):
+    """Format one line for physical device status.
+
+    @type dtype: str
+    @param dtype: a constant from the L{constants.LDS_BLOCK} set
+    @type status: tuple
+    @param status: a tuple as returned from L{backend.FindBlockDevice}
+    @return: the string representing the status
+
+    """
     if not status:
     if not status:
-      buf.write("not active\n")
+      return "not active"
+    txt = ""
+    (path, major, minor, syncp, estt, degr, ldisk) = status
+    if major is None:
+      major_string = "N/A"
     else:
     else:
-      (path, major, minor, syncp, estt, degr) = status
-      buf.write("%s (%d:%d)" % (path, major, minor))
-      if dtype in ("md_raid1", "drbd"):
-        if syncp is not None:
-          sync_text = "*RECOVERING* %5.2f%%," % syncp
-          if estt:
-            sync_text += " ETA %ds" % estt
-          else:
-            sync_text += " ETA unknown"
-        else:
-          sync_text = "in sync"
-        if degr:
-          degr_text = "*DEGRADED*"
-        else:
-          degr_text = "ok"
-        buf.write(" %s, status %s" % (sync_text, degr_text))
-      buf.write("\n")
+      major_string = str(major)
 
 
-  if dev["iv_name"] is not None:
-    data = "  - %s, " % dev["iv_name"]
+    if minor is None:
+      minor_string = "N/A"
+    else:
+      minor_string = str(minor)
+
+    txt += ("%s (%s:%s)" % (path, major_string, minor_string))
+    if dtype in (constants.LD_DRBD8, ):
+      if syncp is not None:
+        sync_text = "*RECOVERING* %5.2f%%," % syncp
+        if estt:
+          sync_text += " ETA %ds" % estt
+        else:
+          sync_text += " ETA unknown"
+      else:
+        sync_text = "in sync"
+      if degr:
+        degr_text = "*DEGRADED*"
+      else:
+        degr_text = "ok"
+      if ldisk:
+        ldisk_text = " *MISSING DISK*"
+      else:
+        ldisk_text = ""
+      txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
+    elif dtype == constants.LD_LV:
+      if ldisk:
+        ldisk_text = " *FAILED* (failed drive?)"
+      else:
+        ldisk_text = ""
+      txt += ldisk_text
+    return txt
+
+  # the header
+  if top_level:
+    if dev["iv_name"] is not None:
+      txt = dev["iv_name"]
+    else:
+      txt = "disk %d" % idx
   else:
   else:
-    data = "  - "
-  data += "type: %s" % dev["dev_type"]
+    txt = "child %d" % idx
+  d1 = ["- %s: %s" % (txt, dev["dev_type"])]
+  data = []
+  if top_level:
+    data.append(("access mode", dev["mode"]))
   if dev["logical_id"] is not None:
   if dev["logical_id"] is not None:
-    data += ", logical_id: %s" % (dev["logical_id"],)
+    try:
+      l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"])
+    except ValueError:
+      l_id = [str(dev["logical_id"])]
+    if len(l_id) == 1:
+      data.append(("logical_id", l_id[0]))
+    else:
+      data.extend(l_id)
   elif dev["physical_id"] is not None:
   elif dev["physical_id"] is not None:
-    data += ", physical_id: %s" % (dev["physical_id"],)
-  buf.write("%*s%s\n" % (2*indent_level, "", data))
-  buf.write("%*s    primary:   " % (2*indent_level, ""))
-  helper(buf, dev["dev_type"], dev["pstatus"])
-
-  if dev["sstatus"]:
-    buf.write("%*s    secondary: " % (2*indent_level, ""))
-    helper(buf, dev["dev_type"], dev["sstatus"])
+    data.append("physical_id:")
+    data.append([dev["physical_id"]])
+  if not static:
+    data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
+  if dev["sstatus"] and not static:
+    data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
 
   if dev["children"]:
 
   if dev["children"]:
-    for child in dev["children"]:
-      _FormatBlockDevInfo(buf, child, indent_level+1)
+    data.append("child devices:")
+    for c_idx, child in enumerate(dev["children"]):
+      data.append(_FormatBlockDevInfo(c_idx, False, child, static))
+  d1.append(data)
+  return d1
 
 
 
 
+def _FormatList(buf, data, indent_level):
+  """Formats a list of data at a given indent level.
+
+  If the element of the list is:
+    - a string, it is simply formatted as is
+    - a tuple, it will be split into key, value and the all the
+      values in a list will be aligned all at the same start column
+    - a list, will be recursively formatted
+
+  @type buf: StringIO
+  @param buf: the buffer into which we write the output
+  @param data: the list to format
+  @type indent_level: int
+  @param indent_level: the indent level to format at
+
+  """
+  max_tlen = max([len(elem[0]) for elem in data
+                 if isinstance(elem, tuple)] or [0])
+  for elem in data:
+    if isinstance(elem, basestring):
+      buf.write("%*s%s\n" % (2*indent_level, "", elem))
+    elif isinstance(elem, tuple):
+      key, value = elem
+      spacer = "%*s" % (max_tlen - len(key), "")
+      buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
+    elif isinstance(elem, list):
+      _FormatList(buf, elem, indent_level+1)
+
 def ShowInstanceConfig(opts, args):
   """Compute instance run-time status.
 
 def ShowInstanceConfig(opts, args):
   """Compute instance run-time status.
 
+  @param opts: the command line options selected by the user
+  @type args: list
+  @param args: either an empty list, and then we query all
+      instances, or should contain a list of instance names
+  @rtype: int
+  @return: the desired exit code
+
   """
   """
+  if not args and not opts.show_all:
+    ToStderr("No instance selected."
+             " Please pass in --all if you want to query all instances.\n"
+             "Note that this can take a long time on a big cluster.")
+    return 1
+  elif args and opts.show_all:
+    ToStderr("Cannot use --all if you specify instance names.")
+    return 1
 
   retcode = 0
 
   retcode = 0
-  op = opcodes.OpQueryInstanceData(instances=args)
+  op = opcodes.OpQueryInstanceData(instances=args, static=opts.static)
   result = SubmitOpCode(op)
   result = SubmitOpCode(op)
-
   if not result:
   if not result:
-    logger.ToStdout("No instances.")
+    ToStdout("No instances.")
     return 1
 
   buf = StringIO()
     return 1
 
   buf = StringIO()
@@ -373,184 +1110,429 @@ def ShowInstanceConfig(opts, args):
   for instance_name in result:
     instance = result[instance_name]
     buf.write("Instance name: %s\n" % instance["name"])
   for instance_name in result:
     instance = result[instance_name]
     buf.write("Instance name: %s\n" % instance["name"])
-    buf.write("State: configured to be %s, actual state is %s\n" %
-              (instance["config_state"], instance["run_state"]))
+    buf.write("State: configured to be %s" % instance["config_state"])
+    if not opts.static:
+      buf.write(", actual state is %s" % instance["run_state"])
+    buf.write("\n")
+    ##buf.write("Considered for memory checks in cluster verify: %s\n" %
+    ##          instance["auto_balance"])
     buf.write("  Nodes:\n")
     buf.write("    - primary: %s\n" % instance["pnode"])
     buf.write("    - secondaries: %s\n" % ", ".join(instance["snodes"]))
     buf.write("  Operating system: %s\n" % instance["os"])
     buf.write("  Nodes:\n")
     buf.write("    - primary: %s\n" % instance["pnode"])
     buf.write("    - secondaries: %s\n" % ", ".join(instance["snodes"]))
     buf.write("  Operating system: %s\n" % instance["os"])
+    if instance.has_key("network_port"):
+      buf.write("  Allocated network port: %s\n" % instance["network_port"])
+    buf.write("  Hypervisor: %s\n" % instance["hypervisor"])
+
+    # custom VNC console information
+    vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
+                                                 None)
+    if vnc_bind_address:
+      port = instance["network_port"]
+      display = int(port) - constants.VNC_BASE_PORT
+      if display > 0 and vnc_bind_address == constants.BIND_ADDRESS_GLOBAL:
+        vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
+                                                   port,
+                                                   display)
+      elif display > 0 and utils.IsValidIP(vnc_bind_address):
+        vnc_console_port = ("%s:%s (node %s) (display %s)" %
+                             (vnc_bind_address, port,
+                              instance["pnode"], display))
+      else:
+        # vnc bind address is a file
+        vnc_console_port = "%s:%s" % (instance["pnode"],
+                                      vnc_bind_address)
+      buf.write("    - console connection: vnc to %s\n" % vnc_console_port)
+
+    for key in instance["hv_actual"]:
+      if key in instance["hv_instance"]:
+        val = instance["hv_instance"][key]
+      else:
+        val = "default (%s)" % instance["hv_actual"][key]
+      buf.write("    - %s: %s\n" % (key, val))
     buf.write("  Hardware:\n")
     buf.write("  Hardware:\n")
-    buf.write("    - memory: %dMiB\n" % instance["memory"])
-    buf.write("    - NICs: %s\n" %
-        ", ".join(["{MAC: %s, IP: %s, bridge: %s}" %
-                   (mac, ip, bridge)
-                     for mac, ip, bridge in instance["nics"]]))
-    buf.write("  Block devices:\n")
-
-    for device in instance["disks"]:
-      _FormatBlockDevInfo(buf, device, 1)
-
-  logger.ToStdout(buf.getvalue().rstrip('\n'))
+    buf.write("    - VCPUs: %d\n" %
+              instance["be_actual"][constants.BE_VCPUS])
+    buf.write("    - memory: %dMiB\n" %
+              instance["be_actual"][constants.BE_MEMORY])
+    buf.write("    - NICs:\n")
+    for idx, (mac, ip, bridge) in enumerate(instance["nics"]):
+      buf.write("      - nic/%d: MAC: %s, IP: %s, bridge: %s\n" %
+                (idx, mac, ip, bridge))
+    buf.write("  Disks:\n")
+
+    for idx, device in enumerate(instance["disks"]):
+      _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static), 2)
+
+  ToStdout(buf.getvalue().rstrip('\n'))
   return retcode
 
 
   return retcode
 
 
-def SetInstanceParms(opts, args):
+def SetInstanceParams(opts, args):
   """Modifies an instance.
 
   All parameters take effect only at the next restart of the instance.
 
   """Modifies an instance.
 
   All parameters take effect only at the next restart of the instance.
 
-  Args:
-    opts - class with options as members
-    args - list with a single element, the instance name
-  Opts used:
-    memory - the new memory size
-    vcpus - the new number of cpus
+  @param opts: the command line options selected by the user
+  @type args: list
+  @param args: should contain only one element, the instance name
+  @rtype: int
+  @return: the desired exit code
 
   """
 
   """
-  if not opts.mem and not opts.vcpus and not opts.ip and not opts.bridge:
-    logger.ToStdout("Please give at least one of the parameters.")
+  if not (opts.nics or opts.disks or
+          opts.hypervisor or opts.beparams):
+    ToStderr("Please give at least one of the parameters.")
     return 1
 
     return 1
 
-  op = opcodes.OpSetInstanceParms(instance_name=args[0], mem=opts.mem,
-                                  vcpus=opts.vcpus, ip=opts.ip,
-                                  bridge=opts.bridge)
-  result = SubmitOpCode(op)
+  for param in opts.beparams:
+    if isinstance(opts.beparams[param], basestring):
+      if opts.beparams[param].lower() == "default":
+        opts.beparams[param] = constants.VALUE_DEFAULT
+
+  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
+                      allowed_values=[constants.VALUE_DEFAULT])
+
+  for param in opts.hypervisor:
+    if isinstance(opts.hypervisor[param], basestring):
+      if opts.hypervisor[param].lower() == "default":
+        opts.hypervisor[param] = constants.VALUE_DEFAULT
+
+  utils.ForceDictType(opts.hypervisor, 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 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 ValueError:
+      pass
+    if disk_op == constants.DDM_ADD:
+      if 'size' not in disk_dict:
+        raise errors.OpPrereqError("Missing required parameter 'size'")
+      disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
+
+  op = opcodes.OpSetInstanceParams(instance_name=args[0],
+                                   nics=opts.nics,
+                                   disks=opts.disks,
+                                   hvparams=opts.hypervisor,
+                                   beparams=opts.beparams,
+                                   force=opts.force)
+
+  # even if here we process the result, we allow submit only
+  result = SubmitOrSend(op, opts)
 
   if result:
 
   if result:
-    logger.ToStdout("Modified instance %s" % args[0])
+    ToStdout("Modified instance %s", args[0])
     for param, data in result:
     for param, data in result:
-      logger.ToStdout(" - %-5s -> %s" % (param, data))
-    logger.ToStdout("Please don't forget that these parameters take effect"
-                    " only at the next start of the instance.")
+      ToStdout(" - %-5s -> %s", param, data)
+    ToStdout("Please don't forget that these parameters take effect"
+             " only at the next start of the instance.")
   return 0
 
 
 # options used in more than one cmd
 node_opt = make_option("-n", "--node", dest="node", help="Target node",
                        metavar="<node>")
   return 0
 
 
 # options used in more than one cmd
 node_opt = make_option("-n", "--node", dest="node", help="Target node",
                        metavar="<node>")
-force_opt = make_option("-f", "--force", dest="force", action="store_true",
-                        default=False, help="Force the operation")
+
+os_opt = cli_option("-o", "--os-type", dest="os", help="What OS to run",
+                    metavar="<os>")
+
+# multi-instance selection options
+m_force_multi = make_option("--force-multiple", dest="force_multi",
+                            help="Do not ask for confirmation when more than"
+                            " one instance is affected",
+                            action="store_true", default=False)
+
+m_pri_node_opt = make_option("--primary", dest="multi_mode",
+                             help="Filter by nodes (primary only)",
+                             const=_SHUTDOWN_NODES_PRI, action="store_const")
+
+m_sec_node_opt = make_option("--secondary", dest="multi_mode",
+                             help="Filter by nodes (secondary only)",
+                             const=_SHUTDOWN_NODES_SEC, action="store_const")
+
+m_node_opt = make_option("--node", dest="multi_mode",
+                         help="Filter by nodes (primary and secondary)",
+                         const=_SHUTDOWN_NODES_BOTH, action="store_const")
+
+m_clust_opt = make_option("--all", dest="multi_mode",
+                          help="Select all instances in the cluster",
+                          const=_SHUTDOWN_CLUSTER, action="store_const")
+
+m_inst_opt = make_option("--instance", dest="multi_mode",
+                         help="Filter by instance name [default]",
+                         const=_SHUTDOWN_INSTANCES, action="store_const")
+
 
 # this is defined separately due to readability only
 add_opts = [
   DEBUG_OPT,
 
 # this is defined separately due to readability only
 add_opts = [
   DEBUG_OPT,
-  node_opt,
-  cli_option("-s", "--os-size", dest="size", help="Disk size",
-             default=20 * 1024, type="unit", metavar="<size>"),
-  cli_option("--swap-size", dest="swap", help="Swap size",
-             default=4 * 1024, type="unit", metavar="<size>"),
-  cli_option("-o", "--os-type", dest="os", help="What OS to run",
-             metavar="<os>"),
-  cli_option("-m", "--memory", dest="mem", help="Memory size",
-              default=128, type="unit", metavar="<mem>"),
-  make_option("-p", "--cpu", dest="vcpus", help="Number of virtual CPUs",
-              default=1, type="int", metavar="<PROC>"),
+  make_option("-n", "--node", dest="node",
+              help="Target node and optional secondary node",
+              metavar="<pnode>[:<snode>]"),
+  os_opt,
+  keyval_option("-B", "--backend", dest="beparams",
+                type="keyval", default={},
+                help="Backend parameters"),
   make_option("-t", "--disk-template", dest="disk_template",
   make_option("-t", "--disk-template", dest="disk_template",
-              help="Custom disk setup (diskless, plain, local_raid1 or"
-              " remote_raid1)", default=None, metavar="TEMPL"),
-  make_option("-i", "--ip", dest="ip",
-              help="IP address ('none' [default], 'auto', or specify address)",
-              default='none', type="string", metavar="<ADDRESS>"),
+              help="Custom disk setup (diskless, file, plain or drbd)",
+              default=None, metavar="TEMPL"),
+  cli_option("-s", "--os-size", dest="sd_size", help="Disk size for a"
+             " single-disk configuration, when not using the --disk option,"
+             " in MiB unless a suffix is used",
+             default=None, type="unit", metavar="<size>"),
+  ikv_option("--disk", help="Disk information",
+             default=[], dest="disks",
+             action="append",
+             type="identkeyval"),
+  ikv_option("--net", help="NIC information",
+             default=[], dest="nics",
+             action="append",
+             type="identkeyval"),
+  make_option("--no-nics", default=False, action="store_true",
+              help="Do not create any network cards for the instance"),
   make_option("--no-wait-for-sync", dest="wait_for_sync", default=True,
               action="store_false", help="Don't wait for sync (DANGEROUS!)"),
   make_option("--no-wait-for-sync", dest="wait_for_sync", default=True,
               action="store_false", help="Don't wait for sync (DANGEROUS!)"),
-  make_option("--secondary-node", dest="snode",
-              help="Secondary node for remote_raid1 disk layout",
-              metavar="<node>"),
-  make_option("-b", "--bridge", dest="bridge",
-              help="Bridge to connect this instance to",
-              default=None, metavar="<bridge>")
+  make_option("--no-start", dest="start", default=True,
+              action="store_false", help="Don't start the instance after"
+              " creation"),
+  make_option("--no-ip-check", dest="ip_check", default=True,
+              action="store_false", help="Don't check that the instance's IP"
+              " is alive (only valid with --no-start)"),
+  make_option("--file-storage-dir", dest="file_storage_dir",
+              help="Relative path under default cluster-wide file storage dir"
+              " to store file-based disks", default=None,
+              metavar="<DIR>"),
+  make_option("--file-driver", dest="file_driver", help="Driver to use"
+              " for image files", default="loop", metavar="<DRIVER>"),
+  make_option("-I", "--iallocator", metavar="<NAME>",
+              help="Select nodes for the instance automatically using the"
+              " <NAME> iallocator plugin", default=None, type="string"),
+  ikv_option("-H", "--hypervisor", dest="hypervisor",
+              help="Hypervisor and hypervisor options, in the format"
+              " hypervisor:option=value,option=value,...", default=None,
+              type="identkeyval"),
+  SUBMIT_OPT,
   ]
 
   ]
 
-
 commands = {
   'add': (AddInstance, ARGS_ONE, add_opts,
 commands = {
   'add': (AddInstance, ARGS_ONE, add_opts,
-          "[opts...] <name>",
+          "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
           "Creates and adds a new instance to the cluster"),
           "Creates and adds a new instance to the cluster"),
-  'add-mirror': (AddMDDRBDComponent, ARGS_ONE,
-                [DEBUG_OPT, node_opt,
-                 make_option("-b", "--disk", dest="disk", metavar="sdX",
-                             help=("The name of the instance disk for which to"
-                                   " add the mirror"))],
-                "-n node -b disk <instance>",
-                "Creates a new mirror for the instance"),
-  'console': (ConnectToInstanceConsole, ARGS_ONE, [DEBUG_OPT],
-              "<instance>",
+  'batch-create': (BatchCreate, ARGS_ONE,
+                   [DEBUG_OPT],
+                   "<instances_file.json>",
+                   "Create a bunch of instances based on specs in the file."),
+  'console': (ConnectToInstanceConsole, ARGS_ONE,
+              [DEBUG_OPT,
+               make_option("--show-cmd", dest="show_command",
+                           action="store_true", default=False,
+                           help=("Show command instead of executing it"))],
+              "[--show-cmd] <instance>",
               "Opens a console on the specified instance"),
   'failover': (FailoverInstance, ARGS_ONE,
               "Opens a console on the specified instance"),
   'failover': (FailoverInstance, ARGS_ONE,
-               [DEBUG_OPT, force_opt,
+               [DEBUG_OPT, FORCE_OPT,
                 make_option("--ignore-consistency", dest="ignore_consistency",
                             action="store_true", default=False,
                             help="Ignore the consistency of the disks on"
                             " the secondary"),
                 make_option("--ignore-consistency", dest="ignore_consistency",
                             action="store_true", default=False,
                             help="Ignore the consistency of the disks on"
                             " the secondary"),
+                SUBMIT_OPT,
                 ],
                "[-f] <instance>",
                "Stops the instance and starts it on the backup node, using"
                 ],
                "[-f] <instance>",
                "Stops the instance and starts it on the backup node, using"
-               " the remote mirror (only for instances of type remote_raid1)"),
-  'info': (ShowInstanceConfig, ARGS_ANY, [DEBUG_OPT], "[<instance>...]",
-           "Show information on the specified instance"),
-  'list': (ListInstances, ARGS_NONE,
-           [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT,
-            make_option("-o", "--output", dest="output", action="store",
-                        type="string", help="Select output fields",
-                        metavar="FIELDS")
-            ],
-           "", "Lists the instances and their status"),
-  'remove': (RemoveInstance, ARGS_ONE, [DEBUG_OPT, force_opt],
+               " the remote mirror (only for instances of type drbd)"),
+  'migrate': (MigrateInstance, ARGS_ONE,
+               [DEBUG_OPT, FORCE_OPT,
+                make_option("--non-live", dest="live",
+                            default=True, action="store_false",
+                            help="Do a non-live migration (this usually means"
+                            " freeze the instance, save the state,"
+                            " transfer and only then resume running on the"
+                            " secondary node)"),
+                make_option("--cleanup", dest="cleanup",
+                            default=False, action="store_true",
+                            help="Instead of performing the migration, try to"
+                            " recover from a failed cleanup. This is safe"
+                            " to run even if the instance is healthy, but it"
+                            " will create extra replication traffic and "
+                            " disrupt briefly the replication (like during the"
+                            " migration"),
+                ],
+               "[-f] <instance>",
+               "Migrate instance to its secondary node"
+               " (only for instances of type drbd)"),
+  'info': (ShowInstanceConfig, ARGS_ANY,
+           [DEBUG_OPT,
+            make_option("-s", "--static", dest="static",
+                        action="store_true", default=False,
+                        help="Only show configuration data, not runtime data"),
+            make_option("--all", dest="show_all",
+                        default=False, action="store_true",
+                        help="Show info on all instances on the cluster."
+                        " This can take a long time to run, use wisely."),
+            ], "[-s] {--all | <instance>...}",
+           "Show information on the specified instance(s)"),
+  'list': (ListInstances, ARGS_ANY,
+           [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, SYNC_OPT],
+           "[<instance>...]",
+           "Lists the instances and their status. The available fields are"
+           " (see the man page for details): status, oper_state, oper_ram,"
+           " name, os, pnode, snodes, admin_state, admin_ram, disk_template,"
+           " ip, mac, bridge, sda_size, sdb_size, vcpus, serial_no,"
+           " hypervisor."
+           " The default field"
+           " list is (in order): %s." % ", ".join(_LIST_DEF_FIELDS),
+           ),
+  'reinstall': (ReinstallInstance, ARGS_ANY,
+                [DEBUG_OPT, FORCE_OPT, os_opt,
+                 m_force_multi,
+                 m_node_opt, m_pri_node_opt, m_sec_node_opt,
+                 m_clust_opt, m_inst_opt,
+                 make_option("--select-os", dest="select_os",
+                             action="store_true", default=False,
+                             help="Interactive OS reinstall, lists available"
+                             " OS templates for selection"),
+                 SUBMIT_OPT,
+                 ],
+                "[-f] <instance>", "Reinstall a stopped instance"),
+  'remove': (RemoveInstance, ARGS_ONE,
+             [DEBUG_OPT, FORCE_OPT,
+              make_option("--ignore-failures", dest="ignore_failures",
+                          action="store_true", default=False,
+                          help=("Remove the instance from the cluster even"
+                                " if there are failures during the removal"
+                                " process (shutdown, disk removal, etc.)")),
+              SUBMIT_OPT,
+              ],
              "[-f] <instance>", "Shuts down the instance and removes it"),
              "[-f] <instance>", "Shuts down the instance and removes it"),
-  'remove-mirror': (RemoveMDDRBDComponent, ARGS_ONE,
-                   [DEBUG_OPT, node_opt,
-                    make_option("-b", "--disk", dest="disk", metavar="sdX",
-                                help=("The name of the instance disk"
-                                      " for which to add the mirror")),
-                    make_option("-p", "--port", dest="port", metavar="PORT",
-                                help=("The port of the drbd device"
-                                      " which to remove from the mirror"),
-                                type="int"),
-                    ],
-                   "-b disk -p port <instance>",
-                   "Removes a mirror from the instance"),
+  'rename': (RenameInstance, ARGS_FIXED(2),
+             [DEBUG_OPT,
+              make_option("--no-ip-check", dest="ignore_ip",
+                          help="Do not check that the IP of the new name"
+                          " is alive",
+                          default=False, action="store_true"),
+              SUBMIT_OPT,
+              ],
+             "<instance> <new_name>", "Rename the instance"),
   'replace-disks': (ReplaceDisks, ARGS_ONE,
                     [DEBUG_OPT,
                      make_option("-n", "--new-secondary", dest="new_secondary",
   'replace-disks': (ReplaceDisks, ARGS_ONE,
                     [DEBUG_OPT,
                      make_option("-n", "--new-secondary", dest="new_secondary",
-                                 metavar="NODE",
-                                 help=("New secondary node (if you want to"
-                                       " change the secondary)"))],
-                    "[-n NODE] <instance>",
+                                 help=("New secondary node (for secondary"
+                                       " node change)"), metavar="NODE",
+                                 default=None),
+                     make_option("-p", "--on-primary", dest="on_primary",
+                                 default=False, action="store_true",
+                                 help=("Replace the disk(s) on the primary"
+                                       " node (only for the drbd template)")),
+                     make_option("-s", "--on-secondary", dest="on_secondary",
+                                 default=False, action="store_true",
+                                 help=("Replace the disk(s) on the secondary"
+                                       " node (only for the drbd template)")),
+                     make_option("--disks", dest="disks", default=None,
+                                 help="Comma-separated list of disks"
+                                 " indices to replace (e.g. 0,2) (optional,"
+                                 " defaults to all disks)"),
+                     make_option("-I", "--iallocator", metavar="<NAME>",
+                                 help="Select new secondary for the instance"
+                                 " automatically using the"
+                                 " <NAME> iallocator plugin (enables"
+                                 " secondary node replacement)",
+                                 default=None, type="string"),
+                     SUBMIT_OPT,
+                     ],
+                    "[-s|-p|-n NODE|-I NAME] <instance>",
                     "Replaces all disks for the instance"),
                     "Replaces all disks for the instance"),
-
-  'modify': (SetInstanceParms, ARGS_ONE,
-             [DEBUG_OPT, force_opt,
-              cli_option("-m", "--memory", dest="mem",
-                         help="Memory size",
-                         default=None, type="unit", metavar="<mem>"),
-              make_option("-p", "--cpu", dest="vcpus",
-                          help="Number of virtual CPUs",
-                          default=None, type="int", metavar="<PROC>"),
-              make_option("-i", "--ip", dest="ip",
-                          help="IP address ('none' or numeric IP)",
-                          default=None, type="string", metavar="<ADDRESS>"),
-              make_option("-b", "--bridge", dest="bridge",
-                          help="Bridge to connect this instance to",
-                          default=None, type="string", metavar="<bridge>")
+  'modify': (SetInstanceParams, ARGS_ONE,
+             [DEBUG_OPT, FORCE_OPT,
+              keyval_option("-H", "--hypervisor", type="keyval",
+                            default={}, dest="hypervisor",
+                            help="Change hypervisor parameters"),
+              keyval_option("-B", "--backend", type="keyval",
+                            default={}, dest="beparams",
+                            help="Change backend parameters"),
+              ikv_option("--disk", help="Disk changes",
+                         default=[], dest="disks",
+                         action="append",
+                         type="identkeyval"),
+              ikv_option("--net", help="NIC changes",
+                         default=[], dest="nics",
+                         action="append",
+                         type="identkeyval"),
+              SUBMIT_OPT,
               ],
              "<instance>", "Alters the parameters of an instance"),
               ],
              "<instance>", "Alters the parameters of an instance"),
-  'shutdown': (ShutdownInstance, ARGS_ONE, [DEBUG_OPT],
+  'shutdown': (ShutdownInstance, ARGS_ANY,
+               [DEBUG_OPT, m_node_opt, m_pri_node_opt, m_sec_node_opt,
+                m_clust_opt, m_inst_opt, m_force_multi,
+                SUBMIT_OPT,
+                ],
                "<instance>", "Stops an instance"),
                "<instance>", "Stops an instance"),
-  'startup': (StartupInstance, ARGS_ONE,
-              [DEBUG_OPT, force_opt,
-               make_option("-e", "--extra", dest="extra_args",
-                           help="Extra arguments for the instance's kernel",
-                           default=None, type="string", metavar="<PARAMS>"),
+  'startup': (StartupInstance, ARGS_ANY,
+              [DEBUG_OPT, FORCE_OPT, m_force_multi,
+               m_node_opt, m_pri_node_opt, m_sec_node_opt,
+               m_clust_opt, m_inst_opt,
+               SUBMIT_OPT,
+               keyval_option("-H", "--hypervisor", type="keyval",
+                             default={}, dest="hvparams",
+                             help="Temporary hypervisor parameters"),
+               keyval_option("-B", "--backend", type="keyval",
+                             default={}, dest="beparams",
+                             help="Temporary backend parameters"),
+               ],
+              "<instance>", "Starts an instance"),
+
+  'reboot': (RebootInstance, ARGS_ANY,
+              [DEBUG_OPT, m_force_multi,
+               make_option("-t", "--type", dest="reboot_type",
+                           help="Type of reboot: soft/hard/full",
+                           default=constants.INSTANCE_REBOOT_HARD,
+                           type="string", metavar="<REBOOT>"),
+               make_option("--ignore-secondaries", dest="ignore_secondaries",
+                           default=False, action="store_true",
+                           help="Ignore errors from secondaries"),
+               m_node_opt, m_pri_node_opt, m_sec_node_opt,
+               m_clust_opt, m_inst_opt,
+               SUBMIT_OPT,
                ],
                ],
-            "<instance>", "Starts an instance"),
-  'activate-disks': (ActivateDisks, ARGS_ONE, [DEBUG_OPT],
+            "<instance>", "Reboots an instance"),
+  'activate-disks': (ActivateDisks, ARGS_ONE, [DEBUG_OPT, SUBMIT_OPT],
                      "<instance>",
                      "Activate an instance's disks"),
                      "<instance>",
                      "Activate an instance's disks"),
-  'deactivate-disks': (DeactivateDisks, ARGS_ONE, [DEBUG_OPT],
+  'deactivate-disks': (DeactivateDisks, ARGS_ONE, [DEBUG_OPT, SUBMIT_OPT],
                        "<instance>",
                        "Deactivate an instance's disks"),
                        "<instance>",
                        "Deactivate an instance's disks"),
+  'grow-disk': (GrowDisk, ARGS_FIXED(3),
+                [DEBUG_OPT, SUBMIT_OPT,
+                 make_option("--no-wait-for-sync",
+                             dest="wait_for_sync", default=True,
+                             action="store_false",
+                             help="Don't wait for sync (DANGEROUS!)"),
+                 ],
+                "<instance> <disk> <size>", "Grow an instance's disk"),
+  'list-tags': (ListTags, ARGS_ONE, [DEBUG_OPT],
+                "<instance_name>", "List the tags of the given instance"),
+  'add-tags': (AddTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
+               "<instance_name> tag...", "Add tags to the given instance"),
+  'remove-tags': (RemoveTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
+                  "<instance_name> tag...", "Remove tags from given instance"),
+  }
+
+#: dictionary with aliases for commands
+aliases = {
+  'activate_block_devs': 'activate-disks',
+  'replace_disks': 'replace-disks',
+  'start': 'startup',
+  'stop': 'shutdown',
   }
 
 if __name__ == '__main__':
   }
 
 if __name__ == '__main__':
-  retcode = GenericMain(commands)
-  sys.exit(retcode)
+  sys.exit(GenericMain(commands, aliases=aliases,
+                       override={"tag_type": constants.TAG_INSTANCE}))