blob: c97c1c2c649bcfd34c6f09287b4a1d26728d192b [file] [log] [blame]
Moshe0bb532c2018-02-26 13:39:57 +02001##############################################################################
2# Copyright 2018 EuropeanSoftwareMarketingLtd.
3# ===================================================================
4# Licensed under the ApacheLicense, Version2.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# http://www.apache.org/licenses/LICENSE-2.0
8#
9# software distributed under the License is distributed on an "AS IS" BASIS,
10# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11# See the License for the specific language governing permissions and limitations under
12# the License
13##############################################################################
14# vnftest comment: this is a modified copy of
15# yardstick/common/openstack_utils.py
16
17from __future__ import absolute_import
18
19import os
20import time
21import sys
22import logging
23
24from keystoneauth1 import loading
25from keystoneauth1 import session
26from cinderclient import client as cinderclient
27from novaclient import client as novaclient
28from glanceclient import client as glanceclient
29from neutronclient.neutron import client as neutronclient
30
31log = logging.getLogger(__name__)
32
33DEFAULT_HEAT_API_VERSION = '1'
34DEFAULT_API_VERSION = '2'
35
Moshe05acf082018-03-20 10:51:42 +020036creds = {}
37
Moshe0bb532c2018-02-26 13:39:57 +020038
39# *********************************************
40# CREDENTIALS
41# *********************************************
42def get_credentials():
43 """Returns a creds dictionary filled with parsed from env"""
Moshe05acf082018-03-20 10:51:42 +020044 if len(creds) == 0:
45 # The most common way to pass these info to the script is to do it
46 # through environment variables.
47 keystone_api_version = os.getenv('OS_IDENTITY_API_VERSION')
Moshe0bb532c2018-02-26 13:39:57 +020048
Moshe05acf082018-03-20 10:51:42 +020049 if keystone_api_version is None or keystone_api_version == '2':
50 keystone_v3 = False
51 creds['tenant_name'] = os.environ.get('OS_TENANT_NAME')
52 else:
53 keystone_v3 = True
54 creds['tenant_name'] = os.environ.get('OS_PROJECT_NAME')
55 creds['project_name'] = os.environ.get('OS_PROJECT_NAME')
Moshe0bb532c2018-02-26 13:39:57 +020056
Moshe05acf082018-03-20 10:51:42 +020057 creds["username"] = os.environ.get("OS_USERNAME")
58 creds["password"] = os.environ.get("OS_PASSWORD")
59 creds["auth_url"] = os.environ.get("OS_AUTH_URL")
60 creds["tenant_id"] = os.environ.get("OS_TENANT_ID")
Moshe0bb532c2018-02-26 13:39:57 +020061
Moshe05acf082018-03-20 10:51:42 +020062 if keystone_v3:
63 if os.getenv('OS_USER_DOMAIN_NAME') is not None:
64 creds.update({
65 "user_domain_name": os.getenv('OS_USER_DOMAIN_NAME')
66 })
67 if os.getenv('OS_PROJECT_DOMAIN_NAME') is not None:
68 creds.update({
69 "project_domain_name": os.getenv('OS_PROJECT_DOMAIN_NAME')
70 })
Moshe0bb532c2018-02-26 13:39:57 +020071 return creds
72
73
74def get_session_auth():
75 loader = loading.get_plugin_loader('password')
76 creds = get_credentials()
77 auth = loader.load_from_options(**creds)
78 return auth
79
80
81def get_session():
82 auth = get_session_auth()
83 try:
84 cacert = os.environ['OS_CACERT']
85 except KeyError:
86 return session.Session(auth=auth)
87 else:
88 insecure = os.getenv('OS_INSECURE', '').lower() == 'true'
89 cacert = False if insecure else cacert
90 return session.Session(auth=auth, verify=cacert)
91
92
93def get_endpoint(service_type, endpoint_type='publicURL'):
94 auth = get_session_auth()
95 # for multi-region, we need to specify region
96 # when finding the endpoint
97 return get_session().get_endpoint(auth=auth,
98 service_type=service_type,
99 endpoint_type=endpoint_type,
100 region_name=os.environ.get(
101 "OS_REGION_NAME"))
102
103
104# *********************************************
105# CLIENTS
106# *********************************************
107def get_heat_api_version(): # pragma: no cover
108 try:
109 api_version = os.environ['HEAT_API_VERSION']
110 except KeyError:
111 return DEFAULT_HEAT_API_VERSION
112 else:
113 log.info("HEAT_API_VERSION is set in env as '%s'", api_version)
114 return api_version
115
116
117def get_cinder_client_version(): # pragma: no cover
118 try:
119 api_version = os.environ['OS_VOLUME_API_VERSION']
120 except KeyError:
121 return DEFAULT_API_VERSION
122 else:
123 log.info("OS_VOLUME_API_VERSION is set in env as '%s'", api_version)
124 return api_version
125
126
127def get_cinder_client(): # pragma: no cover
128 sess = get_session()
129 return cinderclient.Client(get_cinder_client_version(), session=sess)
130
131
132def get_nova_client_version(): # pragma: no cover
133 try:
134 api_version = os.environ['OS_COMPUTE_API_VERSION']
135 except KeyError:
136 return DEFAULT_API_VERSION
137 else:
138 log.info("OS_COMPUTE_API_VERSION is set in env as '%s'", api_version)
139 return api_version
140
141
142def get_nova_client(): # pragma: no cover
143 sess = get_session()
144 return novaclient.Client(get_nova_client_version(), session=sess)
145
146
147def get_neutron_client_version(): # pragma: no cover
148 try:
149 api_version = os.environ['OS_NETWORK_API_VERSION']
150 except KeyError:
151 return DEFAULT_API_VERSION
152 else:
153 log.info("OS_NETWORK_API_VERSION is set in env as '%s'", api_version)
154 return api_version
155
156
157def get_neutron_client(): # pragma: no cover
158 sess = get_session()
159 return neutronclient.Client(get_neutron_client_version(), session=sess)
160
161
162def get_glance_client_version(): # pragma: no cover
163 try:
164 api_version = os.environ['OS_IMAGE_API_VERSION']
165 except KeyError:
166 return DEFAULT_API_VERSION
167 else:
168 log.info("OS_IMAGE_API_VERSION is set in env as '%s'", api_version)
169 return api_version
170
171
172def get_glance_client(): # pragma: no cover
173 sess = get_session()
174 return glanceclient.Client(get_glance_client_version(), session=sess)
175
176
177# *********************************************
178# NOVA
179# *********************************************
180def get_instances(nova_client): # pragma: no cover
181 try:
182 return nova_client.servers.list(search_opts={'all_tenants': 1})
183 except Exception:
184 log.exception("Error [get_instances(nova_client)]")
185
186
187def get_instance_status(nova_client, instance): # pragma: no cover
188 try:
189 return nova_client.servers.get(instance.id).status
190 except Exception:
191 log.exception("Error [get_instance_status(nova_client)]")
192
193
194def 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:
198 log.exception("Error [get_instance_by_name(nova_client, '%s')]",
199 instance_name)
200
201
202def get_aggregates(nova_client): # pragma: no cover
203 try:
204 return nova_client.aggregates.list()
205 except Exception:
206 log.exception("Error [get_aggregates(nova_client)]")
207
208
209def get_availability_zones(nova_client): # pragma: no cover
210 try:
211 return nova_client.availability_zones.list()
212 except Exception:
213 log.exception("Error [get_availability_zones(nova_client)]")
214
215
216def 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:
220 log.exception("Error [get_availability_zone_names(nova_client)]")
221
222
223def 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:
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
234def 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:
239 log.exception("Error [get_aggregate_id(nova_client, %s)]",
240 aggregate_name)
241 else:
242 return _id
243
244
245def 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:
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
258def 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:
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
272def create_keypair(nova_client, 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:
278 log.exception("Error [create_keypair(nova_client)]")
279
280
281def create_instance(json_body): # pragma: no cover
282 try:
283 return get_nova_client().servers.create(**json_body)
284 except Exception:
285 log.exception("Error create instance failed")
286 return None
287
288
289def 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 n 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
307def 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:
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
318def delete_instance(nova_client, instance_id): # pragma: no cover
319 try:
320 nova_client.servers.force_delete(instance_id)
321 except Exception:
322 log.exception("Error [delete_instance(nova_client, '%s')]",
323 instance_id)
324 return False
325 else:
326 return True
327
328
329def 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:
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
342def 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
351def 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:
356 log.exception("Error [delete_aggregate(nova_client, %s)]",
357 aggregate_name)
358 return False
359 else:
360 return True
361
362
363def 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
371def 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:
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
380def 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
388def 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
398def 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
406def check_status(status, name, iterations, interval): # pragma: no cover
407 for i 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
421def delete_flavor(flavor_id): # pragma: no cover
422 try:
423 get_nova_client().flavors.delete(flavor_id)
424 except Exception:
425 log.exception("Error [delete_flavor(nova_client, %s)]", flavor_id)
426 return False
427 else:
428 return True
429
430
431def delete_keypair(nova_client, key): # pragma: no cover
432 try:
433 nova_client.keypairs.delete(key=key)
434 return True
435 except Exception:
436 log.exception("Error [delete_keypair(nova_client)]")
437 return False
438
439
440# *********************************************
441# NEUTRON
442# *********************************************
443def get_network_id(neutron_client, network_name): # pragma: no cover
444 networks = neutron_client.list_networks()['networks']
445 return next((n['id'] for n in networks if n['name'] == network_name), None)
446
447
448def get_port_id_by_ip(neutron_client, ip_address): # pragma: no cover
449 ports = neutron_client.list_ports()['ports']
450 return next((i['id'] for i in ports for j in i.get(
451 'fixed_ips') if j['ip_address'] == ip_address), None)
452
453
454def create_neutron_net(neutron_client, json_body): # pragma: no cover
455 try:
456 network = neutron_client.create_network(body=json_body)
457 return network['network']['id']
458 except Exception:
459 log.error("Error [create_neutron_net(neutron_client)]")
460 raise Exception("operation error")
461 return None
462
463
464def delete_neutron_net(neutron_client, network_id): # pragma: no cover
465 try:
466 neutron_client.delete_network(network_id)
467 return True
468 except Exception:
469 log.error("Error [delete_neutron_net(neutron_client, '%s')]" % network_id)
470 return False
471
472
473def create_neutron_subnet(neutron_client, json_body): # pragma: no cover
474 try:
475 subnet = neutron_client.create_subnet(body=json_body)
476 return subnet['subnets'][0]['id']
477 except Exception:
478 log.error("Error [create_neutron_subnet")
479 raise Exception("operation error")
480 return None
481
482
483def create_neutron_router(neutron_client, json_body): # pragma: no cover
484 try:
485 router = neutron_client.create_router(json_body)
486 return router['router']['id']
487 except Exception:
488 log.error("Error [create_neutron_router(neutron_client)]")
489 raise Exception("operation error")
490 return None
491
492
493def delete_neutron_router(neutron_client, router_id): # pragma: no cover
494 try:
495 neutron_client.delete_router(router=router_id)
496 return True
497 except Exception:
498 log.error("Error [delete_neutron_router(neutron_client, '%s')]" % router_id)
499 return False
500
501
502def remove_gateway_router(neutron_client, router_id): # pragma: no cover
503 try:
504 neutron_client.remove_gateway_router(router_id)
505 return True
506 except Exception:
507 log.error("Error [remove_gateway_router(neutron_client, '%s')]" % router_id)
508 return False
509
510
511def remove_interface_router(neutron_client, router_id, subnet_id,
512 **json_body): # pragma: no cover
513 json_body.update({"subnet_id": subnet_id})
514 try:
515 neutron_client.remove_interface_router(router=router_id,
516 body=json_body)
517 return True
518 except Exception:
519 log.error("Error [remove_interface_router(neutron_client, '%s', "
520 "'%s')]" % (router_id, subnet_id))
521 return False
522
523
524def create_floating_ip(neutron_client, extnet_id): # pragma: no cover
525 props = {'floating_network_id': extnet_id}
526 try:
527 ip_json = neutron_client.create_floatingip({'floatingip': props})
528 fip_addr = ip_json['floatingip']['floating_ip_address']
529 fip_id = ip_json['floatingip']['id']
530 except Exception:
531 log.error("Error [create_floating_ip(neutron_client)]")
532 return None
533 return {'fip_addr': fip_addr, 'fip_id': fip_id}
534
535
536def delete_floating_ip(nova_client, floatingip_id): # pragma: no cover
537 try:
538 nova_client.floating_ips.delete(floatingip_id)
539 return True
540 except Exception:
541 log.error("Error [delete_floating_ip(nova_client, '%s')]" % floatingip_id)
542 return False
543
544
545def 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:
551 log.error("Error [get_security_groups(neutron_client)]")
552 return None
553
554
555def 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
565def create_security_group(neutron_client, sg_name, sg_description): # pragma: no cover
566 json_body = {'security_group': {'name': sg_name,
567 'description': sg_description}}
568 try:
569 secgroup = neutron_client.create_security_group(json_body)
570 return secgroup['security_group']
571 except Exception:
572 log.error("Error [create_security_group(neutron_client, '%s', "
573 "'%s')]" % (sg_name, sg_description))
574 return None
575
576
577def create_secgroup_rule(neutron_client, sg_id, direction, protocol,
578 port_range_min=None, port_range_max=None,
579 **json_body): # pragma: no cover
580 # We create a security group in 2 steps
581 # 1 - we check the format and set the json body accordingly
582 # 2 - we call neturon client to create the security group
583
584 # Format check
585 json_body.update({'security_group_rule': {'direction': direction,
586 'security_group_id': sg_id, 'protocol': protocol}})
587 # parameters may be
588 # - both None => we do nothing
589 # - both Not None => we add them to the json description
590 # but one cannot be None is the other is not None
591 if (port_range_min is not None and port_range_max is not None):
592 # add port_range in json description
593 json_body['security_group_rule']['port_range_min'] = port_range_min
594 json_body['security_group_rule']['port_range_max'] = port_range_max
595 log.debug("Security_group format set (port range included)")
596 else:
597 # either both port range are set to None => do nothing
598 # or one is set but not the other => log it and return False
599 if port_range_min is None and port_range_max is None:
600 log.debug("Security_group format set (no port range mentioned)")
601 else:
602 log.error("Bad security group format."
603 "One of the port range is not properly set:"
604 "range min: {},"
605 "range max: {}".format(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:
614 log.exception("Impossible to create_security_group_rule,"
615 "security group rule probably already exists")
616 return False
617
618
619def create_security_group_full(neutron_client,
620 sg_name, 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'..."
639 % sg_name)
640 if not create_secgroup_rule(neutron_client, sg_id,
641 'ingress', 'icmp'):
642 log.error("Failed to create the security group rule...")
643 return None
644
645 log.debug("Adding SSH rules in security group '%s'..."
646 % sg_name)
647 if not create_secgroup_rule(
648 neutron_client, sg_id, 'ingress', 'tcp', '22', '22'):
649 log.error("Failed to create the security group rule...")
650 return None
651
652 if not create_secgroup_rule(
653 neutron_client, sg_id, 'egress', 'tcp', '22', '22'):
654 log.error("Failed to create the security group rule...")
655 return None
656 return sg_id
657
658
659# *********************************************
660# GLANCE
661# *********************************************
662def get_image_id(glance_client, image_name): # pragma: no cover
663 images = glance_client.images.list()
664 return next((i.id for i in images if i.name == image_name), None)
665
666
667def create_image(glance_client, image_name, file_path, disk_format,
668 container_format, min_disk, min_ram, protected, tag,
669 public, **kwargs): # pragma: no cover
670 if not os.path.isfile(file_path):
671 log.error("Error: file %s does not exist." % file_path)
672 return None
673 try:
674 image_id = get_image_id(glance_client, image_name)
675 if image_id is not None:
676 log.info("Image %s already exists." % image_name)
677 else:
678 log.info("Creating image '%s' from '%s'...", image_name, file_path)
679
680 image = glance_client.images.create(name=image_name,
681 visibility=public,
682 disk_format=disk_format,
683 container_format=container_format,
684 min_disk=min_disk,
685 min_ram=min_ram,
686 tags=tag,
687 protected=protected,
688 **kwargs)
689 image_id = image.id
690 with open(file_path) as image_data:
691 glance_client.images.upload(image_id, image_data)
692 return image_id
693 except Exception:
694 log.error("Error [create_glance_image(glance_client, '%s', '%s', '%s')]",
695 image_name, file_path, public)
696 return None
697
698
699def delete_image(glance_client, image_id): # pragma: no cover
700 try:
701 glance_client.images.delete(image_id)
702
703 except Exception:
704 log.exception("Error [delete_flavor(glance_client, %s)]", image_id)
705 return False
706 else:
707 return True
708
709
710# *********************************************
711# CINDER
712# *********************************************
713def get_volume_id(volume_name): # pragma: no cover
714 volumes = get_cinder_client().volumes.list()
715 return next((v.id for v in volumes if v.name == volume_name), None)
716
717
718def create_volume(cinder_client, volume_name, volume_size,
719 volume_image=False): # pragma: no cover
720 try:
721 if volume_image:
722 volume = cinder_client.volumes.create(name=volume_name,
723 size=volume_size,
724 imageRef=volume_image)
725 else:
726 volume = cinder_client.volumes.create(name=volume_name,
727 size=volume_size)
728 return volume
729 except Exception:
730 log.exception("Error [create_volume(cinder_client, %s)]",
731 (volume_name, volume_size))
732 return None
733
734
735def delete_volume(cinder_client, volume_id, forced=False): # pragma: no cover
736 try:
737 if forced:
738 try:
739 cinder_client.volumes.detach(volume_id)
740 except:
741 log.error(sys.exc_info()[0])
742 cinder_client.volumes.force_delete(volume_id)
743 else:
744 while True:
745 volume = get_cinder_client().volumes.get(volume_id)
746 if volume.status.lower() == 'available':
747 break
748 cinder_client.volumes.delete(volume_id)
749 return True
750 except Exception:
751 log.exception("Error [delete_volume(cinder_client, '%s')]" % volume_id)
752 return False
753
754
755def detach_volume(server_id, volume_id): # pragma: no cover
756 try:
757 get_nova_client().volumes.delete_server_volume(server_id, volume_id)
758 return True
759 except Exception:
760 log.exception("Error [detach_server_volume(nova_client, '%s', '%s')]",
761 server_id, volume_id)
762 return False