test/automated-pytest-suite/testcases/functional/nova/test_resize_vm.py

509 lines
22 KiB
Python
Executable File

#
# Copyright (c) 2019 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
import time
import math
from pytest import fixture, mark, skip, param
from utils.tis_log import LOG
from keywords import vm_helper, nova_helper, host_helper, check_helper, \
glance_helper
from testfixtures.fixture_resources import ResourceCleanup
from consts.stx import FlavorSpec, GuestImages
from consts.reasons import SkipStorageBacking
def id_gen(val):
if isinstance(val, (tuple, list)):
val = '_'.join([str(val_) for val_ in val])
return val
def touch_files_under_vm_disks(vm_id, ephemeral=0, swap=0, vm_type='volume',
disks=None):
expt_len = 1 + int(bool(ephemeral)) + int(bool(swap)) + (
1 if 'with_vol' in vm_type else 0)
LOG.tc_step("Auto mount non-root disk(s)")
mounts = vm_helper.auto_mount_vm_disks(vm_id=vm_id, disks=disks)
assert expt_len == len(mounts)
if bool(swap):
mounts.remove('none')
LOG.tc_step("Create files under vm disks: {}".format(mounts))
file_paths, content = vm_helper.touch_files(vm_id=vm_id, file_dirs=mounts)
return file_paths, content
def get_expt_disk_increase(origin_flavor, dest_flavor, boot_source,
storage_backing):
root_diff = dest_flavor[0] - origin_flavor[0]
ephemeral_diff = dest_flavor[1] - origin_flavor[1]
swap_diff = (dest_flavor[2] - origin_flavor[2]) / 1024
if storage_backing == 'remote':
expected_increase = 0
expect_to_check = True
else:
if boot_source == 'volume':
expected_increase = ephemeral_diff + swap_diff
expect_to_check = False
else:
expected_increase = root_diff + ephemeral_diff + swap_diff
expect_to_check = expected_increase >= 2
return expected_increase, expect_to_check
def get_disk_avail_least(host):
return \
host_helper.get_hypervisor_info(hosts=host,
field='disk_available_least')[host]
def check_correct_post_resize_value(original_disk_value, expected_increase,
host, sleep=True):
if sleep:
time.sleep(65)
post_resize_value = get_disk_avail_least(host)
LOG.info(
"{} original_disk_value: {}. post_resize_value: {}. "
"expected_increase: {}".format(
host, original_disk_value, post_resize_value, expected_increase))
expt_post = original_disk_value + expected_increase
if expected_increase < 0:
# vm is on this host, backup image files may be created if not
# already existed
backup_val = math.ceil(
glance_helper.get_image_size(guest_os=GuestImages.DEFAULT['guest'],
virtual_size=False))
assert expt_post - backup_val <= post_resize_value <= expt_post
elif expected_increase > 0:
# vm moved away from this host, or resized to smaller disk on same
# host, backup files will stay
assert expt_post - 1 <= post_resize_value <= expt_post + 1, \
"disk_available_least on {} expected: {}+-1, actual: {}".format(
host, expt_post, post_resize_value)
else:
assert expt_post == post_resize_value, \
"{} disk_available_least value changed to {} unexpectedly".format(
host, post_resize_value)
return post_resize_value
@fixture(scope='module')
def get_hosts_per_backing(add_admin_role_module):
return host_helper.get_hosts_per_storage_backing()
class TestResizeSameHost:
@fixture(scope='class')
def add_hosts_to_zone(self, request, add_stxauto_zone,
get_hosts_per_backing):
hosts_per_backing = get_hosts_per_backing
avail_hosts = {key: vals[0] for key, vals in hosts_per_backing.items()
if vals}
if not avail_hosts:
skip("No host in any storage aggregate")
nova_helper.add_hosts_to_aggregate(aggregate='stxauto',
hosts=list(avail_hosts.values()))
def remove_hosts_from_zone():
nova_helper.remove_hosts_from_aggregate(aggregate='stxauto',
check_first=False)
request.addfinalizer(remove_hosts_from_zone)
return avail_hosts
@mark.parametrize(('storage_backing', 'origin_flavor', 'dest_flavor',
'boot_source'), [
('remote', (4, 0, 0), (5, 1, 512), 'image'),
('remote', (4, 1, 512), (5, 2, 1024), 'image'),
('remote', (4, 1, 512), (4, 1, 0), 'image'),
# LP1762423
param('remote', (4, 0, 0), (1, 1, 512), 'volume',
marks=mark.priorities('nightly', 'sx_nightly')),
('remote', (4, 1, 512), (8, 2, 1024), 'volume'),
('remote', (4, 1, 512), (0, 1, 0), 'volume'),
('local_image', (4, 0, 0), (5, 1, 512), 'image'),
param('local_image', (4, 1, 512), (5, 2, 1024),
'image',
marks=mark.priorities('nightly', 'sx_nightly')),
('local_image', (5, 1, 512), (5, 1, 0), 'image'),
('local_image', (4, 0, 0), (5, 1, 512), 'volume'),
('local_image', (4, 1, 512), (0, 2, 1024), 'volume'),
('local_image', (4, 1, 512), (1, 1, 0), 'volume'),
# LP1762423
], ids=id_gen)
def test_resize_vm_positive(self, add_hosts_to_zone, storage_backing,
origin_flavor, dest_flavor, boot_source):
"""
Test resizing disks of a vm
- Resize root disk is allowed except 0 & boot-from-image
- Resize to larger or same ephemeral is allowed
- Resize swap to any size is allowed including removing
Args:
storage_backing: The host storage backing required
origin_flavor: The flavor to boot the vm from, listed by GBs for
root, ephemeral, and swap disks, i.e. for a
system with a 2GB root disk, a 1GB ephemeral disk,
and no swap disk: (2, 1, 0)
boot_source: Which source to boot the vm from, either 'volume' or
'image'
add_hosts_to_zone
dest_flavor
Skip Conditions:
- No hosts exist with required storage backing.
Test setup:
- Put a single host of each backing in stxautozone to prevent
migration and instead force resize.
- Create two flavors based on origin_flavor and dest_flavor
- Create a volume or image to boot from.
- Boot VM with origin_flavor
Test Steps:
- Resize VM to dest_flavor with revert
- If vm is booted from image and has a non-remote backing,
check that the amount of disk space post-revert
is around the same pre-revert # TC5155
- Resize VM to dest_flavor with confirm
- If vm is booted from image and has a non-remote backing,
check that the amount of disk space post-confirm
is reflects the increase in disk-space taken up # TC5155
Test Teardown:
- Delete created VM
- Delete created volume or image
- Delete created flavors
- Remove hosts from stxautozone
- Delete stxautozone
"""
vm_host = add_hosts_to_zone.get(storage_backing, None)
if not vm_host:
skip(
SkipStorageBacking.NO_HOST_WITH_BACKING.format(storage_backing))
expected_increase, expect_to_check = get_expt_disk_increase(
origin_flavor, dest_flavor,
boot_source, storage_backing)
LOG.info("Expected_increase of vm compute occupancy is {}".format(
expected_increase))
LOG.tc_step('Create origin flavor')
origin_flavor_id = _create_flavor(origin_flavor, storage_backing)
vm_id = _boot_vm_to_test(boot_source, vm_host, origin_flavor_id)
vm_helper.wait_for_vm_pingable_from_natbox(vm_id)
vm_disks = vm_helper.get_vm_devices_via_virsh(vm_id)
root, ephemeral, swap = origin_flavor
if boot_source == 'volume':
root = GuestImages.IMAGE_FILES[GuestImages.DEFAULT['guest']][1]
file_paths, content = touch_files_under_vm_disks(vm_id=vm_id,
ephemeral=ephemeral,
swap=swap,
vm_type=boot_source,
disks=vm_disks)
if expect_to_check:
LOG.tc_step('Check initial disk usage')
original_disk_value = get_disk_avail_least(vm_host)
LOG.info("{} space left on compute".format(original_disk_value))
LOG.tc_step('Create destination flavor')
dest_flavor_id = _create_flavor(dest_flavor, storage_backing)
LOG.tc_step('Resize vm to dest flavor and revert')
vm_helper.resize_vm(vm_id, dest_flavor_id, revert=True, fail_ok=False)
vm_helper.wait_for_vm_pingable_from_natbox(vm_id)
swap_size = swap
LOG.tc_step("Check files after resize revert")
if storage_backing == 'remote' and swap and dest_flavor[2]:
swap_size = dest_flavor[2]
time.sleep(30)
prev_host = vm_helper.get_vm_host(vm_id)
check_helper.check_vm_files(vm_id=vm_id,
storage_backing=storage_backing, root=root,
ephemeral=ephemeral,
swap=swap_size, vm_type=boot_source,
vm_action=None, file_paths=file_paths,
content=content, disks=vm_disks,
check_volume_root=True)
LOG.tc_step('Resize vm to dest flavor and confirm')
vm_helper.resize_vm(vm_id, dest_flavor_id, revert=False, fail_ok=False)
vm_helper.wait_for_vm_pingable_from_natbox(vm_id)
post_host = vm_helper.get_vm_host(vm_id)
post_root, post_ephemeral, post_swap = dest_flavor
if boot_source == 'volume':
post_root = GuestImages.IMAGE_FILES[GuestImages.DEFAULT['guest']][1]
post_ephemeral = ephemeral if ephemeral else post_ephemeral
LOG.tc_step("Check files after resize attempt")
check_helper.check_vm_files(
vm_id=vm_id, storage_backing=storage_backing,
ephemeral=post_ephemeral,
swap=post_swap, vm_type=boot_source,
vm_action='resize', file_paths=file_paths,
content=content, prev_host=prev_host,
post_host=post_host, root=post_root,
disks=vm_disks,
post_disks=vm_helper.get_vm_devices_via_virsh(vm_id),
check_volume_root=True)
@mark.parametrize(
('storage_backing', 'origin_flavor', 'dest_flavor', 'boot_source'), [
# Root disk can be resized, but cannot be 0
('remote', (5, 0, 0), (0, 0, 0), 'image'),
# check ephemeral disk cannot be smaller than origin
('remote', (5, 2, 512), (5, 1, 512), 'image'),
# check ephemeral disk cannot be smaller than origin
('remote', (1, 1, 512), (1, 0, 512), 'volume'),
# Root disk can be resized, but cannot be 0
('local_image', (5, 0, 0), (0, 0, 0), 'image'),
('local_image', (5, 2, 512), (5, 1, 512), 'image'),
('local_image', (5, 1, 512), (4, 1, 512), 'image'),
('local_image', (5, 1, 512), (4, 1, 0), 'image'),
('local_image', (1, 1, 512), (1, 0, 512), 'volume'),
], ids=id_gen)
def test_resize_vm_negative(self, add_hosts_to_zone, storage_backing,
origin_flavor, dest_flavor, boot_source):
"""
Test resizing disks of a vm not allowed:
- Resize to smaller ephemeral flavor is not allowed
- Resize to zero disk flavor is not allowed (boot from image only)
Args:
storage_backing: The host storage backing required
origin_flavor: The flavor to boot the vm from, listed by GBs for
root, ephemeral, and swap disks, i.e. for a
system with a 2GB root disk, a 1GB ephemeral disk,
and no swap disk: (2, 1, 0)
boot_source: Which source to boot the vm from, either 'volume' or
'image'
Skip Conditions:
- No hosts exist with required storage backing.
Test setup:
- Put a single host of each backing in stxautozone to prevent
migration and instead force resize.
- Create two flavors based on origin_flavor and dest_flavor
- Create a volume or image to boot from.
- Boot VM with origin_flavor
Test Steps:
- Resize VM to dest_flavor with revert
- Resize VM to dest_flavor with confirm
Test Teardown:
- Delete created VM
- Delete created volume or image
- Delete created flavors
- Remove hosts from stxauto zone
- Delete stxauto zone
"""
vm_host = add_hosts_to_zone.get(storage_backing, None)
if not vm_host:
skip("No available host with {} storage backing".format(
storage_backing))
LOG.tc_step('Create origin flavor')
origin_flavor_id = _create_flavor(origin_flavor, storage_backing)
LOG.tc_step('Create destination flavor')
dest_flavor_id = _create_flavor(dest_flavor, storage_backing)
vm_id = _boot_vm_to_test(boot_source, vm_host, origin_flavor_id)
vm_helper.wait_for_vm_pingable_from_natbox(vm_id)
vm_disks = vm_helper.get_vm_devices_via_virsh(vm_id)
root, ephemeral, swap = origin_flavor
file_paths, content = touch_files_under_vm_disks(vm_id=vm_id,
ephemeral=ephemeral,
swap=swap,
vm_type=boot_source,
disks=vm_disks)
LOG.tc_step('Resize vm to dest flavor')
code, output = vm_helper.resize_vm(vm_id, dest_flavor_id, fail_ok=True)
vm_helper.wait_for_vm_pingable_from_natbox(vm_id)
assert vm_helper.get_vm_flavor(
vm_id) == origin_flavor_id, 'VM did not keep origin flavor'
assert code > 0, "Resize VM CLI is not rejected"
LOG.tc_step("Check files after resize attempt")
check_helper.check_vm_files(vm_id=vm_id,
storage_backing=storage_backing, root=root,
ephemeral=ephemeral,
swap=swap, vm_type=boot_source,
vm_action=None, file_paths=file_paths,
content=content, disks=vm_disks)
def _create_flavor(flavor_info, storage_backing):
root_disk = flavor_info[0]
ephemeral = flavor_info[1]
swap = flavor_info[2]
flavor_id = nova_helper.create_flavor(ephemeral=ephemeral, swap=swap,
root_disk=root_disk,
storage_backing=storage_backing)[1]
ResourceCleanup.add('flavor', flavor_id)
return flavor_id
def _boot_vm_to_test(boot_source, vm_host, flavor_id):
LOG.tc_step('Boot a vm with given flavor')
vm_id = vm_helper.boot_vm(flavor=flavor_id, avail_zone='stxauto',
vm_host=vm_host, source=boot_source,
cleanup='function')[1]
return vm_id
def get_cpu_count(hosts_with_backing):
LOG.fixture_step("Find suitable vm host and cpu count and backing of host")
compute_space_dict = {}
vm_host = hosts_with_backing[0]
numa0_used_cpus, numa0_total_cpus = \
host_helper.get_vcpus_per_proc(vm_host)[vm_host][0]
numa0_avail_cpus = len(numa0_total_cpus) - len(numa0_used_cpus)
for host in hosts_with_backing:
free_space = get_disk_avail_least(host)
compute_space_dict[host] = free_space
LOG.info("{} space on {}".format(free_space, host))
# increase quota
LOG.fixture_step("Increase quota of allotted cores")
vm_helper.ensure_vms_quotas(cores_num=int(numa0_avail_cpus + 30))
return vm_host, numa0_avail_cpus, compute_space_dict
class TestResizeDiffHost:
@mark.parametrize('storage_backing', [
'local_image',
'remote',
])
def test_resize_different_comp_node(self, storage_backing,
get_hosts_per_backing):
"""
Test resizing disks of a larger vm onto a different compute node and
check hypervisor statistics to
make sure difference in disk usage of both nodes involved is
correctly reflected
Args:
storage_backing: The host storage backing required
Skip Conditions:
- 2 hosts must exist with required storage backing.
Test setup:
- For each of the two backings tested, the setup will return the
number of nodes for each backing,
the vm host that the vm will initially be created on and the
number of hosts for that backing.
Test Steps:
- Create a flavor with a root disk size that is slightly larger
than the default image used to boot up
the VM
- Create a VM with the aforementioned flavor
- Create a flavor will enough cpus to occupy the rest of the cpus
on the same host as the first VM
- Create another VM on the same host as the first VM
- Create a similar flavor to the first one, except that it has
one more vcpu
- Resize the first VM and confirm that it is on a different host
- Check hypervisor-show on both computes to make sure that disk
usage goes down on the original host and
goes up on the new host
Test Teardown:
- Delete created VMs
- Delete created flavors
"""
hosts_with_backing = get_hosts_per_backing.get(storage_backing, [])
if len(hosts_with_backing) < 2:
skip(SkipStorageBacking.LESS_THAN_TWO_HOSTS_WITH_BACKING.format(
storage_backing))
origin_host, cpu_count, compute_space_dict = get_cpu_count(
hosts_with_backing)
root_disk_size = \
GuestImages.IMAGE_FILES[GuestImages.DEFAULT['guest']][1] + 5
# make vm (1 cpu)
LOG.tc_step("Create flavor with 1 cpu")
numa0_specs = {FlavorSpec.CPU_POLICY: 'dedicated', FlavorSpec.NUMA_0: 0}
flavor_1 = \
nova_helper.create_flavor(ephemeral=0, swap=0,
root_disk=root_disk_size, vcpus=1,
storage_backing=storage_backing)[1]
ResourceCleanup.add('flavor', flavor_1)
nova_helper.set_flavor(flavor_1, **numa0_specs)
LOG.tc_step("Boot a vm with above flavor")
vm_to_resize = \
vm_helper.boot_vm(flavor=flavor_1, source='image',
cleanup='function', vm_host=origin_host)[1]
vm_helper.wait_for_vm_pingable_from_natbox(vm_to_resize)
# launch another vm
LOG.tc_step("Create a flavor to occupy vcpus")
occupy_amount = int(cpu_count) - 1
second_specs = {FlavorSpec.CPU_POLICY: 'dedicated',
FlavorSpec.NUMA_0: 0}
flavor_2 = nova_helper.create_flavor(vcpus=occupy_amount,
storage_backing=storage_backing)[1]
ResourceCleanup.add('flavor', flavor_2)
nova_helper.set_flavor(flavor_2, **second_specs)
LOG.tc_step("Boot a vm with above flavor to occupy remaining vcpus")
vm_2 = vm_helper.boot_vm(flavor=flavor_2, source='image',
cleanup='function', vm_host=origin_host)[1]
vm_helper.wait_for_vm_pingable_from_natbox(vm_2)
LOG.tc_step('Check disk usage before resize')
prev_val_origin_host = get_disk_avail_least(origin_host)
LOG.info("{} space left on compute".format(prev_val_origin_host))
# create a larger flavor and resize
LOG.tc_step("Create a flavor that has an extra vcpu to force resize "
"to a different node")
resize_flavor = nova_helper.create_flavor(
ephemeral=0, swap=0, root_disk=root_disk_size, vcpus=2,
storage_backing=storage_backing)[1]
ResourceCleanup.add('flavor', resize_flavor)
nova_helper.set_flavor(resize_flavor, **numa0_specs)
LOG.tc_step("Resize the vm and verify if it is on a different host")
vm_helper.resize_vm(vm_to_resize, resize_flavor)
new_host = vm_helper.get_vm_host(vm_to_resize)
assert new_host != origin_host, "vm did not change hosts " \
"following resize"
LOG.tc_step('Check disk usage on computes after resize')
if storage_backing == 'remote':
LOG.info("Compute disk usage change should be minimal for "
"remote storage backing")
root_disk_size = 0
check_correct_post_resize_value(prev_val_origin_host, root_disk_size,
origin_host)
prev_val_new_host = compute_space_dict[new_host]
check_correct_post_resize_value(prev_val_new_host, -root_disk_size,
new_host, sleep=False)
vm_helper.wait_for_vm_pingable_from_natbox(vm_to_resize)