Merge "Auto Generated INFO.yaml file"
[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, self._keystone,
64             volume_settings=self.volume_settings,
65             project_name=self._os_creds.project_name)
66         return self.__volume
67
68     def create(self, block=False):
69         """
70         Creates the volume in OpenStack if it does not already exist and
71         returns the domain Volume object
72         :return: The Volume domain object or None
73         """
74         self.initialize()
75
76         if not self.__volume:
77             self.__volume = cinder_utils.create_volume(
78                 self._cinder, self._keystone, self.volume_settings)
79
80             logger.info(
81                 'Created volume with name - %s', self.volume_settings.name)
82             if self.__volume:
83                 if block:
84                     if self.volume_active(block=True):
85                         logger.info('Volume is now active with name - %s',
86                                     self.volume_settings.name)
87                         return self.__volume
88                     else:
89                         raise VolumeCreationError(
90                             'Volume was not created or activated in the '
91                             'alloted amount of time')
92         else:
93             logger.info('Did not create volume due to cleanup mode')
94
95         return self.__volume
96
97     def clean(self):
98         """
99         Cleanse environment of all artifacts
100         :return: void
101         """
102         if self.__volume:
103             try:
104                 if self.volume_active():
105                     cinder_utils.delete_volume(self._cinder, self.__volume)
106                 else:
107                     logger.warn('Timeout waiting to delete volume %s',
108                                 self.__volume.name)
109             except NotFound:
110                 pass
111
112             try:
113                 if self.volume_deleted(block=True):
114                     logger.info(
115                         'Volume has been properly deleted with name - %s',
116                         self.volume_settings.name)
117                     self.__vm = None
118                 else:
119                     logger.error(
120                         'Volume not deleted within the timeout period of %s '
121                         'seconds', VOLUME_DELETE_TIMEOUT)
122             except Exception as e:
123                 logger.error(
124                     'Unexpected error while checking VM instance status - %s',
125                     e)
126
127         self.__volume = None
128
129         super(self.__class__, self).clean()
130
131     def get_volume(self):
132         """
133         Returns the domain Volume object as it was populated when create() was
134         called
135         :return: the object
136         """
137         return self.__volume
138
139     def volume_active(self, block=False, timeout=VOLUME_ACTIVE_TIMEOUT,
140                       poll_interval=POLL_INTERVAL):
141         """
142         Returns true when the volume status returns the value of
143         expected_status_code
144         :param block: When true, thread will block until active or timeout
145                       value in seconds has been exceeded (False)
146         :param timeout: The timeout value
147         :param poll_interval: The polling interval in seconds
148         :return: T/F
149         """
150         return self._volume_status_check(STATUS_ACTIVE, block, timeout,
151                                          poll_interval)
152
153     def volume_in_use(self):
154         """
155         Returns true when the volume status returns the value of
156         expected_status_code
157         :return: T/F
158         """
159         return self._volume_status_check(STATUS_IN_USE, False, 0, 0)
160
161     def volume_deleted(self, block=False, poll_interval=POLL_INTERVAL):
162         """
163         Returns true when the VM status returns the value of
164         expected_status_code or instance retrieval throws a NotFound exception.
165         :param block: When true, thread will block until active or timeout
166                       value in seconds has been exceeded (False)
167         :param poll_interval: The polling interval in seconds
168         :return: T/F
169         """
170         try:
171             return self._volume_status_check(
172                 STATUS_DELETED, block, VOLUME_DELETE_TIMEOUT, poll_interval)
173         except NotFound as e:
174             logger.debug(
175                 "Volume not found when querying status for %s with message "
176                 "%s", STATUS_DELETED, e)
177             return True
178
179     def _volume_status_check(self, expected_status_code, block, timeout,
180                              poll_interval):
181         """
182         Returns true when the volume status returns the value of
183         expected_status_code
184         :param expected_status_code: instance status evaluated with this string
185                                      value
186         :param block: When true, thread will block until active or timeout
187                       value in seconds has been exceeded (False)
188         :param timeout: The timeout value
189         :param poll_interval: The polling interval in seconds
190         :return: T/F
191         """
192         # sleep and wait for volume status change
193         if block:
194             start = time.time()
195         else:
196             start = time.time() - timeout + 1
197
198         while timeout > time.time() - start:
199             status = self._status(expected_status_code)
200             if status:
201                 logger.debug('Volume is active with name - %s',
202                              self.volume_settings.name)
203                 return True
204
205             logger.debug('Retry querying volume status in %s seconds',
206                          str(poll_interval))
207             time.sleep(poll_interval)
208             logger.debug('Volume status query timeout in %s',
209                          str(timeout - (time.time() - start)))
210
211         logger.error(
212             'Timeout checking for volume status for ' + expected_status_code)
213         return False
214
215     def _status(self, expected_status_code):
216         """
217         Returns True when active else False
218         :param expected_status_code: instance status evaluated with this string
219                                      value
220         :return: T/F
221         """
222         status = cinder_utils.get_volume_status(self._cinder, self.__volume)
223         if not status:
224             logger.warning(
225                 'Cannot volume status for volume with ID - %s',
226                 self.__volume.id)
227             return False
228
229         if status == 'ERROR':
230             raise VolumeCreationError(
231                 'Instance had an error during deployment')
232         logger.debug('Instance status is - ' + status)
233         return status == expected_status_code
234
235
236 class VolumeSettings(VolumeConfig):
237     """
238     Class to hold the configuration settings required for creating OpenStack
239     Volume Type Encryption objects
240     deprecated
241     """
242
243     def __init__(self, **kwargs):
244         from warnings import warn
245         warn('Use snaps.config.volume.VolumeConfig instead',
246              DeprecationWarning)
247         super(self.__class__, self).__init__(**kwargs)
248
249
250 class VolumeCreationError(Exception):
251     """
252     Exception to be thrown when an volume cannot be created
253     """
254
255     def __init__(self, message):
256         Exception.__init__(self, message)