Skip to content

Commit f38448f

Browse files
committed
neutron: Add connectivity test with DPDK
The test will prepare hypervisor units in virtual machines by enabling hugepages configuration and rebooting them and subsequent required runtime changes. If the hypervisor units are physical machines the test assumes they already have received the appropriate configuration from the bare metal provisioning system. Launch nested instance using flavor that enables hugepages and attach it directly to the external network and perform connectivity tests. After a successful test the hypervisor units are brought back to their original state to not influence subsequent tests.
1 parent ba0dc02 commit f38448f

File tree

3 files changed

+183
-3
lines changed

3 files changed

+183
-3
lines changed

zaza/openstack/charm_tests/neutron/tests.py

+170-1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import zaza.openstack.charm_tests.test_utils as test_utils
3434
import zaza.openstack.configure.guest as guest
3535
import zaza.openstack.utilities.openstack as openstack_utils
36+
import zaza.utilities.machine_os
3637

3738

3839
class NeutronPluginApiSharedTests(test_utils.OpenStackBaseTest):
@@ -835,6 +836,10 @@ def setUpClass(cls):
835836
# the external provider network
836837
cls.attach_to_external_network = False
837838

839+
# Override this if you want your test to launch instances with a
840+
# specific flavor
841+
cls.instance_flavor = None
842+
838843
@tenacity.retry(wait=tenacity.wait_exponential(multiplier=1, max=60),
839844
reraise=True, stop=tenacity.stop_after_attempt(8))
840845
def validate_instance_can_reach_other(self,
@@ -1112,13 +1117,177 @@ class name + run_tearDown key under the `tests_options` key in
11121117
instance_1, instance_2 = self.retrieve_guests()
11131118
if not all([instance_1, instance_2]):
11141119
self.launch_guests(
1115-
attach_to_external_network=self.attach_to_external_network)
1120+
attach_to_external_network=self.attach_to_external_network,
1121+
flavor_name=self.instance_flavor)
11161122
instance_1, instance_2 = self.retrieve_guests()
11171123
self.check_connectivity(instance_1, instance_2)
11181124
self.run_resource_cleanup = self.get_my_tests_options(
11191125
'run_resource_cleanup', True)
11201126

11211127

