X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/53919782a684d95456d72496f98010e4b2663313..934704ae32e1f4ef193a39356f87f9b554441b45:/lib/client/gnt_node.py diff --git a/lib/client/gnt_node.py b/lib/client/gnt_node.py index 28586d5..1ac4f5a 100644 --- a/lib/client/gnt_node.py +++ b/lib/client/gnt_node.py @@ -27,13 +27,14 @@ # 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 compat from ganeti import errors from ganeti import netutils +from cStringIO import StringIO #: default list of field for L{ListNodes} @@ -60,27 +61,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", @@ -213,7 +198,9 @@ def AddNode(opts, args): 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) @@ -222,55 +209,33 @@ def ListNodes(opts, args): @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) - 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): @@ -423,29 +388,41 @@ def ShowNodeConfig(opts, args): 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", "powered", + "ndparams", "custom_ndparams"], names=args, use_locking=False) - for (name, primary_ip, secondary_ip, pinst, sinst, - is_mc, drained, offline) in result: + for (name, primary_ip, secondary_ip, pinst, sinst, is_mc, drained, offline, + master_capable, vm_capable, powered, ndparams, + ndparams_custom) 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) - 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") + if powered is not None: + ToStdout(" powered: %s", powered) + 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") + ToStdout(" node parameters:") + buf = StringIO() + FormatParameterDict(buf, ndparams_custom, ndparams, level=2) + ToStdout(buf.getvalue().rstrip("\n")) return 0 @@ -489,6 +466,53 @@ def PowercycleNode(opts, args): 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). @@ -650,7 +674,8 @@ def SetNodeParams(opts, args): """ all_changes = [opts.master_candidate, opts.drained, opts.offline, - opts.master_capable, opts.vm_capable] + 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 @@ -661,8 +686,11 @@ def SetNodeParams(opts, args): drained=opts.drained, master_capable=opts.master_capable, vm_capable=opts.vm_capable, + secondary_ip=opts.secondary_ip, 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) @@ -678,7 +706,8 @@ commands = { '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] " " ", "Add a node to the cluster"), @@ -705,21 +734,35 @@ commands = { "[...]", "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...]", - "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, [FORCE_OPT, SUBMIT_OPT, MC_OPT, DRAINED_OPT, OFFLINE_OPT, - CAPAB_MASTER_OPT, CAPAB_VM_OPT, - AUTO_PROMOTE_OPT, DRY_RUN_OPT, PRIORITY_OPT], + CAPAB_MASTER_OPT, CAPAB_VM_OPT, SECONDARY_IP_OPT, + AUTO_PROMOTE_OPT, DRY_RUN_OPT, PRIORITY_OPT, NODE_PARAMS_OPT, + NODE_POWERED_OPT], "", "Alters the parameters of a node"), 'powercycle': ( PowercycleNode, ARGS_ONE_NODE, [FORCE_OPT, CONFIRM_OPT, DRY_RUN_OPT, PRIORITY_OPT], "", "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 ", + "Change power state of node by calling out-of-band helper."), 'remove': ( RemoveNode, ARGS_ONE_NODE, [DRY_RUN_OPT, PRIORITY_OPT], "", "Removes a node from the cluster"),