Migrate router flavors tests

Changes from original tests:
- Adjust migrated imports and configuration.
- Replaced test UUIDs with unique ones.
- Configured devstack single-thread ovn job for feature.
- CLI prefix fetched using 'get_osp_cmd_prefix'.
- Adapted 'check_service_setting' parameters to whitebox method.
- Migrated and adjusted 'cli_create_resource' base method
  for podified/devstack use.

This feature might be modified further soon, so more adjusts and tests
will be done on podified setups soon as well.

Feature isn't enabled in devstack gates, most code depends
on already merged functions or CLI, more relevant to have code merged in
repo for podified testing in other CI jobs.

Change-Id: I63c768b9ad352cb9809d94aa4743a7d5d00f0721
This commit is contained in:
Maor Blaustein 2024-04-11 21:14:24 +03:00
parent ae81daf2a7
commit 7c2b58eb67
3 changed files with 417 additions and 0 deletions

View File

@ -767,6 +767,70 @@ class BaseTempestWhiteboxTestCase(base.BaseTempestTestCase):
'Command failure "{}" on "{}" nodes.'.format(
cmd, group_name))
@classmethod
def cli_create_resource(cls, cmd,
cleanup_method=None,
cleanup_args=None,
env_prefix=None,
no_id_cmd=False,
cleanup=True):
"""Wrapper for OSP resource creation using commands.
Includes sourcing commonly used credentials and common
cleanup command call after test class is done/failed.
:param cmd: Creation command to execute.
Example: 'openstack ... create ...'
:type cmd: str
:param cleanup_method: Cleanup function to handle resource
after test class is finished/failed.
Default method is validate_command from base class in combination
with a default delete command deduced from given create command.
:type cleanup_method: function, optional
:param cleanup_args: Arguments passed to cleanup method.
Default arguments work in combination with default cleanup_method,
which is a single argument, a figured delete appropriate command.
:type cleanup_args: tuple, optional
:param env_prefix: Prefix added to create command.
Default prefix sources test user rc file, usually ~/openrc .
:type env_prefix: str, optional
:param no_id_cmd: When set to True omits command suffix to return
uuid of created resource, then returns all output.
(Useful for non ordinary creation commands, or extra output parsing).
Default is False.
:type no_id_cmd: bool, optional
:param cleanup: When set to False, skips adding cleanup in stack
(therefore not using cleanup_method and cleanup_args).
Default is True.
:type cleanup: bool, optional
:returns: uuid of resource, or all output according to
no_id_cmd boolean.
Default is uuid.
"""
# default to overcloudrc credentials
_env_prefix = env_prefix or cls.get_osp_cmd_prefix()
_get_id_suffix = '' if no_id_cmd else ' -f value -c id'
_cmd = _env_prefix + cmd + _get_id_suffix
_id = cls.validate_command(_cmd).strip()
# default to delete using CLI, and common arguments figured from cmd
if cleanup:
_cleanup_method = cleanup_method or cls.validate_command
_cleanup_args = cleanup_args or (
_cmd.partition('create ')[0] + 'delete {}'.format(_id),)
cls.addClassResourceCleanup(_cleanup_method, *_cleanup_args)
# length of uuid with dashes, as seen in CLI output
if not no_id_cmd and len(_id) != 36:
raise AssertionError(
"Command for resource creation failed: '{}'".format(_cmd))
LOG.debug('Command for resource creation succeeded')
return _id
@classmethod
def find_host_virsh_name(cls, host):
cmd = ("timeout 10 ssh {} sudo virsh list --name | grep -w {}").format(
WB_CONF.hypervisor_host, host)

View File

