Support IPv6 cluster init
[ganeti-local] / scripts / gnt-os
index 155485f..fd96cbe 100755 (executable)
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 # 02110-1301, USA.
 
+"""OS scripts related commands"""
+
+# pylint: disable-msg=W0401,W0613,W0614,C0103
+# W0401: Wildcard import ganeti.cli
+# W0613: Unused argument, since all functions follow the same API
+# W0614: Unused import %s from wildcard import (since we need cli)
+# C0103: Invalid name gnt-os
 
 import sys
-from optparse import make_option
 
 from ganeti.cli import *
+from ganeti import constants
 from ganeti import opcodes
-from ganeti import logger
-from ganeti import objects
 from ganeti import utils
-from ganeti import errors
+
 
 def ListOS(opts, args):
-  """List the OSes existing on this node.
+  """List the valid OSes in the cluster.
+
+  @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
 
   """
-  op = opcodes.OpDiagnoseOS()
-  result = SubmitOpCode(op)
+  op = opcodes.OpDiagnoseOS(output_fields=["name", "valid", "variants"],
+                            names=[])
+  result = SubmitOpCode(op, opts=opts)
 
   if not result:
-    logger.ToStdout("Can't get the OS list")
+    ToStderr("Can't get the OS list")
     return 1
 
-  # filter non-valid OS-es
-  oses = {}
-  for node_name in result:
-    oses[node_name] = [obj for obj in result[node_name]
-                       if isinstance(obj, objects.OS)]
-
-  # Get intersection of all OSes
-  fnode = oses.keys()[0]
-  os_set = set([os_inst.name for os_inst in oses[fnode]])
-  del oses[fnode]
-  for node in oses:
-    os_set &= set([os_inst.name for os_inst in oses[node]])
-
   if not opts.no_headers:
     headers = {"name": "Name"}
   else:
     headers = None
 
+  os_names = []
+  for (name, valid, variants) in result:
+    if valid:
+      os_names.extend([[n] for n in CalculateOSNames(name, variants)])
+
   data = GenerateTable(separator=None, headers=headers, fields=["name"],
-                       data=[[os] for os in os_set])
+                       data=os_names, units=None)
 
   for line in data:
-    logger.ToStdout(line)
+    ToStdout(line)
 
   return 0
 
-def DiagnoseOS(opts, args):
-  """Analyse all OSes on this cluster.
+
+def ShowOSInfo(opts, args):
+  """List detailed information about OSes in the cluster.
+
+  @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
 
   """
-  op = opcodes.OpDiagnoseOS()
-  result = SubmitOpCode(op)
+  op = opcodes.OpDiagnoseOS(output_fields=["name", "valid", "variants",
+                                           "parameters", "api_versions"],
+                            names=[])
+  result = SubmitOpCode(op, opts=opts)
 
   if not result:
-    logger.ToStdout("Can't get the OS list")
+    ToStderr("Can't get the OS list")
     return 1
 
-  format = "%-*s %-*s %s"
+  do_filter = bool(args)
 
-  node_data = result
-  all_os = {}
-  for node_name in node_data:
-    nr = node_data[node_name]
-    if nr:
-      for obj in nr:
-        if isinstance(obj, objects.OS):
-          os_name = obj.name
-        else:
-          os_name = obj.args[0]
-        if os_name not in all_os:
-          all_os[os_name] = {}
-        all_os[os_name][node_name] = obj
+  for (name, valid, variants, parameters, api_versions) in result:
+    if do_filter:
+      if name not in args:
+        continue
+      else:
+        args.remove(name)
+    ToStdout("%s:", name)
+    ToStdout("  - valid: %s", valid)
+    if valid:
+      ToStdout("  - API versions:")
+      for version in sorted(api_versions):
+        ToStdout("    - %s", version)
+      ToStdout("  - variants:")
+      for vname in variants:
+        ToStdout("    - %s", vname)
+      ToStdout("  - parameters:")
+      for pname, pdesc in parameters:
+        ToStdout("    - %s: %s", pname, pdesc)
+    ToStdout("")
 
-  max_name = len('Name')
-  if all_os:
-    max_name = max(max_name, max([len(name) for name in all_os]))
+  if args:
+    for name in args:
+      ToStdout("%s: ", name)
+      ToStdout("")
 
-  max_node = len('Status/Node')
-  max_node = max(max_node, max([len(name) for name in node_data]))
+  return 0
+
+
+def _OsStatus(status, diagnose):
+  """Beautifier function for OS status.
+
+  @type status: boolean
+  @param status: is the OS valid
+  @type diagnose: string
+  @param diagnose: the error message for invalid OSes
+  @rtype: string
+  @return: a formatted status
+
+  """
+  if status:
+    return "valid"
+  else:
+    return "invalid - %s" % diagnose
+
+def DiagnoseOS(opts, args):
+  """Analyse all OSes on this cluster.
+
+  @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
 
-  logger.ToStdout(format % (max_name, 'Name', max_node, 'Status/Node',
-                            'Details'))
+  """
+  op = opcodes.OpDiagnoseOS(output_fields=["name", "valid", "variants",
+                                           "node_status"], names=[])
+  result = SubmitOpCode(op, opts=opts)
 
-  for os_name in all_os:
-    nodes_valid = []
+  if not result:
+    ToStderr("Can't get the OS list")
+    return 1
+
+  has_bad = False
+
+  for os_name, _, os_variants, node_data in result:
+    nodes_valid = {}
     nodes_bad = {}