1128+
class DPDKNeutronNetworkingTest(NeutronNetworkingTest):
1129+
"""Ensure that openstack instances have valid networking with DPDK."""
1130+
1131+
@classmethod
1132+
def setUpClass(cls):
1133+
"""Run class setup for running Neutron API Networking tests."""
1134+
super(DPDKNeutronNetworkingTest, cls).setUpClass()
1135+
1136+
# At this point in time the charms do not support configuring overlay
1137+
# networks with DPDK. To perform end to end validation we need to
1138+
# attach instances directly to the provider network and subsequently
1139+
# DHCP needs to be enabled on that network.
1140+
#
1141+
# Note that for instances wired with DPDK the DHCP request/response is
1142+
# handled as private communication between the ovn-controller and the
1143+
# instance, and as such there is no risk of rogue DHCP replies escaping
1144+
# to the surrounding network.
1145+
cls.attach_to_external_network = True
1146+
cls.instance_flavor = 'hugepages'
1147+
cls.external_subnet = cls.neutron_client.find_resource(
1148+
'subnet',
1149+
neutron_setup.OVERCLOUD_NETWORK_CONFIG['external_subnet_name'])
1150+
if ('dhcp_enabled' not in cls.external_subnet or
1151+
not cls.external_subnet['dhcp_enabled']):
1152+
logging.info('Enabling DHCP on subnet {}'
1153+
.format(cls.external_subnet['name']))
1154+
openstack_utils.update_subnet_dhcp(
1155+
cls.neutron_client, cls.external_subnet, True)
1156+
1157+
cls.nr_1g_hugepages = 4
1158+
# Prepare hypervisor units for the test
1159+
for unit in zaza.model.get_units(
1160+
zaza.utilities.machine_os.get_hv_application(),
1161+
model_name=cls.model_name):
1162+
if not zaza.utilities.machine_os.is_vm(unit.name,
1163+
model_name=cls.model_name):
1164+
logging.info('Unit {} is a physical machine, assuming '
1165+
'hugepages and IOMMU configuration already '
1166+
'performed through kernel command line.')
1167+
continue
1168+
logging.info('Checking CPU topology on {}'.format(unit.name))
1169+
cls._assert_unit_cpu_topology(cls, unit, model_name=cls.model_name)
1170+
logging.info('Enabling hugepages on {}'.format(unit.name))
1171+
zaza.utilities.machine_os.enable_hugepages(
1172+
unit, cls.nr_1g_hugepages, model_name=cls.model_name)
1173+
logging.info('Enabling unsafe VFIO NOIOMMU mode on {}'
1174+
.format(unit.name))
1175+
zaza.utilities.machine_os.enable_vfio_unsafe_noiommu_mode(
1176+
unit, model_name=cls.model_name)
1177+
1178+
def _assert_unit_cpu_topology(self, unit, model_name=None):
1179+
r"""Assert unit under test CPU topology.
1180+
1181+
When using OpenStack as CI substrate:
1182+
1183+
By default, when instance NUMA placement is not specified,
1184+
a topology of N sockets, each with one core and one thread,
1185+
is used for an instance, where N corresponds to the number of
1186+
instance vCPUs requested.
1187+
1188+
In this context a socket is a physical socket on the motherboard
1189+
where a CPU is connected.
1190+
1191+
The DPDK Environment Abstraction Layer (EAL) allocates memory per
1192+
CPU socket, so we want the CPU topology inside the instance to
1193+
mimic something we would be likely to find in the real world and
1194+
at the same time not make the test too heavy.
1195+
1196+
The charm default is to have Open vSwitch allocate 1GB RAM per
1197+
CPU socket.
1198+
1199+
The following command would set the apropriate CPU topology for a
1200+
4 VCPU flavor:
1201+
1202+
openstack flavor set m1.large \
1203+
--property hw:cpu_sockets=2 \
1204+
--property hw:cpu_cores=2 \
1205+
--property hw:cpu_threads=2
1206+
"""
1207+
# Get number of sockets
1208+
cmd = 'lscpu -p|grep -v ^#|cut -f3 -d,|sort|uniq|wc -l'
1209+
sockets = int(zaza.utilities.juju.remote_run(
1210+
unit.name, cmd, model_name=model_name, fatal=True).rstrip())
1211+
1212+
# Get total memory
1213+
cmd = 'cat /proc/meminfo |grep ^MemTotal'
1214+
_, meminfo_value, _ = zaza.utilities.juju.remote_run(
1215+
unit.name,
1216+
cmd,
1217+
model_name=model_name,
1218+
fatal=True).rstrip().split()
1219+
mbtotal = int(meminfo_value) * 1024 / 1000 / 1000
1220+
mbtotalhugepages = self.nr_1g_hugepages * 1024
1221+
1222+
# headroom for operating system and daemons in instance
1223+
mbsystemheadroom = 2048
1224+
# memory to be consumed by the nested instance
1225+
mbinstance = 1024
1226+
1227+
# the amount of hugepage memory OVS / DPDK EAL will allocate
1228+
mbovshugepages = sockets * 1024
1229+
# the amount of hugepage memory available for nested instance
1230+
mbfreehugepages = mbtotalhugepages - mbovshugepages
1231+
1232+
assert (mbtotal - mbtotalhugepages >= mbsystemheadroom and
1233+
mbfreehugepages >= mbinstance), (
1234+
'Unit {} is not suitable for test, please adjust instance '
1235+
'type CPU topology or provide suitable physical machine. '
1236+
'CPU Sockets: {} '
1237+
'Available memory: {} MB '
1238+
'Details:\n{}'
1239+
.format(unit.name,
1240+
sockets,
1241+
mbtotal,
1242+
self._assert_unit_cpu_topology.__doc__))
1243+
1244+
def test_instances_have_networking(self):
1245+
"""Enable DPDK then Validate North/South and East/West networking."""
1246+
with self.config_change(
1247+
{
1248+
'enable-dpdk': False,
1249+
'dpdk-driver': '',
1250+
},
1251+
{
1252+
'enable-dpdk': True,
1253+
'dpdk-driver': 'vfio-pci',
1254+
},
1255+
application_name='ovn-chassis'):
1256+
super().test_instances_have_networking()
1257+
self.run_resource_cleanup = self.get_my_tests_options(
1258+
'run_resource_cleanup', True)
1259+
1260+
def resource_cleanup(self):
1261+
"""Extend to also revert VFIO NOIOMMU mode on units under test."""
1262+
super().resource_cleanup()
1263+
if not self.run_resource_cleanup:
1264+
return
1265+
1266+
if ('dhcp_enabled' not in self.external_subnet or
1267+
not self.external_subnet['dhcp_enabled']):
1268+
logging.info('Disabling DHCP on subnet {}'
1269+
.format(self.external_subnet['name']))
1270+
openstack_utils.update_subnet_dhcp(
1271+
self.neutron_client, self.external_subnet, False)
1272+
1273+
for unit in zaza.model.get_units(
1274+
zaza.utilities.machine_os.get_hv_application(),
1275+
model_name=self.model_name):
1276+
if not zaza.utilities.machine_os.is_vm(unit.name,
1277+
model_name=self.model_name):
1278+
logging.info('Unit {} is a physical machine, assuming '
1279+
'hugepages and IOMMU configuration already '
1280+
'performed through kernel command line.')
1281+
continue
1282+
logging.info('Disabling hugepages on {}'.format(unit.name))
1283+
zaza.utilities.machine_os.disable_hugepages(
1284+
unit, model_name=self.model_name)
1285+
logging.info('Disabling unsafe VFIO NOIOMMU mode on {}'
1286+
.format(unit.name))
1287+
zaza.utilities.machine_os.disable_vfio_unsafe_noiommu_mode(
1288+
unit, model_name=self.model_name)
1289+
1290+
11221291
class NeutronNetworkingVRRPTests(NeutronNetworkingBase):
11231292
"""Check networking when gateways are restarted."""
11241293

