1 # Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
2 # and others. All rights reserved.
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at:
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
19 from cinderclient.exceptions import NotFound
21 from snaps.openstack.openstack_creator import OpenStackVolumeObject
22 from snaps.openstack.utils import cinder_utils
24 __author__ = 'spisarski'
26 logger = logging.getLogger('create_volume')
28 VOLUME_ACTIVE_TIMEOUT = 300
29 VOLUME_DELETE_TIMEOUT = 60
31 STATUS_ACTIVE = 'available'
32 STATUS_FAILED = 'failed'
33 STATUS_DELETED = 'deleted'
36 class OpenStackVolume(OpenStackVolumeObject):
38 Class responsible for managing an volume in OpenStack
41 def __init__(self, os_creds, volume_settings):
44 :param os_creds: The OpenStack connection credentials
45 :param volume_settings: The volume settings
48 super(self.__class__, self).__init__(os_creds)
50 self.volume_settings = volume_settings
55 Loads the existing Volume
56 :return: The Volume domain object or None
58 super(self.__class__, self).initialize()
60 self.__volume = cinder_utils.get_volume(
61 self._cinder, volume_settings=self.volume_settings)
64 def create(self, block=False):
66 Creates the volume in OpenStack if it does not already exist and
67 returns the domain Volume object
68 :return: The Volume domain object or None
73 self.__volume = cinder_utils.create_volume(
74 self._cinder, self.volume_settings)
77 'Created volume with name - %s', self.volume_settings.name)
80 if self.volume_active(block=True):
81 logger.info('Volume is now active with name - %s',
82 self.volume_settings.name)
85 raise VolumeCreationError(
86 'Volume was not created or activated in the '
87 'alloted amount of time')
89 logger.info('Did not create volume due to cleanup mode')
95 Cleanse environment of all artifacts
100 if self.volume_active(block=True):
101 cinder_utils.delete_volume(self._cinder, self.__volume)
103 logger.warn('Timeout waiting to delete volume %s',
109 if self.volume_deleted(block=True):
111 'Volume has been properly deleted with name - %s',
112 self.volume_settings.name)
116 'Volume not deleted within the timeout period of %s '
117 'seconds', VOLUME_DELETE_TIMEOUT)
118 except Exception as e:
120 'Unexpected error while checking VM instance status - %s',
125 def get_volume(self):
127 Returns the domain Volume object as it was populated when create() was
133 def volume_active(self, block=False, timeout=VOLUME_ACTIVE_TIMEOUT,
134 poll_interval=POLL_INTERVAL):
136 Returns true when the volume status returns the value of
138 :param block: When true, thread will block until active or timeout
139 value in seconds has been exceeded (False)
140 :param timeout: The timeout value
141 :param poll_interval: The polling interval in seconds
144 return self._volume_status_check(STATUS_ACTIVE, block, timeout,
147 def volume_deleted(self, block=False, poll_interval=POLL_INTERVAL):
149 Returns true when the VM status returns the value of
150 expected_status_code or instance retrieval throws a NotFound exception.
151 :param block: When true, thread will block until active or timeout
152 value in seconds has been exceeded (False)
153 :param poll_interval: The polling interval in seconds
157 return self._volume_status_check(
158 STATUS_DELETED, block, VOLUME_DELETE_TIMEOUT, poll_interval)
159 except NotFound as e:
161 "Volume not found when querying status for %s with message "
162 "%s", STATUS_DELETED, e)
165 def _volume_status_check(self, expected_status_code, block, timeout,
168 Returns true when the volume status returns the value of
170 :param expected_status_code: instance status evaluated with this string
172 :param block: When true, thread will block until active or timeout
173 value in seconds has been exceeded (False)
174 :param timeout: The timeout value
175 :param poll_interval: The polling interval in seconds
178 # sleep and wait for volume status change
182 start = time.time() - timeout + 10
184 while timeout > time.time() - start:
185 status = self._status(expected_status_code)
187 logger.debug('Volume is active with name - %s',
188 self.volume_settings.name)
191 logger.debug('Retry querying volume status in %s seconds',
193 time.sleep(poll_interval)
194 logger.debug('Volume status query timeout in %s',
195 str(timeout - (time.time() - start)))
198 'Timeout checking for volume status for ' + expected_status_code)
201 def _status(self, expected_status_code):
203 Returns True when active else False
204 :param expected_status_code: instance status evaluated with this string
208 status = cinder_utils.get_volume_status(self._cinder, self.__volume)
211 'Cannot volume status for volume with ID - %s',
215 if status == 'ERROR':
216 raise VolumeCreationError(
217 'Instance had an error during deployment')
218 logger.debug('Instance status is - ' + status)
219 return status == expected_status_code
222 class VolumeSettings:
223 def __init__(self, **kwargs):
226 :param name: the volume's name (required)
227 :param description: the volume's name (required)
228 :param size: the volume's size in GB (default 1)
229 :param image_name: when a glance image is used for the image source
231 :param type_name: the associated volume's type name (optional)
232 :param availability_zone: the name of the compute server on which to
233 deploy the volume (optional)
234 :param multi_attach: when true, volume can be attached to more than one
235 server (default False)
238 self.name = kwargs.get('name')
239 self.description = kwargs.get('description')
240 self.size = int(kwargs.get('size', 1))
241 self.image_name = kwargs.get('image_name')
242 self.type_name = kwargs.get('type_name')
243 self.availability_zone = kwargs.get('availability_zone')
245 if kwargs.get('availability_zone'):
246 self.multi_attach = bool(kwargs.get('availability_zone'))
248 self.multi_attach = False
251 raise VolumeSettingsError("The attribute name is required")
254 class VolumeSettingsError(Exception):
256 Exception to be thrown when an volume settings are incorrect
259 def __init__(self, message):
260 Exception.__init__(self, message)
263 class VolumeCreationError(Exception):
265 Exception to be thrown when an volume cannot be created
268 def __init__(self, message):
269 Exception.__init__(self, message)