@ -0,0 +1,341 @@
# 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 uuid import uuid4 as rand_uuid
from neutron_tempest_plugin import config
from oslo_log import log
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
from whitebox_neutron_tempest_plugin.tests.scenario import base as wb_base
CONF = config.CONF
WB_CONF = CONF.whitebox_neutron_plugin_options
LOG = log.getLogger(__name__)
class RouterFlavorsTestOvn(wb_base.BaseTempestTestCaseOvn):
credentials = ['primary', 'admin']
required_extensions = ['router', 'flavors']
USER_DRIVER_DOT_PATH = \
'neutron.services.ovn_l3.service_providers.user_defined.UserDefined'
@classmethod
def _test_router_flavors_cli_crud(cls):
"""Test CRUD operations for router flavor using openstack CLI.
Many different API requests are affected by feature when configured
Therefore this scenario aims to realistically use many of the
affected API requests with openstack commands,
focus is on the commands which relate to user flavor.
ovn flavor is the default and will be tested by other tests.
"""
# 1) validate new drivers (service providers): ovn and user-defined
# are loaded and presented, ovn is shown as the only default
cls.validate_command(
cls.osp_cmd_prefix + 'openstack network service provider list',
pattern='|.*user-defined.*|.*False.*|.*ovn.*|.*True.*|')
# 2) create a user service profile for the router flavor
cls.profile_create_cmd = (
'openstack network flavor profile create '
'--description "User defined router flavor profile" '
'--enable '
'--driver {} '
'-f value -c id')
cls.user_profile_id = cls.cli_create_resource(
cmd=cls.profile_create_cmd.format(cls.USER_DRIVER_DOT_PATH))
# 3) create user flavor
cls.flavor_create_cmd = (
'openstack network flavor create '
'--service-type L3_ROUTER_NAT '
'--description "User defined flavor for routers" '
'{} '
'-f value -c id')
user_flav_name = data_utils.rand_name('user-flavor')
cls.user_flavor_id = cls.cli_create_resource(
cmd=cls.flavor_create_cmd.format(user_flav_name))
# verify flavor id is in list command output
cls.validate_command(
'{}openstack network flavor list'.format(cls.osp_cmd_prefix),
pattern=cls.user_flavor_id)
# 4) add service profile to router flavor
profile_fmt = \
'openstack network flavor {{}} profile {} {}'.format(
cls.user_flavor_id,
cls.user_profile_id)
cls.cli_create_resource(
profile_fmt.format('add'),
cleanup_args=(cls.osp_cmd_prefix + profile_fmt.format('remove'),),
no_id_cmd=True)
# 5) create routers with user-defined/ovn flavors, set external GW
user_router_name = data_utils.rand_name('user-router')
ovn_router_name = data_utils.rand_name('ovn-router')
cls.user_router_id = cls.cli_create_resource(
cmd='openstack router create --flavor-id {} {}'.format(
cls.user_flavor_id,
user_router_name))
cls.validate_command(
'{}openstack router set --external-gateway {} {}'.format(
cls.osp_cmd_prefix,
CONF.network.public_network_id,
user_router_name))
cls.ovn_router_id = cls.cli_create_resource(
cmd='openstack router create {}'.format(ovn_router_name))
# 6) verify user router is listed
cls.validate_command(
'{}openstack router list'.format(cls.osp_cmd_prefix),
pattern=cls.user_router_id)
# 7) verify resource creation related to user flavor router (ipv4/6):
# network, subnet, ports (ovn router resources covered as default)
# networks
user_net_name = data_utils.rand_name('user-network')
cls.user_network_id = cls.cli_create_resource(
'openstack network create {}'.format(user_net_name))
ovn_net_name = data_utils.rand_name('ovn-network')
cls.ovn_network_id = cls.cli_create_resource(
'openstack network create {}'.format(ovn_net_name))
# subnet
user_subnet_name = data_utils.rand_name('user-subnet')
cls.user_subnet_id = cls.cli_create_resource(
('openstack subnet create --subnet-range 10.1.1.0/24 '
'--network {} {}').format(
user_net_name,
user_subnet_name))
# add subnet to router
router_subnet_fmt = \
'openstack router {{}} subnet {} {}'.format(
cls.user_router_id,
cls.user_subnet_id)
cls.cli_create_resource(
router_subnet_fmt.format('add'),
cleanup_args=(
cls.osp_cmd_prefix + router_subnet_fmt.format('remove'),),
no_id_cmd=True)
# port
user_port_name = data_utils.rand_name('user-port')
cls.user_port_id = cls.cli_create_resource(
('openstack port create --network {} --fixed-ip '
'subnet={} {}').format(
cls.user_network_id,
user_subnet_name,
user_port_name))
if cls.ipv6:
# ipv6 network
user_net_ipv6_name = data_utils.rand_name('user-network-ipv6')
cls.user_network_ipv6_id = cls.cli_create_resource(
'openstack network create {}'.format(user_net_ipv6_name))
# ipv6 subnet
cidr_ipv6 = 'fd5a:3e75:a5d::/64'
ra_address_mode = 'slaac'
user_subnet_ipv6_name = data_utils.rand_name('user-subnet-ipv6')
cls.user_subnet_ipv6_id = cls.cli_create_resource(
('openstack subnet create --ip-version 6 --subnet-range {0} '
'--ipv6-ra-mode {1} --ipv6-address-mode {1} '
'--network {2} {3}').format(
cidr_ipv6,
ra_address_mode,
user_net_ipv6_name,
user_subnet_ipv6_name))
# ipv6 add subnet to router
router_subnet_ipv6_fmt = \
'openstack router {{}} subnet {} {}'.format(
cls.user_router_id,
cls.user_subnet_ipv6_id)
cls.cli_create_resource(
router_subnet_ipv6_fmt.format('add'),
cleanup_args=(cls.osp_cmd_prefix +
router_subnet_ipv6_fmt.format('remove'),),
no_id_cmd=True)
user_port_ipv6_name = data_utils.rand_name('user-port')
# ipv6 port
cls.user_port_ipv6_id = cls.cli_create_resource(
('openstack port create --network {} --fixed-ip '
'subnet={} {}').format(
user_net_ipv6_name,
user_subnet_ipv6_name,
user_port_ipv6_name))
# 8) verify fip creation related to user flavor router
cls.user_fip_id = cls.cli_create_resource(
'openstack floating ip create {} --port {}'.format(
CONF.network.public_network_id,
user_port_name))
@classmethod
def resource_setup(cls):
super(RouterFlavorsTestOvn, cls).resource_setup()
cls.discover_nodes()
# skip tests if OVN router flavors feature isn't enabled
for node in cls.nodes:
if not node['is_controller']:
continue
cls.check_service_setting(
host=node,
service='neutron',
config_files=(WB_CONF.neutron_config,),
param='service_plugins',
value='ovn-router-flavors',
msg='OVN router flavors feature not enabled, skipping tests')
cls.ipv6 = CONF.network_feature_enabled.ipv6_subnet_attributes
cls.osp_cmd_prefix = cls.get_osp_cmd_prefix()
# Testing of router flavors CLI and CRUD starts here
# (first basic test does essential resource setup for other tests)
cls._test_router_flavors_cli_crud()
@decorators.attr(type='negative')
@decorators.idempotent_id('5ad9c4ac-c53f-45dc-8e5e-1207d4d9b05f')
def test_router_flavors_negatives(self):
"""Test CRUD actions expected failures for router flavors when
using openstack commands.
"""
# 1) test add/remove of non existing service profile to network flavor
# using id/name
bad_id = rand_uuid()
# test add non existing profile name
cmd = '{}openstack network flavor add profile {} kamehameha'.format(
self.osp_cmd_prefix,
self.user_flavor_id)
self.assertFalse(
self.validate_command(
cmd,
pattern='No ServiceProfile found for kamehameha',
ret_bool_status=True),
'Command should fail -> "{}"'.format(cmd))
# test remove non existing profile name
self.assertFalse(
self.validate_command(
cmd.replace(' add ', ' remove ', 1),
pattern='No ServiceProfile found for kamehameha',
ret_bool_status=True),
'Command should fail -> "{}"'.format(cmd))
# test add non existing profile id
cmd = '{}openstack network flavor add profile {} {}'.format(
self.osp_cmd_prefix,
self.user_flavor_id,
bad_id)
self.assertFalse(
self.validate_command(
cmd,
pattern='No ServiceProfile found for {}'.format(bad_id),
ret_bool_status=True),
'Command should fail -> "{}"'.format(cmd))
# test remove non existing profile id
self.assertFalse(
self.validate_command(
cmd.replace(' add ', ' remove ', 1),
pattern='No ServiceProfile found for {}'.format(bad_id),
ret_bool_status=True),
'Command should fail -> "{}"'.format(cmd))
# # 2) test removal of service profile from network flavor while
# # there is existing router which uses service profile
# # NOTE(mblue): TEST MAY FAIL, WAITING FOR FIX.
# # related bug: https://bugzilla.redhat.com/show_bug.cgi?id=2237290
# # Pattern argument for failure message can be added when fixed,
# # for now only command failure with non zero exit status verified.
# cmd = '{}openstack network flavor remove profile {} {}'.format(
# self.osp_cmd_prefix,
# self.user_flavor_id,
# self.user_profile_id)
# try:
# self.assertFalse(
# self.validate_command(
# cmd,
# ret_bool_status=True),
# 'Command should fail -> "{}"'.format(cmd))
# # re-add profile in case of failure, prevent chain of failures
# # for next test classes/methods
# except AssertionError:
# LOG.exception('Test failed')
# self.validate_command(cmd.replace(' remove ', ' add ', 1))
# raise
# # 3) test disable of service profile from network flavor while
# # there is existing router which uses service profile
# cmd = '{}openstack network flavor profile set --disable {}'.format(
# self.osp_cmd_prefix,
# self.user_profile_id)
# try:
# self.assertFalse(
# self.validate_command(
# cmd,
# ret_bool_status=True),
# 'Command should fail -> "{}"'.format(cmd))
# # enable profile in case of failure, prevent chain of failures
# # for next test classes/methods
# except AssertionError:
# LOG.exception('Test failed')
# self.validate_command(cmd.replace('--disable ', '--enable ', 1))
# raise
# 4) test show flavor details of already deleted flavor, and bad id
cmd_fmt = '{}openstack network flavor show {{}}'.format(
self.osp_cmd_prefix)
# create and delete temporary flavor
temp_flavor_name = data_utils.rand_name('temp-flavor')
temp_flavor_id = self.cli_create_resource(
cmd=self.flavor_create_cmd.format(temp_flavor_name),
cleanup=False)
self.validate_command(
'{}openstack network flavor delete {}'.format(
self.osp_cmd_prefix,
temp_flavor_id))
# verify failure of flavor show request for deleted id/name
cmd = cmd_fmt.format(temp_flavor_name)
self.assertFalse(
self.validate_command(
cmd,
pattern='No Flavor found for {}'.format(temp_flavor_name),
ret_bool_status=True),
'Command should fail -> "{}"'.format(cmd))
cmd = cmd_fmt.format(temp_flavor_id)
self.assertFalse(
self.validate_command(
cmd,
pattern='No Flavor found for {}'.format(temp_flavor_id),
ret_bool_status=True),
'Command should fail -> "{}"'.format(cmd))
# verify failure of flavor show request for non existing id
cmd = cmd_fmt.format(bad_id)
self.assertFalse(
self.validate_command(
cmd,
pattern='No Flavor found for {}'.format(bad_id),
ret_bool_status=True),
'Command should fail -> "{}"'.format(cmd))
# 5) test deleting network flavor while floating ip routed using
# flavor exists
cmd = '{}openstack network flavor delete {}'.format(
self.osp_cmd_prefix,
self.user_flavor_id)
try:
self.assertFalse(
self.validate_command(
cmd,
pattern='ConflictException: 409',
ret_bool_status=True),
'Command should fail -> "{}"'.format(cmd))
# re-create user flavor in case of failure, prevent chain of failures
# for next test classes/methods
except AssertionError:
LOG.exception('Test failed')
user_flav_name = data_utils.rand_name('user-flavor')
self.user_flavor_id = self.cli_create_resource(
cmd=self.flavor_create_cmd.format(user_flav_name))
raise

