blob: 3650052d7b75e843d6ba7002408e9b3a512e8f16 [file] [log] [blame]
#
#
# Copyright (C) 2007, 2011, 2012, 2013 Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""Instance related QA tests.
"""
import os
import re
import time
from ganeti import utils
from ganeti import constants
from ganeti import pathutils
from ganeti import query
from ganeti.netutils import IP4Address
import qa_config
import qa_daemon
import qa_utils
import qa_error
from qa_filters import stdout_of
from qa_utils import AssertCommand, AssertEqual, AssertIn
from qa_utils import InstanceCheck, INST_DOWN, INST_UP, FIRST_ARG, RETURN_VALUE
from qa_instance_utils import CheckSsconfInstanceList, \
CreateInstanceDrbd8, \
CreateInstanceByDiskTemplate, \
CreateInstanceByDiskTemplateOneNode, \
GetGenericAddParameters
def _GetDiskStatePath(disk):
return "/sys/block/%s/device/state" % disk
def GetInstanceInfo(instance):
"""Return information about the actual state of an instance.
@type instance: string
@param instance: the instance name
@return: a dictionary with the following keys:
- "nodes": instance nodes, a list of strings
- "volumes": instance volume IDs, a list of strings
- "drbd-minors": DRBD minors used by the instance, a dictionary where
keys are nodes, and values are lists of integers (or an empty
dictionary for non-DRBD instances)
- "disk-template": instance disk template
- "storage-type": storage type associated with the instance disk template
"""
node_elem = r"([^,()]+)(?:\s+\([^)]+\))?"
# re_nodelist matches a list of nodes returned by gnt-instance info, e.g.:
# node1.fqdn
# node2.fqdn,node3.fqdn
# node4.fqdn (group mygroup, group UUID 01234567-abcd-0123-4567-0123456789ab)
# FIXME This works with no more than 2 secondaries
re_nodelist = re.compile(node_elem + "(?:," + node_elem + ")?$")
info = qa_utils.GetObjectInfo(["gnt-instance", "info", instance])[0]
nodes = []
for nodeinfo in info["Nodes"]:
if "primary" in nodeinfo:
nodes.append(nodeinfo["primary"])
elif "secondaries" in nodeinfo:
nodestr = nodeinfo["secondaries"]
if nodestr:
m = re_nodelist.match(nodestr)
if m:
nodes.extend(filter(None, m.groups()))
else:
nodes.append(nodestr)
re_drbdnode = re.compile(r"^([^\s,]+),\s+minor=([0-9]+)$")
vols = []
drbd_min = {}
dtypes = []
for (count, diskinfo) in enumerate(info["Disks"]):
(dtype, _) = diskinfo["disk/%s" % count].split(",", 1)
dtypes.append(dtype)
if dtype == constants.DT_DRBD8:
for child in diskinfo["child devices"]:
vols.append(child["logical_id"])
for key in ["nodeA", "nodeB"]:
m = re_drbdnode.match(diskinfo[key])
if not m:
raise qa_error.Error("Cannot parse DRBD info: %s" % diskinfo[key])
node = m.group(1)
minor = int(m.group(2))
minorlist = drbd_min.setdefault(node, [])
minorlist.append(minor)
elif dtype == constants.DT_PLAIN:
vols.append(diskinfo["logical_id"])
# TODO remove and modify calling sites
disk_template = utils.GetDiskTemplateString(dtypes)
storage_type = constants.MAP_DISK_TEMPLATE_STORAGE_TYPE[disk_template]
assert nodes
assert len(nodes) < 2 or vols
return {
"nodes": nodes,
"volumes": vols,
"drbd-minors": drbd_min,
"disk-template": disk_template,
"storage-type": storage_type,
}
def _DestroyInstanceDisks(instance):
"""Remove all the backend disks of an instance.
This is used to simulate HW errors (dead nodes, broken disks...); the
configuration of the instance is not affected.
@type instance: dictionary
@param instance: the instance
"""
info = GetInstanceInfo(instance.name)
# FIXME: destruction/removal should be part of the disk class
if info["storage-type"] == constants.ST_LVM_VG:
vols = info["volumes"]
for node in info["nodes"]:
AssertCommand(["lvremove", "-f"] + vols, node=node)
elif info["storage-type"] in (constants.ST_FILE, constants.ST_SHARED_FILE):
# Note that this works for both file and sharedfile, and this is intended.
storage_dir = qa_config.get("file-storage-dir",
pathutils.DEFAULT_FILE_STORAGE_DIR)
idir = os.path.join(storage_dir, instance.name)
for node in info["nodes"]:
AssertCommand(["rm", "-rf", idir], node=node)
elif info["storage-type"] == constants.ST_DISKLESS:
pass
def _GetInstanceFields(instance, fields):
"""Get the value of one or more fields of an instance.
@type instance: string
@param instance: instance name
@type field: list of string
@param field: name of the fields
@rtype: list of string
@return: value of the fields
"""
master = qa_config.GetMasterNode()
infocmd = utils.ShellQuoteArgs(["gnt-instance", "list", "--no-headers",
"--separator=:", "--units", "m", "-o",
",".join(fields), instance])
return tuple(qa_utils.GetCommandOutput(master.primary, infocmd)
.strip()
.split(":"))
def _GetInstanceField(instance, field):
"""Get the value of a field of an instance.
@type instance: string
@param instance: Instance name
@type field: string
@param field: Name of the field
@rtype: string
"""
return _GetInstanceFields(instance, [field])[0]
def _GetBoolInstanceField(instance, field):
"""Get the Boolean value of a field of an instance.
@type instance: string
@param instance: Instance name
@type field: string
@param field: Name of the field
@rtype: bool
"""
info_out = _GetInstanceField(instance, field)
if info_out == "Y":
return True
elif info_out == "N":
return False
else:
raise qa_error.Error("Field %s of instance %s has a non-Boolean value:"
" %s" % (field, instance, info_out))
def _GetNumInstanceField(instance, field):
"""Get a numeric value of a field of an instance.
@type instance: string
@param instance: Instance name
@type field: string
@param field: Name of the field
@rtype: int or float
"""
info_out = _GetInstanceField(instance, field)
try:
ret = int(info_out)
except ValueError:
try:
ret = float(info_out)
except ValueError:
raise qa_error.Error("Field %s of instance %s has a non-numeric value:"
" %s" % (field, instance, info_out))
return ret
def GetInstanceSpec(instance, spec):
"""Return the current spec for the given parameter.
@type instance: string
@param instance: Instance name
@type spec: string
@param spec: one of the supported parameters: "memory-size", "cpu-count",
"disk-count", "disk-size", "nic-count"
@rtype: tuple
@return: (minspec, maxspec); minspec and maxspec can be different only for
memory and disk size
"""
specmap = {
"memory-size": ["be/minmem", "be/maxmem"],
"cpu-count": ["vcpus"],
"disk-count": ["disk.count"],
"disk-size": ["disk.size/ "],
"nic-count": ["nic.count"],
}
# For disks, first we need the number of disks
if spec == "disk-size":
(numdisk, _) = GetInstanceSpec(instance, "disk-count")
fields = ["disk.size/%s" % k for k in range(0, numdisk)]
else:
assert spec in specmap, "%s not in %s" % (spec, specmap)
fields = specmap[spec]
values = [_GetNumInstanceField(instance, f) for f in fields]
return (min(values), max(values))
def IsFailoverSupported(instance):
return instance.disk_template in constants.DTS_MIRRORED
def IsMigrationSupported(instance):
return instance.disk_template in constants.DTS_MIRRORED
def IsDiskReplacingSupported(instance):
return instance.disk_template == constants.DT_DRBD8
def IsDiskSupported(instance):
return instance.disk_template != constants.DT_DISKLESS
def TestInstanceAddWithPlainDisk(nodes, fail=False):
"""gnt-instance add -t plain"""
if constants.DT_PLAIN in qa_config.GetEnabledDiskTemplates():
instance = CreateInstanceByDiskTemplateOneNode(nodes, constants.DT_PLAIN,
fail=fail)
if not fail:
qa_utils.RunInstanceCheck(instance, True)
return instance
@InstanceCheck(None, INST_UP, RETURN_VALUE)
def TestInstanceAddWithDrbdDisk(nodes):
"""gnt-instance add -t drbd"""
if constants.DT_DRBD8 in qa_config.GetEnabledDiskTemplates():
return CreateInstanceDrbd8(nodes)
@InstanceCheck(None, INST_UP, RETURN_VALUE)
def TestInstanceAddFile(nodes):
"""gnt-instance add -t file"""
assert len(nodes) == 1
if constants.DT_FILE in qa_config.GetEnabledDiskTemplates():
return CreateInstanceByDiskTemplateOneNode(nodes, constants.DT_FILE)
@InstanceCheck(None, INST_UP, RETURN_VALUE)
def TestInstanceAddSharedFile(nodes):
"""gnt-instance add -t sharedfile"""
assert len(nodes) == 1
if constants.DT_SHARED_FILE in qa_config.GetEnabledDiskTemplates():
return CreateInstanceByDiskTemplateOneNode(nodes, constants.DT_SHARED_FILE)
@InstanceCheck(None, INST_UP, RETURN_VALUE)
def TestInstanceAddDiskless(nodes):
"""gnt-instance add -t diskless"""
assert len(nodes) == 1
if constants.DT_DISKLESS in qa_config.GetEnabledDiskTemplates():
return CreateInstanceByDiskTemplateOneNode(nodes, constants.DT_DISKLESS)
@InstanceCheck(None, INST_UP, RETURN_VALUE)
def TestInstanceAddRADOSBlockDevice(nodes):
"""gnt-instance add -t rbd"""
assert len(nodes) == 1
if constants.DT_RBD in qa_config.GetEnabledDiskTemplates():
return CreateInstanceByDiskTemplateOneNode(nodes, constants.DT_RBD)
@InstanceCheck(None, INST_UP, RETURN_VALUE)
def TestInstanceAddGluster(nodes):
"""gnt-instance add -t gluster"""
assert len(nodes) == 1
if constants.DT_GLUSTER in qa_config.GetEnabledDiskTemplates():
return CreateInstanceByDiskTemplateOneNode(nodes, constants.DT_GLUSTER)
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
def TestInstanceRemove(instance):
"""gnt-instance remove"""
AssertCommand(["gnt-instance", "remove", "-f", instance.name])
@InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG)
def TestInstanceStartup(instance):
"""gnt-instance startup"""
AssertCommand(["gnt-instance", "startup", instance.name])
@InstanceCheck(INST_UP, INST_DOWN, FIRST_ARG)
def TestInstanceShutdown(instance):
"""gnt-instance shutdown"""
AssertCommand(["gnt-instance", "shutdown", instance.name])
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
def TestInstanceReboot(instance):
"""gnt-instance reboot"""
options = qa_config.get("options", {})
reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
name = instance.name
for rtype in reboot_types:
AssertCommand(["gnt-instance", "reboot", "--type=%s" % rtype, name])
AssertCommand(["gnt-instance", "shutdown", name])
qa_utils.RunInstanceCheck(instance, False)
AssertCommand(["gnt-instance", "reboot", name])
master = qa_config.GetMasterNode()
cmd = ["gnt-instance", "list", "--no-headers", "-o", "status", name]
result_output = qa_utils.GetCommandOutput(master.primary,
utils.ShellQuoteArgs(cmd))
AssertEqual(result_output.strip(), constants.INSTST_RUNNING)
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
def TestInstanceReinstall(instance):
"""gnt-instance reinstall"""
if instance.disk_template == constants.DT_DISKLESS:
print qa_utils.FormatInfo("Test not supported for diskless instances")
return
qa_storage = qa_config.get("qa-storage")
if qa_storage is None:
print qa_utils.FormatInfo("Test not supported because the additional QA"
" storage is not available")
else:
# Reinstall with OS image from QA storage
url = "%s/busybox.img" % qa_storage
AssertCommand(["gnt-instance", "reinstall",
"--os-parameters", "os-image=" + url,
"-f", instance.name])
# Reinstall with OS image as local file on the node
pnode = _GetInstanceField(instance.name, "pnode")
cmd = ("wget -O busybox.img %s &> /dev/null &&"
" echo $(pwd)/busybox.img") % url
image = qa_utils.GetCommandOutput(pnode, cmd).strip()
AssertCommand(["gnt-instance", "reinstall",
"--os-parameters", "os-image=" + image,
"-f", instance.name])
# Reinstall non existing local file
AssertCommand(["gnt-instance", "reinstall",
"--os-parameters", "os-image=NonExistantOsForQa",
"-f", instance.name], fail=True)
# Reinstall non existing URL
AssertCommand(["gnt-instance", "reinstall",
"--os-parameters", "os-image=http://NonExistantOsForQa",
"-f", instance.name], fail=True)
# Reinstall using OS scripts
AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
# Test with non-existant OS definition
AssertCommand(["gnt-instance", "reinstall", "-f",
"--os-type=NonExistantOsForQa",
instance.name],
fail=True)
# Test with existing OS but invalid variant
AssertCommand(["gnt-instance", "reinstall", "-f", "-o", "debootstrap+ola",
instance.name],
fail=True)
# Test with existing OS but invalid variant
AssertCommand(["gnt-instance", "reinstall", "-f", "-o", "debian-image+ola",
instance.name],
fail=True)
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
def TestInstanceRenameAndBack(rename_source, rename_target):
"""gnt-instance rename
This must leave the instance with the original name, not the target
name.
"""
CheckSsconfInstanceList(rename_source)
# first do a rename to a different actual name, expecting it to fail
qa_utils.AddToEtcHosts(["meeeeh-not-exists", rename_target])
try:
AssertCommand(["gnt-instance", "rename", rename_source, rename_target],
fail=True)
CheckSsconfInstanceList(rename_source)
finally:
qa_utils.RemoveFromEtcHosts(["meeeeh-not-exists", rename_target])
info = GetInstanceInfo(rename_source)
# Check instance volume tags correctly updated. Note that this check is lvm
# specific, so we skip it for non-lvm-based instances.
# FIXME: This will need updating when instances will be able to have
# different disks living on storage pools with etherogeneous storage types.
# FIXME: This check should be put inside the disk/storage class themselves,
# rather than explicitly called here.
if info["storage-type"] == constants.ST_LVM_VG:
# In the lvm world we can check for tags on the logical volume
tags_cmd = ("lvs -o tags --noheadings %s | grep " %
(" ".join(info["volumes"]), ))
else:
# Other storage types don't have tags, so we use an always failing command,
# to make sure it never gets executed
tags_cmd = "false"
# and now rename instance to rename_target...
AssertCommand(["gnt-instance", "rename", rename_source, rename_target])
CheckSsconfInstanceList(rename_target)
qa_utils.RunInstanceCheck(rename_source, False)
qa_utils.RunInstanceCheck(rename_target, False)
# NOTE: tags might not be the exactly as the instance name, due to
# charset restrictions; hence the test might be flaky
if (rename_source != rename_target and
info["storage-type"] == constants.ST_LVM_VG):
for node in info["nodes"]:
AssertCommand(tags_cmd + rename_source, node=node, fail=True)
AssertCommand(tags_cmd + rename_target, node=node, fail=False)
# and back
AssertCommand(["gnt-instance", "rename", rename_target, rename_source])
CheckSsconfInstanceList(rename_source)
qa_utils.RunInstanceCheck(rename_target, False)
if (rename_source != rename_target and
info["storage-type"] == constants.ST_LVM_VG):
for node in info["nodes"]:
AssertCommand(tags_cmd + rename_source, node=node, fail=False)
AssertCommand(tags_cmd + rename_target, node=node, fail=True)
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
def TestInstanceFailover(instance):
"""gnt-instance failover"""
if not IsFailoverSupported(instance):
print qa_utils.FormatInfo("Instance doesn't support failover, skipping"
" test")
return
cmd = ["gnt-instance", "failover", "--force", instance.name]
# failover ...
AssertCommand(cmd)
qa_utils.RunInstanceCheck(instance, True)
# ... and back
AssertCommand(cmd)
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
def TestInstanceMigrate(instance, toggle_always_failover=True):
"""gnt-instance migrate"""
if not IsMigrationSupported(instance):
print qa_utils.FormatInfo("Instance doesn't support migration, skipping"
" test")
return
cmd = ["gnt-instance", "migrate", "--force", instance.name]
af_par = constants.BE_ALWAYS_FAILOVER
af_field = "be/" + constants.BE_ALWAYS_FAILOVER
af_init_val = _GetBoolInstanceField(instance.name, af_field)
# migrate ...
AssertCommand(cmd)
# TODO: Verify the choice between failover and migration
qa_utils.RunInstanceCheck(instance, True)
# ... and back (possibly with always_failover toggled)
if toggle_always_failover:
AssertCommand(["gnt-instance", "modify", "-B",
("%s=%s" % (af_par, not af_init_val)),
instance.name])
AssertCommand(cmd)
# TODO: Verify the choice between failover and migration
qa_utils.RunInstanceCheck(instance, True)
if toggle_always_failover:
AssertCommand(["gnt-instance", "modify", "-B",
("%s=%s" % (af_par, af_init_val)), instance.name])
# TODO: Split into multiple tests
AssertCommand(["gnt-instance", "shutdown", instance.name])
qa_utils.RunInstanceCheck(instance, False)
AssertCommand(cmd, fail=True)
AssertCommand(["gnt-instance", "migrate", "--force", "--allow-failover",
instance.name])
AssertCommand(["gnt-instance", "start", instance.name])
AssertCommand(cmd)
# @InstanceCheck enforces the check that the instance is running
qa_utils.RunInstanceCheck(instance, True)
AssertCommand(["gnt-instance", "modify", "-B",
("%s=%s" %
(constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)),
instance.name])
AssertCommand(cmd)
qa_utils.RunInstanceCheck(instance, True)
# TODO: Verify that a failover has been done instead of a migration
# TODO: Verify whether the default value is restored here (not hardcoded)
AssertCommand(["gnt-instance", "modify", "-B",
("%s=%s" %
(constants.BE_ALWAYS_FAILOVER, constants.VALUE_FALSE)),
instance.name])
AssertCommand(cmd)
qa_utils.RunInstanceCheck(instance, True)
def TestInstanceInfo(instance):
"""gnt-instance info"""
AssertCommand(["gnt-instance", "info", instance.name])
def _TestKVMHotplug(instance):
"""Tests hotplug modification commands, noting that they
"""
args_to_try = [
["--net", "-1:add", "--hotplug"],
["--net", "-1:modify,mac=aa:bb:cc:dd:ee:ff", "--hotplug", "--force"],
["--net", "-1:remove", "--hotplug"],
["--disk", "-1:add,size=1G", "--hotplug"],
["--disk", "-1:remove", "--hotplug"],
]
for alist in args_to_try:
_, stdout, stderr = \
AssertCommand(["gnt-instance", "modify"] + alist + [instance.name])
if "failed" in stdout or "failed" in stderr:
raise qa_error.Error("Hotplugging command failed; please check output"
" for further information")
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
def TestInstanceModify(instance):
"""gnt-instance modify"""
default_hv = qa_config.GetDefaultHypervisor()
# Assume /sbin/init exists on all systems
test_kernel = "/sbin/init"
test_initrd = test_kernel
orig_maxmem = qa_config.get(constants.BE_MAXMEM)
orig_minmem = qa_config.get(constants.BE_MINMEM)
#orig_bridge = qa_config.get("bridge", "xen-br0")
args = [
["-B", "%s=128" % constants.BE_MINMEM],
["-B", "%s=128" % constants.BE_MAXMEM],
["-B", "%s=%s,%s=%s" % (constants.BE_MINMEM, orig_minmem,
constants.BE_MAXMEM, orig_maxmem)],
["-B", "%s=2" % constants.BE_VCPUS],
["-B", "%s=1" % constants.BE_VCPUS],
["-B", "%s=%s" % (constants.BE_VCPUS, constants.VALUE_DEFAULT)],
["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)],
["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_DEFAULT)],
# TODO: bridge tests
#["--bridge", "xen-br1"],
#["--bridge", orig_bridge],
]
# Not all hypervisors support kernel_path(e.g, LXC)
if default_hv in (constants.HT_XEN_PVM,
constants.HT_XEN_HVM,
constants.HT_KVM):
args.extend([
["-H", "%s=%s" % (constants.HV_KERNEL_PATH, test_kernel)],
["-H", "%s=%s" % (constants.HV_KERNEL_PATH, constants.VALUE_DEFAULT)],
])
if default_hv == constants.HT_XEN_PVM:
args.extend([
["-H", "%s=%s" % (constants.HV_INITRD_PATH, test_initrd)],
["-H", "no_%s" % (constants.HV_INITRD_PATH, )],
["-H", "%s=%s" % (constants.HV_INITRD_PATH, constants.VALUE_DEFAULT)],
])
elif default_hv == constants.HT_XEN_HVM:
args.extend([
["-H", "%s=acn" % constants.HV_BOOT_ORDER],
["-H", "%s=%s" % (constants.HV_BOOT_ORDER, constants.VALUE_DEFAULT)],
])
elif default_hv == constants.HT_KVM and \
qa_config.TestEnabled("instance-device-hotplug"):
_TestKVMHotplug(instance)
elif default_hv == constants.HT_LXC:
args.extend([
["-H", "%s=0" % constants.HV_CPU_MASK],
["-H", "%s=%s" % (constants.HV_CPU_MASK, constants.VALUE_DEFAULT)],
["-H", "%s=0" % constants.HV_LXC_NUM_TTYS],
["-H", "%s=%s" % (constants.HV_LXC_NUM_TTYS, constants.VALUE_DEFAULT)],
])
url = "http://example.com/busybox.img"
args.extend([
["--os-parameters", "os-image=" + url],
["--os-parameters", "os-image=default"]
])
for alist in args:
AssertCommand(["gnt-instance", "modify"] + alist + [instance.name])
# check no-modify
AssertCommand(["gnt-instance", "modify", instance.name], fail=True)
# Marking offline while instance is running must fail...
AssertCommand(["gnt-instance", "modify", "--offline", instance.name],
fail=True)
# ...while making it online fails too (needs to be offline first)
AssertCommand(["gnt-instance", "modify", "--online", instance.name],
fail=True)
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
def TestInstanceModifyPrimaryAndBack(instance, currentnode, othernode):
"""gnt-instance modify --new-primary
This will leave the instance on its original primary node, not other node.
"""
if instance.disk_template != constants.DT_FILE:
print qa_utils.FormatInfo("Test only supported for the file disk template")
return
cluster_name = qa_config.get("name")
name = instance.name
current = currentnode.primary
other = othernode.primary
filestorage = qa_config.get("file-storage-dir",
pathutils.DEFAULT_FILE_STORAGE_DIR)
disk = os.path.join(filestorage, name)
AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % other, name],
fail=True)
AssertCommand(["gnt-instance", "shutdown", name])
AssertCommand(["scp", "-oGlobalKnownHostsFile=%s" %
pathutils.SSH_KNOWN_HOSTS_FILE,
"-oCheckHostIp=no", "-oStrictHostKeyChecking=yes",
"-oHashKnownHosts=no", "-oHostKeyAlias=%s" % cluster_name,
"-r", disk, "%s:%s" % (other, filestorage)], node=current)
AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % other, name])
AssertCommand(["gnt-instance", "startup", name])
# and back
AssertCommand(["gnt-instance", "shutdown", name])
AssertCommand(["rm", "-rf", disk], node=other)
AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % current, name])
AssertCommand(["gnt-instance", "startup", name])
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
def TestInstanceStoppedModify(instance):
"""gnt-instance modify (stopped instance)"""
name = instance.name
# Instance was not marked offline; try marking it online once more
AssertCommand(["gnt-instance", "modify", "--online", name])
# Mark instance as offline
AssertCommand(["gnt-instance", "modify", "--offline", name])
# When the instance is offline shutdown should only work with --force,
# while start should never work
AssertCommand(["gnt-instance", "shutdown", name], fail=True)
AssertCommand(["gnt-instance", "shutdown", "--force", name])
AssertCommand(["gnt-instance", "start", name], fail=True)
AssertCommand(["gnt-instance", "start", "--force", name], fail=True)
# Also do offline to offline
AssertCommand(["gnt-instance", "modify", "--offline", name])
# And online again
AssertCommand(["gnt-instance", "modify", "--online", name])
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
def TestInstanceConvertDiskTemplate(instance, requested_conversions):
"""gnt-instance modify -t"""
def _BuildConvertCommand(disk_template, node):
cmd = ["gnt-instance", "modify", "-t", disk_template]
if disk_template == constants.DT_DRBD8:
cmd.extend(["-n", node])
cmd.append(name)
return cmd
if len(requested_conversions) < 2:
print qa_utils.FormatInfo("You must specify more than one convertible"
" disk templates in order to test the conversion"
" feature")
return
name = instance.name
template = instance.disk_template
if template in constants.DTS_NOT_CONVERTIBLE_FROM:
print qa_utils.FormatInfo("Unsupported template %s, skipping conversion"
" test" % template)
return
inodes = qa_config.AcquireManyNodes(2)
master = qa_config.GetMasterNode()
snode = inodes[0].primary
if master.primary == snode:
snode = inodes[1].primary
enabled_disk_templates = qa_config.GetEnabledDiskTemplates()
for templ in requested_conversions:
if (templ == template or
templ not in enabled_disk_templates or
templ in constants.DTS_NOT_CONVERTIBLE_TO):
continue
AssertCommand(_BuildConvertCommand(templ, snode))
# Before we return, convert to the original template
AssertCommand(_BuildConvertCommand(template, snode))
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
def TestInstanceModifyDisks(instance):
"""gnt-instance modify --disk"""
if not IsDiskSupported(instance):
print qa_utils.FormatInfo("Instance doesn't support disks, skipping test")
return
disk_conf = qa_config.GetDiskOptions()[-1]
size = disk_conf.get("size")
name = instance.name
build_cmd = lambda arg: ["gnt-instance", "modify", "--disk", arg, name]
if qa_config.AreSpindlesSupported():
spindles = disk_conf.get("spindles")
spindles_supported = True
else:
# Any number is good for spindles in this case
spindles = 1
spindles_supported = False
AssertCommand(build_cmd("add:size=%s,spindles=%s" % (size, spindles)),
fail=not spindles_supported)
AssertCommand(build_cmd("add:size=%s" % size),
fail=spindles_supported)
# Exactly one of the above commands has succeded, so we need one remove
AssertCommand(build_cmd("remove"))
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
def TestInstanceGrowDisk(instance):
"""gnt-instance grow-disk"""
if instance.disk_template == constants.DT_DISKLESS:
print qa_utils.FormatInfo("Test not supported for diskless instances")
return
name = instance.name
disks = qa_config.GetDiskOptions()
all_size = [d.get("size") for d in disks]
all_grow = [d.get("growth") for d in disks]
if not all_grow:
# missing disk sizes but instance grow disk has been enabled,
# let's set fixed/nomimal growth
all_grow = ["128M" for _ in all_size]
for idx, (size, grow) in enumerate(zip(all_size, all_grow)):
# succeed in grow by amount
AssertCommand(["gnt-instance", "grow-disk", name, str(idx), grow])
# fail in grow to the old size
AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
size], fail=True)
# succeed to grow to old size + 2 * growth
int_size = utils.ParseUnit(size)
int_grow = utils.ParseUnit(grow)
AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
str(int_size + 2 * int_grow)])
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
def TestInstanceDeviceNames(instance):
if instance.disk_template == constants.DT_DISKLESS:
print qa_utils.FormatInfo("Test not supported for diskless instances")
return
name = instance.name
for dev_type in ["disk", "net"]:
if dev_type == "disk":
options = ",size=512M"
if qa_config.AreSpindlesSupported():
options += ",spindles=1"
else:
options = ""
# succeed in adding a device named 'test_device'
AssertCommand(["gnt-instance", "modify",
"--%s=-1:add,name=test_device%s" % (dev_type, options),
name])
# succeed in removing the 'test_device'
AssertCommand(["gnt-instance", "modify",
"--%s=test_device:remove" % dev_type,
name])
# fail to add two devices with the same name
AssertCommand(["gnt-instance", "modify",
"--%s=-1:add,name=test_device%s" % (dev_type, options),
"--%s=-1:add,name=test_device%s" % (dev_type, options),
name], fail=True)
# fail to add a device with invalid name
AssertCommand(["gnt-instance", "modify",
"--%s=-1:add,name=2%s" % (dev_type, options),
name], fail=True)
# Rename disks
disks = qa_config.GetDiskOptions()
disk_names = [d.get("name") for d in disks]
for idx, disk_name in enumerate(disk_names):
# Refer to disk by idx
AssertCommand(["gnt-instance", "modify",
"--disk=%s:modify,name=renamed" % idx,
name])
# Refer to by name and rename to original name
AssertCommand(["gnt-instance", "modify",
"--disk=renamed:modify,name=%s" % disk_name,
name])
if len(disks) >= 2:
# fail in renaming to disks to the same name
AssertCommand(["gnt-instance", "modify",
"--disk=0:modify,name=same_name",
"--disk=1:modify,name=same_name",
name], fail=True)
def TestInstanceList():
"""gnt-instance list"""
qa_utils.GenericQueryTest("gnt-instance", query.INSTANCE_FIELDS.keys())
def TestInstanceListFields():
"""gnt-instance list-fields"""
qa_utils.GenericQueryFieldsTest("gnt-instance", query.INSTANCE_FIELDS.keys())
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
def TestInstanceConsole(instance):
"""gnt-instance console"""
AssertCommand(["gnt-instance", "console", "--show-cmd", instance.name])
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
def TestReplaceDisks(instance, curr_nodes, other_nodes):
"""gnt-instance replace-disks"""
def buildcmd(args):
cmd = ["gnt-instance", "replace-disks"]
cmd.extend(args)
cmd.append(instance.name)
return cmd
if not IsDiskReplacingSupported(instance):
print qa_utils.FormatInfo("Instance doesn't support disk replacing,"
" skipping test")
return
# Currently all supported templates have one primary and one secondary node
assert len(curr_nodes) == 2
snode = curr_nodes[1]
assert len(other_nodes) == 1
othernode = other_nodes[0]
options = qa_config.get("options", {})
use_ialloc = options.get("use-iallocators", True)
for data in [
["-p"],
["-s"],
# A placeholder; the actual command choice depends on use_ialloc
None,
# Restore the original secondary
["--new-secondary=%s" % snode.primary],
]:
if data is None:
if use_ialloc:
data = ["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT]
else:
data = ["--new-secondary=%s" % othernode.primary]
AssertCommand(buildcmd(data))
AssertCommand(buildcmd(["-a"]))
AssertCommand(["gnt-instance", "stop", instance.name])
AssertCommand(buildcmd(["-a"]), fail=True)
AssertCommand(["gnt-instance", "activate-disks", instance.name])
AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
instance.name])
AssertCommand(buildcmd(["-a"]))
AssertCommand(["gnt-instance", "start", instance.name])
def _AssertRecreateDisks(cmdargs, instance, fail=False, check=True,
destroy=True):
"""Execute gnt-instance recreate-disks and check the result
@param cmdargs: Arguments (instance name excluded)
@param instance: Instance to operate on
@param fail: True if the command is expected to fail
@param check: If True and fail is False, check that the disks work
@prama destroy: If True, destroy the old disks first
"""
if destroy:
_DestroyInstanceDisks(instance)
AssertCommand((["gnt-instance", "recreate-disks"] + cmdargs +
[instance.name]), fail)
if not fail and check:
# Quick check that the disks are there
AssertCommand(["gnt-instance", "activate-disks", instance.name])
AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
instance.name])
AssertCommand(["gnt-instance", "deactivate-disks", instance.name])
def _BuildRecreateDisksOpts(en_disks, with_spindles, with_growth,
spindles_supported):
if with_spindles:
if spindles_supported:
if with_growth:
build_spindles_opt = (lambda disk:
",spindles=%s" %
(disk["spindles"] + disk["spindles-growth"]))
else:
build_spindles_opt = (lambda disk:
",spindles=%s" % disk["spindles"])
else:
build_spindles_opt = (lambda _: ",spindles=1")
else:
build_spindles_opt = (lambda _: "")
if with_growth:
build_size_opt = (lambda disk:
"size=%s" % (utils.ParseUnit(disk["size"]) +
utils.ParseUnit(disk["growth"])))
else:
build_size_opt = (lambda disk: "size=%s" % disk["size"])
build_disk_opt = (lambda (idx, disk):
"--disk=%s:%s%s" % (idx, build_size_opt(disk),
build_spindles_opt(disk)))
return map(build_disk_opt, en_disks)
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
def TestRecreateDisks(instance, inodes, othernodes):
"""gnt-instance recreate-disks
@param instance: Instance to work on
@param inodes: List of the current nodes of the instance
@param othernodes: list/tuple of nodes where to temporarily recreate disks
"""
options = qa_config.get("options", {})
use_ialloc = options.get("use-iallocators", True)
other_seq = ":".join([n.primary for n in othernodes])
orig_seq = ":".join([n.primary for n in inodes])
# These fail because the instance is running
_AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
if use_ialloc:
_AssertRecreateDisks(["-I", "hail"], instance, fail=True, destroy=False)
else:
_AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
AssertCommand(["gnt-instance", "stop", instance.name])
# Disks exist: this should fail
_AssertRecreateDisks([], instance, fail=True, destroy=False)
# Unsupported spindles parameters: fail
if not qa_config.AreSpindlesSupported():
_AssertRecreateDisks(["--disk=0:spindles=2"], instance,
fail=True, destroy=False)
# Recreate disks in place
_AssertRecreateDisks([], instance)
# Move disks away
if use_ialloc:
_AssertRecreateDisks(["-I", "hail"], instance)
# Move disks somewhere else
_AssertRecreateDisks(["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT],
instance)
else:
_AssertRecreateDisks(["-n", other_seq], instance)
# Move disks back
_AssertRecreateDisks(["-n", orig_seq], instance)
# Recreate resized disks
# One of the two commands fails because either spindles are given when they
# should not or vice versa
alldisks = qa_config.GetDiskOptions()
spindles_supported = qa_config.AreSpindlesSupported()
disk_opts = _BuildRecreateDisksOpts(enumerate(alldisks), True, True,
spindles_supported)
_AssertRecreateDisks(disk_opts, instance, destroy=True,
fail=not spindles_supported)
disk_opts = _BuildRecreateDisksOpts(enumerate(alldisks), False, True,
spindles_supported)
_AssertRecreateDisks(disk_opts, instance, destroy=False,
fail=spindles_supported)
# Recreate the disks one by one (with the original size)
for (idx, disk) in enumerate(alldisks):
# Only the first call should destroy all the disk
destroy = (idx == 0)
# Again, one of the two commands is expected to fail
disk_opts = _BuildRecreateDisksOpts([(idx, disk)], True, False,
spindles_supported)
_AssertRecreateDisks(disk_opts, instance, destroy=destroy, check=False,
fail=not spindles_supported)
disk_opts = _BuildRecreateDisksOpts([(idx, disk)], False, False,
spindles_supported)
_AssertRecreateDisks(disk_opts, instance, destroy=False, check=False,
fail=spindles_supported)
# This and InstanceCheck decoration check that the disks are working
AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
AssertCommand(["gnt-instance", "start", instance.name])
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
def TestInstanceExport(instance, node):
"""gnt-backup export -n ..."""
name = instance.name
options = ["gnt-backup", "export", "-n", node.primary]
# For files and shared files, the --long-sleep option should be used
if instance.disk_template in [constants.DT_FILE, constants.DT_SHARED_FILE]:
options.append("--long-sleep")
AssertCommand(options + [name])
return qa_utils.ResolveInstanceName(name)
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
def TestInstanceExportWithRemove(instance, node):
"""gnt-backup export --remove-instance"""
AssertCommand(["gnt-backup", "export", "-n", node.primary,
"--remove-instance", instance.name])
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
def TestInstanceExportNoTarget(instance):
"""gnt-backup export (without target node, should fail)"""
AssertCommand(["gnt-backup", "export", instance.name], fail=True)
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
def TestInstanceImport(newinst, node, expnode, name):
"""gnt-backup import"""
templ = constants.DT_PLAIN
if not qa_config.IsTemplateSupported(templ):
return
cmd = (["gnt-backup", "import",
"--disk-template=%s" % templ,
"--no-ip-check",
"--src-node=%s" % expnode.primary,
"--src-dir=%s/%s" % (pathutils.EXPORT_DIR, name),
"--node=%s" % node.primary] +
GetGenericAddParameters(newinst, templ,
force_mac=constants.VALUE_GENERATE))
cmd.append(newinst.name)
AssertCommand(cmd)
newinst.SetDiskTemplate(templ)
def TestBackupList(expnode):
"""gnt-backup list"""
AssertCommand(["gnt-backup", "list", "--node=%s" % expnode.primary])
qa_utils.GenericQueryTest("gnt-backup", query.EXPORT_FIELDS.keys(),
namefield=None, test_unknown=False)
def TestBackupListFields():
"""gnt-backup list-fields"""
qa_utils.GenericQueryFieldsTest("gnt-backup", query.EXPORT_FIELDS.keys())
def TestRemoveInstanceOfflineNode(instance, snode, set_offline, set_online):
"""gnt-instance remove with an off-line node
@param instance: instance
@param snode: secondary node, to be set offline
@param set_offline: function to call to set the node off-line
@param set_online: function to call to set the node on-line
"""
info = GetInstanceInfo(instance.name)
set_offline(snode)
try:
TestInstanceRemove(instance)
finally:
set_online(snode)
# Clean up the disks on the offline node, if necessary
if instance.disk_template not in constants.DTS_EXT_MIRROR:
# FIXME: abstract the cleanup inside the disks
if info["storage-type"] == constants.ST_LVM_VG:
for minor in info["drbd-minors"][snode.primary]:
# DRBD 8.3 syntax comes first, then DRBD 8.4 syntax. The 8.4 syntax
# relies on the fact that we always create a resources for each minor,
# and that this resources is always named resource{minor}.
# As 'drbdsetup 0 down' does return success (even though that's invalid
# syntax), we always have to perform both commands and ignore the
# output.
drbd_shutdown_cmd = \
"(drbdsetup %d down >/dev/null 2>&1;" \
" drbdsetup down resource%d >/dev/null 2>&1) || /bin/true" % \
(minor, minor)
AssertCommand(drbd_shutdown_cmd, node=snode)
AssertCommand(["lvremove", "-f"] + info["volumes"], node=snode)
elif info["storage-type"] == constants.ST_FILE:
filestorage = qa_config.get("file-storage-dir",
pathutils.DEFAULT_FILE_STORAGE_DIR)
disk = os.path.join(filestorage, instance.name)
AssertCommand(["rm", "-rf", disk], node=snode)
def TestInstanceCreationRestrictedByDiskTemplates():
"""Test adding instances for disabled disk templates."""
if qa_config.TestEnabled("cluster-exclusive-storage"):
# These tests are valid only for non-exclusive storage
return
enabled_disk_templates = qa_config.GetEnabledDiskTemplates()
nodes = qa_config.AcquireManyNodes(2)
# Setup the cluster with the enabled_disk_templates
AssertCommand(
["gnt-cluster", "modify",
"--enabled-disk-templates=%s" % ",".join(enabled_disk_templates),
"--ipolicy-disk-templates=%s" % ",".join(enabled_disk_templates)],
fail=False)
# Test instance creation for enabled disk templates
for disk_template in enabled_disk_templates:
instance = CreateInstanceByDiskTemplate(nodes, disk_template, fail=False)
TestInstanceRemove(instance)
instance.Release()
# Test that instance creation fails for disabled disk templates
disabled_disk_templates = list(constants.DISK_TEMPLATES
- set(enabled_disk_templates))
for disk_template in disabled_disk_templates:
instance = CreateInstanceByDiskTemplate(nodes, disk_template, fail=True)
# Test instance creation for after disabling enabled disk templates
if (len(enabled_disk_templates) > 1):
# Partition the disk templates, enable them separately and check if the
# disabled ones cannot be used by instances.
middle = len(enabled_disk_templates) / 2
templates1 = enabled_disk_templates[:middle]
templates2 = enabled_disk_templates[middle:]
for (enabled, disabled) in [(templates1, templates2),
(templates2, templates1)]:
AssertCommand(["gnt-cluster", "modify",
"--enabled-disk-templates=%s" % ",".join(enabled),
"--ipolicy-disk-templates=%s" % ",".join(enabled)],
fail=False)
for disk_template in disabled:
CreateInstanceByDiskTemplate(nodes, disk_template, fail=True)
elif (len(enabled_disk_templates) == 1):
# If only one disk template is enabled in the QA config, we have to enable
# some other templates in order to test if the disabling the only enabled
# disk template prohibits creating instances of that template.
other_disk_templates = list(
set([constants.DT_DISKLESS, constants.DT_BLOCK]) -
set(enabled_disk_templates))
AssertCommand(["gnt-cluster", "modify",
"--enabled-disk-templates=%s" %
",".join(other_disk_templates),
"--ipolicy-disk-templates=%s" %
",".join(other_disk_templates)],
fail=False)
CreateInstanceByDiskTemplate(nodes, enabled_disk_templates[0], fail=True)
else:
raise qa_error.Error("Please enable at least one disk template"
" in your QA setup.")
# Restore initially enabled disk templates
AssertCommand(["gnt-cluster", "modify",
"--enabled-disk-templates=%s" %
",".join(enabled_disk_templates),
"--ipolicy-disk-templates=%s" %
",".join(enabled_disk_templates)],
fail=False)
def _AssertInstance(instance, status, admin_state, admin_state_source):
x, y, z = \
_GetInstanceFields(instance.name,
["status", "admin_state", "admin_state_source"])
AssertEqual(x, status)
AssertEqual(y, admin_state)
AssertEqual(z, admin_state_source)
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
def _TestInstanceUserDown(instance, hv_shutdown_fn):
"""Test different combinations of user shutdown"""
# 1. User shutdown
# 2. Instance start
hv_shutdown_fn()
_AssertInstance(instance,
constants.INSTST_USERDOWN,
constants.ADMINST_UP,
constants.ADMIN_SOURCE)
AssertCommand(["gnt-instance", "start", instance.name])
_AssertInstance(instance,
constants.INSTST_RUNNING,
constants.ADMINST_UP,
constants.ADMIN_SOURCE)
# 1. User shutdown
# 2. Watcher cleanup
# 3. Instance start
hv_shutdown_fn()
_AssertInstance(instance,
constants.INSTST_USERDOWN,
constants.ADMINST_UP,
constants.ADMIN_SOURCE)
qa_daemon.RunWatcherDaemon()
_AssertInstance(instance,
constants.INSTST_USERDOWN,
constants.ADMINST_DOWN,
constants.USER_SOURCE)
AssertCommand(["gnt-instance", "start", instance.name])
_AssertInstance(instance,
constants.INSTST_RUNNING,
constants.ADMINST_UP,
constants.ADMIN_SOURCE)
# 1. User shutdown
# 2. Watcher cleanup
# 3. Instance stop
# 4. Instance start
hv_shutdown_fn()
_AssertInstance(instance,
constants.INSTST_USERDOWN,
constants.ADMINST_UP,
constants.ADMIN_SOURCE)
qa_daemon.RunWatcherDaemon()
_AssertInstance(instance,
constants.INSTST_USERDOWN,
constants.ADMINST_DOWN,
constants.USER_SOURCE)
AssertCommand(["gnt-instance", "shutdown", instance.name])
_AssertInstance(instance,
constants.INSTST_ADMINDOWN,
constants.ADMINST_DOWN,
constants.ADMIN_SOURCE)
AssertCommand(["gnt-instance", "start", instance.name])
_AssertInstance(instance,
constants.INSTST_RUNNING,
constants.ADMINST_UP,
constants.ADMIN_SOURCE)
# 1. User shutdown
# 2. Instance stop
# 3. Instance start
hv_shutdown_fn()
_AssertInstance(instance,
constants.INSTST_USERDOWN,
constants.ADMINST_UP,
constants.ADMIN_SOURCE)
AssertCommand(["gnt-instance", "shutdown", instance.name])
_AssertInstance(instance,
constants.INSTST_ADMINDOWN,
constants.ADMINST_DOWN,
constants.ADMIN_SOURCE)
AssertCommand(["gnt-instance", "start", instance.name])
_AssertInstance(instance,
constants.INSTST_RUNNING,
constants.ADMINST_UP,
constants.ADMIN_SOURCE)
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
def _TestInstanceUserDownXen(instance):
primary = _GetInstanceField(instance.name, "pnode")
fn = lambda: AssertCommand(["xm", "shutdown", "-w", instance.name],
node=primary)
AssertCommand(["gnt-cluster", "modify", "--user-shutdown=true"])
_TestInstanceUserDown(instance, fn)
AssertCommand(["gnt-cluster", "modify", "--user-shutdown=false"])
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
def _TestInstanceUserDownKvm(instance):
def _StopKVMInstance():
AssertCommand("pkill -f \"\\-name %s\"" % instance.name, node=primary)
time.sleep(10)
AssertCommand(["gnt-cluster", "modify", "--user-shutdown=true"])
AssertCommand(["gnt-instance", "modify", "-H", "user_shutdown=true",
instance.name])
# The instance needs to reboot not because the 'user_shutdown'
# parameter was modified but because the KVM daemon need to be
# started, given that the instance was first created with user
# shutdown disabled.
AssertCommand(["gnt-instance", "reboot", instance.name])
primary = _GetInstanceField(instance.name, "pnode")
_TestInstanceUserDown(instance, _StopKVMInstance)
AssertCommand(["gnt-instance", "modify", "-H", "user_shutdown=false",
instance.name])
AssertCommand(["gnt-cluster", "modify", "--user-shutdown=false"])
def TestInstanceUserDown(instance):
"""Tests user shutdown"""
enabled_hypervisors = qa_config.GetEnabledHypervisors()
for (hv, fn) in [(constants.HT_XEN_PVM, _TestInstanceUserDownXen),
(constants.HT_XEN_HVM, _TestInstanceUserDownXen),
(constants.HT_KVM, _TestInstanceUserDownKvm)]:
if hv in enabled_hypervisors:
qa_daemon.TestPauseWatcher()
fn(instance)
qa_daemon.TestResumeWatcher()
else:
print "%s hypervisor is not enabled, skipping test for this hypervisor" \
% hv
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
def TestInstanceCommunication(instance, master):
"""Tests instance communication via 'gnt-instance modify'"""
# Enable instance communication network at the cluster level
network_name = "mynetwork"
cmd = ["gnt-cluster", "modify",
"--instance-communication-network=%s" % network_name]
result_output = qa_utils.GetCommandOutput(master.primary,
utils.ShellQuoteArgs(cmd))
print result_output
# Enable instance communication mechanism for this instance
AssertCommand(["gnt-instance", "modify", "-c", "yes", instance.name])
# Reboot instance for changes to NIC to take effect
AssertCommand(["gnt-instance", "reboot", instance.name])
# Check if the instance is properly configured for instance
# communication.
nic_name = "%s%s" % (constants.INSTANCE_COMMUNICATION_NIC_PREFIX,
instance.name)
## Check the output of 'gnt-instance list'
nic_names = _GetInstanceField(instance.name, "nic.names")
nic_names = map(lambda x: x.strip(" '"), nic_names.strip("[]").split(","))
AssertIn(nic_name, nic_names,
msg="Looking for instance communication TAP interface")
nic_n = nic_names.index(nic_name)
nic_ip = _GetInstanceField(instance.name, "nic.ip/%d" % nic_n)
nic_network = _GetInstanceField(instance.name, "nic.network.name/%d" % nic_n)
nic_mode = _GetInstanceField(instance.name, "nic.mode/%d" % nic_n)
AssertEqual(IP4Address.InNetwork(constants.INSTANCE_COMMUNICATION_NETWORK4,
nic_ip),
True,
msg="Checking if NIC's IP if part of the expected network")
AssertEqual(network_name, nic_network,
msg="Checking if NIC's network name matches the expected value")
AssertEqual(constants.INSTANCE_COMMUNICATION_NETWORK_MODE, nic_mode,
msg="Checking if NIC's mode name matches the expected value")
## Check the output of 'ip route'
cmd = ["ip", "route", "show", nic_ip]
result_output = qa_utils.GetCommandOutput(master.primary,
utils.ShellQuoteArgs(cmd))
result = result_output.split()
AssertEqual(len(result), 5, msg="Checking if the IP route is established")
route_ip = result[0]
route_dev = result[1]
route_tap = result[2]
route_scope = result[3]
route_link = result[4]
AssertEqual(route_ip, nic_ip,
msg="Checking if IP route shows the expected IP")
AssertEqual(route_dev, "dev",
msg="Checking if IP route shows the expected device")
AssertEqual(route_scope, "scope",
msg="Checking if IP route shows the expected scope")
AssertEqual(route_link, "link",
msg="Checking if IP route shows the expected link-level scope")
## Check the output of 'ip address'
cmd = ["ip", "address", "show", "dev", route_tap]
result_output = qa_utils.GetCommandOutput(master.primary,
utils.ShellQuoteArgs(cmd))
result = result_output.splitlines()
AssertEqual(len(result), 3,
msg="Checking if the IP address is established")
result = result.pop().split()
AssertEqual(len(result), 7,
msg="Checking if the IP address has the expected value")
address_ip = result[1]
address_netmask = result[3]
AssertEqual(address_ip, "169.254.169.254/32",
msg="Checking if the TAP interface has the expected IP")
AssertEqual(address_netmask, "169.254.255.255",
msg="Checking if the TAP interface has the expected netmask")
# Disable instance communication mechanism for this instance
AssertCommand(["gnt-instance", "modify", "-c", "no", instance.name])
# Reboot instance for changes to NIC to take effect
AssertCommand(["gnt-instance", "reboot", instance.name])
# Disable instance communication network at cluster level
cmd = ["gnt-cluster", "modify",
"--instance-communication-network=%s" % network_name]
result_output = qa_utils.GetCommandOutput(master.primary,
utils.ShellQuoteArgs(cmd))
print result_output
def _TestRedactionOfSecretOsParams(node, cmd, secret_keys):
"""Tests redaction of secret os parameters
"""
AssertCommand(["gnt-cluster", "modify", "--max-running-jobs", "1"])
debug_delay_id = int(stdout_of(["gnt-debug", "delay", "--print-jobid",
"--submit", "300"]))
cmd_jid = int(stdout_of(cmd))
job_file_abspath = "%s/job-%s" % (pathutils.QUEUE_DIR, cmd_jid)
job_file = qa_utils.MakeNodePath(node, job_file_abspath)
for k in secret_keys:
grep_cmd = ["grep", "\"%s\":\"<redacted>\"" % k, job_file]
AssertCommand(grep_cmd)
AssertCommand(["gnt-job", "cancel", "--kill", "--yes-do-it",
str(debug_delay_id)])
AssertCommand(["gnt-cluster", "modify", "--max-running-jobs", "20"])
AssertCommand(["gnt-job", "wait", str(cmd_jid)])
def TestInstanceAddOsParams():
"""Tests instance add with secret os parameters"""
if not qa_config.IsTemplateSupported(constants.DT_PLAIN):
return
master = qa_config.GetMasterNode()
instance = qa_config.AcquireInstance()
secret_keys = ["param1", "param2"]
cmd = (["gnt-instance", "add",
"--os-type=%s" % qa_config.get("os"),
"--disk-template=%s" % constants.DT_PLAIN,
"--os-parameters-secret",
"param1=secret1,param2=secret2",
"--node=%s" % master.primary] +
GetGenericAddParameters(instance, constants.DT_PLAIN))
cmd.append("--submit")
cmd.append("--print-jobid")
cmd.append(instance.name)
_TestRedactionOfSecretOsParams(master.primary, cmd, secret_keys)
TestInstanceRemove(instance)
instance.Release()
def TestSecretOsParams():
"""Tests secret os parameter transmission"""
master = qa_config.GetMasterNode()
secret_keys = ["param1", "param2"]
cmd = (["gnt-debug", "test-osparams", "--os-parameters-secret",
"param1=secret1,param2=secret2", "--submit", "--print-jobid"])
_TestRedactionOfSecretOsParams(master.primary, cmd, secret_keys)
cmd_output = stdout_of(["gnt-debug", "test-osparams",
"--os-parameters-secret",
"param1=secret1,param2=secret2"])
AssertIn("\'param1\': \'secret1\'", cmd_output)
AssertIn("\'param2\': \'secret2\'", cmd_output)
available_instance_tests = [
("instance-add-plain-disk", constants.DT_PLAIN,
TestInstanceAddWithPlainDisk, 1),
("instance-add-drbd-disk", constants.DT_DRBD8,
TestInstanceAddWithDrbdDisk, 2),
("instance-add-diskless", constants.DT_DISKLESS,
TestInstanceAddDiskless, 1),
("instance-add-file", constants.DT_FILE,
TestInstanceAddFile, 1),
("instance-add-shared-file", constants.DT_SHARED_FILE,
TestInstanceAddSharedFile, 1),
("instance-add-rbd", constants.DT_RBD,
TestInstanceAddRADOSBlockDevice, 1),
("instance-add-gluster", constants.DT_GLUSTER,
TestInstanceAddGluster, 1),
]