Merge "Migrate availability zone tests"

This commit is contained in:
Zuul 2024-03-10 13:13:58 +00:00 committed by Gerrit Code Review
commit 39ba5548ed
3 changed files with 423 additions and 2 deletions

View File

@ -83,5 +83,8 @@ WhiteboxNeutronPluginOptions = [
help='Common user to access openstack nodes via ssh.'),
cfg.StrOpt('overcloud_key_file',
default='/home/tempest/.ssh/id_rsa',
help='ssh private key file path for overcloud nodes access.')
help='ssh private key file path for overcloud nodes access.'),
cfg.StrOpt('neutron_config',
default='/etc/neutron/neutron.conf',
help='Path to neutron configuration file.')
]

View File

@ -66,7 +66,7 @@ class BaseTempestWhiteboxTestCase(base.BaseTempestTestCase):
output, errors = local_utils.run_local_cmd(cmd)
LOG.debug("Stderr: {}".format(errors.decode()))
output = output.decode()
LOG.debug("Output: {}".format(output))
LOG.debug("Output: {}".format(output))
return output.strip()
def get_host_for_server(self, server_id):

View File

@ -0,0 +1,418 @@
# 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.common import utils
from neutron_tempest_plugin import config
from tempest.lib import decorators
from tempest.lib import exceptions
from whitebox_neutron_tempest_plugin.tests.scenario import base
AZ_SUPPORTED_AGENTS = [constants.AGENT_TYPE_DHCP, constants.AGENT_TYPE_L3]
CONF = config.CONF
WB_CONF = config.CONF.whitebox_neutron_plugin_options
class NeutronAvaliabilityzonesTest(base.BaseTempestWhiteboxTestCase):
credentials = ['primary', 'admin']
required_extensions = ['network_availability_zone',
'availability_zone', 'router_availability_zone']
@classmethod
def resource_setup(cls):
super(NeutronAvaliabilityzonesTest, cls).resource_setup()
cls.client = cls.os_adm.network_client
cls.get_neutron_agent_availability_zones()
if not cls.AZs_list:
raise cls.skipException("No availability zones configured")
if cls.has_ovn_support:
raise cls.skipException(
"These availability zone tests are supported on OVS only.")
@classmethod
def get_neutron_agent_availability_zones(cls):
"""Obtain list agents availability_zones for an agent type
Only L3 and DHCP agent support availability_zone, default
availability_zone is "nova".
"""
body = cls.client.list_agents()
agents = body['agents']
cls.AZs_list = []
cls.l3_agent_list = {}
cls.dhcp_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)
if agent.get('agent_type') == constants.AGENT_TYPE_L3:
cls.l3_agent_list[agent.get('host')] = az
else:
cls.dhcp_agent_list[agent.get('host')] = az
cmd = 'sudo podman exec neutron_api crudini --get {} DEFAULT {{}}' \
'|| echo'.format(WB_CONF.neutron_config)
if WB_CONF.openstack_type == 'devstack':
cmd_executor = cls.run_on_master_controller
def integer(int_var):
try:
return int(int_var)
except ValueError:
return 0
def array(list_var):
try:
return_list = list_var.strip().split(',')
if return_list == ['']:
raise AttributeError('Empty string can not be splitted')
return return_list
except AttributeError:
return []
agent_params = {'max_l3_agents_per_router': integer,
'min_l3_agents_per_router': integer,
'dhcp_agents_per_network': integer,
'default_availability_zones': array}
for param, func in agent_params.items():
cls.agent_conf[param] = func(
cmd_executor(cmd.format(param)))
def _check_az_router(self, az, router):
"""Check if a router was deployed over the agents
in the configured Azs
"""
self._check_az_resource(az, router=router)
def _check_az_network(self, az, network):
"""Check if a network was deployed over the agents
in the configured Azs
"""
self._check_az_resource(az, network=network)
def _check_az_resource(self, az, network=None, router=None):
"""Check that resource is correctly spawned in required AZs
Function checks that router or network (resource) is assigned to the
relevant agents plus verifies that there are network namespaces
configured on the nodes.
There are several possible scenarios:
Scenario 1 (not enough agents):
Resource is expected to be deployed over several (lets say 3)
agents, 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 available agents
Scenario 2 (not enough AZs):
Resource is expected to be deployed over several (lets say 3) agents,
but there are less or equal amount of availability zones are provided
as a hint. In this case there should be at least one agent from each
availability zone to host the resource and the all other agents will
be chosen randomly
Scenario 3 (too many AZs):
Resource is expected to be deployed over several (lets say 3)
agents, 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 agent per zone).
"""
if not network and not router:
raise AttributeError('Network or router object required')
elif network and router:
raise AttributeError('Only one object should be provided')
elif network:
resource = network
resource_list = self.dhcp_agent_list
netspace_name = 'qdhcp-' + network['id']
conf_param = 'dhcp_agents_per_network'
list_func = self.client.list_dhcp_agent_hosting_network
else:
resource = router
resource_list = self.l3_agent_list
netspace_name = 'qrouter-' + router['id']
conf_param = 'max_l3_agents_per_router'
list_func = self.client.list_l3_agents_hosting_router
body = list_func(resource['id'])
resource_hosts = list(agent['host'] for agent in body['agents'])
az_hosts = []
for agent_host, agent_zone in resource_list.items():
if agent_zone in az:
az_hosts.append(agent_host)
def test_resource_namespace():
node_name = host.split('.')[0] + (
'.ctlplane' if WB_CONF.openstack_type == 'tripleo' else '')
netns = self.get_node_client(node_name).exec_command('ip netns')
netspaces = []
for ns in netns.split('\n'):
netspaces.append(ns.split(' ')[0])
if netspace_name in netspaces:
return True
else:
return False
# Scenario 1 from docstring
if self.agent_conf[conf_param] >= len(az_hosts):
for host in az_hosts:
self.assertIn(host, resource_hosts)
utils.wait_until_true(test_resource_namespace)
# Scenario 2 from docstring
elif self.agent_conf[conf_param] >= len(az):
for host in resource_hosts:
self.assertIn(host, az_hosts)
utils.wait_until_true(test_resource_namespace)
tmp_zones = []
for agent_details in body['agents']:
self.assertIn(agent_details['availability_zone'], az)
tmp_zones.append(agent_details['availability_zone'])
self.assertEqual(set(az).symmetric_difference(set(tmp_zones)),
set())
# Scenario 3 from docstring
else:
for host in resource_hosts:
self.assertIn(host, az_hosts)
utils.wait_until_true(test_resource_namespace)
tmp_zones = []
for agent_details in body['agents']:
self.assertIn(agent_details['availability_zone'], az)
tmp_zones.append(agent_details['availability_zone'])
self.assertTrue(set(az).issuperset(set(tmp_zones)))
self.assertEqual(len(tmp_zones), len(set(tmp_zones)))
@decorators.idempotent_id('7e677f27-097e-4331-a26c-47f02546d295')
def test_neutron_AZs_router_network_1az(self):
"""Check that a router and network configured with only one
AZ is deployed over nodes in this AZ
"""
for az in self.AZs_list:
network = self.create_network(availability_zone_hints=[az])
subnet = self.create_subnet(network)
router = self.create_router_by_client(availability_zone_hints=[az])
self.create_router_interface(router['id'], subnet['id'])
self._check_az_router([az], router)
self._check_az_network([az], network)
@decorators.idempotent_id('fe77b6df-5de0-4876-9a97-8e6b3e7617e6')
def test_network_created_in_single_az_with_not_enough_agents(self):
"""Verify that network is deployed over one AZ with NOT enough agents
Network should be deployed over all the agents in the availability zone
"""
az_host_counter = {}
for tmp_az in self.dhcp_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.agent_conf['dhcp_agents_per_network']:
az = tmp_az
break
else:
raise self.skipException('No availability zone with not enough '
'resources available')
network = self.create_network(availability_zone_hints=[az])
self.create_subnet(network)
self._check_az_network([az], network)
@decorators.idempotent_id('51bca87e-fc8d-41b0-9a03-d8f2e16de322')
def test_network_created_in_single_az_with_enough_agents(self):
"""Verify that network is deployed over one AZ with enough agents
Network should be deployed over the required amount of the agents
in the specified availability zone
"""
az_host_counter = {}
for tmp_az in self.dhcp_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.agent_conf['dhcp_agents_per_network']:
az = tmp_az
break
else:
raise self.skipException('No availability zone with enough '
'resources available')
network = self.create_network(availability_zone_hints=[az])
self.create_subnet(network)
self._check_az_network([az], network)
@decorators.idempotent_id('f9244141-d176-4d54-b21b-305c81f3aca0')
def test_network_created_in_all_azs(self):
"""Verify that network is created in all available AZs
If the amount of availability zones is more then the
`dhcp_agents_per_network` the test will be scipped as the network
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")
network = self.create_network(
availability_zone_hints=self.AZs_list)
self.create_subnet(network)
self._check_az_network(self.AZs_list, network)
@decorators.idempotent_id('95465489-ef2c-4c25-84d1-650b9b1395b6')
def test_network_created_in_several_azs(self):
"""Verify that network is created in several but not all the AZs
Test will be executed in the environments with more than 1 DHCP agent
per network and with more than 2 availability zones
"""
if len(self.AZs_list) <= 2:
raise self.skipException("More than 2 AZs required")
if self.agent_conf['dhcp_agents_per_network'] <= 1:
raise self.skipException("More than 1 DHCP agents required")
amount = min(self.agent_conf['dhcp_agents_per_network'],
len(self.AZs_list) - 1)
network = self.create_network(
availability_zone_hints=self.AZs_list[:amount])
self.create_subnet(network)
self._check_az_network(self.AZs_list[:amount], network)
@decorators.idempotent_id('c559f79b-3941-4978-9337-ab0e97a7b6f5')
def test_network_created_in_default_azs(self):
"""Verify that network is correctly spawened in default AZs
If there is no parameter specified for default AZs, all the AZs
are recognized as default.
"""
if len(self.agent_conf['default_availability_zones']) > \
self.agent_conf['dhcp_agents_per_network']:
raise self.skipException('something')
network = self.create_network()
self.create_subnet(network)
azs = self.agent_conf['default_availability_zones'] or self.AZs_list
self._check_az_network(azs, network)
@decorators.idempotent_id('2673b58d-6875-4232-bf47-8cc5e9aa4479')
def test_network_creation_failed_for_not_existing_az(self):
"""Verify that network 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_network,
availability_zone_hints=tmp_az_list)
@decorators.idempotent_id('22be1c6d-9bf9-4c79-a9ce-1225fef885ad')
def test_router_created_in_single_az_with_not_enough_agents(self):
"""Verify that router is deployed over one AZ with NOT enough agents
Router should be deployed over all the agents in the availability zone
"""
az_host_counter = {}
for tmp_az in self.l3_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.agent_conf['max_l3_agents_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('a679b0fb-9f25-4faa-8132-405a5afb722a')
def test_router_created_in_single_az_with_enough_agents(self):
"""Verify that router is deployed over one AZ with enough agents
Router should be deployed over the required amount of the agents
in the specified availability zone
"""
az_host_counter = {}
for tmp_az in self.l3_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.agent_conf['max_l3_agents_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('d22c3ee0-0241-4a86-8ded-0a0b9d0fefb9')
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 then the
`max_l3_agents_per_router` the test will be scipped 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('eeb04734-0c78-4fa5-81cb-569bfdc4daeb')
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 L3 agent
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.agent_conf['max_l3_agents_per_router'] <= 1:
raise self.skipException("More than 1 L3 agents required")
amount = min(self.agent_conf['max_l3_agents_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('26d05e2e-c491-43f2-8368-f396e994b14f')
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.
"""
if len(self.agent_conf['default_availability_zones']) > \
self.agent_conf['max_l3_agents_per_router']:
raise self.skipException('something')
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('8ab54f63-2b3c-48fb-87b4-e4785b72c543')
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)