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