Migrate ovn router availability zone tests
Changes from original tests: - Methods and variables set according to OSP deployer tool in base class. - Adjust migrated imports and configuration. - Added OVN backend check instead of configuration check. - Replaced test UUIDs with unique ones. - Fixed typos throughout file. Change-Id: I8656d56d579b73dd39c3bd37c46516ada5f78a6c
This commit is contained in:
parent
0348de6574
commit
e33d47c604
@ -25,7 +25,7 @@ WhiteboxNeutronPluginOptions = [
|
|||||||
cfg.StrOpt('openstack_type',
|
cfg.StrOpt('openstack_type',
|
||||||
default='devstack',
|
default='devstack',
|
||||||
help='Type of openstack deployment, '
|
help='Type of openstack deployment, '
|
||||||
'e.g. devstack, tripeo, podified'),
|
'e.g. devstack, podified'),
|
||||||
cfg.StrOpt('pki_private_key',
|
cfg.StrOpt('pki_private_key',
|
||||||
default='/etc/pki/tls/private/ovn_controller.key',
|
default='/etc/pki/tls/private/ovn_controller.key',
|
||||||
help='File with private key. Need for TLS-everywhere '
|
help='File with private key. Need for TLS-everywhere '
|
||||||
@ -86,5 +86,9 @@ WhiteboxNeutronPluginOptions = [
|
|||||||
help='ssh private key file path for overcloud nodes access.'),
|
help='ssh private key file path for overcloud nodes access.'),
|
||||||
cfg.StrOpt('neutron_config',
|
cfg.StrOpt('neutron_config',
|
||||||
default='/etc/neutron/neutron.conf',
|
default='/etc/neutron/neutron.conf',
|
||||||
help='Path to neutron configuration file.')
|
help='Path to neutron configuration file.'),
|
||||||
|
cfg.IntOpt('ovn_max_controller_gw_ports_per_router',
|
||||||
|
default=1,
|
||||||
|
help='The number of network nodes used '
|
||||||
|
'for the OVN router HA.')
|
||||||
]
|
]
|
||||||
|
@ -59,8 +59,19 @@ class BaseTempestWhiteboxTestCase(base.BaseTempestTestCase):
|
|||||||
sriov_agents = [
|
sriov_agents = [
|
||||||
agent for agent in agents if 'sriov' in agent['binary']]
|
agent for agent in agents if 'sriov' in agent['binary']]
|
||||||
cls.has_sriov_support = True if sriov_agents else False
|
cls.has_sriov_support = True if sriov_agents else False
|
||||||
|
# deployer tool dependent variables
|
||||||
if WB_CONF.openstack_type == 'devstack':
|
if WB_CONF.openstack_type == 'devstack':
|
||||||
cls.master_node_client = cls.get_node_client('localhost')
|
cls.master_node_client = cls.get_node_client('localhost')
|
||||||
|
cls.master_cont_cmd_executor = cls.run_on_master_controller
|
||||||
|
cls.neutron_api_prefix = ''
|
||||||
|
cls.neutron_conf = WB_CONF.neutron_config
|
||||||
|
elif WB_CONF.openstack_type == 'podified':
|
||||||
|
# NOTE(mblue): add podified option
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
LOG.warning(("Unrecognized deployer tool '{}', plugin supports "
|
||||||
|
"openstack_type as devstack/podified."
|
||||||
|
.format(WB_CONF.openstack_type)))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def run_on_master_controller(cls, cmd):
|
def run_on_master_controller(cls, cmd):
|
||||||
|
@ -26,7 +26,7 @@ CONF = config.CONF
|
|||||||
WB_CONF = config.CONF.whitebox_neutron_plugin_options
|
WB_CONF = config.CONF.whitebox_neutron_plugin_options
|
||||||
|
|
||||||
|
|
||||||
class NeutronAvaliabilityzonesTest(base.BaseTempestWhiteboxTestCase):
|
class OvsAvaliabilityzonesTest(base.BaseTempestWhiteboxTestCase):
|
||||||
|
|
||||||
credentials = ['primary', 'admin']
|
credentials = ['primary', 'admin']
|
||||||
required_extensions = ['network_availability_zone',
|
required_extensions = ['network_availability_zone',
|
||||||
@ -34,7 +34,7 @@ class NeutronAvaliabilityzonesTest(base.BaseTempestWhiteboxTestCase):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def resource_setup(cls):
|
def resource_setup(cls):
|
||||||
super(NeutronAvaliabilityzonesTest, cls).resource_setup()
|
super(OvsAvaliabilityzonesTest, cls).resource_setup()
|
||||||
cls.client = cls.os_adm.network_client
|
cls.client = cls.os_adm.network_client
|
||||||
cls.get_neutron_agent_availability_zones()
|
cls.get_neutron_agent_availability_zones()
|
||||||
if not cls.AZs_list:
|
if not cls.AZs_list:
|
||||||
@ -64,10 +64,8 @@ class NeutronAvaliabilityzonesTest(base.BaseTempestWhiteboxTestCase):
|
|||||||
cls.l3_agent_list[agent.get('host')] = az
|
cls.l3_agent_list[agent.get('host')] = az
|
||||||
else:
|
else:
|
||||||
cls.dhcp_agent_list[agent.get('host')] = az
|
cls.dhcp_agent_list[agent.get('host')] = az
|
||||||
cmd = 'sudo podman exec neutron_api crudini --get {} DEFAULT {{}}' \
|
cmd = '{} crudini --get {} DEFAULT {{}} || echo'.format(
|
||||||
'|| echo'.format(WB_CONF.neutron_config)
|
cls.neutron_api_prefix, cls.neutron_conf)
|
||||||
if WB_CONF.openstack_type == 'devstack':
|
|
||||||
cmd_executor = cls.run_on_master_controller
|
|
||||||
|
|
||||||
def integer(int_var):
|
def integer(int_var):
|
||||||
try:
|
try:
|
||||||
@ -90,7 +88,7 @@ class NeutronAvaliabilityzonesTest(base.BaseTempestWhiteboxTestCase):
|
|||||||
'default_availability_zones': array}
|
'default_availability_zones': array}
|
||||||
for param, func in agent_params.items():
|
for param, func in agent_params.items():
|
||||||
cls.agent_conf[param] = func(
|
cls.agent_conf[param] = func(
|
||||||
cmd_executor(cmd.format(param)))
|
cls.master_cont_cmd_executor(cmd.format(param)))
|
||||||
|
|
||||||
def _check_az_router(self, az, router):
|
def _check_az_router(self, az, router):
|
||||||
"""Check if a router was deployed over the agents
|
"""Check if a router was deployed over the agents
|
||||||
@ -152,8 +150,7 @@ class NeutronAvaliabilityzonesTest(base.BaseTempestWhiteboxTestCase):
|
|||||||
az_hosts.append(agent_host)
|
az_hosts.append(agent_host)
|
||||||
|
|
||||||
def test_resource_namespace():
|
def test_resource_namespace():
|
||||||
node_name = host.split('.')[0] + (
|
node_name = host.split('.')[0]
|
||||||
'.ctlplane' if WB_CONF.openstack_type == 'tripleo' else '')
|
|
||||||
netns = self.get_node_client(node_name).exec_command('ip netns')
|
netns = self.get_node_client(node_name).exec_command('ip netns')
|
||||||
netspaces = []
|
netspaces = []
|
||||||
for ns in netns.split('\n'):
|
for ns in netns.split('\n'):
|
||||||
|
@ -0,0 +1,254 @@
|
|||||||
|
# Copyright 2024 Red Hat, Inc.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from neutron_lib import constants
|
||||||
|
from neutron_tempest_plugin import config
|
||||||
|
from oslo_log import log
|
||||||
|
from tempest.lib import decorators
|
||||||
|
from tempest.lib import exceptions
|
||||||
|
|
||||||
|
from whitebox_neutron_tempest_plugin.tests.scenario import base
|
||||||
|
|
||||||
|
AZ_SUPPORTED_AGENTS = ['OVN Controller Gateway agent']
|
||||||
|
CONF = config.CONF
|
||||||
|
WB_CONF = CONF.whitebox_neutron_plugin_options
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class OvnAvaliabilityzonesTest(
|
||||||
|
base.TrafficFlowTest, base.BaseTempestTestCaseOvn):
|
||||||
|
credentials = ['primary', 'admin']
|
||||||
|
required_extensions = ['network_availability_zone',
|
||||||
|
'availability_zone', 'router_availability_zone']
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def resource_setup(cls):
|
||||||
|
super(OvnAvaliabilityzonesTest, cls).resource_setup()
|
||||||
|
if not cls.has_ovn_support:
|
||||||
|
raise cls.skipException(
|
||||||
|
"These availability zone tests are meant for OVN only.")
|
||||||
|
cls.client = cls.os_adm.network_client
|
||||||
|
cls.get_ovn_controller_gw_agent_availability_zones()
|
||||||
|
if not cls.AZs_list:
|
||||||
|
raise cls.skipException("No availability zones configured")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_ovn_controller_gw_agent_availability_zones(cls):
|
||||||
|
"""Obtain availability_zones for OVN Controller Gateway
|
||||||
|
Only OVN Controller Gateway supports router availability_zone
|
||||||
|
"""
|
||||||
|
body = cls.client.list_agents()
|
||||||
|
agents = body['agents']
|
||||||
|
cls.AZs_list = []
|
||||||
|
cls.ovn_controller_gw_agent_list = {}
|
||||||
|
cls.agent_conf = {}
|
||||||
|
for agent in agents:
|
||||||
|
if agent.get('agent_type') in AZ_SUPPORTED_AGENTS:
|
||||||
|
az = agent.get('availability_zone')
|
||||||
|
if (az and (az not in cls.AZs_list)):
|
||||||
|
cls.AZs_list.append(az)
|
||||||
|
cls.ovn_controller_gw_agent_list[
|
||||||
|
agent.get('host').replace('"', '').split('.')[0]] = az
|
||||||
|
cmd = '{} crudini --get {} DEFAULT {{}} || echo'.format(
|
||||||
|
cls.neutron_api_prefix, cls.neutron_conf)
|
||||||
|
|
||||||
|
def array(list_var):
|
||||||
|
return_list = list_var.strip().split(',')
|
||||||
|
if return_list == ['']:
|
||||||
|
LOG.debug('Empty string can not be splitted')
|
||||||
|
return []
|
||||||
|
return return_list
|
||||||
|
|
||||||
|
agent_params = {'default_availability_zones': array}
|
||||||
|
for param, func in agent_params.items():
|
||||||
|
cls.agent_conf[param] = func(
|
||||||
|
cls.master_cont_cmd_executor(cmd.format(param)))
|
||||||
|
# TODO(ccamposr): Hard-coded value, should be extracted from the env
|
||||||
|
# it could be extracted from ovn database
|
||||||
|
cls.max_controller_gw_ports_per_router = \
|
||||||
|
WB_CONF.ovn_max_controller_gw_ports_per_router
|
||||||
|
|
||||||
|
def _check_az_router(self, az, router):
|
||||||
|
"""Check that resource is correctly spawned in required AZs
|
||||||
|
|
||||||
|
Function checks that router is assigned to the relevant
|
||||||
|
network nodes.
|
||||||
|
There are several possible scenarios:
|
||||||
|
Scenario 1 (not enough network nodes in the AZs configured):
|
||||||
|
Resource is expected to be deployed over several (lets say 3)
|
||||||
|
network nodes, but there are less or equal amount of them available
|
||||||
|
in the provided list of availability zones. In this case the resource
|
||||||
|
should be spawned over all the network nodes,
|
||||||
|
Scenario 2 (not enough AZs):
|
||||||
|
Resource is expected to be deployed over several (lets say 3) network
|
||||||
|
nodes, but there are less or equal amount of availability zones are
|
||||||
|
provided as a hint. In this case there should be at least one network
|
||||||
|
node from each availability zone to host the resource and the all other
|
||||||
|
network nodes will be chosen randomly
|
||||||
|
Scenario 3 (too many AZs):
|
||||||
|
Resource is expected to be deployed over several (lets say 3)
|
||||||
|
network nodes, but more (lets say 4) availability zones are provided
|
||||||
|
as a hint. In this case the resource should be spawned over 3 random
|
||||||
|
availability zones (one network node per zone).
|
||||||
|
"""
|
||||||
|
resource_list = self.ovn_controller_gw_agent_list
|
||||||
|
router_port = self.os_admin.network_client.list_ports(
|
||||||
|
device_owner=constants.DEVICE_OWNER_ROUTER_GW,
|
||||||
|
device_id=router['id'])['ports'][0]
|
||||||
|
chassis_list = self.get_router_gateway_chassis_list(
|
||||||
|
router_port['id'])
|
||||||
|
resource_hosts = list(self.get_router_gateway_chassis_by_id(iden)
|
||||||
|
for iden in chassis_list)
|
||||||
|
az_hosts = []
|
||||||
|
for agent_host, agent_zone in resource_list.items():
|
||||||
|
if agent_zone in az:
|
||||||
|
az_hosts.append(agent_host)
|
||||||
|
|
||||||
|
# Scenario 1 from docstring
|
||||||
|
if self.max_controller_gw_ports_per_router >= len(az_hosts):
|
||||||
|
# Check that the router is deployed over all the az_hosts
|
||||||
|
for host in az_hosts:
|
||||||
|
self.assertIn(host, resource_hosts)
|
||||||
|
# Check that router isn't deployed over other az hosts
|
||||||
|
self.assertEqual(len(az_hosts), len(resource_hosts))
|
||||||
|
# Scenario 2 from docstring
|
||||||
|
elif self.max_controller_gw_ports_per_router >= len(az):
|
||||||
|
tmp_zones = []
|
||||||
|
# Check that the router is only deployed over az hosts
|
||||||
|
for host in resource_hosts:
|
||||||
|
self.assertIn(host, az_hosts)
|
||||||
|
tmp_zones.append(resource_list[host])
|
||||||
|
# Check that it is deployed over all the azs
|
||||||
|
self.assertEqual(set(az).symmetric_difference(set(tmp_zones)),
|
||||||
|
set())
|
||||||
|
# Check that the max redundancy is reached
|
||||||
|
self.assertEqual(len(resource_hosts),
|
||||||
|
self.max_controller_gw_ports_per_router)
|
||||||
|
# Scenario 3 from docstring
|
||||||
|
else:
|
||||||
|
tmp_zones = []
|
||||||
|
# Check that the router is only deployed over az hosts
|
||||||
|
for host in resource_hosts:
|
||||||
|
self.assertIn(host, az_hosts)
|
||||||
|
tmp_zones.append(resource_list[host])
|
||||||
|
# Check that it is deployed only th one instance per az
|
||||||
|
self.assertEqual(len(tmp_zones), len(set(tmp_zones)))
|
||||||
|
# Check that the max redundancy is reached
|
||||||
|
self.assertEqual(len(resource_hosts),
|
||||||
|
self.max_controller_gw_ports_per_router)
|
||||||
|
|
||||||
|
@decorators.idempotent_id('bc0439e3-f503-4ebc-9194-0babe155a406')
|
||||||
|
def test_router_created_in_single_az_with_not_enough_agents(self):
|
||||||
|
"""Verify that router is deployed over one AZ with NOT enough network
|
||||||
|
nodes
|
||||||
|
|
||||||
|
Router should be deployed over all the network nodes in
|
||||||
|
the availability zone
|
||||||
|
"""
|
||||||
|
|
||||||
|
az_host_counter = {}
|
||||||
|
for tmp_az in self.ovn_controller_gw_agent_list.values():
|
||||||
|
tmp_counter = az_host_counter.get(tmp_az, 0) + 1
|
||||||
|
az_host_counter[tmp_az] = tmp_counter
|
||||||
|
for tmp_az, counter in az_host_counter.items():
|
||||||
|
if counter < self.max_controller_gw_ports_per_router:
|
||||||
|
az = tmp_az
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise self.skipException('No availability zone with not enough '
|
||||||
|
'resources available')
|
||||||
|
router = self.create_router_by_client(availability_zone_hints=[az])
|
||||||
|
self._check_az_router([az], router)
|
||||||
|
|
||||||
|
@decorators.idempotent_id('584c2d71-282f-4ea1-8360-77bde6cdcbf1')
|
||||||
|
def test_router_created_in_single_az_with_enough_agents(self):
|
||||||
|
"""Verify that router is deployed over one AZ with enough
|
||||||
|
network nodes
|
||||||
|
|
||||||
|
Router should be deployed over the required amount of the
|
||||||
|
network nodes in the specified availability zone
|
||||||
|
"""
|
||||||
|
|
||||||
|
az_host_counter = {}
|
||||||
|
for tmp_az in self.ovn_controller_gw_agent_list.values():
|
||||||
|
tmp_counter = az_host_counter.get(tmp_az, 0) + 1
|
||||||
|
az_host_counter[tmp_az] = tmp_counter
|
||||||
|
for tmp_az, counter in az_host_counter.items():
|
||||||
|
if counter >= self.max_controller_gw_ports_per_router:
|
||||||
|
az = tmp_az
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise self.skipException('No availability zone with enough '
|
||||||
|
'resources available')
|
||||||
|
router = self.create_router_by_client(availability_zone_hints=[az])
|
||||||
|
self._check_az_router([az], router)
|
||||||
|
|
||||||
|
@decorators.idempotent_id('b3c42bd9-6888-4397-88f1-c624048da138')
|
||||||
|
def test_router_created_in_all_azs(self):
|
||||||
|
"""Verify that router is created in all available AZs
|
||||||
|
|
||||||
|
If the amount of availability zones is more than the
|
||||||
|
OVN gw max number the test will be skipped as the router
|
||||||
|
will not be created in all the available AZs
|
||||||
|
"""
|
||||||
|
|
||||||
|
if len(self.AZs_list) == 1:
|
||||||
|
raise self.skipException("Only one availability zone configured "
|
||||||
|
"but multiple zones required")
|
||||||
|
router = self.create_router_by_client(
|
||||||
|
availability_zone_hints=self.AZs_list)
|
||||||
|
self._check_az_router(self.AZs_list, router)
|
||||||
|
|
||||||
|
@decorators.idempotent_id('e9a9ae58-19aa-41e3-bfb6-5c6881db5a17')
|
||||||
|
def test_router_created_in_several_azs(self):
|
||||||
|
"""Verify that router is created in several but not all the AZs
|
||||||
|
|
||||||
|
Test will be executed in the environments with more than 1 OVN gw
|
||||||
|
per router and with more than 2 availability zones
|
||||||
|
"""
|
||||||
|
|
||||||
|
if len(self.AZs_list) <= 2:
|
||||||
|
raise self.skipException("More than 2 AZs required")
|
||||||
|
if self.max_controller_gw_ports_per_router <= 1:
|
||||||
|
raise self.skipException("More than 1 OVN gw required")
|
||||||
|
amount = min(self.max_controller_gw_ports_per_router,
|
||||||
|
len(self.AZs_list) - 1)
|
||||||
|
router = self.create_router_by_client(
|
||||||
|
availability_zone_hints=self.AZs_list[:amount])
|
||||||
|
self._check_az_router(self.AZs_list[:amount], router)
|
||||||
|
|
||||||
|
@decorators.idempotent_id('b2ea83d8-7b2c-4c58-9c3f-d87cc04b4f1b')
|
||||||
|
def test_router_created_in_default_azs(self):
|
||||||
|
"""Verify that router is correctly spawned in default AZs
|
||||||
|
|
||||||
|
If there is no parameter specified for default AZs, all the AZs
|
||||||
|
are recognized as default.
|
||||||
|
"""
|
||||||
|
|
||||||
|
router = self.create_router_by_client()
|
||||||
|
azs = self.agent_conf['default_availability_zones'] or self.AZs_list
|
||||||
|
self._check_az_router(azs, router)
|
||||||
|
|
||||||
|
@decorators.idempotent_id('64bfe42a-3420-4922-8c06-1b87a3d10da2')
|
||||||
|
def test_router_creation_failed_for_not_existing_az(self):
|
||||||
|
"""Verify that router creation failed if AZ doesn't exist"""
|
||||||
|
|
||||||
|
tmp_az_list = []
|
||||||
|
for az in self.AZs_list:
|
||||||
|
tmp_az_list.append(az)
|
||||||
|
tmp_az_list.append('not_existing_az')
|
||||||
|
self.assertRaises(exceptions.NotFound,
|
||||||
|
self.create_router_by_client,
|
||||||
|
availability_zone_hints=tmp_az_list)
|
Loading…
x
Reference in New Issue
Block a user