-    for node_name in node_data:
-      nos = all_os[os_name].get(node_name, None)
-      if isinstance(nos, objects.OS):
-        nodes_valid.append(node_name)
-      elif isinstance(nos, errors.InvalidOS):
-        nodes_bad[node_name] = nos.args[1]
+    nodes_hidden = {}
+    for node_name, node_info in node_data.iteritems():
+      nodes_hidden[node_name] = []
+      if node_info: # at least one entry in the per-node list
+        (fo_path, fo_status, fo_msg, fo_variants,
+         fo_params, fo_api) = node_info.pop(0)
+        fo_msg = "%s (path: %s)" % (_OsStatus(fo_status, fo_msg), fo_path)
+        if fo_api:
+          max_os_api = max(fo_api)
+          fo_msg += " [API versions: %s]" % utils.CommaJoin(fo_api)
+        else:
+          max_os_api = 0
+          fo_msg += " [no API versions declared]"
+        if max_os_api >= constants.OS_API_V15:
+          if fo_variants:
+            fo_msg += " [variants: %s]" % utils.CommaJoin(fo_variants)
+          else:
+            fo_msg += " [no variants]"
+        if max_os_api >= constants.OS_API_V20:
+          if fo_params:
+            fo_msg += (" [parameters: %s]" %
+                       utils.CommaJoin([v[0] for v in fo_params]))
+          else:
+            fo_msg += " [no parameters]"
+        if fo_status:
+          nodes_valid[node_name] = fo_msg
+        else:
+          nodes_bad[node_name] = fo_msg
+        for hpath, hstatus, hmsg, _, _, _ in node_info:
+          nodes_hidden[node_name].append("    [hidden] path: %s, status: %s" %
+                                         (hpath, _OsStatus(hstatus, hmsg)))
       else:
-        nodes_bad[node_name] = "os dir not found"
+        nodes_bad[node_name] = "OS not found"
 
     if nodes_valid and not nodes_bad:
       status = "valid"
     elif not nodes_valid and nodes_bad:
       status = "invalid"
+      has_bad = True
     else:
       status = "partial valid"
-    logger.ToStdout(format % (max_name, os_name, max_node, status, ""))
-    nodes_valid = utils.NiceSort(nodes_valid)
-    for node_name in nodes_valid:
-      logger.ToStdout(format % (max_name, "", max_node, node_name, "valid"))
-    nbk = utils.NiceSort(nodes_bad.keys())
-    for node_name in nbk:
-      logger.ToStdout(format % (max_name, "", max_node,
-                                node_name, nodes_bad[node_name]))
+      has_bad = True
+
+    def _OutputPerNodeOSStatus(msg_map):
+      map_k = utils.NiceSort(msg_map.keys())
+      for node_name in map_k:
+        ToStdout("  Node: %s, status: %s", node_name, msg_map[node_name])
+        for msg in nodes_hidden[node_name]:
+          ToStdout(msg)
+
+    ToStdout("OS: %s [global status: %s]", os_name, status)
+    if os_variants:
+      ToStdout("  Variants: [%s]" % utils.CommaJoin(os_variants))
+    _OutputPerNodeOSStatus(nodes_valid)
+    _OutputPerNodeOSStatus(nodes_bad)
+    ToStdout("")
+
+  return int(has_bad)
+
+
+def ModifyOS(opts, args):
+  """Modify OS parameters for one OS.
+
+  @param opts: the command line options selected by the user
+  @type args: list
+  @param args: should be a list with one entry
+  @rtype: int
+  @return: the desired exit code
+
+  """
+  os = args[0]
+
+  if opts.hvparams:
+    os_hvp = {os: dict(opts.hvparams)}
+  else:
+    os_hvp = None
+
+  if opts.osparams:
+    osp = {os: opts.osparams}
+  else:
+    osp = None
+
+  if not (os_hvp or osp):
+    ToStderr("At least one of OS parameters or hypervisor parameters"
+             " must be passed")
+    return 1
+
+  op = opcodes.OpSetClusterParams(vg_name=None,
+                                  enabled_hypervisors=None,
+                                  hvparams=None,
+                                  beparams=None,
+                                  nicparams=None,
+                                  candidate_pool_size=None,
+                                  os_hvp=os_hvp,
+                                  osparams=osp)
+  SubmitOpCode(op)
+
+  return 0
 
 
 commands = {
-  'list': (ListOS, ARGS_NONE, [DEBUG_OPT, NOHDR_OPT], "",
-           "Lists all valid OSes on the master"),
-  'diagnose': (DiagnoseOS, ARGS_NONE, [DEBUG_OPT], "",
-               "Diagnose all OSes"),
+  'list': (
+    ListOS, ARGS_NONE, [NOHDR_OPT], "", "Lists all valid operating systems"
+    " on the cluster"),
+  'diagnose': (
+    DiagnoseOS, ARGS_NONE, [], "", "Diagnose all operating systems"),
+  'info': (
+    ShowOSInfo, [ArgOs()], [], "", "Show detailed information about "
+    "operating systems"),
+  'modify': (
+    ModifyOS, ARGS_ONE_OS, [HVLIST_OPT, OSPARAMS_OPT], "",
+    "Modify the OS parameters"),
   }
 
 if __name__ == '__main__':