Add compute API admin test for swap volume
This change adds a test for the swap volume API in
the compute service (updating a volume attachment).
This is an admin-only API by default so it uses
admin credentials to perform the volume attachment
update operation. The server and volumes used during
the test are created with non-admin credentials though.
The only virt driver that supports this operation
is libvirt so a feaure flag is added which will be
configured in devstack to run this test in the gate.
The create_volume and attach_volume methods are added
to the base compute API test class so that we can
refactor other compute API tests that have similar
function and reduce duplication. That will come in a
separate change.
This is tested with devstack change:
Ibb6b309574d2c6a06fcecb0626ea21527fb7f412
Co-Authored-By: Matt Riedemann <mriedem@us.ibm.com>
Change-Id: I2d4779de8d21aa84533f4f92d347e932db2de58e
diff --git a/tempest/api/compute/admin/test_volume_swap.py b/tempest/api/compute/admin/test_volume_swap.py
new file mode 100644
index 0000000..f603abd
--- /dev/null
+++ b/tempest/api/compute/admin/test_volume_swap.py
@@ -0,0 +1,75 @@
+# 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 tempest.api.compute import base
+from tempest.common import waiters
+from tempest import config
+from tempest import test
+
+CONF = config.CONF
+
+
+class TestVolumeSwap(base.BaseV2ComputeAdminTest):
+ """The test suite for swapping of volume with admin user.
+
+ The following is the scenario outline:
+ 1. Create a volume "volume1" with non-admin.
+ 2. Create a volume "volume2" with non-admin.
+ 3. Boot an instance "instance1" with non-admin.
+ 4. Attach "volume1" to "instance1" with non-admin.
+ 5. Swap volume from "volume1" to "volume2" as admin.
+ 6. Check the swap volume is successful and "volume2"
+ is attached to "instance1" and "volume1" is in available state.
+ """
+
+ @classmethod
+ def skip_checks(cls):
+ super(TestVolumeSwap, cls).skip_checks()
+ if not CONF.compute_feature_enabled.swap_volume:
+ raise cls.skipException("Swapping volumes is not supported.")
+
+ @classmethod
+ def setup_clients(cls):
+ super(TestVolumeSwap, cls).setup_clients()
+ # We need the admin client for performing the update (swap) volume call
+ cls.servers_admin_client = cls.os_adm.servers_client
+
+ @test.idempotent_id('1769f00d-a693-4d67-a631-6a3496773813')
+ @test.services('volume')
+ def test_volume_swap(self):
+ # Create two volumes.
+ # NOTE(gmann): Volumes are created before server creation so that
+ # volumes cleanup can happen successfully irrespective of which volume
+ # is attached to server.
+ volume1 = self.create_volume()
+ volume2 = self.create_volume()
+ # Boot server
+ server = self.create_test_server(wait_until='ACTIVE')
+ # Attach "volume1" to server
+ self.attach_volume(server, volume1)
+ # Swap volume from "volume1" to "volume2"
+ self.servers_admin_client.update_attached_volume(
+ server['id'], volume1['id'], volumeId=volume2['id'])
+ waiters.wait_for_volume_status(self.volumes_client,
+ volume1['id'], 'available')
+ waiters.wait_for_volume_status(self.volumes_client,
+ volume2['id'], 'in-use')
+ self.addCleanup(self.servers_client.detach_volume,
+ server['id'], volume2['id'])
+ # Verify "volume2" is attached to the server
+ vol_attachments = self.servers_client.list_volume_attachments(
+ server['id'])['volumeAttachments']
+ self.assertEqual(1, len(vol_attachments))
+ self.assertIn(volume2['id'], vol_attachments[0]['volumeId'])
+
+ # TODO(mriedem): Test swapping back from volume2 to volume1 after
+ # nova bug 1490236 is fixed.
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 27afff3..a4578ae 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -120,6 +120,7 @@
cls.images = []
cls.security_groups = []
cls.server_groups = []
+ cls.volumes = []
@classmethod
def resource_cleanup(cls):
@@ -127,6 +128,7 @@
cls.clear_servers()
cls.clear_security_groups()
cls.clear_server_groups()
+ cls.clear_volumes()
super(BaseV2ComputeTest, cls).resource_cleanup()
@classmethod
@@ -370,6 +372,55 @@
self.useFixture(api_microversion_fixture.APIMicroversionFixture(
self.request_microversion))
+ @classmethod
+ def create_volume(cls):
+ """Create a volume and wait for it to become 'available'.
+
+ :returns: The available volume.
+ """
+ vol_name = data_utils.rand_name(cls.__name__ + '-volume')
+ volume = cls.volumes_client.create_volume(
+ size=CONF.volume.volume_size, display_name=vol_name)['volume']
+ cls.volumes.append(volume)
+ waiters.wait_for_volume_status(cls.volumes_client,
+ volume['id'], 'available')
+ return volume
+
+ @classmethod
+ def clear_volumes(cls):
+ LOG.debug('Clearing volumes: %s', ','.join(
+ volume['id'] for volume in cls.volumes))
+ for volume in cls.volumes:
+ try:
+ test_utils.call_and_ignore_notfound_exc(
+ cls.volumes_client.delete_volume, volume['id'])
+ except Exception:
+ LOG.exception('Deleting volume %s failed', volume['id'])
+
+ for volume in cls.volumes:
+ try:
+ cls.volumes_client.wait_for_resource_deletion(volume['id'])
+ except Exception:
+ LOG.exception('Waiting for deletion of volume %s failed',
+ volume['id'])
+
+ def attach_volume(self, server, volume):
+ """Attaches volume to server and waits for 'in-use' volume status."""
+ self.servers_client.attach_volume(
+ server['id'], volumeId=volume['id'])
+ # On teardown detach the volume and wait for it to be available. This
+ # is so we don't error out when trying to delete the volume during
+ # teardown.
+ self.addCleanup(waiters.wait_for_volume_status,
+ self.volumes_client, volume['id'], 'available')
+ # Ignore 404s on detach in case the server is deleted or the volume
+ # is already detached.
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ self.servers_client.detach_volume,
+ server['id'], volume['id'])
+ waiters.wait_for_volume_status(self.volumes_client,
+ volume['id'], 'in-use')
+
class BaseV2ComputeAdminTest(BaseV2ComputeTest):
"""Base test case class for Compute Admin API tests."""
diff --git a/tempest/config.py b/tempest/config.py
index b6fca7e..eeafea6 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -414,7 +414,10 @@
"list indicates all filters are disabled. The full "
"available list of filters is in nova.conf: "
"DEFAULT.scheduler_available_filters"),
-
+ cfg.BoolOpt('swap_volume',
+ default=False,
+ help='Does the test environment support in-place swapping of '
+ 'volumes attached to a server instance?'),
]