Replace neutron get network id with shade.
[yardstick.git] / yardstick / common / openstack_utils.py
1 ##############################################################################
2 # Copyright (c) 2016 Huawei Technologies Co.,Ltd and others.
3 #
4 # All rights reserved. This program and the accompanying materials
5 # are made available under the terms of the Apache License, Version 2.0
6 # which accompanies this distribution, and is available at
7 # http://www.apache.org/licenses/LICENSE-2.0
8 ##############################################################################
9
10 import os
11 import time
12 import sys
13 import logging
14
15 from keystoneauth1 import loading
16 from keystoneauth1 import session
17 import shade
18
19 from cinderclient import client as cinderclient
20 from novaclient import client as novaclient
21 from glanceclient import client as glanceclient
22 from neutronclient.neutron import client as neutronclient
23
24
25 log = logging.getLogger(__name__)
26
27 DEFAULT_HEAT_API_VERSION = '1'
28 DEFAULT_API_VERSION = '2'
29
30
31 # *********************************************
32 #   CREDENTIALS
33 # *********************************************
34 def get_credentials():
35     """Returns a creds dictionary filled with parsed from env"""
36     creds = {}
37
38     keystone_api_version = os.getenv('OS_IDENTITY_API_VERSION')
39
40     if keystone_api_version is None or keystone_api_version == '2':
41         keystone_v3 = False
42         tenant_env = 'OS_TENANT_NAME'
43         tenant = 'tenant_name'
44     else:
45         keystone_v3 = True
46         tenant_env = 'OS_PROJECT_NAME'
47         tenant = 'project_name'
48
49     # The most common way to pass these info to the script is to do it
50     # through environment variables.
51     creds.update({
52         "username": os.environ.get("OS_USERNAME"),
53         "password": os.environ.get("OS_PASSWORD"),
54         "auth_url": os.environ.get("OS_AUTH_URL"),
55         tenant: os.environ.get(tenant_env)
56     })
57
58     if keystone_v3:
59         if os.getenv('OS_USER_DOMAIN_NAME') is not None:
60             creds.update({
61                 "user_domain_name": os.getenv('OS_USER_DOMAIN_NAME')
62             })
63         if os.getenv('OS_PROJECT_DOMAIN_NAME') is not None:
64             creds.update({
65                 "project_domain_name": os.getenv('OS_PROJECT_DOMAIN_NAME')
66             })
67
68     return creds
69
70
71 def get_session_auth():
72     loader = loading.get_plugin_loader('password')
73     creds = get_credentials()
74     auth = loader.load_from_options(**creds)
75     return auth
76
77
78 def get_session():
79     auth = get_session_auth()
80     try:
81         cacert = os.environ['OS_CACERT']
82     except KeyError:
83         return session.Session(auth=auth)
84     else:
85         insecure = os.getenv('OS_INSECURE', '').lower() == 'true'
86         cacert = False if insecure else cacert
87         return session.Session(auth=auth, verify=cacert)
88
89
90 def get_endpoint(service_type, endpoint_type='publicURL'):
91     auth = get_session_auth()
92     # for multi-region, we need to specify region
93     # when finding the endpoint
94     return get_session().get_endpoint(auth=auth,
95                                       service_type=service_type,
96                                       endpoint_type=endpoint_type,
97                                       region_name=os.environ.get(
98                                           "OS_REGION_NAME"))
99
100
101 # *********************************************
102 #   CLIENTS
103 # *********************************************
104 def get_heat_api_version():     # pragma: no cover
105     try:
106         api_version = os.environ['HEAT_API_VERSION']
107     except KeyError:
108         return DEFAULT_HEAT_API_VERSION
109     else:
110         log.info("HEAT_API_VERSION is set in env as '%s'", api_version)
111         return api_version
112
113
114 def get_cinder_client_version():      # pragma: no cover
115     try:
116         api_version = os.environ['OS_VOLUME_API_VERSION']
117     except KeyError:
118         return DEFAULT_API_VERSION
119     else:
120         log.info("OS_VOLUME_API_VERSION is set in env as '%s'", api_version)
121         return api_version
122
123
124 def get_cinder_client():      # pragma: no cover
125     sess = get_session()
126     return cinderclient.Client(get_cinder_client_version(), session=sess)
127
128
129 def get_nova_client_version():      # pragma: no cover
130     try:
131         api_version = os.environ['OS_COMPUTE_API_VERSION']
132     except KeyError:
133         return DEFAULT_API_VERSION
134     else:
135         log.info("OS_COMPUTE_API_VERSION is set in env as '%s'", api_version)
136         return api_version
137
138
139 def get_nova_client():      # pragma: no cover
140     sess = get_session()
141     return novaclient.Client(get_nova_client_version(), session=sess)
142
143
144 def get_neutron_client_version():   # pragma: no cover
145     try:
146         api_version = os.environ['OS_NETWORK_API_VERSION']
147     except KeyError:
148         return DEFAULT_API_VERSION
149     else:
150         log.info("OS_NETWORK_API_VERSION is set in env as '%s'", api_version)
151         return api_version
152
153
154 def get_neutron_client():   # pragma: no cover
155     sess = get_session()
156     return neutronclient.Client(get_neutron_client_version(), session=sess)
157
158
159 def get_glance_client_version():    # pragma: no cover
160     try:
161         api_version = os.environ['OS_IMAGE_API_VERSION']
162     except KeyError:
163         return DEFAULT_API_VERSION
164     else:
165         log.info("OS_IMAGE_API_VERSION is set in env as '%s'", api_version)
166         return api_version
167
168
169 def get_glance_client():    # pragma: no cover
170     sess = get_session()
171     return glanceclient.Client(get_glance_client_version(), session=sess)
172
173
174 def get_shade_client():
175     return shade.openstack_cloud()
176
177 # *********************************************
178 #   NOVA
179 # *********************************************
180 def get_instances(nova_client):
181     try:
182         return nova_client.servers.list(search_opts={'all_tenants': 1})
183     except Exception:  # pylint: disable=broad-except
184         log.exception("Error [get_instances(nova_client)]")
185
186
187 def get_instance_status(nova_client, instance):     # pragma: no cover
188     try:
189         return nova_client.servers.get(instance.id).status
190     except Exception:  # pylint: disable=broad-except
191         log.exception("Error [get_instance_status(nova_client)]")
192
193
194 def get_instance_by_name(nova_client, instance_name):   # pragma: no cover
195     try:
196         return nova_client.servers.find(name=instance_name)
197     except Exception:  # pylint: disable=broad-except
198         log.exception("Error [get_instance_by_name(nova_client, '%s')]",
199                       instance_name)
200
201
202 def get_aggregates(nova_client):    # pragma: no cover
203     try:
204         return nova_client.aggregates.list()
205     except Exception:  # pylint: disable=broad-except
206         log.exception("Error [get_aggregates(nova_client)]")
207
208
209 def get_availability_zones(nova_client):    # pragma: no cover
210     try:
211         return nova_client.availability_zones.list()
212     except Exception:  # pylint: disable=broad-except
213         log.exception("Error [get_availability_zones(nova_client)]")
214
215
216 def get_availability_zone_names(nova_client):   # pragma: no cover
217     try:
218         return [az.zoneName for az in get_availability_zones(nova_client)]
219     except Exception:  # pylint: disable=broad-except
220         log.exception("Error [get_availability_zone_names(nova_client)]")
221
222
223 def create_aggregate(nova_client, aggregate_name, av_zone):  # pragma: no cover
224     try:
225         nova_client.aggregates.create(aggregate_name, av_zone)
226     except Exception:  # pylint: disable=broad-except
227         log.exception("Error [create_aggregate(nova_client, %s, %s)]",
228                       aggregate_name, av_zone)
229         return False
230     else:
231         return True
232
233
234 def get_aggregate_id(nova_client, aggregate_name):      # pragma: no cover
235     try:
236         aggregates = get_aggregates(nova_client)
237         _id = next((ag.id for ag in aggregates if ag.name == aggregate_name))
238     except Exception:  # pylint: disable=broad-except
239         log.exception("Error [get_aggregate_id(nova_client, %s)]",
240                       aggregate_name)
241     else:
242         return _id
243
244
245 def add_host_to_aggregate(nova_client, aggregate_name,
246                           compute_host):    # pragma: no cover
247     try:
248         aggregate_id = get_aggregate_id(nova_client, aggregate_name)
249         nova_client.aggregates.add_host(aggregate_id, compute_host)
250     except Exception:  # pylint: disable=broad-except
251         log.exception("Error [add_host_to_aggregate(nova_client, %s, %s)]",
252                       aggregate_name, compute_host)
253         return False
254     else:
255         return True
256
257
258 def create_aggregate_with_host(nova_client, aggregate_name, av_zone,
259                                compute_host):    # pragma: no cover
260     try:
261         create_aggregate(nova_client, aggregate_name, av_zone)
262         add_host_to_aggregate(nova_client, aggregate_name, compute_host)
263     except Exception:  # pylint: disable=broad-except
264         log.exception("Error [create_aggregate_with_host("
265                       "nova_client, %s, %s, %s)]",
266                       aggregate_name, av_zone, compute_host)
267         return False
268     else:
269         return True
270
271
272 def create_keypair(name, key_path=None):    # pragma: no cover
273     try:
274         with open(key_path) as fpubkey:
275             keypair = get_nova_client().keypairs.create(name=name, public_key=fpubkey.read())
276             return keypair
277     except Exception:  # pylint: disable=broad-except
278         log.exception("Error [create_keypair(nova_client)]")
279
280
281 def create_instance(json_body):    # pragma: no cover
282     try:
283         return get_nova_client().servers.create(**json_body)
284     except Exception:  # pylint: disable=broad-except
285         log.exception("Error create instance failed")
286         return None
287
288
289 def create_instance_and_wait_for_active(json_body):    # pragma: no cover
290     SLEEP = 3
291     VM_BOOT_TIMEOUT = 180
292     nova_client = get_nova_client()
293     instance = create_instance(json_body)
294     count = VM_BOOT_TIMEOUT / SLEEP
295     for _ in range(count, -1, -1):
296         status = get_instance_status(nova_client, instance)
297         if status.lower() == "active":
298             return instance
299         elif status.lower() == "error":
300             log.error("The instance went to ERROR status.")
301             return None
302         time.sleep(SLEEP)
303     log.error("Timeout booting the instance.")
304     return None
305
306
307 def attach_server_volume(server_id, volume_id, device=None):    # pragma: no cover
308     try:
309         get_nova_client().volumes.create_server_volume(server_id, volume_id, device)
310     except Exception:  # pylint: disable=broad-except
311         log.exception("Error [attach_server_volume(nova_client, '%s', '%s')]",
312                       server_id, volume_id)
313         return False
314     else:
315         return True
316
317
318 def delete_instance(nova_client, instance_id):      # pragma: no cover
319     try:
320         nova_client.servers.force_delete(instance_id)
321     except Exception:  # pylint: disable=broad-except
322         log.exception("Error [delete_instance(nova_client, '%s')]",
323                       instance_id)
324         return False
325     else:
326         return True
327
328
329 def remove_host_from_aggregate(nova_client, aggregate_name,
330                                compute_host):  # pragma: no cover
331     try:
332         aggregate_id = get_aggregate_id(nova_client, aggregate_name)
333         nova_client.aggregates.remove_host(aggregate_id, compute_host)
334     except Exception:  # pylint: disable=broad-except
335         log.exception("Error remove_host_from_aggregate(nova_client, %s, %s)",
336                       aggregate_name, compute_host)
337         return False
338     else:
339         return True
340
341
342 def remove_hosts_from_aggregate(nova_client,
343                                 aggregate_name):   # pragma: no cover
344     aggregate_id = get_aggregate_id(nova_client, aggregate_name)
345     hosts = nova_client.aggregates.get(aggregate_id).hosts
346     assert(
347         all(remove_host_from_aggregate(nova_client, aggregate_name, host)
348             for host in hosts))
349
350
351 def delete_aggregate(nova_client, aggregate_name):  # pragma: no cover
352     try:
353         remove_hosts_from_aggregate(nova_client, aggregate_name)
354         nova_client.aggregates.delete(aggregate_name)
355     except Exception:  # pylint: disable=broad-except
356         log.exception("Error [delete_aggregate(nova_client, %s)]",
357                       aggregate_name)
358         return False
359     else:
360         return True
361
362
363 def get_server_by_name(name):   # pragma: no cover
364     try:
365         return get_nova_client().servers.list(search_opts={'name': name})[0]
366     except IndexError:
367         log.exception('Failed to get nova client')
368         raise
369
370
371 def create_flavor(name, ram, vcpus, disk, **kwargs):   # pragma: no cover
372     try:
373         return get_nova_client().flavors.create(name, ram, vcpus, disk, **kwargs)
374     except Exception:  # pylint: disable=broad-except
375         log.exception("Error [create_flavor(nova_client, %s, %s, %s, %s, %s)]",
376                       name, ram, disk, vcpus, kwargs['is_public'])
377         return None
378
379
380 def get_image_by_name(name):    # pragma: no cover
381     images = get_nova_client().images.list()
382     try:
383         return next((a for a in images if a.name == name))
384     except StopIteration:
385         log.exception('No image matched')
386
387
388 def get_flavor_id(nova_client, flavor_name):    # pragma: no cover
389     flavors = nova_client.flavors.list(detailed=True)
390     flavor_id = ''
391     for f in flavors:
392         if f.name == flavor_name:
393             flavor_id = f.id
394             break
395     return flavor_id
396
397
398 def get_flavor_by_name(name):   # pragma: no cover
399     flavors = get_nova_client().flavors.list()
400     try:
401         return next((a for a in flavors if a.name == name))
402     except StopIteration:
403         log.exception('No flavor matched')
404
405
406 def check_status(status, name, iterations, interval):   # pragma: no cover
407     for _ in range(iterations):
408         try:
409             server = get_server_by_name(name)
410         except IndexError:
411             log.error('Cannot found %s server', name)
412             raise
413
414         if server.status == status:
415             return True
416
417         time.sleep(interval)
418     return False
419
420
421 def delete_flavor(flavor_id):    # pragma: no cover
422     try:
423         get_nova_client().flavors.delete(flavor_id)
424     except Exception:  # pylint: disable=broad-except
425         log.exception("Error [delete_flavor(nova_client, %s)]", flavor_id)
426         return False
427     else:
428         return True
429
430
431 def delete_keypair(nova_client, key):     # pragma: no cover
432     try:
433         nova_client.keypairs.delete(key=key)
434         return True
435     except Exception:  # pylint: disable=broad-except
436         log.exception("Error [delete_keypair(nova_client)]")
437         return False
438
439
440 # *********************************************
441 #   NEUTRON
442 # *********************************************
443 def get_network_id(shade_client, network_name):
444     networks = shade_client.list_networks({'name': network_name})
445     if networks:
446         return networks[0]['id']
447
448
449 def create_neutron_net(neutron_client, json_body):      # pragma: no cover
450     try:
451         network = neutron_client.create_network(body=json_body)
452         return network['network']['id']
453     except Exception:  # pylint: disable=broad-except
454         log.error("Error [create_neutron_net(neutron_client)]")
455         raise Exception("operation error")
456
457
458 def delete_neutron_net(neutron_client, network_id):      # pragma: no cover
459     try:
460         neutron_client.delete_network(network_id)
461         return True
462     except Exception:  # pylint: disable=broad-except
463         log.error("Error [delete_neutron_net(neutron_client, '%s')]",
464                   network_id)
465         return False
466
467
468 def create_neutron_subnet(neutron_client, json_body):      # pragma: no cover
469     try:
470         subnet = neutron_client.create_subnet(body=json_body)
471         return subnet['subnets'][0]['id']
472     except Exception:  # pylint: disable=broad-except
473         log.error("Error [create_neutron_subnet")
474         raise Exception("operation error")
475
476
477 def create_neutron_router(neutron_client, json_body):      # pragma: no cover
478     try:
479         router = neutron_client.create_router(json_body)
480         return router['router']['id']
481     except Exception:  # pylint: disable=broad-except
482         log.error("Error [create_neutron_router(neutron_client)]")
483         raise Exception("operation error")
484
485
486 def delete_neutron_router(neutron_client, router_id):      # pragma: no cover
487     try:
488         neutron_client.delete_router(router=router_id)
489         return True
490     except Exception:  # pylint: disable=broad-except
491         log.error("Error [delete_neutron_router(neutron_client, '%s')]",
492                   router_id)
493         return False
494
495
496 def remove_gateway_router(neutron_client, router_id):      # pragma: no cover
497     try:
498         neutron_client.remove_gateway_router(router_id)
499         return True
500     except Exception:  # pylint: disable=broad-except
501         log.error("Error [remove_gateway_router(neutron_client, '%s')]",
502                   router_id)
503         return False
504
505
506 def remove_interface_router(neutron_client, router_id, subnet_id,
507                             **json_body):      # pragma: no cover
508     json_body.update({"subnet_id": subnet_id})
509     try:
510         neutron_client.remove_interface_router(router=router_id,
511                                                body=json_body)
512         return True
513     except Exception:  # pylint: disable=broad-except
514         log.error("Error [remove_interface_router(neutron_client, '%s', "
515                   "'%s')]", router_id, subnet_id)
516         return False
517
518
519 def create_floating_ip(neutron_client, extnet_id):      # pragma: no cover
520     props = {'floating_network_id': extnet_id}
521     try:
522         ip_json = neutron_client.create_floatingip({'floatingip': props})
523         fip_addr = ip_json['floatingip']['floating_ip_address']
524         fip_id = ip_json['floatingip']['id']
525     except Exception:  # pylint: disable=broad-except
526         log.error("Error [create_floating_ip(neutron_client)]")
527         return None
528     return {'fip_addr': fip_addr, 'fip_id': fip_id}
529
530
531 def delete_floating_ip(nova_client, floatingip_id):      # pragma: no cover
532     try:
533         nova_client.floating_ips.delete(floatingip_id)
534         return True
535     except Exception:  # pylint: disable=broad-except
536         log.error("Error [delete_floating_ip(nova_client, '%s')]",
537                   floatingip_id)
538         return False
539
540
541 def get_security_groups(neutron_client):      # pragma: no cover
542     try:
543         security_groups = neutron_client.list_security_groups()[
544             'security_groups']
545         return security_groups
546     except Exception:  # pylint: disable=broad-except
547         log.error("Error [get_security_groups(neutron_client)]")
548         return None
549
550
551 def get_security_group_id(neutron_client, sg_name):      # pragma: no cover
552     security_groups = get_security_groups(neutron_client)
553     id = ''
554     for sg in security_groups:
555         if sg['name'] == sg_name:
556             id = sg['id']
557             break
558     return id
559
560
561 def create_security_group(neutron_client, sg_name, sg_description):      # pragma: no cover
562     json_body = {'security_group': {'name': sg_name,
563                                     'description': sg_description}}
564     try:
565         secgroup = neutron_client.create_security_group(json_body)
566         return secgroup['security_group']
567     except Exception:  # pylint: disable=broad-except
568         log.error("Error [create_security_group(neutron_client, '%s', "
569                   "'%s')]", sg_name, sg_description)
570         return None
571
572
573 def create_secgroup_rule(neutron_client, sg_id, direction, protocol,
574                          port_range_min=None, port_range_max=None,
575                          **json_body):      # pragma: no cover
576     # We create a security group in 2 steps
577     # 1 - we check the format and set the json body accordingly
578     # 2 - we call neturon client to create the security group
579
580     # Format check
581     json_body.update({'security_group_rule': {'direction': direction,
582                      'security_group_id': sg_id, 'protocol': protocol}})
583     # parameters may be
584     # - both None => we do nothing
585     # - both Not None => we add them to the json description
586     # but one cannot be None is the other is not None
587     if (port_range_min is not None and port_range_max is not None):
588         # add port_range in json description
589         json_body['security_group_rule']['port_range_min'] = port_range_min
590         json_body['security_group_rule']['port_range_max'] = port_range_max
591         log.debug("Security_group format set (port range included)")
592     else:
593         # either both port range are set to None => do nothing
594         # or one is set but not the other => log it and return False
595         if port_range_min is None and port_range_max is None:
596             log.debug("Security_group format set (no port range mentioned)")
597         else:
598             log.error("Bad security group format."
599                       "One of the port range is not properly set:"
600                       "range min: %s, range max: %s", port_range_min,
601                       port_range_max)
602             return False
603
604     # Create security group using neutron client
605     try:
606         neutron_client.create_security_group_rule(json_body)
607         return True
608     except Exception:  # pylint: disable=broad-except
609         log.exception("Impossible to create_security_group_rule,"
610                       "security group rule probably already exists")
611         return False
612
613
614 def create_security_group_full(neutron_client,
615                                sg_name, sg_description):      # pragma: no cover
616     sg_id = get_security_group_id(neutron_client, sg_name)
617     if sg_id != '':
618         log.info("Using existing security group '%s'...", sg_name)
619     else:
620         log.info("Creating security group  '%s'...", sg_name)
621         SECGROUP = create_security_group(neutron_client,
622                                          sg_name,
623                                          sg_description)
624         if not SECGROUP:
625             log.error("Failed to create the security group...")
626             return None
627
628         sg_id = SECGROUP['id']
629
630         log.debug("Security group '%s' with ID=%s created successfully.",
631                   SECGROUP['name'], sg_id)
632
633         log.debug("Adding ICMP rules in security group '%s'...", sg_name)
634         if not create_secgroup_rule(neutron_client, sg_id,
635                                     'ingress', 'icmp'):
636             log.error("Failed to create the security group rule...")
637             return None
638
639         log.debug("Adding SSH rules in security group '%s'...", sg_name)
640         if not create_secgroup_rule(
641                 neutron_client, sg_id, 'ingress', 'tcp', '22', '22'):
642             log.error("Failed to create the security group rule...")
643             return None
644
645         if not create_secgroup_rule(
646                 neutron_client, sg_id, 'egress', 'tcp', '22', '22'):
647             log.error("Failed to create the security group rule...")
648             return None
649     return sg_id
650
651
652 # *********************************************
653 #   GLANCE
654 # *********************************************
655 def get_image_id(glance_client, image_name):    # pragma: no cover
656     images = glance_client.images.list()
657     return next((i.id for i in images if i.name == image_name), None)
658
659
660 def create_image(glance_client, image_name, file_path, disk_format,
661                  container_format, min_disk, min_ram, protected, tag,
662                  public, **kwargs):    # pragma: no cover
663     if not os.path.isfile(file_path):
664         log.error("Error: file %s does not exist.", file_path)
665         return None
666     try:
667         image_id = get_image_id(glance_client, image_name)
668         if image_id is not None:
669             log.info("Image %s already exists.", image_name)
670         else:
671             log.info("Creating image '%s' from '%s'...", image_name, file_path)
672
673             image = glance_client.images.create(name=image_name,
674                                                 visibility=public,
675                                                 disk_format=disk_format,
676                                                 container_format=container_format,
677                                                 min_disk=min_disk,
678                                                 min_ram=min_ram,
679                                                 tags=tag,
680                                                 protected=protected,
681                                                 **kwargs)
682             image_id = image.id
683             with open(file_path) as image_data:
684                 glance_client.images.upload(image_id, image_data)
685         return image_id
686     except Exception:  # pylint: disable=broad-except
687         log.error("Error [create_glance_image(glance_client, '%s', '%s', '%s')]",
688                   image_name, file_path, public)
689         return None
690
691
692 def delete_image(glance_client, image_id):    # pragma: no cover
693     try:
694         glance_client.images.delete(image_id)
695
696     except Exception:  # pylint: disable=broad-except
697         log.exception("Error [delete_flavor(glance_client, %s)]", image_id)
698         return False
699     else:
700         return True
701
702
703 # *********************************************
704 #   CINDER
705 # *********************************************
706 def get_volume_id(volume_name):    # pragma: no cover
707     volumes = get_cinder_client().volumes.list()
708     return next((v.id for v in volumes if v.name == volume_name), None)
709
710
711 def create_volume(cinder_client, volume_name, volume_size,
712                   volume_image=False):    # pragma: no cover
713     try:
714         if volume_image:
715             volume = cinder_client.volumes.create(name=volume_name,
716                                                   size=volume_size,
717                                                   imageRef=volume_image)
718         else:
719             volume = cinder_client.volumes.create(name=volume_name,
720                                                   size=volume_size)
721         return volume
722     except Exception:  # pylint: disable=broad-except
723         log.exception("Error [create_volume(cinder_client, %s)]",
724                       (volume_name, volume_size))
725         return None
726
727
728 def delete_volume(cinder_client, volume_id, forced=False):      # pragma: no cover
729     try:
730         if forced:
731             try:
732                 cinder_client.volumes.detach(volume_id)
733             except Exception:  # pylint: disable=broad-except
734                 log.error(sys.exc_info()[0])
735             cinder_client.volumes.force_delete(volume_id)
736         else:
737             while True:
738                 volume = get_cinder_client().volumes.get(volume_id)
739                 if volume.status.lower() == 'available':
740                     break
741             cinder_client.volumes.delete(volume_id)
742         return True
743     except Exception:  # pylint: disable=broad-except
744         log.exception("Error [delete_volume(cinder_client, '%s')]", volume_id)
745         return False
746
747
748 def detach_volume(server_id, volume_id):      # pragma: no cover
749     try:
750         get_nova_client().volumes.delete_server_volume(server_id, volume_id)
751         return True
752     except Exception:  # pylint: disable=broad-except
753         log.exception("Error [detach_server_volume(nova_client, '%s', '%s')]",
754                       server_id, volume_id)
755         return False