gnt-debug: Extend job queue tests
[ganeti-local] / scripts / gnt-instance
index 45444f1..75ecd63 100755 (executable)
@@ -1,7 +1,7 @@
 #!/usr/bin/python
 #
 
-# Copyright (C) 2006, 2007 Google Inc.
+# Copyright (C) 2006, 2007, 2008, 2009, 2010 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
@@ -34,8 +34,10 @@ from cStringIO import StringIO
 from ganeti.cli import *
 from ganeti import opcodes
 from ganeti import constants
+from ganeti import compat
 from ganeti import utils
 from ganeti import errors
+from ganeti import netutils
 
 
 _SHUTDOWN_CLUSTER = "cluster"
@@ -226,8 +228,9 @@ def GenericManyOps(operation, fn):
     for name in inames:
       op = fn(name, opts)
       jex.QueueJob(name, op)
-    jex.WaitOrShow(not opts.submit_only)
-    return 0
+    results = jex.WaitOrShow(not opts.submit_only)
+    rcode = compat.all(row[0] for row in results)
+    return int(not rcode)
   return realfn
 
 
@@ -256,6 +259,7 @@ def ListInstances(opts, args):
       "snodes": "Secondary_Nodes", "admin_state": "Autostart",
       "oper_state": "Running",
       "oper_ram": "Memory", "disk_template": "Disk_template",
+      "oper_vcpus": "VCPUs",
       "ip": "IP_address", "mac": "MAC_address",
       "nic_mode": "NIC_Mode", "nic_link": "NIC_Link",
       "bridge": "Bridge",
@@ -314,6 +318,9 @@ def ListInstances(opts, args):
       elif field == "oper_ram":
         if val is None:
           val = "(node down)"
+      elif field == "oper_vcpus":
+        if val is None:
+          val = "(node down)"
       elif field == "sda_size" or field == "sdb_size":
         if val is None:
           val = "N/A"
@@ -323,6 +330,8 @@ def ListInstances(opts, args):
         val = ",".join(str(item) for item in val)
       elif val is None:
         val = "-"
+      if opts.roman_integers and isinstance(val, int):
+        val = compat.TryToRoman(val)
       row[idx] = str(val)
 
   data = GenerateTable(separator=opts.separator, headers=headers,
@@ -384,6 +393,7 @@ def BatchCreate(opts, args):
                     "hypervisor": None,
                     "hvparams": {},
                     "file_storage_dir": None,
+                    "force_variant": False,
                     "file_driver": 'loop'}
 
   def _PopulateWithDefaults(spec):
@@ -423,12 +433,16 @@ def BatchCreate(opts, args):
     ToStderr("Can't parse the instance definition file: %s" % str(err))
     return 1
 
+  if not isinstance(instance_data, dict):
+    ToStderr("The instance definition file is not in dict format.")
+    return 1
+
   jex = JobExecutor(opts=opts)
 
   # Iterate over the instances and do:
   #  * Populate the specs with default value
   #  * Validate the instance specs
-  i_names = utils.NiceSort(instance_data.keys())
+  i_names = utils.NiceSort(instance_data.keys()) # pylint: disable-msg=E1103
   for name in i_names:
     specs = instance_data[name]
     specs = _PopulateWithDefaults(specs)
@@ -471,7 +485,7 @@ def BatchCreate(opts, args):
                                   disk_template=specs['template'],
                                   mode=constants.INSTANCE_CREATE,
                                   os_type=specs['os'],
-                                  force_variant=opts.force_variant,
+                                  force_variant=specs["force_variant"],
                                   pnode=specs['primary_node'],
                                   snode=specs['secondary_node'],
                                   nics=tmp_nics,
@@ -545,16 +559,17 @@ def ReinstallInstance(opts, args):
   else:
     os_name = opts.os
 
-  # third, get confirmation: multi-reinstall requires --force-multi
-  # *and* --force, single-reinstall just --force
+  # third, get confirmation: multi-reinstall requires --force-multi,
+  # single-reinstall either --force or --force-multi (--force-multi is
+  # a stronger --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
+    if not (opts.force_multi or
             _ConfirmOperation(inames, "reinstall", extra=warn_msg)):
       return 1
   else:
