Refactoring of VolumeSettings to extend VolumeConfig
[snaps.git] / snaps / openstack / create_volume.py
1 # Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
2 #                    and others.  All rights reserved.
3 #
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:
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
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.
15
16 import logging
17 import time
18
19 from cinderclient.exceptions import NotFound
20
21 from snaps.config.volume import VolumeConfig
22 from snaps.openstack.openstack_creator import OpenStackVolumeObject
23 from snaps.openstack.utils import cinder_utils
24
25 __author__ = 'spisarski'
26
27 logger = logging.getLogger('create_volume')
28
29 VOLUME_ACTIVE_TIMEOUT = 300
30 VOLUME_DELETE_TIMEOUT = 60
31 POLL_INTERVAL = 3
32 STATUS_ACTIVE = 'available'
33 STATUS_IN_USE = 'in-use'
34 STATUS_FAILED = 'error'
35 STATUS_DELETED = 'deleted'
36
37
38 class OpenStackVolume(OpenStackVolumeObject):
39     """
40     Class responsible for managing an volume in OpenStack
41     """
42
43     def __init__(self, os_creds, volume_settings):
44         """
45         Constructor
46         :param os_creds: The OpenStack connection credentials
47         :param volume_settings: The volume settings
48         :return:
49         """
50         super(self.__class__, self).__init__(os_creds)
51
52         self.volume_settings = volume_settings
53         self.__volume = None
54
55     def initialize(self):
56         """
57         Loads the existing Volume
58         :return: The Volume domain object or None
59         """
60         super(self.__class__, self).initialize()
61
62         self.__volume = cinder_utils.get_volume(
63             self._cinder, volume_settings=self.volume_settings)
64         return self.__volume
65
66     def create(self, block=False):
67         """
68         Creates the volume in OpenStack if it does not already exist and
69         returns the domain Volume object
70         :return: The Volume domain object or None
71         """
72         self.initialize()
73
74         if not self.__volume:
75             self.__volume = cinder_utils.create_volume(
76                 self._cinder, self.volume_settings)
77
78             logger.info(
79                 'Created volume with name - %s', self.volume_settings.name)
80             if self.__volume:
81                 if block:
82                     if self.volume_active(block=True):
83                         logger.info('Volume is now active with name - %s',
84                                     self.volume_settings.name)
85                         return self.__volume
86                     else:
87                         raise VolumeCreationError(
88                             'Volume was not created or activated in the '
89                             'alloted amount of time')
90         else:
91             logger.info('Did not create volume due to cleanup mode')
92
93         return self.__volume
94
95     def clean(self):
96         """
97         Cleanse environment of all artifacts
98         :return: void
99         """
100         if self.__volume:
101             try:
102                 if self.volume_active():
103                     cinder_utils.delete_volume(self._cinder, self.__volume)
104                 else:
105                     logger.warn('Timeout waiting to delete volume %s',
106                                 self.__volume.name)
107             except NotFound:
108                 pass
109
110             try:
111                 if self.volume_deleted(block=True):
112                     logger.info(
113                         'Volume has been properly deleted with name - %s',
114                         self.volume_settings.name)
115                     self.__vm = None
116                 else:
117                     logger.error(
118                         'Volume not deleted within the timeout period of %s '
119                         'seconds', VOLUME_DELETE_TIMEOUT)
120             except Exception as e:
121                 logger.error(
122                     'Unexpected error while checking VM instance status - %s',
123                     e)
124
125         self.__volume = None
126
127     def get_volume(self):
128         """
129         Returns the domain Volume object as it was populated when create() was
130         called
131         :return: the object
132         """
133         return self.__volume
134
135     def volume_active(self, block=False, timeout=VOLUME_ACTIVE_TIMEOUT,
136                       poll_interval=POLL_INTERVAL):
137         """
138         Returns true when the volume status returns the value of
139         expected_status_code
140         :param block: When true, thread will block until active or timeout
141                       value in seconds has been exceeded (False)
142         :param timeout: The timeout value
143         :param poll_interval: The polling interval in seconds
144         :return: T/F
145         """
146         return self._volume_status_check(STATUS_ACTIVE, block, timeout,
147                                          poll_interval)
148
149     def volume_in_use(self):
150         """
151         Returns true when the volume status returns the value of
152         expected_status_code
153         :return: T/F
154         """
155         return self._volume_status_check(STATUS_IN_USE, False, 0, 0)
156
157     def volume_deleted(self, block=False, poll_interval=POLL_INTERVAL):
158         """
159         Returns true when the VM status returns the value of
160         expected_status_code or instance retrieval throws a NotFound exception.
161         :param block: When true, thread will block until active or timeout
162                       value in seconds has been exceeded (False)
163         :param poll_interval: The polling interval in seconds
164         :return: T/F
165         """
166         try:
167             return self._volume_status_check(
168                 STATUS_DELETED, block, VOLUME_DELETE_TIMEOUT, poll_interval)
169         except NotFound as e:
170             logger.debug(
171                 "Volume not found when querying status for %s with message "
172                 "%s", STATUS_DELETED, e)
173             return True
174
175     def _volume_status_check(self, expected_status_code, block, timeout,
176                              poll_interval):
177         """
178         Returns true when the volume status returns the value of
179         expected_status_code
180         :param expected_status_code: instance status evaluated with this string
181                                      value
182         :param block: When true, thread will block until active or timeout
183                       value in seconds has been exceeded (False)
184         :param timeout: The timeout value
185         :param poll_interval: The polling interval in seconds
186         :return: T/F
187         """
188         # sleep and wait for volume status change
189         if block:
190             start = time.time()
191         else:
192             start = time.time() - timeout + 1
193
194         while timeout > time.time() - start:
195             status = self._status(expected_status_code)
196             if status:
197                 logger.debug('Volume is active with name - %s',
198                              self.volume_settings.name)
199                 return True
200
201             logger.debug('Retry querying volume status in %s seconds',
202                          str(poll_interval))
203             time.sleep(poll_interval)
204             logger.debug('Volume status query timeout in %s',
205                          str(timeout - (time.time() - start)))
206
207         logger.error(
208             'Timeout checking for volume status for ' + expected_status_code)
209         return False
210
211     def _status(self, expected_status_code):
212         """
213         Returns True when active else False
214         :param expected_status_code: instance status evaluated with this string
215                                      value
216         :return: T/F
217         """
218         status = cinder_utils.get_volume_status(self._cinder, self.__volume)
219         if not status:
220             logger.warning(
221                 'Cannot volume status for volume with ID - %s',
222                 self.__volume.id)
223             return False
224
225         if status == 'ERROR':
226             raise VolumeCreationError(
227                 'Instance had an error during deployment')
228         logger.debug('Instance status is - ' + status)
229         return status == expected_status_code
230
231
232 class VolumeSettings(VolumeConfig):
233     """
234     Class to hold the configuration settings required for creating OpenStack
235     Volume Type Encryption objects
236     deprecated
237     """
238
239     def __init__(self, **kwargs):
240         from warnings import warn
241         warn('Use snaps.config.volume.VolumeConfig instead',
242              DeprecationWarning)
243         super(self.__class__, self).__init__(**kwargs)
244
245
246 class VolumeCreationError(Exception):
247     """
248     Exception to be thrown when an volume cannot be created
249     """
250
251     def __init__(self, message):
252         Exception.__init__(self, message)