X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/25be0c756d404fd0c5891e272962d12914dfceb9..958d01f8f739093f752bff4af259f3e9bb7ff4c7:/lib/cli.py diff --git a/lib/cli.py b/lib/cli.py index e2e1e31..61c0e14 100644 --- a/lib/cli.py +++ b/lib/cli.py @@ -27,6 +27,7 @@ import textwrap import os.path import time import logging +import errno from cStringIO import StringIO from ganeti import utils @@ -104,6 +105,7 @@ __all__ = [ "NEW_RAPI_CERT_OPT", "NEW_SECONDARY_OPT", "NIC_PARAMS_OPT", + "NODE_FORCE_JOIN_OPT", "NODE_LIST_OPT", "NODE_PLACEMENT_OPT", "NODEGROUP_OPT", @@ -178,6 +180,7 @@ __all__ = [ "ToStderr", "ToStdout", "FormatError", "FormatQueryResult", + "FormatParameterDict", "GenerateTable", "AskUser", "FormatTimestamp", @@ -336,7 +339,8 @@ ARGS_MANY_NODES = [ArgNode()] ARGS_MANY_GROUPS = [ArgGroup()] ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)] ARGS_ONE_NODE = [ArgNode(min=1, max=1)] -ARGS_ONE_GROUP = [ArgInstance(min=1, max=1)] +# TODO +ARGS_ONE_GROUP = [ArgGroup(min=1, max=1)] ARGS_ONE_OS = [ArgOs(min=1, max=1)] @@ -421,7 +425,7 @@ def AddTags(opts, args): _ExtendTags(opts, args) if not args: raise errors.OpPrereqError("No tags to be added") - op = opcodes.OpAddTags(kind=kind, name=name, tags=args) + op = opcodes.OpTagsSet(kind=kind, name=name, tags=args) SubmitOpCode(op, opts=opts) @@ -438,7 +442,7 @@ def RemoveTags(opts, args): _ExtendTags(opts, args) if not args: raise errors.OpPrereqError("No tags to be removed") - op = opcodes.OpDelTags(kind=kind, name=name, tags=args) + op = opcodes.OpTagsDel(kind=kind, name=name, tags=args) SubmitOpCode(op, opts=opts) @@ -884,6 +888,9 @@ NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check", default=True, action="store_false", help="Disable SSH key fingerprint checking") +NODE_FORCE_JOIN_OPT = cli_option("--force-join", dest="force_join", + default=False, action="store_true", + help="Force the joining of a node") MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate", type="bool", default=None, metavar=_YORNO, @@ -891,11 +898,14 @@ MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate", OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO, type="bool", default=None, - help="Set the offline flag on the node") + help=("Set the offline flag on the node" + " (cluster does not communicate with offline" + " nodes)")) DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO, type="bool", default=None, - help="Set the drained flag on the node") + help=("Set the drained flag on the node" + " (excluded from allocation operations)")) CAPAB_MASTER_OPT = cli_option("--master-capable", dest="master_capable", type="bool", default=None, metavar=_YORNO, @@ -928,8 +938,9 @@ CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None, help="Set the candidate pool size") VG_NAME_OPT = cli_option("--vg-name", dest="vg_name", - help="Enables LVM and specifies the volume group" - " name (cluster-wide) for disk allocation [xenvg]", + help=("Enables LVM and specifies the volume group" + " name (cluster-wide) for disk allocation" + " [%s]" % constants.DEFAULT_VG), metavar="VG", default=None) YES_DOIT_OPT = cli_option("--yes-do-it", dest="yes_do_it", @@ -1909,8 +1920,8 @@ def GenericMain(commands, override=None, aliases=None): for key, val in override.iteritems(): setattr(options, key, val) - utils.SetupLogging(constants.LOG_COMMANDS, debug=options.debug, - stderr_logging=True, program=binary) + utils.SetupLogging(constants.LOG_COMMANDS, binary, debug=options.debug, + stderr_logging=True) if old_cmdline: logging.info("run with arguments '%s'", old_cmdline) @@ -1924,6 +1935,17 @@ def GenericMain(commands, override=None, aliases=None): result, err_msg = FormatError(err) logging.exception("Error during command processing") ToStderr(err_msg) + except KeyboardInterrupt: + result = constants.EXIT_FAILURE + ToStderr("Aborted. Note that if the operation created any jobs, they" + " might have been submitted and" + " will continue to run in the background.") + except IOError, err: + if err.errno == errno.EPIPE: + # our terminal went away, we'll exit + sys.exit(constants.EXIT_FAILURE) + else: + raise return result @@ -2053,7 +2075,7 @@ def GenericInstanceCreate(mode, opts, args): else: raise errors.ProgrammerError("Invalid creation mode %s" % mode) - op = opcodes.OpCreateInstance(instance_name=instance, + op = opcodes.OpInstanceCreate(instance_name=instance, disks=disks, disk_template=opts.disk_template, nics=nics, @@ -2367,17 +2389,23 @@ class _QueryColumnFormatter: """Callable class for formatting fields of a query. """ - def __init__(self, fn, status_fn): + def __init__(self, fn, status_fn, verbose): """Initializes this class. @type fn: callable @param fn: Formatting function @type status_fn: callable @param status_fn: Function to report fields' status + @type verbose: boolean + @param verbose: whether to use verbose field descriptions or not """ self._fn = fn self._status_fn = status_fn + if verbose: + self._desc_index = 0 + else: + self._desc_index = 1 def __call__(self, data): """Returns a field's string representation. @@ -2388,29 +2416,20 @@ class _QueryColumnFormatter: # Report status self._status_fn(status) - if status == constants.QRFS_NORMAL: + if status == constants.RS_NORMAL: return self._fn(value) assert value is None, \ "Found value %r for abnormal status %s" % (value, status) - if status == constants.QRFS_UNKNOWN: - return "(unknown)" - - if status == constants.QRFS_NODATA: - return "(nodata)" - - if status == constants.QRFS_UNAVAIL: - return "(unavail)" - - if status == constants.QRFS_OFFLINE: - return "(offline)" + if status in constants.RSS_DESCRIPTION: + return constants.RSS_DESCRIPTION[status][self._desc_index] raise NotImplementedError("Unknown status %s" % status) def FormatQueryResult(result, unit=None, format_override=None, separator=None, - header=False): + header=False, verbose=False): """Formats data in L{objects.QueryResponse}. @type result: L{objects.QueryResponse} @@ -2425,6 +2444,8 @@ def FormatQueryResult(result, unit=None, format_override=None, separator=None, @param separator: String used to separate fields @type header: bool @param header: Whether to output header row + @type verbose: boolean + @param verbose: whether to use verbose field descriptions or not """ if unit is None: @@ -2436,7 +2457,7 @@ def FormatQueryResult(result, unit=None, format_override=None, separator=None, if format_override is None: format_override = {} - stats = dict.fromkeys(constants.QRFS_ALL, 0) + stats = dict.fromkeys(constants.RS_ALL, 0) def _RecordStatus(status): if status in stats: @@ -2447,22 +2468,23 @@ def FormatQueryResult(result, unit=None, format_override=None, separator=None, assert fdef.title and fdef.name (fn, align_right) = _GetColumnFormatter(fdef, format_override, unit) columns.append(TableColumn(fdef.title, - _QueryColumnFormatter(fn, _RecordStatus), + _QueryColumnFormatter(fn, _RecordStatus, + verbose), align_right)) table = FormatTable(result.data, columns, header, separator) # Collect statistics - assert len(stats) == len(constants.QRFS_ALL) + assert len(stats) == len(constants.RS_ALL) assert compat.all(count >= 0 for count in stats.values()) # Determine overall status. If there was no data, unknown fields must be # detected via the field definitions. - if (stats[constants.QRFS_UNKNOWN] or + if (stats[constants.RS_UNKNOWN] or (not result.data and _GetUnknownFields(result.fields))): status = QR_UNKNOWN elif compat.any(count > 0 for key, count in stats.items() - if key != constants.QRFS_NORMAL): + if key != constants.RS_NORMAL): status = QR_INCOMPLETE else: status = QR_NORMAL @@ -2496,7 +2518,7 @@ def _WarnUnknownFields(fdefs): def GenericList(resource, fields, names, unit, separator, header, cl=None, - format_override=None): + format_override=None, verbose=False): """Generic implementation for listing all items of a resource. @param resource: One of L{constants.QR_OP_LUXI} @@ -2515,6 +2537,8 @@ def GenericList(resource, fields, names, unit, separator, header, cl=None, @type format_override: dict @param format_override: Dictionary for overriding field formatting functions, indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY} + @type verbose: boolean + @param verbose: whether to use verbose field descriptions or not """ if cl is None: @@ -2529,7 +2553,8 @@ def GenericList(resource, fields, names, unit, separator, header, cl=None, (status, data) = FormatQueryResult(response, unit=unit, separator=separator, header=header, - format_override=format_override) + format_override=format_override, + verbose=verbose) for line in data: ToStdout(line) @@ -2777,13 +2802,20 @@ def _ToStream(stream, txt, *args): @param txt: the message """ - if args: - args = tuple(args) - stream.write(txt % args) - else: - stream.write(txt) - stream.write('\n') - stream.flush() + try: + if args: + args = tuple(args) + stream.write(txt % args) + else: + stream.write(txt) + stream.write('\n') + stream.flush() + except IOError, err: + if err.errno == errno.EPIPE: + # our terminal went away, we'll exit + sys.exit(constants.EXIT_FAILURE) + else: + raise def ToStdout(txt, *args): @@ -2941,3 +2973,21 @@ class JobExecutor(object): else: ToStderr("Failure for %s: %s", name, result) return [row[1:3] for row in self.jobs] + + +def FormatParameterDict(buf, param_dict, actual, level=1): + """Formats a parameter dictionary. + + @type buf: L{StringIO} + @param buf: the buffer into which to write + @type param_dict: dict + @param param_dict: the own parameters + @type actual: dict + @param actual: the current parameter set (including defaults) + @param level: Level of indent + + """ + indent = " " * level + for key in sorted(actual): + val = param_dict.get(key, "default (%s)" % actual[key]) + buf.write("%s- %s: %s\n" % (indent, key, val))