From 38d6a8ce3c9bce63cf1bc8222c5a94070701ef17 Mon Sep 17 00:00:00 2001 From: spisarski Date: Thu, 19 Oct 2017 14:31:22 -0600 Subject: Third patch for volume support. * Added support for volumes integrated with QoS and encryption. * Created tests for volumes at an API and state machine level. JIRA: SNAPS-197 Change-Id: I07326875b9f1a30e50389531d0d2571ee648675f Signed-off-by: spisarski --- snaps/openstack/utils/cinder_utils.py | 84 ++++++++++++++++- snaps/openstack/utils/tests/cinder_utils_tests.py | 110 ++++++++++++++++++++++ 2 files changed, 193 insertions(+), 1 deletion(-) (limited to 'snaps/openstack/utils') diff --git a/snaps/openstack/utils/cinder_utils.py b/snaps/openstack/utils/cinder_utils.py index d13277d..e40b471 100644 --- a/snaps/openstack/utils/cinder_utils.py +++ b/snaps/openstack/utils/cinder_utils.py @@ -17,7 +17,8 @@ import logging from cinderclient.client import Client from cinderclient.exceptions import NotFound -from snaps.domain.volume import QoSSpec, VolumeType, VolumeTypeEncryption +from snaps.domain.volume import ( + QoSSpec, VolumeType, VolumeTypeEncryption, Volume) from snaps.openstack.utils import keystone_utils __author__ = 'spisarski' @@ -42,6 +43,87 @@ def cinder_client(os_creds): region_name=os_creds.region_name) +def get_volume(cinder, volume_name=None, volume_settings=None): + """ + Returns an OpenStack volume object for a given name + :param cinder: the Cinder client + :param volume_name: the volume name to lookup + :param volume_settings: the volume settings used for lookups + :return: the volume object or None + """ + if volume_settings: + volume_name = volume_settings.name + + volumes = cinder.volumes.list() + for volume in volumes: + if volume.name == volume_name: + return Volume( + name=volume.name, volume_id=volume.id, + description=volume.description, size=volume.size, + vol_type=volume.volume_type, + availability_zone=volume.availability_zone, + multi_attach=volume.multiattach) + + +def get_volume_by_id(cinder, volume_id): + """ + Returns an OpenStack volume object for a given name + :param cinder: the Cinder client + :param volume_id: the volume ID to lookup + :return: the SNAPS-OO Domain Volume object or None + """ + volume = cinder.volumes.get(volume_id) + return Volume( + name=volume.name, volume_id=volume.id, description=volume.description, + size=volume.size, vol_type=volume.volume_type, + availability_zone=volume.availability_zone, + multi_attach=volume.multiattach) + + +def get_volume_status(cinder, volume): + """ + Returns a new OpenStack Volume object for a given OpenStack volume object + :param cinder: the Cinder client + :param volume: the domain Volume object + :return: the OpenStack Volume object + """ + os_volume = cinder.volumes.get(volume.id) + return os_volume.status + + +def create_volume(cinder, volume_settings): + """ + Creates and returns OpenStack volume object with an external URL + :param cinder: the cinder client + :param volume_settings: the volume settings object + :return: the OpenStack volume object + :raise Exception if using a file and it cannot be found + """ + created_volume = cinder.volumes.create( + name=volume_settings.name, description=volume_settings.description, + size=volume_settings.size, imageRef=volume_settings.image_name, + volume_type=volume_settings.type_name, + availability_zone=volume_settings.availability_zone, + multiattach=volume_settings.multi_attach) + + return Volume( + name=created_volume.name, volume_id=created_volume.id, + description=created_volume.description, + size=created_volume.size, vol_type=created_volume.volume_type, + availability_zone=created_volume.availability_zone, + multi_attach=created_volume.multiattach) + + +def delete_volume(cinder, volume): + """ + Deletes an volume from OpenStack + :param cinder: the cinder client + :param volume: the volume to delete + """ + logger.info('Deleting volume named - %s', volume.name) + cinder.volumes.delete(volume.id) + + def get_volume_type(cinder, volume_type_name=None, volume_type_settings=None): """ Returns an OpenStack volume type object for a given name diff --git a/snaps/openstack/utils/tests/cinder_utils_tests.py b/snaps/openstack/utils/tests/cinder_utils_tests.py index a45167e..6fd92e3 100644 --- a/snaps/openstack/utils/tests/cinder_utils_tests.py +++ b/snaps/openstack/utils/tests/cinder_utils_tests.py @@ -15,9 +15,12 @@ import logging import uuid +import time from cinderclient.exceptions import NotFound, BadRequest +from snaps.openstack import create_volume from snaps.openstack.create_qos import QoSSettings, Consumer +from snaps.openstack.create_volume import VolumeSettings from snaps.openstack.create_volume_type import ( VolumeTypeSettings, VolumeTypeEncryptionSettings, ControlLocation) from snaps.openstack.tests import validation_utils @@ -57,6 +60,113 @@ class CinderSmokeTests(OSComponentTestCase): cinder.volumes.list() +class CinderUtilsVolumeTests(OSComponentTestCase): + """ + Test for the CreateVolume class defined in create_volume.py + """ + + def setUp(self): + """ + Instantiates the CreateVolume object that is responsible for + downloading and creating an OS volume file within OpenStack + """ + guid = uuid.uuid4() + self.volume_name = self.__class__.__name__ + '-' + str(guid) + self.volume = None + self.cinder = cinder_utils.cinder_client(self.os_creds) + + def tearDown(self): + """ + Cleans the remote OpenStack objects + """ + if self.volume: + try: + cinder_utils.delete_volume(self.cinder, self.volume) + except NotFound: + pass + + self.assertTrue(volume_deleted(self.cinder, self.volume)) + + def test_create_simple_volume(self): + """ + Tests the cinder_utils.create_volume() + """ + volume_settings = VolumeSettings(name=self.volume_name) + self.volume = cinder_utils.create_volume( + self.cinder, volume_settings) + self.assertIsNotNone(self.volume) + self.assertEqual(self.volume_name, self.volume.name) + + self.assertTrue(volume_active(self.cinder, self.volume)) + + volume = cinder_utils.get_volume( + self.cinder, volume_settings=volume_settings) + self.assertIsNotNone(volume) + validation_utils.objects_equivalent(self.volume, volume) + + def test_create_delete_volume(self): + """ + Tests the cinder_utils.create_volume() + """ + volume_settings = VolumeSettings(name=self.volume_name) + self.volume = cinder_utils.create_volume( + self.cinder, volume_settings) + self.assertIsNotNone(self.volume) + self.assertEqual(self.volume_name, self.volume.name) + + self.assertTrue(volume_active(self.cinder, self.volume)) + + volume = cinder_utils.get_volume( + self.cinder, volume_settings=volume_settings) + self.assertIsNotNone(volume) + validation_utils.objects_equivalent(self.volume, volume) + + cinder_utils.delete_volume(self.cinder, self.volume) + self.assertTrue(volume_deleted(self.cinder, self.volume)) + self.assertIsNone( + cinder_utils.get_volume(self.cinder, volume_settings)) + + +def volume_active(cinder, volume): + """ + Returns true if volume becomes active + :param cinder: + :param volume: + :return: + """ + end_time = time.time() + create_volume.VOLUME_ACTIVE_TIMEOUT + while time.time() < end_time: + status = cinder_utils.get_volume_status(cinder, volume) + if status == create_volume.STATUS_ACTIVE: + return True + elif status == create_volume.STATUS_FAILED: + return False + time.sleep(3) + + return False + + +def volume_deleted(cinder, volume): + """ + Returns true if volume becomes active + :param cinder: + :param volume: + :return: + """ + end_time = time.time() + create_volume.VOLUME_ACTIVE_TIMEOUT + while time.time() < end_time: + try: + status = cinder_utils.get_volume_status(cinder, volume) + if status == create_volume.STATUS_DELETED: + return True + except NotFound: + return True + + time.sleep(3) + + return False + + class CinderUtilsQoSTests(OSComponentTestCase): """ Test for the CreateQos class defined in create_qos.py -- cgit 1.2.3-korg