-    if not opts.force:
+    if not (opts.force or opts.force_multi):
       usertext = ("This will reinstall the instance %s and remove"
                   " all data. Continue?") % inames[0]
       if not AskUser(usertext):
@@ -613,10 +628,19 @@ def RenameInstance(opts, args):
   @return: the desired exit code
 
   """
+  if not opts.name_check:
+    if not AskUser("As you disabled the check of the DNS entry, please verify"
+                   " that '%s' is a FQDN. Continue?" % args[1]):
+      return 1
+
   op = opcodes.OpRenameInstance(instance_name=args[0],
                                 new_name=args[1],
-                                ignore_ip=not opts.ip_check)
-  SubmitOrSend(op, opts)
+                                ip_check=opts.ip_check,
+                                name_check=opts.name_check)
+  result = SubmitOrSend(op, opts)
+
+  ToStdout("Instance '%s' renamed to '%s'", args[0], result)
+
   return 0
 
 
@@ -872,13 +896,22 @@ def MigrateInstance(opts, args):
     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?")
+    usertext += (" might impact the instance if anything goes wrong"
+                 " (e.g. due to bugs in the hypervisor). Continue?")
     if not AskUser(usertext):
       return 1
 
-  op = opcodes.OpMigrateInstance(instance_name=instance_name, live=opts.live,
+  # this should be removed once --non-live is deprecated
+  if not opts.live and opts.migration_mode is not None:
+    raise errors.OpPrereqError("Only one of the --non-live and "
+                               "--migration-mode options can be passed",
+                               errors.ECODE_INVAL)
+  if not opts.live: # --non-live passed
+    mode = constants.HT_MIGRATION_NONLIVE
+  else:
+    mode = opts.migration_mode
+
+  op = opcodes.OpMigrateInstance(instance_name=instance_name, mode=mode,
                                  cleanup=opts.cleanup)
   SubmitOpCode(op, cl=cl, opts=opts)
   return 0
@@ -938,16 +971,18 @@ def ConnectToInstanceConsole(opts, args):
       os._exit(1) # pylint: disable-msg=W0212
 
 
-def _FormatLogicalID(dev_type, logical_id):
+def _FormatLogicalID(dev_type, logical_id, roman):
   """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),
+      ("nodeA", "%s, minor=%s" % (node_a, compat.TryToRoman(minor_a,
+                                                            convert=roman))),
+      ("nodeB", "%s, minor=%s" % (node_b, compat.TryToRoman(minor_b,
+                                                            convert=roman))),
+      ("port", compat.TryToRoman(port, convert=roman)),
       ("auth key", key),
       ]
   elif dev_type == constants.LD_LV:
@@ -959,7 +994,7 @@ def _FormatLogicalID(dev_type, logical_id):
   return data
 
 
