Add scenario test: test instances with cinder volumes
This patch adds a scenario test instances with cinder volumes launches on all available compute nodes, up to CONF.compute.min_compute_nodes. Also 1 additional configuration is added: volume_types_for_data_volume. Also function for create and add security group to server is moved to tempest/scenario/manager.py to avoid code duplication. Change-Id: I346a9505bc942e66aedad2029215617d0918a885
This commit is contained in:
parent
5c64e3913a
commit
d744598179
@ -0,0 +1,8 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
A new config option in the ``volume_feature_enabled`` section,
|
||||
``volume_types_for_data_volume``, is added to allow the user to specify
|
||||
which volume types can be used for data volumes in a new test
|
||||
``test_instances_with_cinder_volumes_on_all_compute_nodes``. By default,
|
||||
this option is set to None.
|
@ -1111,7 +1111,11 @@ VolumeFeaturesGroup = [
|
||||
default=True,
|
||||
help='Does the cloud support extending the size of a volume '
|
||||
'which has snapshot? Some drivers do not support this '
|
||||
'operation.')
|
||||
'operation.'),
|
||||
cfg.StrOpt('volume_types_for_data_volume',
|
||||
default=None,
|
||||
help='Volume types used for data volumes. Multiple volume '
|
||||
'types can be assigned.'),
|
||||
|
||||
]
|
||||
|
||||
|
@ -734,6 +734,31 @@ class ScenarioTest(tempest.test.BaseTestCase):
|
||||
|
||||
return rules
|
||||
|
||||
def create_and_add_security_group_to_server(self, server):
|
||||
"""Create a security group and add it to the server.
|
||||
|
||||
:param server: The server to add the security group to.
|
||||
:return: The security group was added to the server.
|
||||
"""
|
||||
|
||||
secgroup = self.create_security_group()
|
||||
self.servers_client.add_security_group(server['id'],
|
||||
name=secgroup['name'])
|
||||
self.addCleanup(self.servers_client.remove_security_group,
|
||||
server['id'], name=secgroup['name'])
|
||||
|
||||
def wait_for_secgroup_add():
|
||||
body = (self.servers_client.show_server(server['id'])
|
||||
['server'])
|
||||
return {'name': secgroup['name']} in body['security_groups']
|
||||
|
||||
if not test_utils.call_until_true(wait_for_secgroup_add,
|
||||
CONF.compute.build_timeout,
|
||||
CONF.compute.build_interval):
|
||||
msg = ('Timed out waiting for adding security group %s to server '
|
||||
'%s' % (secgroup['id'], server['id']))
|
||||
raise lib_exc.TimeoutException(msg)
|
||||
|
||||
def get_remote_client(self, ip_address, username=None, private_key=None,
|
||||
server=None):
|
||||
"""Get a SSH client to a remote server
|
||||
@ -1103,6 +1128,15 @@ class ScenarioTest(tempest.test.BaseTestCase):
|
||||
self.assertIsNone(floating_ip['port_id'])
|
||||
return floating_ip
|
||||
|
||||
def create_file(self, ip_address, path, private_key=None, server=None,
|
||||
username=None):
|
||||
"""Create a file on a remote server"""
|
||||
ssh_client = self.get_remote_client(ip_address,
|
||||
private_key=private_key,
|
||||
server=server,
|
||||
username=username)
|
||||
ssh_client.exec_command('sudo mkdir -p %s' % path)
|
||||
|
||||
def create_timestamp(self, ip_address, dev_name=None, mount_path='/mnt',
|
||||
private_key=None, server=None, username=None,
|
||||
fs='vfat'):
|
||||
|
225
tempest/scenario/test_instances_with_cinder_volumes.py
Normal file
225
tempest/scenario/test_instances_with_cinder_volumes.py
Normal file
@ -0,0 +1,225 @@
|
||||
# Copyright 2024 Openstack Foundation
|
||||
# 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 oslo_log import log as logging
|
||||
|
||||
from tempest.common import utils
|
||||
from tempest.common import waiters
|
||||
from tempest import config
|
||||
from tempest.lib import decorators
|
||||
from tempest.lib import exceptions
|
||||
from tempest.scenario import manager
|
||||
|
||||
|
||||
CONF = config.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TestInstancesWithCinderVolumes(manager.ScenarioTest):
|
||||
"""This is cinder volumes test.
|
||||
|
||||
Tests are below:
|
||||
* test_instances_with_cinder_volumes_on_all_compute_nodes
|
||||
"""
|
||||
|
||||
compute_min_microversion = '2.60'
|
||||
|
||||
@decorators.idempotent_id('d0e3c1a3-4b0a-4b0e-8b0a-4b0e8b0a4b0e')
|
||||
@decorators.attr(type=['slow', 'multinode'])
|
||||
@utils.services('compute', 'volume', 'image', 'network')
|
||||
def test_instances_with_cinder_volumes_on_all_compute_nodes(self):
|
||||
"""Test instances with cinder volumes launches on all compute nodes
|
||||
|
||||
Steps:
|
||||
1. Create an image
|
||||
2. Create a keypair
|
||||
3. Create a bootable volume from the image and of the given volume
|
||||
type
|
||||
4. Boot an instance from the bootable volume on each available
|
||||
compute node, up to CONF.compute.min_compute_nodes
|
||||
5. Create a volume using each volume_types_for_data_volume on all
|
||||
available compute nodes, up to CONF.compute.min_compute_nodes.
|
||||
Total number of volumes is equal to
|
||||
compute nodes * len(volume_types_for_data_volume)
|
||||
6. Attach volumes to the instances
|
||||
7. Assign floating IP to all instances
|
||||
8. Configure security group for ssh access to all instances
|
||||
9. Confirm ssh access to all instances
|
||||
10. Run write test to all volumes through ssh connection per
|
||||
instance
|
||||
11. Clean up the sources, an instance, volumes, keypair and image
|
||||
"""
|
||||
boot_volume_type = (CONF.volume.volume_type or
|
||||
self.create_volume_type()['name'])
|
||||
|
||||
# create an image
|
||||
image = self.image_create()
|
||||
|
||||
# create keypair
|
||||
keypair = self.create_keypair()
|
||||
|
||||
# check all available zones for booting instances
|
||||
available_zone = \
|
||||
self.os_admin.availability_zone_client.list_availability_zones(
|
||||
detail=True)['availabilityZoneInfo']
|
||||
|
||||
hosts = []
|
||||
for zone in available_zone:
|
||||
if zone['zoneState']['available']:
|
||||
for host in zone['hosts']:
|
||||
if 'nova-compute' in zone['hosts'][host] and \
|
||||
zone['hosts'][host]['nova-compute']['available'] and \
|
||||
not host.endswith('-ironic'):
|
||||
hosts.append({'zone': zone['zoneName'],
|
||||
'host_name': host})
|
||||
|
||||
# fail if there is less hosts than minimal number of instances
|
||||
if len(hosts) < CONF.compute.min_compute_nodes:
|
||||
raise exceptions.InvalidConfiguration(
|
||||
"Host list %s is shorter than min_compute_nodes. " % hosts)
|
||||
|
||||
# get volume types
|
||||
volume_types = []
|
||||
if CONF.volume_feature_enabled.volume_types_for_data_volume:
|
||||
types = CONF.volume_feature_enabled.volume_types_for_data_volume
|
||||
volume_types = types.split(',')
|
||||
else:
|
||||
# no user specified volume types, create 2 default ones
|
||||
volume_types.append(self.create_volume_type()['name'])
|
||||
volume_types.append(self.create_volume_type()['name'])
|
||||
|
||||
hosts_to_boot_servers = hosts[:CONF.compute.min_compute_nodes]
|
||||
LOG.debug("List of hosts selected to boot servers %s: ",
|
||||
hosts_to_boot_servers)
|
||||
|
||||
# create volumes so that we dont need to wait for them to be created
|
||||
# and save them in a list
|
||||
created_volumes = []
|
||||
for host in hosts_to_boot_servers:
|
||||
for volume_type in volume_types:
|
||||
created_volumes.append(
|
||||
self.create_volume(volume_type=volume_type,
|
||||
wait_until=None)
|
||||
)
|
||||
|
||||
bootable_volumes = []
|
||||
for host in hosts_to_boot_servers:
|
||||
# create boot volume from image and of the given volume type
|
||||
bootable_volumes.append(
|
||||
self.create_volume(
|
||||
imageRef=image, volume_type=boot_volume_type,
|
||||
wait_until=None)
|
||||
)
|
||||
|
||||
# boot server
|
||||
servers = []
|
||||
|
||||
for bootable_volume in bootable_volumes:
|
||||
|
||||
# wait for bootable volumes to become available
|
||||
waiters.wait_for_volume_resource_status(
|
||||
self.volumes_client, bootable_volume['id'], 'available')
|
||||
|
||||
# create an instance from bootable volume
|
||||
server = self.boot_instance_from_resource(
|
||||
source_id=bootable_volume['id'],
|
||||
source_type='volume',
|
||||
keypair=keypair,
|
||||
wait_until=None
|
||||
)
|
||||
servers.append(server)
|
||||
|
||||
start = 0
|
||||
end = len(volume_types)
|
||||
for server in servers:
|
||||
attached_volumes = []
|
||||
|
||||
# wait for server to become active
|
||||
waiters.wait_for_server_status(self.servers_client,
|
||||
server['id'], 'ACTIVE')
|
||||
|
||||
# attach volumes to the instances
|
||||
for volume in created_volumes[start:end]:
|
||||
|
||||
# wait for volume to become available
|
||||
waiters.wait_for_volume_resource_status(
|
||||
self.volumes_client, volume['id'], 'available')
|
||||
|
||||
attached_volume = self.nova_volume_attach(server, volume)
|
||||
attached_volumes.append(attached_volume)
|
||||
LOG.debug("Attached volume %s to server %s",
|
||||
attached_volume['id'], server['id'])
|
||||
|
||||
# assign floating ip
|
||||
floating_ip = None
|
||||
if (CONF.network_feature_enabled.floating_ips and
|
||||
CONF.network.floating_network_name):
|
||||
fip = self.create_floating_ip(server)
|
||||
floating_ip = self.associate_floating_ip(
|
||||
fip, server)
|
||||
ssh_ip = floating_ip['floating_ip_address']
|
||||
else:
|
||||
ssh_ip = self.get_server_ip(server)
|
||||
|
||||
# create security group
|
||||
self.create_and_add_security_group_to_server(server)
|
||||
|
||||
# confirm ssh access
|
||||
self.linux_client = self.get_remote_client(
|
||||
ssh_ip, private_key=keypair['private_key'],
|
||||
server=server
|
||||
)
|
||||
|
||||
# run write test on all volumes
|
||||
for volume in attached_volumes:
|
||||
|
||||
waiters.wait_for_volume_resource_status(
|
||||
self.volumes_client, volume['id'], 'in-use')
|
||||
|
||||
# get the mount path
|
||||
mount_path = f"/mnt/{volume['attachments'][0]['device'][5:]}"
|
||||
|
||||
# create file for mounting on server
|
||||
self.create_file(ssh_ip, mount_path,
|
||||
private_key=keypair['private_key'],
|
||||
server=server)
|
||||
|
||||
# dev name volume['attachments'][0]['device'][5:] is like
|
||||
# /dev/vdb, we need to remove /dev/ -> first 5 chars
|
||||
timestamp_before = self.create_timestamp(
|
||||
ssh_ip, private_key=keypair['private_key'], server=server,
|
||||
dev_name=volume['attachments'][0]['device'][5:],
|
||||
mount_path=mount_path
|
||||
)
|
||||
timestamp_after = self.get_timestamp(
|
||||
ssh_ip, private_key=keypair['private_key'], server=server,
|
||||
dev_name=volume['attachments'][0]['device'][5:],
|
||||
mount_path=mount_path
|
||||
)
|
||||
self.assertEqual(timestamp_before, timestamp_after)
|
||||
|
||||
# delete volume
|
||||
self.nova_volume_detach(server, volume)
|
||||
self.volumes_client.delete_volume(volume['id'])
|
||||
|
||||
if floating_ip:
|
||||
# delete the floating IP, this should refresh the server
|
||||
# addresses
|
||||
self.disassociate_floating_ip(floating_ip)
|
||||
waiters.wait_for_server_floating_ip(
|
||||
self.servers_client, server, floating_ip,
|
||||
wait_for_disassociate=True)
|
||||
|
||||
start += len(volume_types)
|
||||
end += len(volume_types)
|
@ -19,9 +19,7 @@ from tempest.common import custom_matchers
|
||||
from tempest.common import utils
|
||||
from tempest.common import waiters
|
||||
from tempest import config
|
||||
from tempest.lib.common.utils import test_utils
|
||||
from tempest.lib import decorators
|
||||
from tempest.lib import exceptions
|
||||
from tempest.scenario import manager
|
||||
|
||||
CONF = config.CONF
|
||||
@ -73,25 +71,6 @@ class TestMinimumBasicScenario(manager.ScenarioTest):
|
||||
disks = self.linux_client.get_disks()
|
||||
self.assertEqual(1, disks.count(CONF.compute.volume_device_name))
|
||||
|
||||
def create_and_add_security_group_to_server(self, server):
|
||||
secgroup = self.create_security_group()
|
||||
self.servers_client.add_security_group(server['id'],
|
||||
name=secgroup['name'])
|
||||
self.addCleanup(self.servers_client.remove_security_group,
|
||||
server['id'], name=secgroup['name'])
|
||||
|
||||
def wait_for_secgroup_add():
|
||||
body = (self.servers_client.show_server(server['id'])
|
||||
['server'])
|
||||
return {'name': secgroup['name']} in body['security_groups']
|
||||
|
||||
if not test_utils.call_until_true(wait_for_secgroup_add,
|
||||
CONF.compute.build_timeout,
|
||||
CONF.compute.build_interval):
|
||||
msg = ('Timed out waiting for adding security group %s to server '
|
||||
'%s' % (secgroup['id'], server['id']))
|
||||
raise exceptions.TimeoutException(msg)
|
||||
|
||||
@decorators.attr(type='slow')
|
||||
@decorators.idempotent_id('bdbb5441-9204-419d-a225-b4fdbfb1a1a8')
|
||||
@utils.services('compute', 'volume', 'image', 'network')
|
||||
|
Loading…
Reference in New Issue
Block a user