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

502 lines
21 KiB
Python
Executable File

#
# Copyright (c) 2019 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
import re
import random
from pytest import fixture, mark, skip, param
import keywords.host_helper
from utils.tis_log import LOG
from consts.stx import FlavorSpec, ImageMetadata, NovaCLIOutput
from keywords import nova_helper, vm_helper, system_helper, cinder_helper, \
host_helper, glance_helper
MEMPAGE_HEADERS = ('app_total_4K', 'app_hp_avail_2M', 'app_hp_avail_1G')
def skip_4k_for_ovs(mempage_size):
if mempage_size in (None, 'any', 'small') and not system_helper.is_avs():
skip("4K VM is unsupported by OVS by default")
@fixture(scope='module')
def prepare_resource(add_admin_role_module):
hypervisor = random.choice(host_helper.get_up_hypervisors())
flavor = nova_helper.create_flavor(name='flavor-1g', ram=1024,
cleanup='module')[1]
vol_id = cinder_helper.create_volume('vol-mem_page_size',
cleanup='module')[1]
return hypervisor, flavor, vol_id
def _get_expt_indices(mempage_size):
if mempage_size in ('small', None):
expt_mempage_indices = (0,)
elif str(mempage_size) == '2048':
expt_mempage_indices = (1,)
elif str(mempage_size) == '1048576':
expt_mempage_indices = (2,)
elif mempage_size == 'large':
expt_mempage_indices = (1, 2)
else:
expt_mempage_indices = (0, 1, 2)
return expt_mempage_indices
def is_host_mem_sufficient(host, mempage_size=None, mem_gib=1):
host_mems_per_proc = host_helper.get_host_memories(host,
headers=MEMPAGE_HEADERS)
mempage_size = 'small' if not mempage_size else mempage_size
expt_mempage_indices = _get_expt_indices(mempage_size)
for proc, mems_for_proc in host_mems_per_proc.items():
pages_4k, pages_2m, pages_1g = mems_for_proc
mems_for_proc = (int(pages_4k * 4 / 1048576),
int(pages_2m * 2 / 1024), int(pages_1g))
for index in expt_mempage_indices:
avail_g_for_memsize = mems_for_proc[index]
if avail_g_for_memsize >= mem_gib:
LOG.info("{} has sufficient {} mempages to launch {}G "
"vm".format(host, mempage_size, mem_gib))
return True, host_mems_per_proc
LOG.info("{} does not have sufficient {} mempages to launch {}G "
"vm".format(host, mempage_size, mem_gib))
return False, host_mems_per_proc
def check_mempage_change(vm, host, prev_host_mems, mempage_size=None,
mem_gib=1, numa_node=None):
expt_mempage_indics = _get_expt_indices(mempage_size)
if numa_node is None:
numa_node = vm_helper.get_vm_numa_nodes_via_ps(vm_id=vm, host=host)[0]
prev_host_mems = prev_host_mems[numa_node]
current_host_mems = host_helper.get_host_memories(
host, headers=MEMPAGE_HEADERS)[numa_node]
if 0 in expt_mempage_indics:
if current_host_mems[1:] == prev_host_mems[1:] and \
abs(prev_host_mems[0] - current_host_mems[
0]) <= mem_gib * 512 * 1024 / 4:
return
for i in expt_mempage_indics:
if i == 0:
continue
expt_pagecount = 1 if i == 2 else 1024
if prev_host_mems[i] - expt_pagecount == current_host_mems[i]:
LOG.info("{} {} memory page reduced by {}GiB as "
"expected".format(host, MEMPAGE_HEADERS[i], mem_gib))
return
LOG.info("{} {} memory pages - Previous: {}, current: "
"{}".format(host, MEMPAGE_HEADERS[i],
prev_host_mems[i], current_host_mems[i]))
assert 0, "{} available vm {} memory page count did not change as " \
"expected".format(host, mempage_size)
@mark.parametrize('mem_page_size', [
param('2048', marks=mark.domain_sanity),
param('large', marks=mark.p1),
param('small', marks=mark.domain_sanity),
param('1048576', marks=mark.p3),
])
def test_vm_mem_pool_default_config(prepare_resource, mem_page_size):
"""
Test memory used by vm is taken from the expected memory pool
Args:
prepare_resource (tuple): test fixture
mem_page_size (str): mem page size setting in flavor
Setup:
- Create a flavor with 1G RAM (module)
- Create a volume with default values (module)
- Select a hypervisor to launch vm on
Test Steps:
- Set memory page size flavor spec to given value
- Attempt to boot a vm with above flavor and a basic volume
- Verify the system is taking memory from the expected memory pool:
- If boot vm succeeded:
- Calculate the available/used memory change on the vm host
- Verify the memory is taken from memory pool specified via
mem_page_size
- If boot vm failed:
- Verify system attempted to take memory from expected pool,
but insufficient memory is available
Teardown:
- Delete created vm
- Delete created volume and flavor (module)
"""
hypervisor, flavor_1g, volume_ = prepare_resource
LOG.tc_step("Set memory page size extra spec in flavor")
nova_helper.set_flavor(flavor_1g,
**{FlavorSpec.CPU_POLICY: 'dedicated',
FlavorSpec.MEM_PAGE_SIZE: mem_page_size})
LOG.tc_step("Check system host-memory-list before launch vm")
is_sufficient, prev_host_mems = is_host_mem_sufficient(
host=hypervisor, mempage_size=mem_page_size)
LOG.tc_step("Boot a vm with mem page size spec - {}".format(mem_page_size))
code, vm_id, msg = vm_helper.boot_vm('mempool_' + mem_page_size, flavor_1g,
source='volume', fail_ok=True,
vm_host=hypervisor, source_id=volume_,
cleanup='function')
if not is_sufficient:
LOG.tc_step("Check boot vm rejected due to insufficient memory from "
"{} pool".format(mem_page_size))
assert 1 == code, "{} vm launched successfully when insufficient " \
"mempage configured on {}". \
format(mem_page_size, hypervisor)
else:
LOG.tc_step("Check vm launches successfully and {} available mempages "
"change accordingly".format(hypervisor))
assert 0 == code, "VM failed to launch with '{}' " \
"mempages".format(mem_page_size)
check_mempage_change(vm_id, host=hypervisor,
prev_host_mems=prev_host_mems,
mempage_size=mem_page_size)
def get_hosts_to_configure(candidates):
hosts_selected = [None, None]
hosts_to_configure = [None, None]
max_4k, expt_p1_4k, max_1g, expt_p1_1g = \
1.5 * 1048576 / 4, 2.5 * 1048576 / 4, 1, 2
for host in candidates:
host_mems = host_helper.get_host_memories(host, headers=MEMPAGE_HEADERS)
if 1 not in host_mems:
LOG.info("{} has only 1 processor".format(host))
continue
proc0_mems, proc1_mems = host_mems[0], host_mems[1]
p0_4k, p1_4k, p0_1g, p1_1g = \
proc0_mems[0], proc1_mems[0], proc0_mems[2], proc1_mems[2]
if p0_4k <= max_4k and p0_1g <= max_1g:
if not hosts_selected[1] and p1_4k >= expt_p1_4k and \
p1_1g <= max_1g:
hosts_selected[1] = host
elif not hosts_selected[0] and p1_4k <= max_4k and \
p1_1g >= expt_p1_1g:
hosts_selected[0] = host
if None not in hosts_selected:
LOG.info("1G and 4k hosts already configured and selected: "
"{}".format(hosts_selected))
break
else:
for i in range(len(hosts_selected)):
if hosts_selected[i] is None:
hosts_selected[i] = hosts_to_configure[i] = \
list(set(candidates) - set(hosts_selected))[0]
LOG.info("Hosts selected: {}; To be configured: "
"{}".format(hosts_selected, hosts_to_configure))
return hosts_selected, hosts_to_configure
class TestConfigMempage:
MEM_CONFIGS = [None, 'any', 'large', 'small', '2048', '1048576']
@fixture(scope='class')
def add_1g_and_4k_pages(self, request, config_host_class,
skip_for_one_proc, add_stxauto_zone,
add_admin_role_module):
storage_backing, candidate_hosts = \
keywords.host_helper.get_storage_backing_with_max_hosts()
if len(candidate_hosts) < 2:
skip("Less than two up hosts have same storage backing")
LOG.fixture_step("Check mempage configs for hypervisors and select "
"host to use or configure")
hosts_selected, hosts_to_configure = get_hosts_to_configure(
candidate_hosts)
if set(hosts_to_configure) != {None}:
def _modify(host):
is_1g = True if hosts_selected.index(host) == 0 else False
proc1_kwargs = {'gib_1g': 2, 'gib_4k_range': (None, 2)} if \
is_1g else {'gib_1g': 0, 'gib_4k_range': (2, None)}
kwargs = {'gib_1g': 0, 'gib_4k_range': (None, 2)}, proc1_kwargs
actual_mems = host_helper._get_actual_mems(host=host)
LOG.fixture_step("Modify {} proc0 to have 0 of 1G pages and "
"<2GiB of 4K pages".format(host))
host_helper.modify_host_memory(host, proc=0,
actual_mems=actual_mems,
**kwargs[0])
LOG.fixture_step("Modify {} proc1 to have >=2GiB of {} "
"pages".format(host, '1G' if is_1g else '4k'))
host_helper.modify_host_memory(host, proc=1,
actual_mems=actual_mems,
**kwargs[1])
for host_to_config in hosts_to_configure:
if host_to_config:
config_host_class(host=host_to_config, modify_func=_modify)
LOG.fixture_step("Check mem pages for {} are modified "
"and updated successfully".
format(host_to_config))
host_helper.wait_for_memory_update(host=host_to_config)
LOG.fixture_step("Check host memories for {} after mem config "
"completed".format(hosts_selected))
_, hosts_unconfigured = get_hosts_to_configure(hosts_selected)
assert not hosts_unconfigured[0], \
"Failed to configure {}. Expt: proc0:1g<2,4k<2gib;" \
"proc1:1g>=2,4k<2gib".format(hosts_unconfigured[0])
assert not hosts_unconfigured[1], \
"Failed to configure {}. Expt: proc0:1g<2,4k<2gib;" \
"proc1:1g<2,4k>=2gib".format(hosts_unconfigured[1])
LOG.fixture_step('(class) Add hosts to stxauto aggregate: '
'{}'.format(hosts_selected))
nova_helper.add_hosts_to_aggregate(aggregate='stxauto',
hosts=hosts_selected)
def remove_host_from_zone():
LOG.fixture_step('(class) Remove hosts from stxauto aggregate: '
'{}'.format(hosts_selected))
nova_helper.remove_hosts_from_aggregate(aggregate='stxauto',
check_first=False)
request.addfinalizer(remove_host_from_zone)
return hosts_selected, storage_backing
@fixture(scope='class')
def flavor_2g(self, add_1g_and_4k_pages):
hosts, storage_backing = add_1g_and_4k_pages
LOG.fixture_step("Create a 2G memory flavor to be used by mempage "
"testcases")
flavor = nova_helper.create_flavor(name='flavor-2g', ram=2048,
storage_backing=storage_backing,
cleanup='class')[1]
return flavor, hosts, storage_backing
@fixture(scope='class')
def image_mempage(self):
LOG.fixture_step("(class) Create a glance image for mempage testcases")
image_id = glance_helper.create_image(name='mempage',
cleanup='class')[1]
return image_id
@fixture()
def check_alarms(self, add_1g_and_4k_pages):
hosts, storage_backing = add_1g_and_4k_pages
host_helper.get_hypervisor_info(hosts=hosts)
for host in hosts:
host_helper.get_host_memories(host, wait_for_update=False)
@fixture(params=MEM_CONFIGS)
def flavor_mem_page_size(self, request, flavor_2g):
flavor_id = flavor_2g[0]
mem_page_size = request.param
skip_4k_for_ovs(mem_page_size)
if mem_page_size is None:
nova_helper.unset_flavor(flavor_id, FlavorSpec.MEM_PAGE_SIZE)
else:
nova_helper.set_flavor(flavor_id,
**{FlavorSpec.MEM_PAGE_SIZE: mem_page_size})
return mem_page_size
@mark.parametrize('image_mem_page_size', MEM_CONFIGS)
def test_boot_vm_mem_page_size(self, flavor_2g, flavor_mem_page_size,
image_mempage, image_mem_page_size):
"""
Test boot vm with various memory page size setting in flavor and image.
Args:
flavor_2g (tuple): flavor id of a flavor with ram set to 2G,
hosts configured and storage_backing
flavor_mem_page_size (str): memory page size extra spec value to
set in flavor
image_mempage (str): image id for tis image
image_mem_page_size (str): memory page metadata value to set in
image
Setup:
- Create a flavor with 2G RAM (module)
- Get image id of tis image (module)
Test Steps:
- Set/Unset flavor memory page size extra spec with given value (
unset if None is given)
- Set/Unset image memory page size metadata with given value (
unset if None if given)
- Attempt to boot a vm with above flavor and image
- Verify boot result based on the mem page size values in the
flavor and image
Teardown:
- Delete vm if booted
- Delete created flavor (module)
"""
skip_4k_for_ovs(image_mem_page_size)
flavor_id, hosts, storage_backing = flavor_2g
if image_mem_page_size is None:
glance_helper.unset_image(image_mempage,
properties=ImageMetadata.MEM_PAGE_SIZE)
expt_code = 0
else:
glance_helper.set_image(image=image_mempage,
properties={ImageMetadata.MEM_PAGE_SIZE:
image_mem_page_size})
if flavor_mem_page_size is None:
expt_code = 4
elif flavor_mem_page_size.lower() in ['any', 'large']:
expt_code = 0
else:
expt_code = 0 if flavor_mem_page_size.lower() == \
image_mem_page_size.lower() else 4
LOG.tc_step("Attempt to boot a vm with flavor_mem_page_size: {}, and "
"image_mem_page_size: {}. And check return "
"code is {}.".format(flavor_mem_page_size,
image_mem_page_size, expt_code))
actual_code, vm_id, msg = vm_helper.boot_vm(name='mem_page_size',
flavor=flavor_id,
source='image',
source_id=image_mempage,
fail_ok=True,
avail_zone='stxauto',
cleanup='function')
assert expt_code == actual_code, "Expect boot vm to return {}; " \
"Actual result: {} with msg: " \
"{}".format(expt_code, actual_code,
msg)
if expt_code != 0:
assert re.search(
NovaCLIOutput.VM_BOOT_REJECT_MEM_PAGE_SIZE_FORBIDDEN, msg)
else:
assert vm_helper.get_vm_host(vm_id) in hosts, \
"VM is not booted on hosts in stxauto zone"
LOG.tc_step("Ensure VM is pingable from NatBox")
vm_helper.wait_for_vm_pingable_from_natbox(vm_id)
@mark.parametrize('mem_page_size', [
param('1048576', marks=mark.priorities('domain_sanity', 'nightly')),
param('large'),
param('small', marks=mark.nightly),
])
def test_schedule_vm_mempage_config(self, flavor_2g, mem_page_size):
"""
Test memory used by vm is taken from the expected memory pool and the
vm was scheduled on the correct
host/processor
Args:
flavor_2g (tuple): flavor id of a flavor with ram set to 2G,
hosts, storage_backing
mem_page_size (str): mem page size setting in flavor
Setup:
- Create host aggregate
- Add two hypervisors to the host aggregate
- Host-0 configuration:
- Processor-0:
- Insufficient 1g pages to boot vm that requires 2g
- Insufficient 4k pages to boot vm that requires 2g
- Processor-1:
- Sufficient 1g pages to boot vm that requires 2g
- Insufficient 4k pages to boot vm that requires 2g
- Host-1 configuration:
- Processor-0:
- Insufficient 1g pages to boot vm that requires 2g
- Insufficient 4k pages to boot vm that requires 2g
- Processor-1:
- Insufficient 1g pages to boot vm that requires 2g
- Sufficient 4k pages to boot vm that requires 2g
- Configure a compute to have 4 1G hugepages (module)
- Create a flavor with 2G RAM (module)
- Create a volume with default values (module)
Test Steps:
- Set memory page size flavor spec to given value
- Boot a vm with above flavor and a basic volume
- Calculate the available/used memory change on the vm host
- Verify the memory is taken from 1G hugepage memory pool
- Verify the vm was booted on a supporting host
Teardown:
- Delete created vm
- Delete created volume and flavor (module)
- Re-Configure the compute to have 0 hugepages (module)
- Revert host mem pages back to original
"""
skip_4k_for_ovs(mem_page_size)
flavor_id, hosts_configured, storage_backing = flavor_2g
LOG.tc_step("Set memory page size extra spec in flavor")
nova_helper.set_flavor(flavor_id,
**{FlavorSpec.CPU_POLICY: 'dedicated',
FlavorSpec.MEM_PAGE_SIZE: mem_page_size})
host_helper.wait_for_hypervisors_up(hosts_configured)
prev_computes_mems = {}
for host in hosts_configured:
prev_computes_mems[host] = host_helper.get_host_memories(
host=host, headers=MEMPAGE_HEADERS)
LOG.tc_step(
"Boot a vm with mem page size spec - {}".format(mem_page_size))
host_1g, host_4k = hosts_configured
code, vm_id, msg = vm_helper.boot_vm('mempool_configured', flavor_id,
fail_ok=True,
avail_zone='stxauto',
cleanup='function')
assert 0 == code, "VM is not successfully booted."
instance_name, vm_host = vm_helper.get_vm_values(
vm_id, fields=[":instance_name", ":host"], strict=False)
vm_node = vm_helper.get_vm_numa_nodes_via_ps(
vm_id=vm_id, instance_name=instance_name, host=vm_host)
if mem_page_size == '1048576':
assert host_1g == vm_host, \
"VM is not created on the configured host " \
"{}".format(hosts_configured[0])
assert vm_node == [1], "VM (huge) did not boot on the correct " \
"processor"
elif mem_page_size == 'small':
assert host_4k == vm_host, "VM is not created on the configured " \
"host {}".format(hosts_configured[1])
assert vm_node == [1], "VM (small) did not boot on the correct " \
"processor"
else:
assert vm_host in hosts_configured
LOG.tc_step("Calculate memory change on vm host - {}".format(vm_host))
check_mempage_change(vm_id, vm_host,
prev_host_mems=prev_computes_mems[vm_host],
mempage_size=mem_page_size, mem_gib=2,
numa_node=vm_node[0])
LOG.tc_step("Ensure vm is pingable from NatBox")
vm_helper.wait_for_vm_pingable_from_natbox(vm_id)