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_IN_USE = 'in-use'
33 STATUS_FAILED = 'error'
34 STATUS_DELETED = 'deleted'
37 class OpenStackVolume(OpenStackVolumeObject):
39 Class responsible for managing an volume in OpenStack
42 def __init__(self, os_creds, volume_settings):
45 :param os_creds: The OpenStack connection credentials
46 :param volume_settings: The volume settings
49 super(self.__class__, self).__init__(os_creds)
51 self.volume_settings = volume_settings
56 Loads the existing Volume
57 :return: The Volume domain object or None
59 super(self.__class__, self).initialize()
61 self.__volume = cinder_utils.get_volume(
62 self._cinder, volume_settings=self.volume_settings)
65 def create(self, block=False):
67 Creates the volume in OpenStack if it does not already exist and
68 returns the domain Volume object
69 :return: The Volume domain object or None
74 self.__volume = cinder_utils.create_volume(
75 self._cinder, self.volume_settings)
78 'Created volume with name - %s', self.volume_settings.name)
81 if self.volume_active(block=True):
82 logger.info('Volume is now active with name - %s',
83 self.volume_settings.name)
86 raise VolumeCreationError(
87 'Volume was not created or activated in the '
88 'alloted amount of time')
90 logger.info('Did not create volume due to cleanup mode')
96 Cleanse environment of all artifacts
101 if self.volume_active():
102 cinder_utils.delete_volume(self._cinder, self.__volume)
104 logger.warn('Timeout waiting to delete volume %s',
110 if self.volume_deleted(block=True):
112 'Volume has been properly deleted with name - %s',
113 self.volume_settings.name)
117 'Volume not deleted within the timeout period of %s '
118 'seconds', VOLUME_DELETE_TIMEOUT)
119 except Exception as e:
121 'Unexpected error while checking VM instance status - %s',
126 def get_volume(self):
128 Returns the domain Volume object as it was populated when create() was
134 def volume_active(self, block=False, timeout=VOLUME_ACTIVE_TIMEOUT,
135 poll_interval=POLL_INTERVAL):
137 Returns true when the volume status returns the value of
139 :param block: When true, thread will block until active or timeout
140 value in seconds has been exceeded (False)
141 :param timeout: The timeout value
142 :param poll_interval: The polling interval in seconds
145 return self._volume_status_check(STATUS_ACTIVE, block, timeout,
148 def volume_in_use(self):
150 Returns true when the volume status returns the value of
154 return self._volume_status_check(STATUS_IN_USE, False, 0, 0)
156 def volume_deleted(self, block=False, poll_interval=POLL_INTERVAL):
158 Returns true when the VM status returns the value of
159 expected_status_code or instance retrieval throws a NotFound exception.
160 :param block: When true, thread will block until active or timeout
161 value in seconds has been exceeded (False)
162 :param poll_interval: The polling interval in seconds
166 return self._volume_status_check(
167 STATUS_DELETED, block, VOLUME_DELETE_TIMEOUT, poll_interval)
168 except NotFound as e:
170 "Volume not found when querying status for %s with message "
171 "%s", STATUS_DELETED, e)
174 def _volume_status_check(self, expected_status_code, block, timeout,
177 Returns true when the volume status returns the value of
179 :param expected_status_code: instance status evaluated with this string
181 :param block: When true, thread will block until active or timeout
182 value in seconds has been exceeded (False)
183 :param timeout: The timeout value
184 :param poll_interval: The polling interval in seconds
187 # sleep and wait for volume status change
191 start = time.time() - timeout + 1
193 while timeout > time.time() - start:
194 status = self._status(expected_status_code)
196 logger.debug('Volume is active with name - %s',
197 self.volume_settings.name)
200 logger.debug('Retry querying volume status in %s seconds',
202 time.sleep(poll_interval)
203 logger.debug('Volume status query timeout in %s',
204 str(timeout - (time.time() - start)))
207 'Timeout checking for volume status for ' + expected_status_code)
210 def _status(self, expected_status_code):
212 Returns True when active else False
213 :param expected_status_code: instance status evaluated with this string
217 status = cinder_utils.get_volume_status(self._cinder, self.__volume)
220 'Cannot volume status for volume with ID - %s',
224 if status == 'ERROR':
225 raise VolumeCreationError(
226 'Instance had an error during deployment')
227 logger.debug('Instance status is - ' + status)
228 return status == expected_status_code
231 class VolumeSettings:
232 def __init__(self, **kwargs):
235 :param name: the volume's name (required)
236 :param description: the volume's name (optional)
237 :param size: the volume's size in GB (default 1)
238 :param image_name: when a glance image is used for the image source
240 :param type_name: the associated volume's type name (optional)
241 :param availability_zone: the name of the compute server on which to
242 deploy the volume (optional)
243 :param multi_attach: when true, volume can be attached to more than one
244 server (default False)
247 self.name = kwargs.get('name')
248 self.description = kwargs.get('description')
249 self.size = int(kwargs.get('size', 1))
250 self.image_name = kwargs.get('image_name')
251 self.type_name = kwargs.get('type_name')
252 self.availability_zone = kwargs.get('availability_zone')
254 if kwargs.get('availability_zone'):
255 self.multi_attach = bool(kwargs.get('availability_zone'))
257 self.multi_attach = False
260 raise VolumeSettingsError("The attribute name is required")
263 class VolumeSettingsError(Exception):
265 Exception to be thrown when an volume settings are incorrect
268 def __init__(self, message):
269 Exception.__init__(self, message)
272 class VolumeCreationError(Exception):
274 Exception to be thrown when an volume cannot be created
277 def __init__(self, message):
278 Exception.__init__(self, message)