X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/a57981c5e684c1b7055d7be31d144ca43bb34d65..905108aba23c253f17ad1a4f2bf4883b8108bda5:/lib/cli.py diff --git a/lib/cli.py b/lib/cli.py index 7946072..4aeb162 100644 --- a/lib/cli.py +++ b/lib/cli.py @@ -143,6 +143,7 @@ __all__ = [ "POWER_DELAY_OPT", "PREALLOC_WIPE_DISKS_OPT", "PRIMARY_IP_VERSION_OPT", + "PRIMARY_ONLY_OPT", "PRIORITY_OPT", "RAPI_CERT_OPT", "READD_OPT", @@ -152,6 +153,7 @@ __all__ = [ "RESERVED_LVS_OPT", "ROMAN_OPT", "SECONDARY_IP_OPT", + "SECONDARY_ONLY_OPT", "SELECT_OS_OPT", "SEP_OPT", "SHOWCMD_OPT", @@ -160,11 +162,13 @@ __all__ = [ "SRC_DIR_OPT", "SRC_NODE_OPT", "SUBMIT_OPT", + "STARTUP_PAUSED_OPT", "STATIC_OPT", "SYNC_OPT", "TAG_ADD_OPT", "TAG_SRC_OPT", "TIMEOUT_OPT", + "TO_GROUP_OPT", "UIDPOOL_OPT", "USEUNITS_OPT", "USE_REPL_NET_OPT", @@ -513,7 +517,7 @@ def check_ident_key_val(option, opt, value): # pylint: disable-msg=W0613 """ if ":" not in value: - ident, rest = value, '' + ident, rest = value, "" else: ident, rest = value.split(":", 1) @@ -617,7 +621,7 @@ SEP_OPT = cli_option("--separator", default=None, " (defaults to one space)")) USEUNITS_OPT = cli_option("--units", default=None, - dest="units", choices=('h', 'm', 'g', 't'), + dest="units", choices=("h", "m", "g", "t"), help="Specify units for output (one of h/m/g/t)") FIELDS_OPT = cli_option("-o", "--output", dest="output", action="store", @@ -800,7 +804,8 @@ NODE_LIST_OPT = cli_option("-n", "--node", dest="nodes", default=[], " times, if not given defaults to all nodes)", completion_suggest=OPT_COMPL_ONE_NODE) -NODEGROUP_OPT = cli_option("-g", "--node-group", +NODEGROUP_OPT_NAME = "--node-group" +NODEGROUP_OPT = cli_option("-g", NODEGROUP_OPT_NAME, dest="nodegroup", help="Node group (name or uuid)", metavar="", @@ -976,7 +981,7 @@ VG_NAME_OPT = cli_option("--vg-name", dest="vg_name", " [%s]" % constants.DEFAULT_VG), metavar="VG", default=None) -YES_DOIT_OPT = cli_option("--yes-do-it", dest="yes_do_it", +YES_DOIT_OPT = cli_option("--yes-do-it", "--ya-rly", dest="yes_do_it", help="Destroy cluster", action="store_true") NOVOTING_OPT = cli_option("--no-voting", dest="no_voting", @@ -1210,6 +1215,26 @@ NO_REMEMBER_OPT = cli_option("--no-remember", help="Perform but do not record the change" " in the configuration") +PRIMARY_ONLY_OPT = cli_option("-p", "--primary-only", + default=False, action="store_true", + help="Evacuate primary instances only") + +SECONDARY_ONLY_OPT = cli_option("-s", "--secondary-only", + default=False, action="store_true", + help="Evacuate secondary instances only" + " (applies only to internally mirrored" + " disk templates, e.g. %s)" % + utils.CommaJoin(constants.DTS_INT_MIRROR)) + +STARTUP_PAUSED_OPT = cli_option("--paused", dest="startup_paused", + action="store_true", default=False, + help="Pause instance at startup") + +TO_GROUP_OPT = cli_option("--to", dest="to", metavar="", + help="Destination node group (name or uuid)", + default=None, action="append", + completion_suggest=OPT_COMPL_ONE_NODEGROUP) + #: Options provided by all commands COMMON_OPTS = [DEBUG_OPT] @@ -1233,6 +1258,7 @@ COMMON_CREATE_OPTS = [ OSPARAMS_OPT, OS_SIZE_OPT, SUBMIT_OPT, + TAG_ADD_OPT, DRY_RUN_OPT, PRIORITY_OPT, ] @@ -1392,8 +1418,8 @@ def SplitNodeOption(value): """Splits the value of a --node option. """ - if value and ':' in value: - return value.split(':', 1) + if value and ":" in value: + return value.split(":", 1) else: return (value, None) @@ -1410,7 +1436,7 @@ def CalculateOSNames(os_name, os_variants): """ if os_variants: - return ['%s+%s' % (os_name, v) for v in os_variants] + return ["%s+%s" % (os_name, v) for v in os_variants] else: return [os_name] @@ -1452,12 +1478,12 @@ def AskUser(text, choices=None): """ if choices is None: - choices = [('y', True, 'Perform the operation'), - ('n', False, 'Do not perform the operation')] + choices = [("y", True, "Perform the operation"), + ("n", False, "Do not perform the operation")] if not choices or not isinstance(choices, list): raise errors.ProgrammerError("Invalid choices argument to AskUser") for entry in choices: - if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == '?': + if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == "?": raise errors.ProgrammerError("Invalid choices element to AskUser") answer = choices[-1][1] @@ -1472,18 +1498,18 @@ def AskUser(text, choices=None): try: chars = [entry[0] for entry in choices] chars[-1] = "[%s]" % chars[-1] - chars.append('?') + chars.append("?") maps = dict([(entry[0], entry[1]) for entry in choices]) while True: f.write(text) - f.write('\n') + f.write("\n") f.write("/".join(chars)) f.write(": ") line = f.readline(2).strip().lower() if line in maps: answer = maps[line] break - elif line == '?': + elif line == "?": for entry in choices: f.write(" %s - %s\n" % (entry[0], entry[2])) f.write("\n") @@ -1730,7 +1756,7 @@ class StdioJobPollReportCb(JobPollReportCbBase): ToStderr("Job %s is waiting in queue", job_id) self.notified_queued = True - elif status == constants.JOB_STATUS_WAITLOCK and not self.notified_waitlock: + elif status == constants.JOB_STATUS_WAITING and not self.notified_waitlock: ToStderr("Job %s is trying to acquire all necessary locks", job_id) self.notified_waitlock = True @@ -1940,7 +1966,7 @@ def FormatError(err): retcode = 0 else: obuf.write("Unhandled exception: %s" % msg) - return retcode, obuf.getvalue().rstrip('\n') + return retcode, obuf.getvalue().rstrip("\n") def GenericMain(commands, override=None, aliases=None): @@ -2119,7 +2145,7 @@ def GenericInstanceCreate(mode, opts, args): disks[didx] = ddict if opts.tags is not None: - tags = opts.tags.split(',') + tags = opts.tags.split(",") else: tags = [] @@ -2235,7 +2261,7 @@ class _RunWhileClusterStoppedHelper: """ # Pause watcher by acquiring an exclusive lock on watcher state file self.feedback_fn("Blocking watcher") - watcher_block = utils.FileLock.Open(constants.WATCHER_STATEFILE) + watcher_block = utils.FileLock.Open(constants.WATCHER_LOCK_FILE) try: # TODO: Currently, this just blocks. There's no timeout. # TODO: Should it be a shared lock? @@ -2356,7 +2382,7 @@ def GenerateTable(headers, fields, separator, data, if separator is None: mlens = [0 for name in fields] - format_str = ' '.join(format_fields) + format_str = " ".join(format_fields) else: format_str = separator.replace("%", "%%").join(format_fields) @@ -2395,7 +2421,7 @@ def GenerateTable(headers, fields, separator, data, for line in data: args = [] if line is None: - line = ['-' for _ in fields] + line = ["-" for _ in fields] for idx in range(len(fields)): if separator is None: args.append(mlens[idx]) @@ -2635,18 +2661,7 @@ def GenericList(resource, fields, names, unit, separator, header, cl=None, if not names: names = None - if (force_filter or - (names and len(names) == 1 and qlang.MaybeFilter(names[0]))): - try: - (filter_text, ) = names - except ValueError: - raise errors.OpPrereqError("Exactly one argument must be given as a" - " filter") - - logging.debug("Parsing '%s' as filter", filter_text) - filter_ = qlang.ParseFilter(filter_text) - else: - filter_ = qlang.MakeSimpleFilter("name", names) + filter_ = qlang.MakeFilter(names, force_filter) response = cl.Query(resource, fields, filter_) @@ -2801,7 +2816,7 @@ def FormatTimestamp(ts): """ if not isinstance (ts, (tuple, list)) or len(ts) != 2: - return '?' + return "?" sec, usec = ts return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec @@ -2824,11 +2839,11 @@ def ParseTimespec(value): if not value: raise errors.OpPrereqError("Empty time specification passed") suffix_map = { - 's': 1, - 'm': 60, - 'h': 3600, - 'd': 86400, - 'w': 604800, + "s": 1, + "m": 60, + "h": 3600, + "d": 86400, + "w": 604800, } if value[-1] not in suffix_map: try: @@ -2849,7 +2864,7 @@ def ParseTimespec(value): def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False, - filter_master=False): + filter_master=False, nodegroup=None): """Returns the names of online nodes. This function will also log a warning on stderr with the names of @@ -2870,28 +2885,60 @@ def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False, @param filter_master: if True, do not return the master node in the list (useful in coordination with secondary_ips where we cannot check our node name against the list) + @type nodegroup: string + @param nodegroup: If set, only return nodes in this node group """ if cl is None: cl = GetClient() - if secondary_ips: - name_idx = 2 - else: - name_idx = 0 + filter_ = [] + + if nodes: + filter_.append(qlang.MakeSimpleFilter("name", nodes)) + + if nodegroup is not None: + filter_.append([qlang.OP_OR, [qlang.OP_EQUAL, "group", nodegroup], + [qlang.OP_EQUAL, "group.uuid", nodegroup]]) if filter_master: - master_node = cl.QueryConfigValues(["master_node"])[0] - filter_fn = lambda x: x != master_node + filter_.append([qlang.OP_NOT, [qlang.OP_TRUE, "master"]]) + + if filter_: + if len(filter_) > 1: + final_filter = [qlang.OP_AND] + filter_ + else: + assert len(filter_) == 1 + final_filter = filter_[0] else: - filter_fn = lambda _: True + final_filter = None + + result = cl.Query(constants.QR_NODE, ["name", "offline", "sip"], final_filter) + + def _IsOffline(row): + (_, (_, offline), _) = row + return offline + + def _GetName(row): + ((_, name), _, _) = row + return name + + def _GetSip(row): + (_, _, (_, sip)) = row + return sip + + (offline, online) = compat.partition(result.data, _IsOffline) - result = cl.QueryNodes(names=nodes, fields=["name", "offline", "sip"], - use_locking=False) - offline = [row[0] for row in result if row[1]] if offline and not nowarn: - ToStderr("Note: skipping offline node(s): %s" % utils.CommaJoin(offline)) - return [row[name_idx] for row in result if not row[1] and filter_fn(row[0])] + ToStderr("Note: skipping offline node(s): %s" % + utils.CommaJoin(map(_GetName, offline))) + + if secondary_ips: + fn = _GetSip + else: + fn = _GetName + + return map(fn, online) def _ToStream(stream, txt, *args): @@ -2909,7 +2956,7 @@ def _ToStream(stream, txt, *args): stream.write(txt % args) else: stream.write(txt) - stream.write('\n') + stream.write("\n") stream.flush() except IOError, err: if err.errno == errno.EPIPE: @@ -3014,7 +3061,7 @@ class JobExecutor(object): for job_data, status in zip(self.jobs, result): if (isinstance(status, list) and status and status[0] in (constants.JOB_STATUS_QUEUED, - constants.JOB_STATUS_WAITLOCK, + constants.JOB_STATUS_WAITING, constants.JOB_STATUS_CANCELING)): # job is still present and waiting continue