Merge pull request #746 from openstack-charmers/hugepages-vfio
Add connectivity test with DPDK
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,5 +6,6 @@ dist/
|
||||
zaza.openstack.egg-info/
|
||||
.coverage
|
||||
.vscode/
|
||||
*.swp
|
||||
# Sphinx
|
||||
doc/build
|
||||
|
||||
@@ -1500,6 +1500,20 @@ class TestOpenStackUtils(ut_utils.BaseTestCase):
|
||||
self.configure_networking_charms.assert_called_once_with(
|
||||
'fakenetworkingdata', expect, use_juju_wait=False)
|
||||
|
||||
def test_update_subnet_dhcp(self):
|
||||
neutron_client = mock.MagicMock()
|
||||
openstack_utils.update_subnet_dhcp(
|
||||
neutron_client, {'id': 'aId'}, True)
|
||||
neutron_client.update_subnet.assert_called_once_with(
|
||||
'aId',
|
||||
{'subnet': {'enable_dhcp': True}})
|
||||
neutron_client.reset_mock()
|
||||
openstack_utils.update_subnet_dhcp(
|
||||
neutron_client, {'id': 'aId'}, False)
|
||||
neutron_client.update_subnet.assert_called_once_with(
|
||||
'aId',
|
||||
{'subnet': {'enable_dhcp': False}})
|
||||
|
||||
|
||||
class TestAsyncOpenstackUtils(ut_utils.AioTestCase):
|
||||
|
||||
|
||||
@@ -43,6 +43,8 @@ OVERCLOUD_NETWORK_CONFIG = {
|
||||
"prefix_len": "24",
|
||||
"subnetpool_name": "pooled_subnets",
|
||||
"subnetpool_prefix": "192.168.0.0/16",
|
||||
"project_net_name": "private",
|
||||
"project_subnet_name": "private_subnet",
|
||||
}
|
||||
|
||||
OVERCLOUD_PROVIDER_VLAN_NETWORK_CONFIG = {
|
||||
@@ -67,29 +69,19 @@ DEFAULT_UNDERCLOUD_NETWORK_CONFIG = {
|
||||
}
|
||||
|
||||
|
||||
def basic_overcloud_network(limit_gws=None):
|
||||
"""Run setup for neutron networking.
|
||||
|
||||
Configure the following:
|
||||
The overcloud network using subnet pools
|
||||
def undercloud_and_charm_setup(limit_gws=None):
|
||||
"""Perform undercloud and charm setup for network plumbing.
|
||||
|
||||
:param limit_gws: Limit the number of gateways that get a port attached
|
||||
:type limit_gws: int
|
||||
"""
|
||||
cli_utils.setup_logging()
|
||||
|
||||
# Get network configuration settings
|
||||
network_config = {}
|
||||
# Declared overcloud settings
|
||||
network_config.update(OVERCLOUD_NETWORK_CONFIG)
|
||||
# Default undercloud settings
|
||||
network_config.update(DEFAULT_UNDERCLOUD_NETWORK_CONFIG)
|
||||
# Environment specific settings
|
||||
network_config.update(generic_utils.get_undercloud_env_vars())
|
||||
|
||||
# Get keystone session
|
||||
keystone_session = openstack_utils.get_overcloud_keystone_session()
|
||||
|
||||
# Get optional use_juju_wait for network option
|
||||
options = (lifecycle_utils
|
||||
.get_charm_config(fatal=False)
|
||||
@@ -118,6 +110,33 @@ def basic_overcloud_network(limit_gws=None):
|
||||
' charm network configuration.'
|
||||
.format(provider_type))
|
||||
|
||||
|
||||
def basic_overcloud_network(limit_gws=None):
|
||||
"""Run setup for neutron networking.
|
||||
|
||||
Configure the following:
|
||||
The overcloud network using subnet pools
|
||||
|
||||
:param limit_gws: Limit the number of gateways that get a port attached
|
||||
:type limit_gws: int
|
||||
"""
|
||||
cli_utils.setup_logging()
|
||||
|
||||
# Get network configuration settings
|
||||
network_config = {}
|
||||
# Declared overcloud settings
|
||||
network_config.update(OVERCLOUD_NETWORK_CONFIG)
|
||||
# Default undercloud settings
|
||||
network_config.update(DEFAULT_UNDERCLOUD_NETWORK_CONFIG)
|
||||
# Environment specific settings
|
||||
network_config.update(generic_utils.get_undercloud_env_vars())
|
||||
|
||||
# Get keystone session
|
||||
keystone_session = openstack_utils.get_overcloud_keystone_session()
|
||||
|
||||
# Perform undercloud and charm setup for network plumbing
|
||||
undercloud_and_charm_setup(limit_gws=limit_gws)
|
||||
|
||||
# Configure the overcloud network
|
||||
network.setup_sdn(network_config, keystone_session=keystone_session)
|
||||
|
||||
|
||||
@@ -28,10 +28,12 @@ from neutronclient.common import exceptions as neutronexceptions
|
||||
|
||||
import yaml
|
||||
import zaza
|
||||
import zaza.openstack.charm_tests.neutron.setup as neutron_setup
|
||||
import zaza.openstack.charm_tests.nova.utils as nova_utils
|
||||
import zaza.openstack.charm_tests.test_utils as test_utils
|
||||
import zaza.openstack.configure.guest as guest
|
||||
import zaza.openstack.utilities.openstack as openstack_utils
|
||||
import zaza.utilities.machine_os
|
||||
|
||||
|
||||
class NeutronPluginApiSharedTests(test_utils.OpenStackBaseTest):
|
||||
@@ -805,6 +807,11 @@ class NeutronOvsVsctlTest(NeutronPluginApiSharedTests):
|
||||
self.assertEqual(actual_external_id, expected_external_id)
|
||||
|
||||
|
||||
def router_address_from_subnet(subnet):
|
||||
"""Retrieve router address from subnet."""
|
||||
return subnet['gateway_ip']
|
||||
|
||||
|
||||
class NeutronNetworkingBase(test_utils.OpenStackBaseTest):
|
||||
"""Base for checking openstack instances have valid networking."""
|
||||
|
||||
@@ -818,6 +825,21 @@ class NeutronNetworkingBase(test_utils.OpenStackBaseTest):
|
||||
cls.neutron_client = (
|
||||
openstack_utils.get_neutron_session_client(cls.keystone_session))
|
||||
|
||||
cls.project_subnet = cls.neutron_client.find_resource(
|
||||
'subnet',
|
||||
neutron_setup.OVERCLOUD_NETWORK_CONFIG['project_subnet_name'])
|
||||
cls.external_subnet = cls.neutron_client.find_resource(
|
||||
'subnet',
|
||||
neutron_setup.OVERCLOUD_NETWORK_CONFIG['external_subnet_name'])
|
||||
|
||||
# Override this if you want your test to attach instances directly to
|
||||
# the external provider network
|
||||
cls.attach_to_external_network = False
|
||||
|
||||
# Override this if you want your test to launch instances with a
|
||||
# specific flavor
|
||||
cls.instance_flavor = None
|
||||
|
||||
@tenacity.retry(wait=tenacity.wait_exponential(multiplier=1, max=60),
|
||||
reraise=True, stop=tenacity.stop_after_attempt(8))
|
||||
def validate_instance_can_reach_other(self,
|
||||
@@ -840,8 +862,10 @@ class NeutronNetworkingBase(test_utils.OpenStackBaseTest):
|
||||
:param mtu: Check that we can send non-fragmented packets of given size
|
||||
:type mtu: Optional[int]
|
||||
"""
|
||||
floating_1 = floating_ips_from_instance(instance_1)[0]
|
||||
floating_2 = floating_ips_from_instance(instance_2)[0]
|
||||
if not self.attach_to_external_network:
|
||||
floating_1 = floating_ips_from_instance(instance_1)[0]
|
||||
floating_2 = floating_ips_from_instance(instance_2)[0]
|
||||
address_1 = fixed_ips_from_instance(instance_1)[0]
|
||||
address_2 = fixed_ips_from_instance(instance_2)[0]
|
||||
|
||||
username = guest.boot_tests['bionic']['username']
|
||||
@@ -859,15 +883,21 @@ class NeutronNetworkingBase(test_utils.OpenStackBaseTest):
|
||||
'ping -M do -s {} -c 1'.format(packetsize))
|
||||
|
||||
for cmd in cmds:
|
||||
openstack_utils.ssh_command(
|
||||
username, floating_1, 'instance-1',
|
||||
'{} {}'.format(cmd, address_2),
|
||||
password=password, privkey=privkey, verify=verify)
|
||||
if self.attach_to_external_network:
|
||||
openstack_utils.ssh_command(
|
||||
username, address_1, 'instance-1',
|
||||
'{} {}'.format(cmd, address_2),
|
||||
password=password, privkey=privkey, verify=verify)
|
||||
else:
|
||||
openstack_utils.ssh_command(
|
||||
username, floating_1, 'instance-1',
|
||||
'{} {}'.format(cmd, address_2),
|
||||
password=password, privkey=privkey, verify=verify)
|
||||
|
||||
openstack_utils.ssh_command(
|
||||
username, floating_1, 'instance-1',
|
||||
'{} {}'.format(cmd, floating_2),
|
||||
password=password, privkey=privkey, verify=verify)
|
||||
openstack_utils.ssh_command(
|
||||
username, floating_1, 'instance-1',
|
||||
'{} {}'.format(cmd, floating_2),
|
||||
password=password, privkey=privkey, verify=verify)
|
||||
|
||||
@tenacity.retry(wait=tenacity.wait_exponential(multiplier=1, max=60),
|
||||
reraise=True, stop=tenacity.stop_after_attempt(8))
|
||||
@@ -875,11 +905,6 @@ class NeutronNetworkingBase(test_utils.OpenStackBaseTest):
|
||||
"""
|
||||
Validate that an instance can reach it's primary gateway.
|
||||
|
||||
We make the assumption that the router's IP is 192.168.0.1
|
||||
as that's the network that is setup in
|
||||
neutron.setup.basic_overcloud_network which is used in all
|
||||
Zaza Neutron validations.
|
||||
|
||||
:param instance: The instance to check networking from
|
||||
:type instance: nova_client.Server
|
||||
|
||||
@@ -889,7 +914,12 @@ class NeutronNetworkingBase(test_utils.OpenStackBaseTest):
|
||||
:param mtu: Check that we can send non-fragmented packets of given size
|
||||
:type mtu: Optional[int]
|
||||
"""
|
||||
address = floating_ips_from_instance(instance)[0]
|
||||
if self.attach_to_external_network:
|
||||
router = router_address_from_subnet(self.external_subnet)
|
||||
address = fixed_ips_from_instance(instance)[0]
|
||||
else:
|
||||
router = router_address_from_subnet(self.project_subnet)
|
||||
address = floating_ips_from_instance(instance)[0]
|
||||
|
||||
username = guest.boot_tests['bionic']['username']
|
||||
password = guest.boot_tests['bionic'].get('password')
|
||||
@@ -907,7 +937,7 @@ class NeutronNetworkingBase(test_utils.OpenStackBaseTest):
|
||||
|
||||
for cmd in cmds:
|
||||
openstack_utils.ssh_command(
|
||||
username, address, 'instance', '{} 192.168.0.1'.format(cmd),
|
||||
username, address, 'instance', '{} {}'.format(cmd, router),
|
||||
password=password, privkey=privkey, verify=verify)
|
||||
|
||||
@tenacity.retry(wait=tenacity.wait_exponential(min=5, max=60),
|
||||
@@ -1086,13 +1116,77 @@ class NeutronNetworkingTest(NeutronNetworkingBase):
|
||||
"""
|
||||
instance_1, instance_2 = self.retrieve_guests()
|
||||
if not all([instance_1, instance_2]):
|
||||
self.launch_guests()
|
||||
self.launch_guests(
|
||||
attach_to_external_network=self.attach_to_external_network,
|
||||
flavor_name=self.instance_flavor)
|
||||
instance_1, instance_2 = self.retrieve_guests()
|
||||
self.check_connectivity(instance_1, instance_2)
|
||||
self.run_resource_cleanup = self.get_my_tests_options(
|
||||
'run_resource_cleanup', True)
|
||||
|
||||
|
||||
class DPDKNeutronNetworkingTest(NeutronNetworkingTest):
|
||||
"""Ensure that openstack instances have valid networking with DPDK."""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
"""Run class setup for running Neutron API Networking tests."""
|
||||
super(DPDKNeutronNetworkingTest, cls).setUpClass()
|
||||
|
||||
# At this point in time the charms do not support configuring overlay
|
||||
# networks with DPDK. To perform end to end validation we need to
|
||||
# attach instances directly to the provider network and subsequently
|
||||
# DHCP needs to be enabled on that network.
|
||||
#
|
||||
# Note that for instances wired with DPDK the DHCP request/response is
|
||||
# handled as private communication between the ovn-controller and the
|
||||
# instance, and as such there is no risk of rogue DHCP replies escaping
|
||||
# to the surrounding network.
|
||||
cls.attach_to_external_network = True
|
||||
cls.instance_flavor = 'hugepages'
|
||||
cls.external_subnet = cls.neutron_client.find_resource(
|
||||
'subnet',
|
||||
neutron_setup.OVERCLOUD_NETWORK_CONFIG['external_subnet_name'])
|
||||
if ('dhcp_enabled' not in cls.external_subnet or
|
||||
not cls.external_subnet['dhcp_enabled']):
|
||||
logging.info('Enabling DHCP on subnet {}'
|
||||
.format(cls.external_subnet['name']))
|
||||
openstack_utils.update_subnet_dhcp(
|
||||
cls.neutron_client, cls.external_subnet, True)
|
||||
|
||||
def test_instances_have_networking(self):
|
||||
"""Enable DPDK then Validate North/South and East/West networking."""
|
||||
self.enable_hugepages_vfio_on_hvs_in_vms(4)
|
||||
with self.config_change(
|
||||
{
|
||||
'enable-dpdk': False,
|
||||
'dpdk-driver': '',
|
||||
},
|
||||
{
|
||||
'enable-dpdk': True,
|
||||
'dpdk-driver': 'vfio-pci',
|
||||
},
|
||||
application_name='ovn-chassis'):
|
||||
super().test_instances_have_networking()
|
||||
self.run_resource_cleanup = self.get_my_tests_options(
|
||||
'run_resource_cleanup', True)
|
||||
|
||||
def resource_cleanup(self):
|
||||
"""Extend to also revert VFIO NOIOMMU mode on units under test."""
|
||||
super().resource_cleanup()
|
||||
if not self.run_resource_cleanup:
|
||||
return
|
||||
|
||||
if ('dhcp_enabled' not in self.external_subnet or
|
||||
not self.external_subnet['dhcp_enabled']):
|
||||
logging.info('Disabling DHCP on subnet {}'
|
||||
.format(self.external_subnet['name']))
|
||||
openstack_utils.update_subnet_dhcp(
|
||||
self.neutron_client, self.external_subnet, False)
|
||||
|
||||
self.disable_hugepages_vfio_on_hvs_in_vms()
|
||||
|
||||
|
||||
class NeutronNetworkingVRRPTests(NeutronNetworkingBase):
|
||||
"""Check networking when gateways are restarted."""
|
||||
|
||||
|
||||
@@ -65,5 +65,14 @@ FLAVORS = {
|
||||
'hw:tpm_model': 'tpm-crb',
|
||||
},
|
||||
},
|
||||
'hugepages': {
|
||||
'flavorid': 'auto',
|
||||
'ram': 1024,
|
||||
'disk': 20,
|
||||
'vcpus': 1,
|
||||
'extra-specs': {
|
||||
'hw:mem_page_size': 'large',
|
||||
},
|
||||
},
|
||||
}
|
||||
KEYPAIR_NAME = 'zaza'
|
||||
|
||||
@@ -26,6 +26,7 @@ import zaza.model
|
||||
import zaza.openstack.charm_tests.test_utils as test_utils
|
||||
import zaza.openstack.utilities.generic as generic_utils
|
||||
import zaza.openstack.utilities.openstack as openstack_utils
|
||||
import zaza.utilities.juju
|
||||
|
||||
|
||||
class BaseCharmOperationTest(test_utils.BaseCharmTest):
|
||||
@@ -177,7 +178,8 @@ class ChassisCharmOperationTest(BaseCharmOperationTest):
|
||||
'target_deploy_status', {})
|
||||
new_target_deploy_status = stored_target_deploy_status.copy()
|
||||
new_target_deploy_status[self.application_name] = {
|
||||
'ovn-chassis': 'blocked',
|
||||
'workload-status': 'blocked',
|
||||
'workload-status-message': 'Wrong format',
|
||||
}
|
||||
if 'target_deploy_status' in self.test_config:
|
||||
self.test_config['target_deploy_status'].update(
|
||||
@@ -186,13 +188,146 @@ class ChassisCharmOperationTest(BaseCharmOperationTest):
|
||||
self.test_config['target_deploy_status'] = new_target_deploy_status
|
||||
|
||||
with self.config_change(
|
||||
{'bridge-interface-mappings': ''},
|
||||
self.config_current(
|
||||
application_name=self.application_name,
|
||||
keys=['bridge-interface-mappings']),
|
||||
{'bridge-interface-mappings': 'incorrect'}):
|
||||
logging.info('Charm went into blocked state as expected, restore '
|
||||
'configuration')
|
||||
self.test_config[
|
||||
'target_deploy_status'] = stored_target_deploy_status
|
||||
|
||||
def _openvswitch_switch_dpdk_installed(self):
|
||||
"""Assert that the openvswitch-switch-dpdk package is installed.
|
||||
|
||||
:raises: zaza.model.CommandRunFailed
|
||||
"""
|
||||
cmd = 'dpkg-query -s openvswitch-switch-dpdk'
|
||||
for unit in zaza.model.get_units(self.application_name):
|
||||
zaza.utilities.juju.remote_run(
|
||||
unit.name, cmd, model_name=self.model_name, fatal=True)
|
||||
|
||||
def _ovs_dpdk_init_configured(self):
|
||||
"""Assert that DPDK is configured.
|
||||
|
||||
:raises: AssertionError, zaza.model.CommandRunFailed
|
||||
"""
|
||||
cmd = 'ovs-vsctl get open-vswitch . other_config:dpdk-init'
|
||||
for unit in zaza.model.get_units(self.application_name):
|
||||
result = zaza.utilities.juju.remote_run(
|
||||
unit.name,
|
||||
cmd,
|
||||
model_name=self.model_name,
|
||||
fatal=True).rstrip()
|
||||
assert result == '"true"', (
|
||||
'DPDK not configured on {}'.format(unit.name))
|
||||
|
||||
def _ovs_dpdk_initialized(self):
|
||||
"""Assert that OVS successfully initialized DPDK.
|
||||
|
||||
:raises: AssertionError, zaza.model.CommandRunFailed
|
||||
"""
|
||||
cmd = 'ovs-vsctl get open-vswitch . dpdk_initialized'
|
||||
for unit in zaza.model.get_units(self.application_name):
|
||||
result = zaza.utilities.juju.remote_run(
|
||||
unit.name,
|
||||
cmd,
|
||||
model_name=self.model_name,
|
||||
fatal=True).rstrip()
|
||||
assert result == 'true', (
|
||||
'DPDK not initialized on {}'.format(unit.name))
|
||||
|
||||
def _ovs_br_ex_port_is_system_interface(self):
|
||||
"""Assert br-ex bridge is created and has system port in it.
|
||||
|
||||
:raises: zaza.model.CommandRunFailed
|
||||
"""
|
||||
cmd = ('ip link show dev $(ovs-vsctl --bare --columns name '
|
||||
'find port external_ids:charm-ovn-chassis=br-ex)')
|
||||
for unit in zaza.model.get_units(self.application_name):
|
||||
zaza.utilities.juju.remote_run(
|
||||
unit.name, cmd, model_name=self.model_name, fatal=True)
|
||||
|
||||
def _ovs_br_ex_port_is_dpdk_interface(self):
|
||||
"""Assert br-ex bridge is created and has DPDK port in it.
|
||||
|
||||
:raises: zaza.model.CommandRunFailed
|
||||
"""
|
||||
cmd = (
|
||||
'dpdk-devbind.py --status-dev net '
|
||||
'| grep ^$(ovs-vsctl --bare --columns options '
|
||||
'find interface external_ids:charm-ovn-chassis=br-ex '
|
||||
'|cut -f2 -d=)'
|
||||
'|grep "drv=vfio-pci unused=$"')
|
||||
for unit in zaza.model.get_units(self.application_name):
|
||||
zaza.utilities.juju.remote_run(
|
||||
unit.name, cmd, model_name=self.model_name, fatal=True)
|
||||
|
||||
def _ovs_br_ex_interface_not_in_error(self):
|
||||
"""Assert br-ex bridge is created and interface is not in error.
|
||||
|
||||
:raises: AssertionError, zaza.model.CommandRunFailed
|
||||
"""
|
||||
cmd = (
|
||||
'ovs-vsctl --bare --columns error '
|
||||
'find interface external_ids:charm-ovn-chassis=br-ex')
|
||||
for unit in zaza.model.get_units(self.application_name):
|
||||
result = zaza.utilities.juju.remote_run(
|
||||
unit.name,
|
||||
cmd,
|
||||
model_name=self.model_name,
|
||||
fatal=True).rstrip()
|
||||
assert result == '', result
|
||||
|
||||
def _dpdk_pre_post_flight_check(self):
|
||||
"""Assert state of the system before and after enable/disable DPDK."""
|
||||
with self.assertRaises(
|
||||
zaza.model.CommandRunFailed,
|
||||
msg='openvswitch-switch-dpdk unexpectedly installed'):
|
||||
self._openvswitch_switch_dpdk_installed()
|
||||
with self.assertRaises(
|
||||
zaza.model.CommandRunFailed,
|
||||
msg='OVS unexpectedly configured for DPDK'):
|
||||
self._ovs_dpdk_init_configured()
|
||||
with self.assertRaises(
|
||||
AssertionError,
|
||||
msg='OVS unexpectedly has DPDK initialized'):
|
||||
self._ovs_dpdk_initialized()
|
||||
|
||||
def test_enable_dpdk(self):
|
||||
"""Confirm that transitioning to/from DPDK works."""
|
||||
logging.info('Pre-flight check')
|
||||
self._dpdk_pre_post_flight_check()
|
||||
self._ovs_br_ex_port_is_system_interface()
|
||||
|
||||
self.enable_hugepages_vfio_on_hvs_in_vms(4)
|
||||
with self.config_change(
|
||||
{
|
||||
'enable-dpdk': False,
|
||||
'dpdk-driver': '',
|
||||
},
|
||||
{
|
||||
'enable-dpdk': True,
|
||||
'dpdk-driver': 'vfio-pci',
|
||||
},
|
||||
application_name='ovn-chassis'):
|
||||
logging.info('Checking openvswitch-switch-dpdk is installed')
|
||||
self._openvswitch_switch_dpdk_installed()
|
||||
logging.info('Checking DPDK is configured in OVS')
|
||||
self._ovs_dpdk_init_configured()
|
||||
logging.info('Checking DPDK is successfully initialized in OVS')
|
||||
self._ovs_dpdk_initialized()
|
||||
logging.info('Checking that br-ex configed with DPDK interface...')
|
||||
self._ovs_br_ex_port_is_dpdk_interface()
|
||||
logging.info('and is not in error.')
|
||||
self._ovs_br_ex_interface_not_in_error()
|
||||
|
||||
logging.info('Post-flight check')
|
||||
self._dpdk_pre_post_flight_check()
|
||||
|
||||
self.disable_hugepages_vfio_on_hvs_in_vms()
|
||||
self._ovs_br_ex_port_is_system_interface()
|
||||
|
||||
|
||||
class OVSOVNMigrationTest(test_utils.BaseCharmTest):
|
||||
"""OVS to OVN migration tests."""
|
||||
|
||||
@@ -28,6 +28,7 @@ import zaza.openstack.configure.guest as configure_guest
|
||||
import zaza.openstack.utilities.openstack as openstack_utils
|
||||
import zaza.openstack.utilities.generic as generic_utils
|
||||
import zaza.openstack.charm_tests.glance.setup as glance_setup
|
||||
import zaza.utilities.machine_os
|
||||
|
||||
|
||||
def skipIfNotHA(service_name):
|
||||
@@ -599,6 +600,122 @@ class BaseCharmTest(unittest.TestCase):
|
||||
for unit in units:
|
||||
model.run_on_unit(unit, "hooks/update-status")
|
||||
|
||||
def assert_unit_cpu_topology(self, unit, nr_1g_hugepages):
|
||||
r"""Assert unit under test CPU topology.
|
||||
|
||||
When using OpenStack as CI substrate:
|
||||
|
||||
By default, when instance NUMA placement is not specified,
|
||||
a topology of N sockets, each with one core and one thread,
|
||||
is used for an instance, where N corresponds to the number of
|
||||
instance vCPUs requested.
|
||||
|
||||
In this context a socket is a physical socket on the motherboard
|
||||
where a CPU is connected.
|
||||
|
||||
The DPDK Environment Abstraction Layer (EAL) allocates memory per
|
||||
CPU socket, so we want the CPU topology inside the instance to
|
||||
mimic something we would be likely to find in the real world and
|
||||
at the same time not make the test too heavy.
|
||||
|
||||
The charm default is to have Open vSwitch allocate 1GB RAM per
|
||||
CPU socket.
|
||||
|
||||
The following command would set the apropriate CPU topology for a
|
||||
4 VCPU, 8 GB RAM flavor:
|
||||
|
||||
openstack flavor set onesocketm1.large \
|
||||
--property hw:cpu_sockets=1 \
|
||||
--property hw:cpu_cores=2 \
|
||||
--property hw:cpu_threads=2
|
||||
|
||||
For validation of operation with multiple sockets, the following
|
||||
command would set the apropriate CPU topology for a
|
||||
8 VCPU, 16GB RAM flavor:
|
||||
|
||||
openstack flavor set twosocketm1.xlarge \
|
||||
--property hw:cpu_sockets=2 \
|
||||
--property hw:cpu_cores=2 \
|
||||
--property hw:cpu_threads=2 \
|
||||
--property hw:numa_nodes=2
|
||||
"""
|
||||
# Get number of sockets
|
||||
cmd = 'lscpu -p|grep -v ^#|cut -f3 -d,|sort|uniq|wc -l'
|
||||
sockets = int(zaza.utilities.juju.remote_run(
|
||||
unit.name, cmd, model_name=self.model_name, fatal=True).rstrip())
|
||||
|
||||
# Get total memory
|
||||
cmd = 'cat /proc/meminfo |grep ^MemTotal'
|
||||
_, meminfo_value, _ = zaza.utilities.juju.remote_run(
|
||||
unit.name,
|
||||
cmd,
|
||||
model_name=self.model_name,
|
||||
fatal=True).rstrip().split()
|
||||
mbtotal = int(meminfo_value) * 1024 / 1000 / 1000
|
||||
mbtotalhugepages = nr_1g_hugepages * 1024
|
||||
|
||||
# headroom for operating system and daemons in instance
|
||||
mbsystemheadroom = 2048
|
||||
# memory to be consumed by the nested instance
|
||||
mbinstance = 1024
|
||||
|
||||
# the amount of hugepage memory OVS / DPDK EAL will allocate
|
||||
mbovshugepages = sockets * 1024
|
||||
# the amount of hugepage memory available for nested instance
|
||||
mbfreehugepages = mbtotalhugepages - mbovshugepages
|
||||
|
||||
assert (mbtotal - mbtotalhugepages >= mbsystemheadroom and
|
||||
mbfreehugepages >= mbinstance), (
|
||||
'Unit {} is not suitable for test, please adjust instance '
|
||||
'type CPU topology or provide suitable physical machine. '
|
||||
'CPU Sockets: {} '
|
||||
'Available memory: {} MB '
|
||||
'Details:\n{}'
|
||||
.format(unit.name,
|
||||
sockets,
|
||||
mbtotal,
|
||||
self.assert_unit_cpu_topology.__doc__))
|
||||
|
||||
def enable_hugepages_vfio_on_hvs_in_vms(self, nr_1g_hugepages):
|
||||
"""Enable hugepages and unsafe VFIO NOIOMMU on virtual hypervisors."""
|
||||
for unit in model.get_units(
|
||||
zaza.utilities.machine_os.get_hv_application(),
|
||||
model_name=self.model_name):
|
||||
if not zaza.utilities.machine_os.is_vm(unit.name,
|
||||
model_name=self.model_name):
|
||||
logging.info('Unit {} is a physical machine, assuming '
|
||||
'hugepages and IOMMU configuration already '
|
||||
'performed through kernel command line.')
|
||||
continue
|
||||
logging.info('Checking CPU topology on {}'.format(unit.name))
|
||||
self.assert_unit_cpu_topology(unit, nr_1g_hugepages)
|
||||
logging.info('Enabling hugepages on {}'.format(unit.name))
|
||||
zaza.utilities.machine_os.enable_hugepages(
|
||||
unit, nr_1g_hugepages, model_name=self.model_name)
|
||||
logging.info('Enabling unsafe VFIO NOIOMMU mode on {}'
|
||||
.format(unit.name))
|
||||
zaza.utilities.machine_os.enable_vfio_unsafe_noiommu_mode(
|
||||
unit, model_name=self.model_name)
|
||||
|
||||
def disable_hugepages_vfio_on_hvs_in_vms(self):
|
||||
"""Disable hugepages and unsafe VFIO NOIOMMU on virtual hypervisors."""
|
||||
for unit in model.get_units(
|
||||
zaza.utilities.machine_os.get_hv_application(),
|
||||
model_name=self.model_name):
|
||||
if not zaza.utilities.machine_os.is_vm(unit.name,
|
||||
model_name=self.model_name):
|
||||
logging.info('Unit {} is a physical machine, assuming '
|
||||
'hugepages and IOMMU configuration already '
|
||||
'performed through kernel command line.')
|
||||
continue
|
||||
logging.info('Disabling hugepages on {}'.format(unit.name))
|
||||
zaza.utilities.machine_os.disable_hugepages(
|
||||
unit, model_name=self.model_name)
|
||||
logging.info('Disabling unsafe VFIO NOIOMMU mode on {}'
|
||||
.format(unit.name))
|
||||
zaza.utilities.machine_os.disable_vfio_unsafe_noiommu_mode(
|
||||
unit, model_name=self.model_name)
|
||||
|
||||
|
||||
class OpenStackBaseTest(BaseCharmTest):
|
||||
"""Generic helpers for testing OpenStack API charms."""
|
||||
@@ -634,7 +751,8 @@ class OpenStackBaseTest(BaseCharmTest):
|
||||
pass
|
||||
|
||||
def launch_guest(self, guest_name, userdata=None, use_boot_volume=False,
|
||||
instance_key=None, flavor_name=None):
|
||||
instance_key=None, flavor_name=None,
|
||||
attach_to_external_network=False):
|
||||
"""Launch one guest to use in tests.
|
||||
|
||||
Note that it is up to the caller to have set the RESOURCE_PREFIX class
|
||||
@@ -651,6 +769,9 @@ class OpenStackBaseTest(BaseCharmTest):
|
||||
:type use_boot_volume: boolean
|
||||
:param instance_key: Key to collect associated config data with.
|
||||
:type instance_key: Optional[str]
|
||||
:param attach_to_external_network: Attach instance directly to external
|
||||
network.
|
||||
:type attach_to_external_network: bool
|
||||
:returns: Nova instance objects
|
||||
:rtype: Server
|
||||
"""
|
||||
@@ -679,9 +800,11 @@ class OpenStackBaseTest(BaseCharmTest):
|
||||
vm_name=instance_name,
|
||||
use_boot_volume=use_boot_volume,
|
||||
userdata=userdata,
|
||||
flavor_name=flavor_name)
|
||||
flavor_name=flavor_name,
|
||||
attach_to_external_network=attach_to_external_network)
|
||||
|
||||
def launch_guests(self, userdata=None):
|
||||
def launch_guests(self, userdata=None, attach_to_external_network=False,
|
||||
flavor_name=None):
|
||||
"""Launch two guests to use in tests.
|
||||
|
||||
Note that it is up to the caller to have set the RESOURCE_PREFIX class
|
||||
@@ -689,6 +812,9 @@ class OpenStackBaseTest(BaseCharmTest):
|
||||
|
||||
:param userdata: Userdata to attach to instance
|
||||
:type userdata: Optional[str]
|
||||
:param attach_to_external_network: Attach instance directly to external
|
||||
network.
|
||||
:type attach_to_external_network: bool
|
||||
:returns: List of launched Nova instance objects
|
||||
:rtype: List[Server]
|
||||
"""
|
||||
@@ -697,7 +823,9 @@ class OpenStackBaseTest(BaseCharmTest):
|
||||
launched_instances.append(
|
||||
self.launch_guest(
|
||||
guest_name='ins-{}'.format(guest_number),
|
||||
userdata=userdata))
|
||||
userdata=userdata,
|
||||
attach_to_external_network=attach_to_external_network,
|
||||
flavor_name=flavor_name))
|
||||
return launched_instances
|
||||
|
||||
def retrieve_guest(self, guest_name):
|
||||
|
||||
@@ -53,7 +53,7 @@ boot_tests = {
|
||||
def launch_instance(instance_key, use_boot_volume=False, vm_name=None,
|
||||
private_network_name=None, image_name=None,
|
||||
flavor_name=None, external_network_name=None, meta=None,
|
||||
userdata=None):
|
||||
userdata=None, attach_to_external_network=False):
|
||||
"""Launch an instance.
|
||||
|
||||
:param instance_key: Key to collect associated config data with.
|
||||
@@ -76,6 +76,9 @@ def launch_instance(instance_key, use_boot_volume=False, vm_name=None,
|
||||
:type meta: dict
|
||||
:param userdata: Configuration to use upon launch, used by cloud-init.
|
||||
:type userdata: str
|
||||
:param attach_to_external_network: Attach instance directly to external
|
||||
network.
|
||||
:type attach_to_external_network: bool
|
||||
:returns: the created instance
|
||||
:rtype: novaclient.Server
|
||||
"""
|
||||
@@ -94,12 +97,18 @@ def launch_instance(instance_key, use_boot_volume=False, vm_name=None,
|
||||
flavor = nova_client.flavors.find(name=flavor_name)
|
||||
|
||||
private_network_name = private_network_name or "private"
|
||||
net = neutron_client.find_resource("network", private_network_name)
|
||||
nics = [{'net-id': net.get('id')}]
|
||||
|
||||
meta = meta or {}
|
||||
external_network_name = external_network_name or "ext_net"
|
||||
|
||||
if attach_to_external_network:
|
||||
instance_network_name = external_network_name
|
||||
else:
|
||||
instance_network_name = private_network_name
|
||||
|
||||
net = neutron_client.find_resource("network", instance_network_name)
|
||||
nics = [{'net-id': net.get('id')}]
|
||||
|
||||
if use_boot_volume:
|
||||
bdmv2 = [{
|
||||
'boot_index': '0',
|
||||
@@ -143,12 +152,19 @@ def launch_instance(instance_key, use_boot_volume=False, vm_name=None,
|
||||
port = openstack_utils.get_ports_from_device_id(
|
||||
neutron_client,
|
||||
instance.id)[0]
|
||||
logging.info('Assigning floating ip.')
|
||||
ip = openstack_utils.create_floating_ip(
|
||||
neutron_client,
|
||||
external_network_name,
|
||||
port=port)['floating_ip_address']
|
||||
logging.info('Assigned floating IP {} to {}'.format(ip, vm_name))
|
||||
if attach_to_external_network:
|
||||
logging.info('attach_to_external_network={}, not assigning floating IP'
|
||||
.format(attach_to_external_network))
|
||||
ip = port['fixed_ips'][0]['ip_address']
|
||||
logging.info('Using fixed IP {} on network {} for {}'
|
||||
.format(ip, instance_network_name, vm_name))
|
||||
else:
|
||||
logging.info('Assigning floating ip.')
|
||||
ip = openstack_utils.create_floating_ip(
|
||||
neutron_client,
|
||||
external_network_name,
|
||||
port=port)['floating_ip_address']
|
||||
logging.info('Assigned floating IP {} to {}'.format(ip, vm_name))
|
||||
try:
|
||||
for attempt in Retrying(
|
||||
stop=stop_after_attempt(8),
|
||||
|
||||
@@ -87,6 +87,7 @@ from zaza.openstack.utilities import (
|
||||
generic as generic_utils,
|
||||
openstack as openstack_utils,
|
||||
)
|
||||
import zaza.openstack.utilities.exceptions
|
||||
|
||||
import zaza.utilities.juju as juju_utils
|
||||
|
||||
@@ -139,7 +140,8 @@ def setup_sdn(network_config, keystone_session=None):
|
||||
network_config["default_gateway"],
|
||||
network_config["external_net_cidr"],
|
||||
network_config["start_floating_ip"],
|
||||
network_config["end_floating_ip"])
|
||||
network_config["end_floating_ip"],
|
||||
dhcp=True)
|
||||
provider_router = (
|
||||
openstack_utils.create_provider_router(neutron_client, project_id))
|
||||
openstack_utils.plug_extnet_into_router(
|
||||
@@ -164,14 +166,16 @@ def setup_sdn(network_config, keystone_session=None):
|
||||
neutron_client,
|
||||
project_id,
|
||||
shared=False,
|
||||
network_type=network_config["network_type"])
|
||||
network_type=network_config["network_type"],
|
||||
net_name=network_config["project_net_name"])
|
||||
project_subnet = openstack_utils.create_project_subnet(
|
||||
neutron_client,
|
||||
project_id,
|
||||
project_network,
|
||||
network_config.get("private_net_cidr"),
|
||||
subnetpool=subnetpool,
|
||||
ip_version=ip_version)
|
||||
ip_version=ip_version,
|
||||
subnet_name=network_config["project_subnet_name"])
|
||||
openstack_utils.update_subnet_dns(
|
||||
neutron_client,
|
||||
project_subnet,
|
||||
@@ -273,14 +277,19 @@ def setup_gateway_ext_port(network_config, keystone_session=None,
|
||||
else:
|
||||
net_id = None
|
||||
|
||||
# If we're using netplan, we need to add the new interface to the guest
|
||||
current_release = openstack_utils.get_os_release()
|
||||
bionic_queens = openstack_utils.get_os_release('bionic_queens')
|
||||
if current_release >= bionic_queens:
|
||||
logging.warn("Adding second interface for dataport to guest netplan "
|
||||
"for bionic-queens and later")
|
||||
add_dataport_to_netplan = True
|
||||
else:
|
||||
try:
|
||||
# If we're using netplan, we need to add the new interface to the guest
|
||||
current_release = openstack_utils.get_os_release()
|
||||
bionic_queens = openstack_utils.get_os_release('bionic_queens')
|
||||
if current_release >= bionic_queens:
|
||||
logging.warn("Adding second interface for dataport to guest "
|
||||
"netplan for bionic-queens and later")
|
||||
add_dataport_to_netplan = True
|
||||
else:
|
||||
add_dataport_to_netplan = False
|
||||
except zaza.openstack.utilities.exceptions.ApplicationNotFound:
|
||||
# The setup_gateway_ext_port helper may be used with non-OpenStack
|
||||
# workloads.
|
||||
add_dataport_to_netplan = False
|
||||
|
||||
logging.info("Configuring network for OpenStack undercloud/provider")
|
||||
|
||||
@@ -650,7 +650,10 @@ def dvr_enabled():
|
||||
:returns: True when DVR is enabled, False otherwise
|
||||
:rtype: bool
|
||||
"""
|
||||
return get_application_config_option('neutron-api', 'enable-dvr')
|
||||
try:
|
||||
return get_application_config_option('neutron-api', 'enable-dvr')
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
|
||||
def ngw_present():
|
||||
@@ -1328,6 +1331,24 @@ def update_subnet_dns(neutron_client, subnet, dns_servers):
|
||||
neutron_client.update_subnet(subnet['id'], msg)
|
||||
|
||||
|
||||
def update_subnet_dhcp(neutron_client, subnet, enable_dhcp):
|
||||
"""Update subnet DHCP status.
|
||||
|
||||
:param neutron_client: Authenticated neutronclient
|
||||
:type neutron_client: neutronclient.Client object
|
||||
:param subnet: Subnet object
|
||||
:type subnet: dict
|
||||
:param enable_dhcp: Whether DHCP should be enabled or not
|
||||
:type enable_dhcp: bool
|
||||
"""
|
||||
msg = {
|
||||
'subnet': {
|
||||
'enable_dhcp': enable_dhcp,
|
||||
}
|
||||
}
|
||||
neutron_client.update_subnet(subnet['id'], msg)
|
||||
|
||||
|
||||
def create_provider_router(neutron_client, project_id):
|
||||
"""Create the provider router.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user