zaza/openstack/charm_tests/nova/utils.py

+9
Original file line numberDiff line numberDiff line change
@@ -65,5 +65,14 @@
6565
'hw:tpm_model': 'tpm-crb',
6666
},
6767
},
68+
'hugepages': {
69+
'flavorid': 'auto',
70+
'ram': 1024,
71+
'disk': 20,
72+
'vcpus': 1,
73+
'extra-specs': {
74+
'hw:mem_page_size': 'large',
75+
},
76+
},
6877
}
6978
KEYPAIR_NAME = 'zaza'

zaza/openstack/charm_tests/test_utils.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -686,7 +686,8 @@ def launch_guest(self, guest_name, userdata=None, use_boot_volume=False,
686686
flavor_name=flavor_name,
687687
attach_to_external_network=attach_to_external_network)
688688

689-
def launch_guests(self, userdata=None, attach_to_external_network=False):
689+
def launch_guests(self, userdata=None, attach_to_external_network=False,
690+
flavor_name=None):
690691
"""Launch two guests to use in tests.
691692
692693
Note that it is up to the caller to have set the RESOURCE_PREFIX class
@@ -706,7 +707,8 @@ def launch_guests(self, userdata=None, attach_to_external_network=False):
706707
self.launch_guest(
707708
guest_name='ins-{}'.format(guest_number),
708709
userdata=userdata,
709-
attach_to_external_network=attach_to_external_network))
710+
attach_to_external_network=attach_to_external_network,
711+
flavor_name=flavor_name))
710712
return launched_instances
711713

712714
def retrieve_guest(self, guest_name):

0 commit comments

Comments
 (0)