View File

@ -23,6 +23,7 @@
(^whitebox_neutron_tempest_plugin.tests.scenario)"
tempest_exclude_regex: "\
(^whitebox_neutron_tempest_plugin.tests.scenario.test_metadata_rate_limiting)|\
(^whitebox_neutron_tempest_plugin.tests.scenario.test_router_flavors)|\
(^whitebox_neutron_tempest_plugin.tests.scenario.test_security_group_logging)|\
(^whitebox_neutron_tempest_plugin.tests.scenario.test_l3ha_ovn)|\
(test_multicast.*restart)|\
@ -416,9 +417,20 @@
name: whitebox-neutron-tempest-plugin-ovn-single-thread
parent: whitebox-neutron-tempest-plugin-ovn
vars:
network_api_extensions_ovn:
- vlan-transparent
- ovn-router-flavors
devstack_localrc:
NETWORK_API_EXTENSIONS: "{{ (network_api_extensions_common + network_api_extensions_ovn) | join(',') }}"
devstack_local_conf:
post-config:
$NEUTRON_CONF:
service_providers:
service_provider: "L3_ROUTER_NAT:user-defined:neutron.services.ovn_l3.service_providers.user_defined.UserDefined"
tempest_concurrency: 1
tempest_test_regex: "\
(^whitebox_neutron_tempest_plugin.tests.scenario.test_metadata_rate_limiting)|\
(^whitebox_neutron_tempest_plugin.tests.scenario.test_router_flavors)|\
(^whitebox_neutron_tempest_plugin.tests.scenario.test_security_group_logging)|\
(^whitebox_neutron_tempest_plugin.tests.scenario.test_l3ha_ovn)|\
(test_multicast.*restart)|\