#
#
-# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Google Inc.
+# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 Google Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
import logging
import errno
import itertools
+import shlex
from cStringIO import StringIO
from ganeti import utils
"DEBUG_SIMERR_OPT",
"DISKIDX_OPT",
"DISK_OPT",
+ "DISK_PARAMS_OPT",
"DISK_TEMPLATE_OPT",
"DRAINED_OPT",
"DRY_RUN_OPT",
"DEFAULT_IALLOCATOR_OPT",
"IDENTIFY_DEFAULTS_OPT",
"IGNORE_CONSIST_OPT",
+ "IGNORE_ERRORS_OPT",
"IGNORE_FAILURES_OPT",
"IGNORE_OFFLINE_OPT",
"IGNORE_REMOVE_FAILURES_OPT",
"MAC_PREFIX_OPT",
"MAINTAIN_NODE_HEALTH_OPT",
"MASTER_NETDEV_OPT",
+ "MASTER_NETMASK_OPT",
"MC_OPT",
"MIGRATION_MODE_OPT",
"NET_OPT",
"NOVOTING_OPT",
"NO_REMEMBER_OPT",
"NWSYNC_OPT",
+ "OFFLINE_INST_OPT",
+ "ONLINE_INST_OPT",
"ON_PRIMARY_OPT",
"ON_SECONDARY_OPT",
"OFFLINE_OPT",
"REMOVE_INSTANCE_OPT",
"REMOVE_UIDS_OPT",
"RESERVED_LVS_OPT",
+ "RUNTIME_MEM_OPT",
"ROMAN_OPT",
"SECONDARY_IP_OPT",
"SECONDARY_ONLY_OPT",
"SHOWCMD_OPT",
"SHUTDOWN_TIMEOUT_OPT",
"SINGLE_NODE_OPT",
+ "SPECS_CPU_COUNT_OPT",
+ "SPECS_DISK_COUNT_OPT",
+ "SPECS_DISK_SIZE_OPT",
+ "SPECS_MEM_SIZE_OPT",
+ "SPECS_NIC_COUNT_OPT",
+ "IPOLICY_DISK_TEMPLATES",
+ "IPOLICY_VCPU_RATIO",
"SPICE_CACERT_OPT",
"SPICE_CERT_OPT",
"SRC_DIR_OPT",
"TO_GROUP_OPT",
"UIDPOOL_OPT",
"USEUNITS_OPT",
+ "USE_EXTERNAL_MIP_SCRIPT",
"USE_REPL_NET_OPT",
"VERBOSE_OPT",
"VG_NAME_OPT",
"YES_DOIT_OPT",
+ "DISK_STATE_OPT",
+ "HV_STATE_OPT",
+ "IGNORE_IPOLICY_OPT",
+ "INSTANCE_POLICY_OPTS",
# Generic functions for CLI programs
"ConfirmOperation",
"GenericMain",
raise errors.ParameterError("Invalid boolean value '%s'" % value)
+def check_list(option, opt, value): # pylint: disable=W0613
+ """Custom parser for comma-separated lists.
+
+ """
+ # we have to make this explicit check since "".split(",") is [""],
+ # not an empty list :(
+ if not value:
+ return []
+ else:
+ return utils.UnescapeAndSplit(value)
+
+
# completion_suggestion is normally a list. Using numeric values not evaluating
# to False for dynamic completion.
(OPT_COMPL_MANY_NODES,
"keyval",
"unit",
"bool",
+ "list",
)
TYPE_CHECKER = Option.TYPE_CHECKER.copy()
TYPE_CHECKER["identkeyval"] = check_ident_key_val
TYPE_CHECKER["keyval"] = check_key_val
TYPE_CHECKER["unit"] = check_unit
TYPE_CHECKER["bool"] = check_bool
+ TYPE_CHECKER["list"] = check_list
# optparse.py sets make_option, so we do it for our own option class, too
default=True, action="store_false",
help="Don't wait for sync (DANGEROUS!)")
+ONLINE_INST_OPT = cli_option("--online", dest="online_inst",
+ action="store_true", default=False,
+ help="Enable offline instance")
+
+OFFLINE_INST_OPT = cli_option("--offline", dest="offline_inst",
+ action="store_true", default=False,
+ help="Disable down instance")
+
DISK_TEMPLATE_OPT = cli_option("-t", "--disk-template", dest="disk_template",
help=("Custom disk setup (%s)" %
utils.CommaJoin(constants.DISK_TEMPLATES)),
default={}, dest="hvparams",
help="Hypervisor parameters")
+DISK_PARAMS_OPT = cli_option("-D", "--disk-parameters", dest="diskparams",
+ help="Disk template parameters, in the format"
+ " template:option=value,option=value,...",
+ type="identkeyval", action="append", default=[])
+
+SPECS_MEM_SIZE_OPT = cli_option("--specs-mem-size", dest="ispecs_mem_size",
+ type="keyval", default={},
+ help="Memory count specs: min, max, std"
+ " (in MB)")
+
+SPECS_CPU_COUNT_OPT = cli_option("--specs-cpu-count", dest="ispecs_cpu_count",
+ type="keyval", default={},
+ help="CPU count specs: min, max, std")
+
+SPECS_DISK_COUNT_OPT = cli_option("--specs-disk-count",
+ dest="ispecs_disk_count",
+ type="keyval", default={},
+ help="Disk count specs: min, max, std")
+
+SPECS_DISK_SIZE_OPT = cli_option("--specs-disk-size", dest="ispecs_disk_size",
+ type="keyval", default={},
+ help="Disk size specs: min, max, std (in MB)")
+
+SPECS_NIC_COUNT_OPT = cli_option("--specs-nic-count", dest="ispecs_nic_count",
+ type="keyval", default={},
+ help="NIC count specs: min, max, std")
+
+IPOLICY_DISK_TEMPLATES = cli_option("--ipolicy-disk-templates",
+ dest="ipolicy_disk_templates",
+ type="list", default=None,
+ help="Comma-separated list of"
+ " enabled disk templates")
+
+IPOLICY_VCPU_RATIO = cli_option("--ipolicy-vcpu-ratio",
+ dest="ipolicy_vcpu_ratio",
+ type="float", default=None,
+ help="The maximum allowed vcpu-to-cpu ratio")
+
HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
help="Hypervisor and hypervisor options, in the"
" format hypervisor:option=value,option=value,...",
metavar="NETDEV",
default=None)
+MASTER_NETMASK_OPT = cli_option("--master-netmask", dest="master_netmask",
+ help="Specify the netmask of the master IP",
+ metavar="NETMASK",
+ default=None)
+
+USE_EXTERNAL_MIP_SCRIPT = cli_option("--use-external-mip-script",
+ dest="use_external_mip_script",
+ help="Specify whether to run a user-provided"
+ " script for the master IP address turnup and"
+ " turndown operations",
+ type="bool", metavar=_YORNO, default=None)
+
GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
help="Specify the default directory (cluster-"
"wide) for storing the file-based disks [%s]" %
default=None, action="append",
completion_suggest=OPT_COMPL_ONE_NODEGROUP)
+IGNORE_ERRORS_OPT = cli_option("-I", "--ignore-errors", default=[],
+ action="append", dest="ignore_errors",
+ choices=list(constants.CV_ALL_ECODES_STRINGS),
+ help="Error code to be ignored")
+
+DISK_STATE_OPT = cli_option("--disk-state", default=[], dest="disk_state",
+ action="append",
+ help=("Specify disk state information in the format"
+ " storage_type/identifier:option=value,..."),
+ type="identkeyval")
+
+HV_STATE_OPT = cli_option("--hypervisor-state", default=[], dest="hv_state",
+ action="append",
+ help=("Specify hypervisor state information in the"
+ " format hypervisor:option=value,..."),
+ type="identkeyval")
+
+IGNORE_IPOLICY_OPT = cli_option("--ignore-ipolicy", dest="ignore_ipolicy",
+ action="store_true", default=False,
+ help="Ignore instance policy violations")
+
+RUNTIME_MEM_OPT = cli_option("-m", "--runtime-memory", dest="runtime_mem",
+ help="Sets the instance's runtime memory,"
+ " ballooning it up or down to the new value",
+ default=None, type="unit", metavar="<size>")
#: Options provided by all commands
COMMON_OPTS = [DEBUG_OPT]
PRIORITY_OPT,
]
+# common instance policy options
+INSTANCE_POLICY_OPTS = [
+ SPECS_CPU_COUNT_OPT,
+ SPECS_DISK_COUNT_OPT,
+ SPECS_DISK_SIZE_OPT,
+ SPECS_MEM_SIZE_OPT,
+ SPECS_NIC_COUNT_OPT,
+ IPOLICY_DISK_TEMPLATES,
+ IPOLICY_VCPU_RATIO,
+ ]
+
-def _ParseArgs(argv, commands, aliases):
+def _ParseArgs(argv, commands, aliases, env_override):
"""Parser for the command line arguments.
This function parses the arguments and returns the function which
@param commands: dictionary with special contents, see the design
doc for cmdline handling
@param aliases: dictionary with command aliases {'alias': 'target, ...}
+ @param env_override: list of env variables allowed for default args
"""
+ assert not (env_override - set(commands))
+
if len(argv) == 0:
binary = "<command>"
else:
cmd = aliases[cmd]
+ if cmd in env_override:
+ args_env_name = ("%s_%s" % (binary.replace("-", "_"), cmd)).upper()
+ env_args = os.environ.get(args_env_name)
+ if env_args:
+ argv = utils.InsertAtPos(argv, 1, shlex.split(env_args))
+
func, args_def, parser_opts, usage, description = commands[cmd]
parser = OptionParser(option_list=parser_opts + COMMON_OPTS,
description=description,
formatter=TitledHelpFormatter(),
usage="%%prog %s %s" % (cmd, usage))
parser.disable_interspersed_args()
- options, args = parser.parse_args()
+ options, args = parser.parse_args(args=argv[1:])
if not _CheckArguments(cmd, args_def, args):
return None, None, None
return retcode, obuf.getvalue().rstrip("\n")
-def GenericMain(commands, override=None, aliases=None):
+def GenericMain(commands, override=None, aliases=None,
+ env_override=frozenset()):
"""Generic main function for all the gnt-* commands.
- Arguments:
- - commands: a dictionary with a special structure, see the design doc
- for command line handling.
- - override: if not None, we expect a dictionary with keys that will
- override command line options; this can be used to pass
- options from the scripts to generic functions
- - aliases: dictionary with command aliases {'alias': 'target, ...}
+ @param commands: a dictionary with a special structure, see the design doc
+ for command line handling.
+ @param override: if not None, we expect a dictionary with keys that will
+ override command line options; this can be used to pass
+ options from the scripts to generic functions
+ @param aliases: dictionary with command aliases {'alias': 'target, ...}
+ @param env_override: list of environment names which are allowed to submit
+ default args for commands
"""
# save the program name and the entire command line for later logging
if sys.argv:
- binary = os.path.basename(sys.argv[0]) or sys.argv[0]
+ binary = os.path.basename(sys.argv[0])
+ if not binary:
+ binary = sys.argv[0]
+
if len(sys.argv) >= 2:
- binary += " " + sys.argv[1]
- old_cmdline = " ".join(sys.argv[2:])
+ logname = utils.ShellQuoteArgs([binary, sys.argv[1]])
else:
- old_cmdline = ""
+ logname = binary
+
+ cmdline = utils.ShellQuoteArgs([binary] + sys.argv[1:])
else:
binary = "<unknown program>"
- old_cmdline = ""
+ cmdline = "<unknown>"
if aliases is None:
aliases = {}
try:
- func, options, args = _ParseArgs(sys.argv, commands, aliases)
+ func, options, args = _ParseArgs(sys.argv, commands, aliases, env_override)
except errors.ParameterError, err:
result, err_msg = FormatError(err)
ToStderr(err_msg)
for key, val in override.iteritems():
setattr(options, key, val)
- utils.SetupLogging(constants.LOG_COMMANDS, binary, debug=options.debug,
+ utils.SetupLogging(constants.LOG_COMMANDS, logname, debug=options.debug,
stderr_logging=True)
- if old_cmdline:
- logging.info("run with arguments '%s'", old_cmdline)
- else:
- logging.info("run with no arguments")
+ logging.info("Command line: %s", cmdline)
try:
result = func(options, args)
else:
tags = []
- utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
+ utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_COMPAT)
utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
if mode == constants.INSTANCE_CREATE:
src_path=src_path,
tags=tags,
no_install=no_install,
- identify_defaults=identify_defaults)
+ identify_defaults=identify_defaults,
+ ignore_ipolicy=opts.ignore_ipolicy)
SubmitOrSend(op, opts)
return 0
@param verbose: whether to use verbose field descriptions or not
"""
- if cl is None:
- cl = GetClient()
-
if not names:
names = None
- filter_ = qlang.MakeFilter(names, force_filter)
+ qfilter = qlang.MakeFilter(names, force_filter)
+
+ if cl is None:
+ cl = GetClient()
- response = cl.Query(resource, fields, filter_)
+ response = cl.Query(resource, fields, qfilter)
found_unknown = _WarnUnknownFields(response.fields)
if cl is None:
cl = GetClient()
- filter_ = []
+ qfilter = []
if nodes:
- filter_.append(qlang.MakeSimpleFilter("name", nodes))
+ qfilter.append(qlang.MakeSimpleFilter("name", nodes))
if nodegroup is not None:
- filter_.append([qlang.OP_OR, [qlang.OP_EQUAL, "group", nodegroup],
+ qfilter.append([qlang.OP_OR, [qlang.OP_EQUAL, "group", nodegroup],
[qlang.OP_EQUAL, "group.uuid", nodegroup]])
if filter_master:
- filter_.append([qlang.OP_NOT, [qlang.OP_TRUE, "master"]])
+ qfilter.append([qlang.OP_NOT, [qlang.OP_TRUE, "master"]])
- if filter_:
- if len(filter_) > 1:
- final_filter = [qlang.OP_AND] + filter_
+ if qfilter:
+ if len(qfilter) > 1:
+ final_filter = [qlang.OP_AND] + qfilter
else:
- assert len(filter_) == 1
- final_filter = filter_[0]
+ assert len(qfilter) == 1
+ final_filter = qfilter[0]
else:
final_filter = None
for (_, _, ops) in self.queue:
# SubmitJob will remove the success status, but raise an exception if
# the submission fails, so we'll notice that anyway.
- results.append([True, self.cl.SubmitJob(ops)])
+ results.append([True, self.cl.SubmitJob(ops)[0]])
else:
results = self.cl.SubmitManyJobs([ops for (_, _, ops) in self.queue])
for ((status, data), (idx, name, _)) in zip(results, self.queue):