gnt-node modify: Adding --node-powered=yes|no
[ganeti-local] / lib / client / gnt_node.py
index 26e5ca8..24a9b25 100644 (file)
 # C0103: Invalid name gnt-node
 
 from ganeti.cli import *
 # C0103: Invalid name gnt-node
 
 from ganeti.cli import *
+from ganeti import cli
 from ganeti import bootstrap
 from ganeti import opcodes
 from ganeti import utils
 from ganeti import constants
 from ganeti import bootstrap
 from ganeti import opcodes
 from ganeti import utils
 from ganeti import constants
-from ganeti import compat
 from ganeti import errors
 from ganeti import netutils
 
 from ganeti import errors
 from ganeti import netutils
 
@@ -60,27 +60,11 @@ _LIST_STOR_DEF_FIELDS = [
   ]
 
 
   ]
 
 
-#: headers (and full field list for L{ListNodes}
-_LIST_HEADERS = {
-  "name": "Node", "pinst_cnt": "Pinst", "sinst_cnt": "Sinst",
-  "pinst_list": "PriInstances", "sinst_list": "SecInstances",
-  "pip": "PrimaryIP", "sip": "SecondaryIP",
-  "dtotal": "DTotal", "dfree": "DFree",
-  "mtotal": "MTotal", "mnode": "MNode", "mfree": "MFree",
-  "bootid": "BootID",
-  "ctotal": "CTotal", "cnodes": "CNodes", "csockets": "CSockets",
-  "tags": "Tags",
-  "serial_no": "SerialNo",
-  "master_candidate": "MasterC",
-  "master": "IsMaster",
-  "offline": "Offline", "drained": "Drained",
-  "role": "Role",
-  "ctime": "CTime", "mtime": "MTime", "uuid": "UUID",
-  "master_capable": "MasterCapable", "vm_capable": "VMCapable",
-  }
+#: default list of power commands
+_LIST_POWER_COMMANDS = ["on", "off", "cycle", "status"]
 
 
 
 
-#: headers (and full field list for L{ListStorage}
+#: headers (and full field list) for L{ListStorage}
 _LIST_STOR_HEADERS = {
   constants.SF_NODE: "Node",
   constants.SF_TYPE: "Type",
 _LIST_STOR_HEADERS = {
   constants.SF_NODE: "Node",
   constants.SF_TYPE: "Type",
@@ -213,7 +197,9 @@ def AddNode(opts, args):
   bootstrap.SetupNodeDaemon(cluster_name, node, opts.ssh_key_check)
 
   op = opcodes.OpAddNode(node_name=args[0], secondary_ip=sip,
   bootstrap.SetupNodeDaemon(cluster_name, node, opts.ssh_key_check)
 
   op = opcodes.OpAddNode(node_name=args[0], secondary_ip=sip,
-                         readd=opts.readd, group=opts.nodegroup)
+                         readd=opts.readd, group=opts.nodegroup,
+                         vm_capable=opts.vm_capable, ndparams=opts.ndparams,
+                         master_capable=opts.master_capable)
   SubmitOpCode(op, opts=opts)
 
 
   SubmitOpCode(op, opts=opts)
 
 
@@ -222,55 +208,33 @@ def ListNodes(opts, args):
 
   @param opts: the command line options selected by the user
   @type args: list
 
   @param opts: the command line options selected by the user
   @type args: list
-  @param args: should be an empty list
+  @param args: nodes to list, or empty for all
   @rtype: int
   @return: the desired exit code
 
   """
   selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
 
   @rtype: int
   @return: the desired exit code
 
   """
   selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
 
-  output = GetClient().QueryNodes(args, selected_fields, opts.do_locking)
+  fmtoverride = dict.fromkeys(["pinst_list", "sinst_list", "tags"],
+                              (",".join, False))
 
 
-  if not opts.no_headers:
-    headers = _LIST_HEADERS
-  else:
-    headers = None
+  return GenericList(constants.QR_NODE, selected_fields, args, opts.units,
+                     opts.separator, not opts.no_headers,
+                     format_override=fmtoverride)
 
 
-  unitfields = ["dtotal", "dfree", "mtotal", "mnode", "mfree"]
 
 
-  numfields = ["dtotal", "dfree",
-               "mtotal", "mnode", "mfree",
-               "pinst_cnt", "sinst_cnt",
-               "ctotal", "serial_no"]
+def ListNodeFields(opts, args):
+  """List node fields.
 
 
-  list_type_fields = ("pinst_list", "sinst_list", "tags")
-  # change raw values to nicer strings
-  for row in output:
-    for idx, field in enumerate(selected_fields):
-      val = row[idx]
-      if field in list_type_fields:
-        val = ",".join(val)
-      elif field in ('master', 'master_candidate', 'offline', 'drained',
-                     'master_capable', 'vm_capable'):
-        if val:
-          val = 'Y'
-        else:
-          val = 'N'
-      elif field == "ctime" or field == "mtime":
-        val = utils.FormatTime(val)
-      elif val is None:
-        val = "?"
-      elif opts.roman_integers and isinstance(val, int):
-        val = compat.TryToRoman(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)
+  @param opts: the command line options selected by the user
+  @type args: list
+  @param args: fields to list, or empty for all
+  @rtype: int
+  @return: the desired exit code
 
 
-  return 0
+  """
+  return GenericListFields(constants.QR_NODE, args, opts.separator,
+                           not opts.no_headers)
 
 
 def EvacuateNode(opts, args):
 
 
 def EvacuateNode(opts, args):
@@ -423,29 +387,33 @@ def ShowNodeConfig(opts, args):
   cl = GetClient()
   result = cl.QueryNodes(fields=["name", "pip", "sip",
                                  "pinst_list", "sinst_list",
   cl = GetClient()
   result = cl.QueryNodes(fields=["name", "pip", "sip",
                                  "pinst_list", "sinst_list",
-                                 "master_candidate", "drained", "offline"],
+                                 "master_candidate", "drained", "offline",
+                                 "master_capable", "vm_capable"],
                          names=args, use_locking=False)
 
   for (name, primary_ip, secondary_ip, pinst, sinst,
                          names=args, use_locking=False)
 
   for (name, primary_ip, secondary_ip, pinst, sinst,
-       is_mc, drained, offline) in result:
+       is_mc, drained, offline, master_capable, vm_capable) in result:
     ToStdout("Node name: %s", name)
     ToStdout("  primary ip: %s", primary_ip)
     ToStdout("  secondary ip: %s", secondary_ip)
     ToStdout("  master candidate: %s", is_mc)
     ToStdout("  drained: %s", drained)
     ToStdout("  offline: %s", offline)
     ToStdout("Node name: %s", name)
     ToStdout("  primary ip: %s", primary_ip)
     ToStdout("  secondary ip: %s", secondary_ip)
     ToStdout("  master candidate: %s", is_mc)
     ToStdout("  drained: %s", drained)
     ToStdout("  offline: %s", offline)
-    if pinst:
-      ToStdout("  primary for instances:")
-      for iname in utils.NiceSort(pinst):
-        ToStdout("    - %s", iname)
-    else:
-      ToStdout("  primary for no instances")
-    if sinst:
-      ToStdout("  secondary for instances:")
-      for iname in utils.NiceSort(sinst):
-        ToStdout("    - %s", iname)
-    else:
-      ToStdout("  secondary for no instances")
+    ToStdout("  master_capable: %s", master_capable)
+    ToStdout("  vm_capable: %s", vm_capable)
+    if vm_capable:
+      if pinst:
+        ToStdout("  primary for instances:")
+        for iname in utils.NiceSort(pinst):
+          ToStdout("    - %s", iname)
+      else:
+        ToStdout("  primary for no instances")
+      if sinst:
+        ToStdout("  secondary for instances:")
+        for iname in utils.NiceSort(sinst):
+          ToStdout("    - %s", iname)
+      else:
+        ToStdout("  secondary for no instances")
 
   return 0
 
 
   return 0
 
@@ -489,6 +457,53 @@ def PowercycleNode(opts, args):
   return 0
 
 
   return 0
 
 
+def PowerNode(opts, args):
+  """Change/ask power state of a node.
+
+  @param opts: the command line options selected by the user
+  @type args: list
+  @param args: should contain only one element, the name of
+      the node to be removed
+  @rtype: int
+  @return: the desired exit code
+
+  """
+  command = args[0]
+  node = args[1]
+
+  if command not in _LIST_POWER_COMMANDS:
+    ToStderr("power subcommand %s not supported." % command)
+    return constants.EXIT_FAILURE
+
+  oob_command = "power-%s" % command
+
+  opcodelist = []
+  if oob_command == constants.OOB_POWER_OFF:
+    opcodelist.append(opcodes.OpSetNodeParams(node_name=node, offline=True,
+                                              auto_promote=opts.auto_promote))
+
+  opcodelist.append(opcodes.OpOobCommand(node_name=node, command=oob_command))
+
+  cli.SetGenericOpcodeOpts(opcodelist, opts)
+
+  job_id = cli.SendJob(opcodelist)
+
+  # We just want the OOB Opcode status
+  # If it fails PollJob gives us the error message in it
+  result = cli.PollJob(job_id)[-1]
+
+  if result:
+    if oob_command == constants.OOB_POWER_STATUS:
+      text = "The machine is %spowered"
+      if result[constants.OOB_POWER_STATUS_POWERED]:
+        result = text % ""
+      else:
+        result = text % "not "
+    ToStderr(result)
+
+  return constants.EXIT_SUCCESS
+
+
 def ListVolumes(opts, args):
   """List logical volumes on node(s).
 
 def ListVolumes(opts, args):
   """List logical volumes on node(s).
 
@@ -649,7 +664,10 @@ def SetNodeParams(opts, args):
   @return: the desired exit code
 
   """
   @return: the desired exit code
 
   """
-  if [opts.master_candidate, opts.drained, opts.offline].count(None) == 3:
+  all_changes = [opts.master_candidate, opts.drained, opts.offline,
+                 opts.master_capable, opts.vm_capable, opts.secondary_ip,
+                 opts.ndparams]
+  if all_changes.count(None) == len(all_changes):
     ToStderr("Please give at least one of the parameters.")
     return 1
 
     ToStderr("Please give at least one of the parameters.")
     return 1
 
@@ -658,8 +676,12 @@ def SetNodeParams(opts, args):
                                offline=opts.offline,
                                drained=opts.drained,
                                master_capable=opts.master_capable,
                                offline=opts.offline,
                                drained=opts.drained,
                                master_capable=opts.master_capable,
+                               vm_capable=opts.vm_capable,
+                               secondary_ip=opts.secondary_ip,
                                force=opts.force,
                                force=opts.force,
-                               auto_promote=opts.auto_promote)
+                               ndparams=opts.ndparams,
+                               auto_promote=opts.auto_promote,
+                               powered=opts.node_powered)
 
   # even if here we process the result, we allow submit only
   result = SubmitOrSend(op, opts)
 
   # even if here we process the result, we allow submit only
   result = SubmitOrSend(op, opts)
