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