Commit 72884fef authored by Michael Hanselmann's avatar Michael Hanselmann
Browse files

Merge branch 'devel-2.2'



* devel-2.2:
  Add simple unittest for utils.CommaJoin
  LUDelTags: Improve formatting of error message
  LUGetTags: Acquire locks in shared mode
  gnt-cluster: Replace hardcoded “xenvg” with value retrieved from master
  Export VG name via LUQueryConfigValues
  RAPI QA: Override MAC address when moving instance
  move-instance: Allow overriding instance parameters
  cli: Move parsing of --net option to separate function
  kvm: collapse two consecutive extend calls
  kvm: Introduce support for -mem-path

Conflicts:
	test/ganeti.cli_unittest.py: Trivial
Signed-off-by: default avatarMichael Hanselmann <hansmi@google.com>
Reviewed-by: default avatarIustin Pop <iustin@google.com>
parents e32e7886 750022e0
......@@ -83,6 +83,8 @@ destination-related options default to the source value (e.g. setting
When moving a single instance: Secondary node on destination cluster.
``--iallocator``
Iallocator for creating instance on destination cluster.
``--hypervisor-parameters``/``--backend-parameters``/``--os-parameters``/``--net``
When moving a single instance: Override instances' parameters.
``--parallel``
Number of instance moves to run in parallel.
``--verbose``/``--debug``
......
......@@ -1840,6 +1840,30 @@ def GenericMain(commands, override=None, aliases=None):
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.
......@@ -1861,17 +1885,7 @@ def GenericInstanceCreate(mode, opts, args):
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 = []
......
......@@ -4226,7 +4226,7 @@ class LUQueryConfigValues(NoHooksLU):
REQ_BGL = False
_FIELDS_DYNAMIC = utils.FieldSet()
_FIELDS_STATIC = utils.FieldSet("cluster_name", "master_node", "drain_flag",
"watcher_pause")
"watcher_pause", "volume_group_name")
def CheckArguments(self):
_CheckOutputFields(static=self._FIELDS_STATIC,
......@@ -4250,6 +4250,8 @@ class LUQueryConfigValues(NoHooksLU):
entry = os.path.exists(constants.JOB_QUEUE_DRAIN_FILE)
elif field == "watcher_pause":
entry = utils.ReadWatcherPauseFile(constants.WATCHER_PAUSEFILE)
elif field == "volume_group_name":
entry = self.cfg.GetVGName()
else:
raise errors.ParameterError(field)
values.append(entry)
......@@ -9785,6 +9787,9 @@ class TagsLU(NoHooksLU): # pylint: disable-msg=W0223
self.op.name = _ExpandInstanceName(self.cfg, self.op.name)
self.needed_locks[locking.LEVEL_INSTANCE] = self.op.name
# FIXME: Acquire BGL for cluster tag operations (as of this writing it's
# not possible to acquire the BGL based on opcode parameters)
def CheckPrereq(self):
"""Check prerequisites.
......@@ -9811,6 +9816,12 @@ class LUGetTags(TagsLU):
]
REQ_BGL = False
def ExpandNames(self):
TagsLU.ExpandNames(self)
# Share locks as this is only a read operation
self.share_locks = dict.fromkeys(locking.LEVELS, 1)
def Exec(self, feedback_fn):
"""Returns the tag list.
......@@ -9917,12 +9928,13 @@ class LUDelTags(TagsLU):
objects.TaggableObject.ValidateTag(tag)
del_tags = frozenset(self.op.tags)
cur_tags = self.target.GetTags()
if not del_tags <= cur_tags:
diff_tags = del_tags - cur_tags
diff_names = ["'%s'" % tag for tag in diff_tags]
diff_names.sort()
diff_tags = del_tags - cur_tags
if diff_tags:
diff_names = ("'%s'" % i for i in sorted(diff_tags))
raise errors.OpPrereqError("Tag(s) %s not found" %
(",".join(diff_names)), errors.ECODE_NOENT)
(utils.CommaJoin(diff_names), ),
errors.ECODE_NOENT)
def Exec(self, feedback_fn):
"""Remove the tag from the object.
......
......@@ -567,6 +567,7 @@ HV_KVM_FLAG = "kvm_flag"
HV_VHOST_NET = "vhost_net"
HV_KVM_USE_CHROOT = "use_chroot"
HV_CPU_MASK = "cpu_mask"
HV_MEM_PATH = "mem_path"
HVS_PARAMETER_TYPES = {
HV_BOOT_ORDER: VTYPE_STRING,
......@@ -603,6 +604,7 @@ HVS_PARAMETER_TYPES = {
HV_VHOST_NET: VTYPE_BOOL,
HV_KVM_USE_CHROOT: VTYPE_BOOL,
HV_CPU_MASK: VTYPE_STRING,
HV_MEM_PATH: VTYPE_STRING,
}
HVS_PARAMETERS = frozenset(HVS_PARAMETER_TYPES.keys())
......@@ -971,6 +973,7 @@ HVC_DEFAULTS = {
HV_KVM_FLAG: "",
HV_VHOST_NET: False,
HV_KVM_USE_CHROOT: False,
HV_MEM_PATH: "",
},
HT_FAKE: {
},
......
......@@ -206,6 +206,7 @@ class KVMHypervisor(hv_base.BaseHypervisor):
hv_base.ParamInSet(False, constants.HT_KVM_FLAG_VALUES),
constants.HV_VHOST_NET: hv_base.NO_CHECK,
constants.HV_KVM_USE_CHROOT: hv_base.NO_CHECK,
constants.HV_MEM_PATH: hv_base.OPT_DIR_CHECK,
}
_MIGRATION_STATUS_RE = re.compile('Migration\s+status:\s+(\w+)',
......@@ -568,6 +569,10 @@ class KVMHypervisor(hv_base.BaseHypervisor):
root_append.append('console=ttyS0,38400')
kvm_cmd.extend(['-append', ' '.join(root_append)])
mem_path = hvp[constants.HV_MEM_PATH]
if mem_path:
kvm_cmd.extend(["-mem-path", mem_path, "-mem-prealloc"])
mouse_type = hvp[constants.HV_USB_MOUSE]
vnc_bind_address = hvp[constants.HV_VNC_BIND_ADDRESS]
......
......@@ -658,6 +658,19 @@
</listitem>
</varlistentry>
<varlistentry>
<term>mem_path</term>
<listitem>
<simpara>Valid for the KVM hypervisor.</simpara>
<simpara>This option passes the -mem-path argument to kvm with
the path (on the node) to the mount point of the hugetlbfs
file system, along with the -mem-prealloc argument too.
</simpara>
</listitem>
</varlistentry>
<varlistentry>
<term>use_chroot</term>
<listitem>
......
......@@ -416,6 +416,7 @@ def TestInterClusterInstanceMove(src_instance, dest_instance, pnode, snode):
"--dest-instance-name=%s" % destname,
"--dest-primary-node=%s" % pnode["primary"],
"--dest-secondary-node=%s" % snode["primary"],
"--net=0:mac=%s" % constants.VALUE_GENERATE,
master["primary"],
master["primary"],
......
......@@ -436,8 +436,10 @@ def VerifyDisks(opts, args):
@return: the desired exit code
"""
cl = GetClient()
op = opcodes.OpVerifyDisks()
result = SubmitOpCode(op, opts=opts)
result = SubmitOpCode(op, opts=opts, cl=cl)
if not isinstance(result, (list, tuple)) or len(result) != 3:
raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
......@@ -459,13 +461,15 @@ def VerifyDisks(opts, args):
op = opcodes.OpActivateInstanceDisks(instance_name=iname)
try:
ToStdout("Activating disks for instance '%s'", iname)
SubmitOpCode(op, opts=opts)
SubmitOpCode(op, opts=opts, cl=cl)
except errors.GenericError, err:
nret, msg = FormatError(err)
retcode |= nret
ToStderr("Error activating disks for instance %s: %s", iname, msg)
if missing:
(vg_name, ) = cl.QueryConfigValues(["volume_group_name"])
for iname, ival in missing.iteritems():
all_missing = compat.all(x[0] in bad_nodes for x in ival)
if all_missing:
......@@ -476,11 +480,12 @@ def VerifyDisks(opts, args):
ival.sort()
for node, vol in ival:
if node in bad_nodes:
ToStdout("\tbroken node %s /dev/xenvg/%s", node, vol)
ToStdout("\tbroken node %s /dev/%s/%s", node, vg_name, vol)
else:
ToStdout("\t%s /dev/xenvg/%s", node, vol)
ToStdout("\t%s /dev/%s/%s", node, vg_name, vol)
ToStdout("You need to run replace_disks for all the above"
" instances, if this message persist after fixing nodes.")
" instances, if this message persist after fixing nodes.")
retcode |= 1
return retcode
......
......@@ -450,5 +450,26 @@ class TestConstants(unittest.TestCase):
sorted(constants.OP_PRIO_SUBMIT_VALID, reverse=True))
class TestParseNicOption(unittest.TestCase):
def test(self):
self.assertEqual(cli.ParseNicOption([("0", { "link": "eth0", })]),
[{ "link": "eth0", }])
self.assertEqual(cli.ParseNicOption([("5", { "ip": "192.0.2.7", })]),
[{}, {}, {}, {}, {}, { "ip": "192.0.2.7", }])
def testErrors(self):
for i in [None, "", "abc", "zero", "Hello World", "\0", []]:
self.assertRaises(errors.OpPrereqError, cli.ParseNicOption,
[(i, { "link": "eth0", })])
self.assertRaises(errors.OpPrereqError, cli.ParseNicOption,
[("0", i)])
self.assertRaises(errors.TypeEnforcementError, cli.ParseNicOption,
[(0, { True: False, })])
self.assertRaises(errors.TypeEnforcementError, cli.ParseNicOption,
[(3, { "mode": [], })])
if __name__ == '__main__':
testutils.GanetiTestProgram()
......@@ -2311,5 +2311,15 @@ class TestShellWriter(unittest.TestCase):
self.assertEqual(buf.getvalue(), "")
class TestCommaJoin(unittest.TestCase):
def test(self):
self.assertEqual(utils.CommaJoin([]), "")
self.assertEqual(utils.CommaJoin([1, 2, 3]), "1, 2, 3")
self.assertEqual(utils.CommaJoin(["Hello"]), "Hello")
self.assertEqual(utils.CommaJoin(["Hello", "World"]), "Hello, World")
self.assertEqual(utils.CommaJoin(["Hello", "World", 99]),
"Hello, World, 99")
if __name__ == '__main__':
testutils.GanetiTestProgram()
......@@ -36,6 +36,7 @@ from ganeti import cli
from ganeti import constants
from ganeti import utils
from ganeti import workerpool
from ganeti import objects
from ganeti import compat
from ganeti import rapi
......@@ -269,7 +270,8 @@ class InstanceMove(object):
"""
def __init__(self, src_instance_name, dest_instance_name,
dest_pnode, dest_snode, dest_iallocator):
dest_pnode, dest_snode, dest_iallocator,
hvparams, beparams, osparams, nics):
"""Initializes this class.
@type src_instance_name: string
......@@ -282,6 +284,14 @@ class InstanceMove(object):
@param dest_snode: Name of secondary node on destination cluster
@type dest_iallocator: string or None
@param dest_iallocator: Name of iallocator to use
@type hvparams: dict or None
@param hvparams: Hypervisor parameters to override
@type beparams: dict or None
@param beparams: Backend parameters to override
@type osparams: dict or None
@param osparams: OS parameters to override
@type nics: dict or None
@param nics: NICs to override
"""
self.src_instance_name = src_instance_name
......@@ -289,6 +299,10 @@ class InstanceMove(object):
self.dest_pnode = dest_pnode
self.dest_snode = dest_snode
self.dest_iallocator = dest_iallocator
self.hvparams = hvparams
self.beparams = beparams
self.osparams = osparams
self.nics = nics
self.error_message = None
......@@ -414,7 +428,9 @@ class MoveDestExecutor(object):
job_id = self._CreateInstance(dest_client, mrt.move.dest_instance_name,
mrt.move.dest_pnode, mrt.move.dest_snode,
mrt.move.dest_iallocator,
mrt.src_instinfo, mrt.src_expinfo)
mrt.src_instinfo, mrt.src_expinfo,
mrt.move.hvparams, mrt.move.beparams,
mrt.move.beparams, mrt.move.nics)
mrt.PollJob(dest_client, job_id,
remote_import_fn=compat.partial(self._SetImportInfo, mrt))
......@@ -437,7 +453,9 @@ class MoveDestExecutor(object):
mrt.dest_to_source.release()
@staticmethod
def _CreateInstance(cl, name, pnode, snode, iallocator, instance, expinfo):
def _CreateInstance(cl, name, pnode, snode, iallocator, instance, expinfo,
override_hvparams, override_beparams, override_osparams,
override_nics):
"""Starts the instance creation in remote import mode.
@type cl: L{rapi.client.GanetiRapiClient}
......@@ -454,6 +472,14 @@ class MoveDestExecutor(object):
@param instance: Instance details from source cluster
@type expinfo: dict
@param expinfo: Prepared export information from source cluster
@type override_hvparams: dict or None
@param override_hvparams: Hypervisor parameters to override
@type override_beparams: dict or None
@param override_beparams: Backend parameters to override
@type override_osparams: dict or None
@param override_osparams: OS parameters to override
@type override_nics: dict or None
@param override_nics: NICs to override
@return: Job ID
"""
......@@ -471,12 +497,32 @@ class MoveDestExecutor(object):
constants.INIC_LINK: link,
} for ip, mac, mode, link in instance["nics"]]
if len(override_nics) > len(nics):
raise Error("Can not create new NICs")
if override_nics:
assert len(override_nics) <= len(nics)
for idx, (nic, override) in enumerate(zip(nics, override_nics)):
nics[idx] = objects.FillDict(nic, override)
# TODO: Should this be the actual up/down status? (run_state)
start = (instance["config_state"] == "up")
assert len(disks) == len(instance["disks"])
assert len(nics) == len(instance["nics"])
inst_beparams = instance["be_instance"]
if not inst_beparams:
inst_beparams = {}
inst_hvparams = instance["hv_instance"]
if not inst_hvparams:
inst_hvparams = {}
inst_osparams = instance["os_instance"]
if not inst_osparams:
inst_osparams = {}
return cl.CreateInstance(constants.INSTANCE_REMOTE_IMPORT,
name, disk_template, disks, nics,
os=instance["os"],
......@@ -489,9 +535,12 @@ class MoveDestExecutor(object):
source_handshake=expinfo["handshake"],
source_x509_ca=expinfo["x509_ca"],
source_instance_name=instance["name"],
beparams=instance["be_instance"],
hvparams=instance["hv_instance"],
osparams=instance["os_instance"])
beparams=objects.FillDict(inst_beparams,
override_beparams),
hvparams=objects.FillDict(inst_hvparams,
override_hvparams),
osparams=objects.FillDict(inst_osparams,
override_osparams))
class MoveSourceExecutor(object):
......@@ -708,6 +757,10 @@ def ParseOptions():
parser.add_option(cli.DEBUG_OPT)
parser.add_option(cli.VERBOSE_OPT)
parser.add_option(cli.IALLOCATOR_OPT)
parser.add_option(cli.BACKEND_OPT)
parser.add_option(cli.HVOPTS_OPT)
parser.add_option(cli.OSPARAMS_OPT)
parser.add_option(cli.NET_OPT)
parser.add_option(SRC_RAPI_PORT_OPT)
parser.add_option(SRC_CA_FILE_OPT)
parser.add_option(SRC_USERNAME_OPT)
......@@ -757,13 +810,24 @@ def CheckOptions(parser, options, args):
options.dest_primary_node or
options.dest_secondary_node):
parser.error("An iallocator or the destination node is required")
if options.hvparams:
utils.ForceDictType(options.hvparams, constants.HVS_PARAMETER_TYPES)
if options.beparams:
utils.ForceDictType(options.beparams, constants.BES_PARAMETER_TYPES)
if options.nics:
options.nics = cli.ParseNicOption(options.nics)
else:
# Moving more than one instance
if (options.dest_instance_name or options.dest_primary_node or
options.dest_secondary_node):
parser.error("The options --dest-instance-name, --dest-primary-node and"
" --dest-secondary-node can only be used when moving exactly"
" one instance")
options.dest_secondary_node or options.hvparams or
options.beparams or options.osparams or options.nics):
parser.error("The options --dest-instance-name, --dest-primary-node,"
" --dest-secondary-node, --hypervisor-parameters,"
" --backend-parameters, --os-parameters and --net can"
" only be used when moving exactly one instance")
if not options.iallocator:
parser.error("An iallocator must be specified for moving more than one"
......@@ -797,6 +861,9 @@ def main():
assert len(instance_names) == 1 or options.iallocator
assert (len(instance_names) > 1 or options.iallocator or
options.dest_primary_node or options.dest_secondary_node)
assert (len(instance_names) == 1 or
not (options.hvparams or options.beparams or options.osparams or
options.nics))
# Prepare list of instance moves
moves = []
......@@ -811,7 +878,9 @@ def main():
moves.append(InstanceMove(src_instance_name, dest_instance_name,
options.dest_primary_node,
options.dest_secondary_node,
options.iallocator))
options.iallocator, options.hvparams,
options.beparams, options.osparams,
options.nics))
assert len(moves) == len(instance_names)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment