summaryrefslogtreecommitdiffstats
path: root/snaps/openstack/create_volume.py
diff options
context:
space:
mode:
Diffstat (limited to 'snaps/openstack/create_volume.py')
-rw-r--r--snaps/openstack/create_volume.py269
1 files changed, 269 insertions, 0 deletions
diff --git a/snaps/openstack/create_volume.py b/snaps/openstack/create_volume.py
new file mode 100644
index 0000000..9baad7e
--- /dev/null
+++ b/snaps/openstack/create_volume.py
@@ -0,0 +1,269 @@
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
+# and others. 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.
+
+import logging
+import time
+
+from cinderclient.exceptions import NotFound
+
+from snaps.openstack.openstack_creator import OpenStackVolumeObject
+from snaps.openstack.utils import cinder_utils
+
+__author__ = 'spisarski'
+
+logger = logging.getLogger('create_volume')
+
+VOLUME_ACTIVE_TIMEOUT = 300
+VOLUME_DELETE_TIMEOUT = 60
+POLL_INTERVAL = 3
+STATUS_ACTIVE = 'available'
+STATUS_FAILED = 'failed'
+STATUS_DELETED = 'deleted'
+
+
+class OpenStackVolume(OpenStackVolumeObject):
+ """
+ Class responsible for managing an volume in OpenStack
+ """
+
+ def __init__(self, os_creds, volume_settings):
+ """
+ Constructor
+ :param os_creds: The OpenStack connection credentials
+ :param volume_settings: The volume settings
+ :return:
+ """
+ super(self.__class__, self).__init__(os_creds)
+
+ self.volume_settings = volume_settings
+ self.__volume = None
+
+ def initialize(self):
+ """
+ Loads the existing Volume
+ :return: The Volume domain object or None
+ """
+ super(self.__class__, self).initialize()
+
+ self.__volume = cinder_utils.get_volume(
+ self._cinder, volume_settings=self.volume_settings)
+ return self.__volume
+
+ def create(self, block=False):
+ """
+ Creates the volume in OpenStack if it does not already exist and
+ returns the domain Volume object
+ :return: The Volume domain object or None
+ """
+ self.initialize()
+
+ if not self.__volume:
+ self.__volume = cinder_utils.create_volume(
+ self._cinder, self.volume_settings)
+
+ logger.info(
+ 'Created volume with name - %s', self.volume_settings.name)
+ if self.__volume:
+ if block:
+ if self.volume_active(block=True):
+ logger.info('Volume is now active with name - %s',
+ self.volume_settings.name)
+ return self.__volume
+ else:
+ raise VolumeCreationError(
+ 'Volume was not created or activated in the '
+ 'alloted amount of time')
+ else:
+ logger.info('Did not create volume due to cleanup mode')
+
+ return self.__volume
+
+ def clean(self):
+ """
+ Cleanse environment of all artifacts
+ :return: void
+ """
+ if self.__volume:
+ try:
+ if self.volume_active(block=True):
+ cinder_utils.delete_volume(self._cinder, self.__volume)
+ else:
+ logger.warn('Timeout waiting to delete volume %s',
+ self.__volume.name)
+ except NotFound:
+ pass
+
+ try:
+ if self.volume_deleted(block=True):
+ logger.info(
+ 'Volume has been properly deleted with name - %s',
+ self.volume_settings.name)
+ self.__vm = None
+ else:
+ logger.error(
+ 'Volume not deleted within the timeout period of %s '
+ 'seconds', VOLUME_DELETE_TIMEOUT)
+ except Exception as e:
+ logger.error(
+ 'Unexpected error while checking VM instance status - %s',
+ e)
+
+ self.__volume = None
+
+ def get_volume(self):
+ """
+ Returns the domain Volume object as it was populated when create() was
+ called
+ :return: the object
+ """
+ return self.__volume
+
+ def volume_active(self, block=False, timeout=VOLUME_ACTIVE_TIMEOUT,
+ poll_interval=POLL_INTERVAL):
+ """
+ Returns true when the volume status returns the value of
+ expected_status_code
+ :param block: When true, thread will block until active or timeout
+ value in seconds has been exceeded (False)
+ :param timeout: The timeout value
+ :param poll_interval: The polling interval in seconds
+ :return: T/F
+ """
+ return self._volume_status_check(STATUS_ACTIVE, block, timeout,
+ poll_interval)
+
+ def volume_deleted(self, block=False, poll_interval=POLL_INTERVAL):
+ """
+ Returns true when the VM status returns the value of
+ expected_status_code or instance retrieval throws a NotFound exception.
+ :param block: When true, thread will block until active or timeout
+ value in seconds has been exceeded (False)
+ :param poll_interval: The polling interval in seconds
+ :return: T/F
+ """
+ try:
+ return self._volume_status_check(
+ STATUS_DELETED, block, VOLUME_DELETE_TIMEOUT, poll_interval)
+ except NotFound as e:
+ logger.debug(
+ "Volume not found when querying status for %s with message "
+ "%s", STATUS_DELETED, e)
+ return True
+
+ def _volume_status_check(self, expected_status_code, block, timeout,
+ poll_interval):
+ """
+ Returns true when the volume status returns the value of
+ expected_status_code
+ :param expected_status_code: instance status evaluated with this string
+ value
+ :param block: When true, thread will block until active or timeout
+ value in seconds has been exceeded (False)
+ :param timeout: The timeout value
+ :param poll_interval: The polling interval in seconds
+ :return: T/F
+ """
+ # sleep and wait for volume status change
+ if block:
+ start = time.time()
+ else:
+ start = time.time() - timeout + 10
+
+ while timeout > time.time() - start:
+ status = self._status(expected_status_code)
+ if status:
+ logger.debug('Volume is active with name - %s',
+ self.volume_settings.name)
+ return True
+
+ logger.debug('Retry querying volume status in %s seconds',
+ str(poll_interval))
+ time.sleep(poll_interval)
+ logger.debug('Volume status query timeout in %s',
+ str(timeout - (time.time() - start)))
+
+ logger.error(
+ 'Timeout checking for volume status for ' + expected_status_code)
+ return False
+
+ def _status(self, expected_status_code):
+ """
+ Returns True when active else False
+ :param expected_status_code: instance status evaluated with this string
+ value
+ :return: T/F
+ """
+ status = cinder_utils.get_volume_status(self._cinder, self.__volume)
+ if not status:
+ logger.warning(
+ 'Cannot volume status for volume with ID - %s',
+ self.__volume.id)
+ return False
+
+ if status == 'ERROR':
+ raise VolumeCreationError(
+ 'Instance had an error during deployment')
+ logger.debug('Instance status is - ' + status)
+ return status == expected_status_code
+
+
+class VolumeSettings:
+ def __init__(self, **kwargs):
+ """
+ Constructor
+ :param name: the volume's name (required)
+ :param description: the volume's name (required)
+ :param size: the volume's size in GB (default 1)
+ :param image_name: when a glance image is used for the image source
+ (optional)
+ :param type_name: the associated volume's type name (optional)
+ :param availability_zone: the name of the compute server on which to
+ deploy the volume (optional)
+ :param multi_attach: when true, volume can be attached to more than one
+ server (default False)
+ """
+
+ self.name = kwargs.get('name')
+ self.description = kwargs.get('description')
+ self.size = int(kwargs.get('size', 1))
+ self.image_name = kwargs.get('image_name')
+ self.type_name = kwargs.get('type_name')
+ self.availability_zone = kwargs.get('availability_zone')
+
+ if kwargs.get('availability_zone'):
+ self.multi_attach = bool(kwargs.get('availability_zone'))
+ else:
+ self.multi_attach = False
+
+ if not self.name:
+ raise VolumeSettingsError("The attribute name is required")
+
+
+class VolumeSettingsError(Exception):
+ """
+ Exception to be thrown when an volume settings are incorrect
+ """
+
+ def __init__(self, message):
+ Exception.__init__(self, message)
+
+
+class VolumeCreationError(Exception):
+ """
+ Exception to be thrown when an volume cannot be created
+ """
+
+ def __init__(self, message):
+ Exception.__init__(self, message)