Closing keystone sessions after done with them.
[snaps.git] / snaps / openstack / utils / cinder_utils.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 import logging
16
17 from cinderclient.client import Client
18 from cinderclient.exceptions import NotFound
19
20 from snaps.domain.volume import (
21     QoSSpec, VolumeType, VolumeTypeEncryption, Volume)
22 from snaps.openstack.utils import keystone_utils
23
24 __author__ = 'spisarski'
25
26 logger = logging.getLogger('cinder_utils')
27
28 VERSION_2 = 2
29 VERSION_3 = 3
30
31 """
32 Utilities for basic neutron API calls
33 """
34
35
36 def cinder_client(os_creds, session=None):
37     """
38     Creates and returns a cinder client object
39     :param os_creds: the credentials for connecting to the OpenStack remote API
40     :param session: the keystone session object (optional)
41     :return: the cinder client
42     """
43     if not session:
44         session = keystone_utils.keystone_session(os_creds)
45
46     return Client(version=os_creds.volume_api_version,
47                   session=session,
48                   region_name=os_creds.region_name)
49
50
51 def get_volume(cinder, keystone=None, volume_name=None, volume_settings=None,
52                project_name=None):
53     """
54     Returns an OpenStack volume object for a given name
55     :param cinder: the Cinder client
56     :param keystone: the Keystone client (required if project_name or
57                      volume_settings.project_name is not None
58     :param volume_name: the volume name to lookup
59     :param volume_settings: the volume settings used for lookups
60     :param project_name: the name of the project associated with the volume
61     :return: the volume object or None
62     """
63     if volume_settings:
64         volume_name = volume_settings.name
65
66     volumes = cinder.volumes.list()
67     for os_volume in volumes:
68         if os_volume.name == volume_name:
69             project_id = None
70             if hasattr(os_volume, 'os-vol-tenant-attr:tenant_id'):
71                 project_id = getattr(
72                     os_volume, 'os-vol-tenant-attr:tenant_id')
73
74             if volume_settings and volume_settings.project_name:
75                 project_name = volume_settings.project_name
76
77             if project_name:
78                 project = keystone_utils.get_project_by_id(
79                     keystone, project_id)
80
81                 if project and project.name == project_name:
82                     return __map_os_volume_to_domain(os_volume)
83             else:
84                 return __map_os_volume_to_domain(os_volume)
85
86
87 def __get_os_volume_by_id(cinder, volume_id):
88     """
89     Returns an OpenStack volume object for a given name
90     :param cinder: the Cinder client
91     :param volume_id: the volume ID to lookup
92     :return: the SNAPS-OO Domain Volume object or None
93     """
94     return cinder.volumes.get(volume_id)
95
96
97 def get_volume_by_id(cinder, volume_id):
98     """
99     Returns an OpenStack volume object for a given name
100     :param cinder: the Cinder client
101     :param volume_id: the volume ID to lookup
102     :return: the SNAPS-OO Domain Volume object or None
103     """
104     os_volume = __get_os_volume_by_id(cinder, volume_id)
105     return __map_os_volume_to_domain(os_volume)
106
107
108 def __map_os_volume_to_domain(os_volume):
109     """
110     Returns a SNAPS-OO domain Volume object that is created by an OpenStack
111     Volume object
112     :param os_volume: the OpenStack volume object
113     :return: Volume domain object
114     """
115     project_id = None
116     if hasattr(os_volume, 'os-vol-tenant-attr:tenant_id'):
117         project_id = getattr(
118             os_volume, 'os-vol-tenant-attr:tenant_id')
119
120     return Volume(
121         name=os_volume.name, volume_id=os_volume.id,
122         project_id=project_id, description=os_volume.description,
123         size=os_volume.size, vol_type=os_volume.volume_type,
124         availability_zone=os_volume.availability_zone,
125         multi_attach=os_volume.multiattach,
126         attachments=os_volume.attachments)
127
128
129 def get_volume_status(cinder, volume):
130     """
131     Returns a new OpenStack Volume object for a given OpenStack volume object
132     :param cinder: the Cinder client
133     :param volume: the domain Volume object
134     :return: the OpenStack Volume object
135     """
136     os_volume = cinder.volumes.get(volume.id)
137     return os_volume.status
138
139
140 def create_volume(cinder, keystone, volume_settings):
141     """
142     Creates and returns OpenStack volume object with an external URL
143     :param cinder: the cinder client
144     :param keystone: the keystone client
145     :param volume_settings: the volume settings object
146     :return: the OpenStack volume object
147     :raise Exception if using a file and it cannot be found
148     """
149     project_id = None
150     if volume_settings.project_name:
151         project = keystone_utils.get_project(
152             keystone, project_name=volume_settings.project_name)
153         if project:
154             project_id = project.id
155         else:
156             raise KeystoneUtilsException(
157                 'Project cannot be found with name - '
158                 + volume_settings.project_name)
159     os_volume = cinder.volumes.create(
160         name=volume_settings.name,
161         project_id=project_id,
162         description=volume_settings.description,
163         size=volume_settings.size,
164         imageRef=volume_settings.image_name,
165         volume_type=volume_settings.type_name,
166         availability_zone=volume_settings.availability_zone,
167         multiattach=volume_settings.multi_attach)
168
169     return __map_os_volume_to_domain(os_volume)
170
171
172 def delete_volume(cinder, volume):
173     """
174     Deletes an volume from OpenStack
175     :param cinder: the cinder client
176     :param volume: the volume to delete
177     """
178     logger.info('Deleting volume named - %s', volume.name)
179     return cinder.volumes.delete(volume.id)
180
181
182 def get_volume_type(cinder, volume_type_name=None, volume_type_settings=None):
183     """
184     Returns an OpenStack volume type object for a given name
185     :param cinder: the Cinder client
186     :param volume_type_name: the volume type name to lookup
187     :param volume_type_settings: the volume type settings used for lookups
188     :return: the volume type object or None
189     """
190     if not volume_type_name and not volume_type_settings:
191         return None
192
193     if volume_type_settings:
194         volume_type_name = volume_type_settings.name
195
196     volume_types = cinder.volume_types.list()
197     for vol_type in volume_types:
198         if vol_type.name == volume_type_name:
199             encryption = get_volume_encryption_by_type(cinder, vol_type)
200             return VolumeType(vol_type.name, vol_type.id, vol_type.is_public,
201                               encryption, None)
202
203
204 def __get_os_volume_type_by_id(cinder, volume_type_id):
205     """
206     Returns an OpenStack volume type object for a given name
207     :param cinder: the Cinder client
208     :param volume_type_id: the volume_type ID to lookup
209     :return: the SNAPS-OO Domain Volume object or None
210     """
211     try:
212         return cinder.volume_types.get(volume_type_id)
213     except NotFound:
214         logger.info('Volume with ID [%s] does not exist',
215                     volume_type_id)
216
217
218 def get_volume_type_by_id(cinder, volume_type_id):
219     """
220     Returns an OpenStack volume type object for a given name
221     :param cinder: the Cinder client
222     :param volume_type_id: the volume_type ID to lookup
223     :return: the SNAPS-OO Domain Volume object or None
224     """
225     os_vol_type = __get_os_volume_type_by_id(cinder, volume_type_id)
226     if os_vol_type:
227         temp_vol_type = VolumeType(os_vol_type.name, os_vol_type.id,
228                                    os_vol_type.is_public, None, None)
229         encryption = get_volume_encryption_by_type(cinder, temp_vol_type)
230
231         qos_spec = None
232         if os_vol_type.qos_specs_id:
233             qos_spec = get_qos_by_id(cinder, os_vol_type.qos_specs_id)
234
235         return VolumeType(os_vol_type.name, os_vol_type.id,
236                           os_vol_type.is_public, encryption, qos_spec)
237
238
239 def create_volume_type(cinder, type_settings):
240     """
241     Creates and returns OpenStack volume type object with an external URL
242     :param cinder: the cinder client
243     :param type_settings: the volume type settings object
244     :return: the volume type domain object
245     :raise Exception if using a file and it cannot be found
246     """
247     vol_type = cinder.volume_types.create(
248         type_settings.name, type_settings.description,
249         type_settings.public)
250
251     vol_encryption = None
252     if type_settings.encryption:
253         try:
254             vol_encryption = create_volume_encryption(
255                 cinder, vol_type, type_settings.encryption)
256         except Exception as e:
257             logger.warn('Error creating volume encryption - %s', e)
258
259     qos_spec = None
260     if type_settings.qos_spec_name:
261         try:
262             qos_spec = get_qos(cinder, qos_name=type_settings.qos_spec_name)
263             cinder.qos_specs.associate(qos_spec, vol_type.id)
264         except NotFound as e:
265             logger.warn('Unable to locate qos_spec named %s - %s',
266                         type_settings.qos_spec_name, e)
267
268     return VolumeType(vol_type.name, vol_type.id, vol_type.is_public,
269                       vol_encryption, qos_spec)
270
271
272 def delete_volume_type(cinder, vol_type):
273     """
274     Deletes an volume from OpenStack
275     :param cinder: the cinder client
276     :param vol_type: the VolumeType domain object
277     """
278     logger.info('Deleting volume named - %s', vol_type.name)
279     cinder.volume_types.delete(vol_type.id)
280
281
282 def get_volume_encryption_by_type(cinder, volume_type):
283     """
284     Returns an OpenStack volume type object for a given name
285     :param cinder: the Cinder client
286     :param volume_type: the VolumeType domain object
287     :return: the VolumeEncryption domain object or None
288     """
289     os_vol_type = __get_os_volume_type_by_id(cinder, volume_type.id)
290     encryption = cinder.volume_encryption_types.get(os_vol_type)
291     if hasattr(encryption, 'encryption_id'):
292         cipher = None
293         if hasattr(encryption, 'cipher'):
294             cipher = encryption.cipher
295         key_size = None
296         if hasattr(encryption, 'key_size'):
297             key_size = encryption.key_size
298         return VolumeTypeEncryption(
299             encryption.encryption_id, encryption.volume_type_id,
300             encryption.control_location, encryption.provider, cipher, key_size)
301
302
303 def create_volume_encryption(cinder, volume_type, encryption_settings):
304     """
305     Creates and returns OpenStack volume type object with an external URL
306     :param cinder: the cinder client
307     :param volume_type: the VolumeType object to associate the encryption
308     :param encryption_settings: the volume type encryption settings object
309     :return: the VolumeTypeEncryption domain object
310     """
311     specs = {'name': encryption_settings.name,
312              'provider': encryption_settings.provider_class}
313     if encryption_settings.key_size:
314         specs['key_size'] = encryption_settings.key_size
315     if encryption_settings.provider_class:
316         specs['provider_class'] = encryption_settings.provider_class
317     if encryption_settings.control_location:
318         specs['control_location'] = encryption_settings.control_location.value
319     if encryption_settings.cipher:
320         specs['cipher'] = encryption_settings.cipher
321
322     encryption = cinder.volume_encryption_types.create(volume_type.id, specs)
323
324     cipher = None
325     if hasattr(encryption, 'cipher'):
326         cipher = encryption.cipher
327     key_size = None
328     if hasattr(encryption, 'key_size'):
329         key_size = encryption.key_size
330     return VolumeTypeEncryption(
331         encryption.encryption_id, encryption.volume_type_id,
332         encryption.control_location, encryption.provider, cipher, key_size)
333
334
335 def delete_volume_type_encryption(cinder, vol_type):
336     """
337     Deletes an volume from OpenStack
338     :param cinder: the cinder client
339     :param vol_type: the associated VolumeType domain object
340     """
341     logger.info('Deleting volume encryption for volume type - %s',
342                 vol_type.name)
343     os_vol_type = __get_os_volume_type_by_id(cinder, vol_type.id)
344     cinder.volume_encryption_types.delete(os_vol_type)
345
346
347 def __get_os_qos(cinder, qos_name=None, qos_settings=None):
348     """
349     Returns an OpenStack QoS object for a given name
350     :param cinder: the Cinder client
351     :param qos_name: the qos name to lookup
352     :param qos_settings: the qos settings used for lookups
353     :return: the qos object or None
354     """
355     if not qos_name and not qos_settings:
356         return None
357
358     if qos_settings:
359         qos_name = qos_settings.name
360
361     qoss = cinder.qos_specs.list()
362     for qos in qoss:
363         if qos.name == qos_name:
364             return qos
365
366
367 def get_qos(cinder, qos_name=None, qos_settings=None):
368     """
369     Returns an OpenStack QoS object for a given name
370     :param cinder: the Cinder client
371     :param qos_name: the qos name to lookup
372     :param qos_settings: the qos settings used for lookups
373     :return: the qos object or None
374     """
375     os_qos = __get_os_qos(cinder, qos_name, qos_settings)
376     if os_qos:
377         return QoSSpec(name=os_qos.name, spec_id=os_qos.id,
378                        consumer=os_qos.consumer)
379
380
381 def get_qos_by_id(cinder, qos_id):
382     """
383     Returns an OpenStack qos object for a given name
384     :param cinder: the Cinder client
385     :param qos_id: the qos ID to lookup
386     :return: the SNAPS-OO Domain Volume object or None
387     """
388     qos = cinder.qos_specs.get(qos_id)
389     return QoSSpec(name=qos.name, spec_id=qos.id, consumer=qos.consumer)
390
391
392 def create_qos(cinder, qos_settings):
393     """
394     Creates and returns OpenStack qos object with an external URL
395     :param cinder: the cinder client
396     :param qos_settings: the qos settings object
397     :return: the qos domain object
398     :raise Exception if using a file and it cannot be found
399     """
400     specs = qos_settings.specs
401     specs['consumer'] = qos_settings.consumer.value
402     qos = cinder.qos_specs.create(qos_settings.name, qos_settings.specs)
403     return QoSSpec(name=qos.name, spec_id=qos.id, consumer=qos.consumer)
404
405
406 def delete_qos(cinder, qos):
407     """
408     Deletes an QoS from OpenStack
409     :param cinder: the cinder client
410     :param qos: the qos domain object to delete
411     """
412     logger.info('Deleting QoS named - %s', qos.name)
413     cinder.qos_specs.delete(qos.id)
414
415
416 class KeystoneUtilsException(Exception):
417     """
418     Exception when calls to the Keystone client cannot be served properly
419     """