#
#
-# Copyright (C) 2006, 2007 Google Inc.
+# Copyright (C) 2006, 2007, 2008, 2009, 2010 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
from ganeti import rpc
from ganeti import ssh
from ganeti import compat
+from ganeti import netutils
from optparse import (OptionParser, TitledHelpFormatter,
Option, OptionValueError)
# Command line options
"ADD_UIDS_OPT",
"ALLOCATABLE_OPT",
+ "ALLOC_POLICY_OPT",
"ALL_OPT",
"AUTO_PROMOTE_OPT",
"AUTO_REPLACE_OPT",
"BACKEND_OPT",
+ "BLK_OS_OPT",
+ "CAPAB_MASTER_OPT",
+ "CAPAB_VM_OPT",
"CLEANUP_OPT",
"CLUSTER_DOMAIN_SECRET_OPT",
"CONFIRM_OPT",
"DISK_OPT",
"DISK_TEMPLATE_OPT",
"DRAINED_OPT",
+ "DRY_RUN_OPT",
+ "DRBD_HELPER_OPT",
"EARLY_RELEASE_OPT",
"ENABLED_HV_OPT",
"ERROR_CODES_OPT",
"FORCE_OPT",
"FORCE_VARIANT_OPT",
"GLOBAL_FILEDIR_OPT",
+ "HID_OS_OPT",
"HVLIST_OPT",
"HVOPTS_OPT",
"HYPERVISOR_OPT",
"IALLOCATOR_OPT",
+ "DEFAULT_IALLOCATOR_OPT",
"IDENTIFY_DEFAULTS_OPT",
"IGNORE_CONSIST_OPT",
"IGNORE_FAILURES_OPT",
+ "IGNORE_OFFLINE_OPT",
"IGNORE_REMOVE_FAILURES_OPT",
"IGNORE_SECONDARIES_OPT",
"IGNORE_SIZE_OPT",
+ "INTERVAL_OPT",
"MAC_PREFIX_OPT",
"MAINTAIN_NODE_HEALTH_OPT",
"MASTER_NETDEV_OPT",
"MC_OPT",
+ "MIGRATION_MODE_OPT",
"NET_OPT",
"NEW_CLUSTER_CERT_OPT",
"NEW_CLUSTER_DOMAIN_SECRET_OPT",
"NIC_PARAMS_OPT",
"NODE_LIST_OPT",
"NODE_PLACEMENT_OPT",
+ "NODEGROUP_OPT",
+ "NODE_PARAMS_OPT",
+ "NODRBD_STORAGE_OPT",
"NOHDR_OPT",
"NOIPCHECK_OPT",
"NO_INSTALL_OPT",
"ON_PRIMARY_OPT",
"ON_SECONDARY_OPT",
"OFFLINE_OPT",
+ "OSPARAMS_OPT",
"OS_OPT",
"OS_SIZE_OPT",
+ "PREALLOC_WIPE_DISKS_OPT",
+ "PRIMARY_IP_VERSION_OPT",
+ "PRIORITY_OPT",
"RAPI_CERT_OPT",
"READD_OPT",
"REBOOT_TYPE_OPT",
"REMOVE_INSTANCE_OPT",
"REMOVE_UIDS_OPT",
+ "RESERVED_LVS_OPT",
"ROMAN_OPT",
"SECONDARY_IP_OPT",
"SELECT_OS_OPT",
"GenerateTable",
"AskUser",
"FormatTimestamp",
+ "FormatLogMessage",
# Tags functions
"ListTags",
"AddTags",
# command line options support infrastructure
"ARGS_MANY_INSTANCES",
"ARGS_MANY_NODES",
+ "ARGS_MANY_GROUPS",
"ARGS_NONE",
"ARGS_ONE_INSTANCE",
"ARGS_ONE_NODE",
+ "ARGS_ONE_GROUP",
"ARGS_ONE_OS",
"ArgChoice",
"ArgCommand",
"ArgFile",
+ "ArgGroup",
"ArgHost",
"ArgInstance",
"ArgJobId",
"OPT_COMPL_ONE_IALLOCATOR",
"OPT_COMPL_ONE_INSTANCE",
"OPT_COMPL_ONE_NODE",
+ "OPT_COMPL_ONE_NODEGROUP",
"OPT_COMPL_ONE_OS",
"cli_option",
"SplitNodeOption",
"CalculateOSNames",
+ "ParseFields",
+ "COMMON_CREATE_OPTS",
]
NO_PREFIX = "no_"
UN_PREFIX = "-"
+#: Priorities (sorted)
+_PRIORITY_NAMES = [
+ ("low", constants.OP_PRIO_LOW),
+ ("normal", constants.OP_PRIO_NORMAL),
+ ("high", constants.OP_PRIO_HIGH),
+ ]
+
+#: Priority dictionary for easier lookup
+# TODO: Replace this and _PRIORITY_NAMES with a single sorted dictionary once
+# we migrate to Python 2.6
+_PRIONAME_TO_VALUE = dict(_PRIORITY_NAMES)
+
class _Argument:
def __init__(self, min=0, max=None): # pylint: disable-msg=W0622
"""
+
+class ArgGroup(_Argument):
+ """Node group argument.
+
+ """
+
+
class ArgJobId(_Argument):
"""Job ID argument.
ARGS_NONE = []
ARGS_MANY_INSTANCES = [ArgInstance()]
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)]
ARGS_ONE_OS = [ArgOs(min=1, max=1)]
if not args:
raise errors.OpPrereqError("No tags to be added")
op = opcodes.OpAddTags(kind=kind, name=name, tags=args)
- SubmitOpCode(op)
+ SubmitOpCode(op, opts=opts)
def RemoveTags(opts, args):
if not args:
raise errors.OpPrereqError("No tags to be removed")
op = opcodes.OpDelTags(kind=kind, name=name, tags=args)
- SubmitOpCode(op)
+ SubmitOpCode(op, opts=opts)
def check_unit(option, opt, value): # pylint: disable-msg=W0613
OPT_COMPL_ONE_INSTANCE,
OPT_COMPL_ONE_OS,
OPT_COMPL_ONE_IALLOCATOR,
- OPT_COMPL_INST_ADD_NODES) = range(100, 106)
+ OPT_COMPL_INST_ADD_NODES,
+ OPT_COMPL_ONE_NODEGROUP) = range(100, 107)
OPT_COMPL_ALL = frozenset([
OPT_COMPL_MANY_NODES,
OPT_COMPL_ONE_OS,
OPT_COMPL_ONE_IALLOCATOR,
OPT_COMPL_INST_ADD_NODES,
+ OPT_COMPL_ONE_NODEGROUP,
])
CONFIRM_OPT = cli_option("--yes", dest="confirm", action="store_true",
default=False, help="Do not require confirmation")
+IGNORE_OFFLINE_OPT = cli_option("--ignore-offline", dest="ignore_offline",
+ action="store_true", default=False,
+ help=("Ignore offline nodes and do as much"
+ " as possible"))
+
TAG_SRC_OPT = cli_option("--from", dest="tags_source",
default=None, help="File with tag names")
help=("Grab locks while doing the queries"
" in order to ensure more consistent results"))
-_DRY_RUN_OPT = cli_option("--dry-run", default=False,
- action="store_true",
- help=("Do not execute the operation, just run the"
- " check steps and verify it it could be"
- " executed"))
+DRY_RUN_OPT = cli_option("--dry-run", default=False,
+ action="store_true",
+ help=("Do not execute the operation, just run the"
+ " check steps and verify it it could be"
+ " executed"))
VERBOSE_OPT = cli_option("-v", "--verbose", default=False,
action="store_true",
default=None, type="string",
completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
+DEFAULT_IALLOCATOR_OPT = cli_option("-I", "--default-iallocator",
+ metavar="<NAME>",
+ help="Set the default instance allocator plugin",
+ default=None, type="string",
+ completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
+
OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run",
metavar="<os>",
completion_suggest=OPT_COMPL_ONE_OS)
+OSPARAMS_OPT = cli_option("-O", "--os-parameters", dest="osparams",
+ type="keyval", default={},
+ help="OS parameters")
+
FORCE_VARIANT_OPT = cli_option("--force-variant", dest="force_variant",
action="store_true", default=False,
help="Force an unknown variant")
" freeze the instance, save the state, transfer and"
" only then resume running on the secondary node)")
+MIGRATION_MODE_OPT = cli_option("--migration-mode", dest="migration_mode",
+ default=None,
+ choices=list(constants.HT_MIGRATION_MODES),
+ help="Override default migration mode (choose"
+ " either live or non-live")
+
NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node",
help="Target node and optional secondary node",
metavar="<pnode>[:<snode>]",
" times, if not given defaults to all nodes)",
completion_suggest=OPT_COMPL_ONE_NODE)
+NODEGROUP_OPT = cli_option("-g", "--node-group",
+ dest="nodegroup",
+ help="Node group (name or uuid)",
+ metavar="<nodegroup>",
+ default=None, type="string",
+ completion_suggest=OPT_COMPL_ONE_NODEGROUP)
+
SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node",
metavar="<node>",
completion_suggest=OPT_COMPL_ONE_NODE)
type="bool", default=None,
help="Set the drained flag on the node")
+CAPAB_MASTER_OPT = cli_option("--master-capable", dest="master_capable",
+ type="bool", default=None, metavar=_YORNO,
+ help="Set the master_capable flag on the node")
+
+CAPAB_VM_OPT = cli_option("--vm-capable", dest="vm_capable",
+ type="bool", default=None, metavar=_YORNO,
+ help="Set the vm_capable flag on the node")
+
ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable",
type="bool", default=None, metavar=_YORNO,
help="Set the allocatable flag on a volume")
dest="candidate_pool_size", type="int",
help="Set the candidate pool size")
-VG_NAME_OPT = cli_option("-g", "--vg-name", dest="vg_name",
+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]",
metavar="VG", default=None)
default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
help="Maximum time to wait for instance shutdown")
+INTERVAL_OPT = cli_option("--interval", dest="interval", type="int",
+ default=None,
+ help=("Number of seconds between repetions of the"
+ " command"))
+
EARLY_RELEASE_OPT = cli_option("--early-release",
dest="early_release", default=False,
action="store_true",
" ranges separated by commas, to be"
" removed from the user-id pool"))
+RESERVED_LVS_OPT = cli_option("--reserved-lvs", default=None,
+ action="store", dest="reserved_lvs",
+ help=("A comma-separated list of reserved"
+ " logical volumes names, that will be"
+ " ignored by cluster verify"))
+
ROMAN_OPT = cli_option("--roman",
dest="roman_integers", default=False,
action="store_true",
help="Use roman numbers for positive integers")
+DRBD_HELPER_OPT = cli_option("--drbd-usermode-helper", dest="drbd_helper",
+ action="store", default=None,
+ help="Specifies usermode helper for DRBD")
+
+NODRBD_STORAGE_OPT = cli_option("--no-drbd-storage", dest="drbd_storage",
+ action="store_false", default=True,
+ help="Disable support for DRBD")
+
+PRIMARY_IP_VERSION_OPT = \
+ cli_option("--primary-ip-version", default=constants.IP4_VERSION,
+ action="store", dest="primary_ip_version",
+ metavar="%d|%d" % (constants.IP4_VERSION,
+ constants.IP6_VERSION),
+ help="Cluster-wide IP version for primary IP")
+
+PRIORITY_OPT = cli_option("--priority", default=None, dest="priority",
+ metavar="|".join(name for name, _ in _PRIORITY_NAMES),
+ choices=_PRIONAME_TO_VALUE.keys(),
+ help="Priority for opcode processing")
+
+HID_OS_OPT = cli_option("--hidden", dest="hidden",
+ type="bool", default=None, metavar=_YORNO,
+ help="Sets the hidden flag on the OS")
+
+BLK_OS_OPT = cli_option("--blacklisted", dest="blacklisted",
+ type="bool", default=None, metavar=_YORNO,
+ help="Sets the blacklisted flag on the OS")
+
+PREALLOC_WIPE_DISKS_OPT = cli_option("--prealloc-wipe-disks", default=None,
+ type="bool", metavar=_YORNO,
+ dest="prealloc_wipe_disks",
+ help=("Wipe disks prior to instance"
+ " creation"))
+
+NODE_PARAMS_OPT = cli_option("--node-parameters", dest="ndparams",
+ type="keyval", default=None,
+ help="Node parameters")
+
+ALLOC_POLICY_OPT = cli_option("--alloc-policy", dest="alloc_policy",
+ action="store", metavar="POLICY", default=None,
+ help="Allocation policy for the node group")
+
+
+#: Options provided by all commands
+COMMON_OPTS = [DEBUG_OPT]
+
+# common options for creating instances. add and import then add their own
+# specific ones.
+COMMON_CREATE_OPTS = [
+ BACKEND_OPT,
+ DISK_OPT,
+ DISK_TEMPLATE_OPT,
+ FILESTORE_DIR_OPT,
+ FILESTORE_DRIVER_OPT,
+ HYPERVISOR_OPT,
+ IALLOCATOR_OPT,
+ NET_OPT,
+ NODE_PLACEMENT_OPT,
+ NOIPCHECK_OPT,
+ NONAMECHECK_OPT,
+ NONICS_OPT,
+ NWSYNC_OPT,
+ OSPARAMS_OPT,
+ OS_SIZE_OPT,
+ SUBMIT_OPT,
+ DRY_RUN_OPT,
+ PRIORITY_OPT,
+ ]
def _ParseArgs(argv, commands, aliases):
binary = argv[0].split("/")[-1]
if len(argv) > 1 and argv[1] == "--version":
- ToStdout("%s (ganeti) %s", binary, constants.RELEASE_VERSION)
+ ToStdout("%s (ganeti %s) %s", binary, constants.VCS_VERSION,
+ constants.RELEASE_VERSION)
# Quit right away. That way we don't have to care about this special
# argument. optparse.py does it the same.
sys.exit(0)
cmd = aliases[cmd]
func, args_def, parser_opts, usage, description = commands[cmd]
- parser = OptionParser(option_list=parser_opts + [_DRY_RUN_OPT, DEBUG_OPT],
+ parser = OptionParser(option_list=parser_opts + COMMON_OPTS,
description=description,
formatter=TitledHelpFormatter(),
usage="%%prog %s %s" % (cmd, usage))
return [os_name]
-def UsesRPC(fn):
- def wrapper(*args, **kwargs):
- rpc.Init()
- try:
- return fn(*args, **kwargs)
- finally:
- rpc.Shutdown()
- return wrapper
+def ParseFields(selected, default):
+ """Parses the values of "--field"-like options.
+
+ @type selected: string or None
+ @param selected: User-selected options
+ @type default: list
+ @param default: Default fields
+
+ """
+ if selected is None:
+ return default
+
+ if selected.startswith("+"):
+ return default + selected[1:].split(",")
+
+ return selected.split(",")
+
+
+UsesRPC = rpc.RunWithRPC
def AskUser(text, choices=None):
"""
ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)),
- utils.SafeEncode(log_msg))
+ FormatLogMessage(log_type, log_msg))
def ReportNotChanged(self, job_id, status):
"""Called if a job hasn't changed in a while.
self.notified_waitlock = True
-def PollJob(job_id, cl=None, feedback_fn=None):
+def FormatLogMessage(log_type, log_msg):
+ """Formats a job message according to its type.
+
+ """
+ if log_type != constants.ELOG_MESSAGE:
+ log_msg = str(log_msg)
+
+ return utils.SafeEncode(log_msg)
+
+
+def PollJob(job_id, cl=None, feedback_fn=None, reporter=None):
"""Function to poll for the result of a job.
@type job_id: job identified
if cl is None:
cl = GetClient()
- if feedback_fn:
- reporter = FeedbackFnJobPollReportCb(feedback_fn)
- else:
- reporter = StdioJobPollReportCb()
+ if reporter is None:
+ if feedback_fn:
+ reporter = FeedbackFnJobPollReportCb(feedback_fn)
+ else:
+ reporter = StdioJobPollReportCb()
+ elif feedback_fn:
+ raise errors.ProgrammerError("Can't specify reporter and feedback function")
return GenericPollJob(job_id, _LuxiJobPollCb(cl), reporter)
-def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None):
+def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None, reporter=None):
"""Legacy function to submit an opcode.
This is just a simple wrapper over the construction of the processor
SetGenericOpcodeOpts([op], opts)
- job_id = SendJob([op], cl)
+ job_id = SendJob([op], cl=cl)
- op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn)
+ op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn,
+ reporter=reporter)
return op_results[0]
if not options:
return
for op in opcode_list:
- op.dry_run = options.dry_run
op.debug_level = options.debug
+ if hasattr(options, "dry_run"):
+ op.dry_run = options.dry_run
+ if getattr(options, "priority", None) is not None:
+ op.priority = _PRIONAME_TO_VALUE[options.priority]
def GetClient():
elif isinstance(err, errors.HooksFailure):
obuf.write("Failure: hooks general failure: %s" % msg)
elif isinstance(err, errors.ResolverError):
- this_host = utils.HostInfo.SysName()
+ this_host = netutils.Hostname.GetSysName()
if err.args[0] == this_host:
msg = "Failure: can't resolve my own hostname ('%s')"
else:
elif isinstance(err, luxi.TimeoutError):
obuf.write("Timeout while talking to the master daemon. Error:\n"
"%s" % msg)
+ elif isinstance(err, luxi.PermissionError):
+ obuf.write("It seems you don't have permissions to connect to the"
+ " master daemon.\nPlease retry as a different user.")
elif isinstance(err, luxi.ProtocolError):
obuf.write("Unhandled protocol error while talking to the master daemon:\n"
"%s" % msg)
+ elif isinstance(err, errors.JobLost):
+ obuf.write("Error checking job status: %s" % msg)
elif isinstance(err, errors.GenericError):
obuf.write("Unhandled Ganeti error: %s" % msg)
elif isinstance(err, JobSubmittedException):
return result
+def ParseNicOption(optvalue):
+ """Parses the value of the --net option(s).
+
+ """
+ try:
+ nic_max = max(int(nidx[0]) + 1 for nidx in optvalue)
+ except (TypeError, ValueError), err:
+ raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
+
+ nics = [{}] * nic_max
+ for nidx, ndict in optvalue:
+ nidx = int(nidx)
+
+ if not isinstance(ndict, dict):
+ raise errors.OpPrereqError("Invalid nic/%d value: expected dict,"
+ " got %s" % (nidx, ndict))
+
+ utils.ForceDictType(ndict, constants.INIC_PARAMS_TYPES)
+
+ nics[nidx] = ndict
+
+ return nics
+
+
def GenericInstanceCreate(mode, opts, args):
"""Add an instance to the cluster via either creation or import.
hypervisor, hvparams = opts.hypervisor
if opts.nics:
- try:
- nic_max = max(int(nidx[0]) + 1 for nidx in opts.nics)
- except ValueError, err:
- raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
- nics = [{}] * nic_max
- for nidx, ndict in opts.nics:
- nidx = int(nidx)
- if not isinstance(ndict, dict):
- msg = "Invalid nic/%d value: expected dict, got %s" % (nidx, ndict)
- raise errors.OpPrereqError(msg)
- nics[nidx] = ndict
+ nics = ParseNicOption(opts.nics)
elif opts.no_nics:
# no nics
nics = []
if mode == constants.INSTANCE_CREATE:
start = opts.start
os_type = opts.os
+ force_variant = opts.force_variant
src_node = None
src_path = None
no_install = opts.no_install
elif mode == constants.INSTANCE_IMPORT:
start = False
os_type = None
+ force_variant = False
src_node = opts.src_node
src_path = opts.src_dir
no_install = None
hypervisor=hypervisor,
hvparams=hvparams,
beparams=opts.beparams,
+ osparams=opts.osparams,
mode=mode,
start=start,
os_type=os_type,
+ force_variant=force_variant,
src_node=src_node,
src_path=src_path,
no_install=no_install,
if separator is None:
mlens = [0 for name in fields]
- format = ' '.join(format_fields)
+ format_str = ' '.join(format_fields)
else:
- format = separator.replace("%", "%%").join(format_fields)
+ format_str = separator.replace("%", "%%").join(format_fields)
for row in data:
if row is None:
mlens[idx] = max(mlens[idx], len(hdr))
args.append(mlens[idx])
args.append(hdr)
- result.append(format % tuple(args))
+ result.append(format_str % tuple(args))
if separator is None:
assert len(mlens) == len(fields)
if separator is None:
args.append(mlens[idx])
args.append(line[idx])
- result.append(format % tuple(args))
+ result.append(format_str % tuple(args))
return result
assert result
for job_data, status in zip(self.jobs, result):
- if status[0] in (constants.JOB_STATUS_QUEUED,
- constants.JOB_STATUS_WAITLOCK,
- constants.JOB_STATUS_CANCELING):
- # job is still waiting
+ if (isinstance(status, list) and status and
+ status[0] in (constants.JOB_STATUS_QUEUED,
+ constants.JOB_STATUS_WAITLOCK,
+ constants.JOB_STATUS_CANCELING)):
+ # job is still present and waiting
continue
- # good candidate found
+ # good candidate found (either running job or lost job)
self.jobs.remove(job_data)
return job_data
try:
job_result = PollJob(jid, cl=self.cl, feedback_fn=self.feedback_fn)
success = True
+ except errors.JobLost, err:
+ _, job_result = FormatError(err)
+ ToStderr("Job %s for %s has been archived, cannot check its result",
+ jid, name)
+ success = False
except (errors.GenericError, luxi.ProtocolError), err:
_, job_result = FormatError(err)
success = False
ToStdout("%s: %s", result, name)
else:
ToStderr("Failure for %s: %s", name, result)
+ return [row[1:3] for row in self.jobs]