-def _FormatBlockDevInfo(idx, top_level, dev, static):
+def _FormatBlockDevInfo(idx, top_level, dev, static, roman):
   """Show block device information.
 
   This is only used by L{ShowInstanceConfig}, but it's too big to be
@@ -974,6 +1009,8 @@ def _FormatBlockDevInfo(idx, top_level, dev, static):
   @type static: boolean
   @param static: wheter the device information doesn't contain
       runtime information but only static data
+  @type roman: boolean
+  @param roman: whether to try to use roman integers
   @return: a list of either strings, tuples or lists
       (which should be formatted at a higher indent level)
 
@@ -995,19 +1032,19 @@ def _FormatBlockDevInfo(idx, top_level, dev, static):
     if major is None:
       major_string = "N/A"
     else:
-      major_string = str(major)
+      major_string = str(compat.TryToRoman(major, convert=roman))
 
     if minor is None:
       minor_string = "N/A"
     else:
-      minor_string = str(minor)
+      minor_string = str(compat.TryToRoman(minor, convert=roman))
 
     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
+          sync_text += " ETA %ss" % compat.TryToRoman(estt, convert=roman)
         else:
           sync_text += " ETA unknown"
       else:
@@ -1036,9 +1073,9 @@ def _FormatBlockDevInfo(idx, top_level, dev, static):
     if dev["iv_name"] is not None:
       txt = dev["iv_name"]
     else:
-      txt = "disk %d" % idx
+      txt = "disk %s" % compat.TryToRoman(idx, convert=roman)
   else:
-    txt = "child %d" % idx
+    txt = "child %s" % compat.TryToRoman(idx, convert=roman)
   if isinstance(dev["size"], int):
     nice_size = utils.FormatUnit(dev["size"], "h")
   else:
@@ -1049,7 +1086,7 @@ def _FormatBlockDevInfo(idx, top_level, dev, static):
     data.append(("access mode", dev["mode"]))
   if dev["logical_id"] is not None:
     try:
-      l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"])
+      l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"], roman)
     except ValueError:
       l_id = [str(dev["logical_id"])]
     if len(l_id) == 1:
@@ -1067,7 +1104,7 @@ def _FormatBlockDevInfo(idx, top_level, dev, static):
   if dev["children"]:
     data.append("child devices:")
     for c_idx, child in enumerate(dev["children"]):
-      data.append(_FormatBlockDevInfo(c_idx, False, child, static))
+      data.append(_FormatBlockDevInfo(c_idx, False, child, static, roman))
   d1.append(data)
   return d1
 
@@ -1101,6 +1138,21 @@ def _FormatList(buf, data, indent_level):
       _FormatList(buf, elem, indent_level+1)
 
 
+def _FormatParameterDict(buf, per_inst, actual):
+  """Formats a parameter dictionary.
+
+  @type buf: L{StringIO}
+  @param buf: the buffer into which to write
+  @type per_inst: dict
+  @param per_inst: the instance's own parameters
+  @type actual: dict
+  @param actual: the current parameter set (including defaults)
+
+  """
+  for key in sorted(actual):
+    val = per_inst.get(key, "default (%s)" % actual[key])
+    buf.write("    - %s: %s\n" % (key, val))
+
 def ShowInstanceConfig(opts, args):
   """Compute instance run-time status.
 
@@ -1134,7 +1186,9 @@ def ShowInstanceConfig(opts, args):
     instance = result[instance_name]
     buf.write("Instance name: %s\n" % instance["name"])
     buf.write("UUID: %s\n" % instance["uuid"])
-    buf.write("Serial number: %s\n" % instance["serial_no"])
+    buf.write("Serial number: %s\n" %
+              compat.TryToRoman(instance["serial_no"],
+                                convert=opts.roman_integers))
     buf.write("Creation time: %s\n" % utils.FormatTime(instance["ctime"]))
     buf.write("Modification time: %s\n" % utils.FormatTime(instance["mtime"]))
     buf.write("State: configured to be %s" % instance["config_state"])
@@ -1147,8 +1201,11 @@ def ShowInstanceConfig(opts, args):
     buf.write("    - primary: %s\n" % instance["pnode"])
     buf.write("    - secondaries: %s\n" % utils.CommaJoin(instance["snodes"]))
     buf.write("  Operating system: %s\n" % instance["os"])
+    _FormatParameterDict(buf, instance["os_instance"], instance["os_actual"])
     if instance.has_key("network_port"):
-      buf.write("  Allocated network port: %s\n" % instance["network_port"])
+      buf.write("  Allocated network port: %s\n" %
+                compat.TryToRoman(instance["network_port"],
+                                  convert=opts.roman_integers))
     buf.write("  Hypervisor: %s\n" % instance["hypervisor"])
 
     # custom VNC console information
@@ -1157,11 +1214,11 @@ def ShowInstanceConfig(opts, args):
     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:
+      if display > 0 and vnc_bind_address == constants.IP4_ADDRESS_ANY:
         vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
                                                    port,
                                                    display)
-      elif display > 0 and utils.IsValidIP(vnc_bind_address):
+      elif display > 0 and netutils.IsValidIP4(vnc_bind_address):
         vnc_console_port = ("%s:%s (node %s) (display %s)" %
                              (vnc_bind_address, port,
                               instance["pnode"], display))
@@ -1171,17 +1228,14 @@ def ShowInstanceConfig(opts, args):
                                       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))
+    _FormatParameterDict(buf, instance["hv_instance"], instance["hv_actual"])
     buf.write("  Hardware:\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("    - VCPUs: %s\n" %
+              compat.TryToRoman(instance["be_actual"][constants.BE_VCPUS],
+                                convert=opts.roman_integers))
+    buf.write("    - memory: %sMiB\n" %
+              compat.TryToRoman(instance["be_actual"][constants.BE_MEMORY],
+                                convert=opts.roman_integers))
     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" %