@@ -675,7 +697,8 @@ commands = {
   'add': (
     AddNode, [ArgHost(min=1, max=1)],
     [SECONDARY_IP_OPT, READD_OPT, NOSSH_KEYCHECK_OPT, NONODE_SETUP_OPT,
   'add': (
     AddNode, [ArgHost(min=1, max=1)],
     [SECONDARY_IP_OPT, READD_OPT, NOSSH_KEYCHECK_OPT, NONODE_SETUP_OPT,
-     VERBOSE_OPT, NODEGROUP_OPT, PRIORITY_OPT],
+     VERBOSE_OPT, NODEGROUP_OPT, PRIORITY_OPT, CAPAB_MASTER_OPT,
+     CAPAB_VM_OPT, NODE_PARAMS_OPT],
     "[-s ip] [--readd] [--no-ssh-key-check] [--no-node-setup]  [--verbose] "
     " <node_name>",
     "Add a node to the cluster"),
     "[-s ip] [--readd] [--no-ssh-key-check] [--no-node-setup]  [--verbose] "
     " <node_name>",
     "Add a node to the cluster"),
@@ -702,20 +725,35 @@ commands = {
     "[<node_name>...]", "Show information about the node(s)"),
   'list': (
     ListNodes, ARGS_MANY_NODES,
     "[<node_name>...]", "Show information about the node(s)"),
   'list': (
     ListNodes, ARGS_MANY_NODES,
-    [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, SYNC_OPT, ROMAN_OPT],
+    [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT],
     "[nodes...]",
     "[nodes...]",
-    "Lists the nodes in the cluster. The available fields are (see the man"
-    " page for details): %s. The default field list is (in order): %s." %
-    (utils.CommaJoin(_LIST_HEADERS), utils.CommaJoin(_LIST_DEF_FIELDS))),
+    "Lists the nodes in the cluster. The available fields can be shown using"
+    " the \"list-fields\" command (see the man page for details)."
+    " The default field list is (in order): %s." %
+    utils.CommaJoin(_LIST_DEF_FIELDS)),
+  "list-fields": (
+    ListNodeFields, [ArgUnknown()],
+    [NOHDR_OPT, SEP_OPT],
+    "[fields...]",
+    "Lists all available fields for nodes"),
   'modify': (
     SetNodeParams, ARGS_ONE_NODE,
   'modify': (
     SetNodeParams, ARGS_ONE_NODE,
-    [FORCE_OPT, SUBMIT_OPT, MC_OPT, DRAINED_OPT, OFFLINE_OPT, CAPAB_MASTER_OPT,
-     AUTO_PROMOTE_OPT, DRY_RUN_OPT, PRIORITY_OPT],
+    [FORCE_OPT, SUBMIT_OPT, MC_OPT, DRAINED_OPT, OFFLINE_OPT,
+     CAPAB_MASTER_OPT, CAPAB_VM_OPT, SECONDARY_IP_OPT,
+     AUTO_PROMOTE_OPT, DRY_RUN_OPT, PRIORITY_OPT, NODE_PARAMS_OPT,
+     NODE_POWERED_OPT],
     "<node_name>", "Alters the parameters of a node"),
   'powercycle': (
     PowercycleNode, ARGS_ONE_NODE,
     [FORCE_OPT, CONFIRM_OPT, DRY_RUN_OPT, PRIORITY_OPT],
     "<node_name>", "Tries to forcefully powercycle a node"),
     "<node_name>", "Alters the parameters of a node"),
   'powercycle': (
     PowercycleNode, ARGS_ONE_NODE,
     [FORCE_OPT, CONFIRM_OPT, DRY_RUN_OPT, PRIORITY_OPT],
     "<node_name>", "Tries to forcefully powercycle a node"),
+  'power': (
+    PowerNode,
+    [ArgChoice(min=1, max=1, choices=_LIST_POWER_COMMANDS),
+     ArgNode(min=1, max=1)],
+    [SUBMIT_OPT, AUTO_PROMOTE_OPT, PRIORITY_OPT],
+    "on|off|cycle|status <node>",
+    "Change power state of node by calling out-of-band helper."),
   'remove': (
     RemoveNode, ARGS_ONE_NODE, [DRY_RUN_OPT, PRIORITY_OPT],
     "<node_name>", "Removes a node from the cluster"),
   'remove': (
     RemoveNode, ARGS_ONE_NODE, [DRY_RUN_OPT, PRIORITY_OPT],
     "<node_name>", "Removes a node from the cluster"),