@@ -1189,7 +1243,8 @@ def ShowInstanceConfig(opts, args):
     buf.write("  Disks:\n")
 
     for idx, device in enumerate(instance["disks"]):
-      _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static), 2)
+      _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static,
+                  opts.roman_integers), 2)
 
   ToStdout(buf.getvalue().rstrip('\n'))
   return retcode
@@ -1208,7 +1263,7 @@ def SetInstanceParams(opts, args):
 
   """
   if not (opts.nics or opts.disks or opts.disk_template or
-          opts.hvparams or opts.beparams):
+          opts.hvparams or opts.beparams or opts.os or opts.osparams):
     ToStderr("Please give at least one of the parameters.")
     return 1
 
@@ -1261,6 +1316,9 @@ def SetInstanceParams(opts, args):
                                    remote_node=opts.node,
                                    hvparams=opts.hvparams,
                                    beparams=opts.beparams,
+                                   os_name=opts.os,
+                                   osparams=opts.osparams,
+                                   force_variant=opts.force_variant,
                                    force=opts.force)
 
   # even if here we process the result, we allow submit only
@@ -1337,8 +1395,10 @@ add_opts = [
   NONICS_OPT,
   NOSTART_OPT,
   NWSYNC_OPT,
+  OSPARAMS_OPT,
   OS_OPT,
   FORCE_VARIANT_OPT,
+  NO_INSTALL_OPT,
   OS_SIZE_OPT,
   SUBMIT_OPT,
   ]
@@ -1363,7 +1423,7 @@ commands = {
     " using the remote mirror (only for instances of type drbd)"),
   'migrate': (
     MigrateInstance, ARGS_ONE_INSTANCE,
-    [FORCE_OPT, NONLIVE_OPT, CLEANUP_OPT],
+    [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, CLEANUP_OPT],
     "[-f] <instance>", "Migrate instance to its secondary node"
     " (only for instances of type drbd)"),
   'move': (
@@ -1373,17 +1433,18 @@ commands = {
     " (only for instances of type file and lv)"),
   'info': (
     ShowInstanceConfig, ARGS_MANY_INSTANCES,
-    [STATIC_OPT, ALL_OPT],
+    [STATIC_OPT, ALL_OPT, ROMAN_OPT],
     "[-s] {--all | <instance>...}",
     "Show information on the specified instance(s)"),
   'list': (
     ListInstances, ARGS_MANY_INSTANCES,
-    [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, SYNC_OPT],
+    [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, SYNC_OPT, ROMAN_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, nic_mode, nic_link, sda_size, sdb_size, vcpus, serial_no,"
+    " oper_vcpus, name, os, pnode, snodes, admin_state, admin_ram,"
+    " disk_template, ip, mac, nic_mode, nic_link, sda_size, sdb_size,"
+    " vcpus, serial_no,"
     " nic.count, nic.mac/N, nic.ip/N, nic.mode/N, nic.link/N,"
     " nic.macs, nic.ips, nic.modes, nic.links,"
     " disk.count, disk.size/N, disk.sizes,"
@@ -1406,7 +1467,7 @@ commands = {
   'rename': (
     RenameInstance,
     [ArgInstance(min=1, max=1), ArgHost(min=1, max=1)],
-    [NOIPCHECK_OPT, SUBMIT_OPT],
+    [NOIPCHECK_OPT, NONAMECHECK_OPT, SUBMIT_OPT],
     "<instance> <new_name>", "Rename the instance"),
   'replace-disks': (
     ReplaceDisks, ARGS_ONE_INSTANCE,
@@ -1417,7 +1478,8 @@ commands = {
   'modify': (
     SetInstanceParams, ARGS_ONE_INSTANCE,
     [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT,
-     DISK_TEMPLATE_OPT, SINGLE_NODE_OPT],
+     DISK_TEMPLATE_OPT, SINGLE_NODE_OPT, OS_OPT, FORCE_VARIANT_OPT,
+     OSPARAMS_OPT],
     "<instance>", "Alters the parameters of an instance"),
   'shutdown': (
     GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()],