Replace nova delete instance with shade client.
[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
38     Keystone API version used is 3; v2 was deprecated in 2014 (Icehouse). Along
39     with this deprecation, environment variable 'OS_TENANT_NAME' is replaced by
40     'OS_PROJECT_NAME'.
41     """
42     creds = {'username': os.environ.get('OS_USERNAME'),
43              'password': os.environ.get('OS_PASSWORD'),
44              'auth_url': os.environ.get('OS_AUTH_URL'),
45              'project_name': os.environ.get('OS_PROJECT_NAME')
46              }
47
48     if os.getenv('OS_USER_DOMAIN_NAME'):
49         creds['user_domain_name'] = os.getenv('OS_USER_DOMAIN_NAME')
50     if os.getenv('OS_PROJECT_DOMAIN_NAME'):
51         creds['project_domain_name'] = os.getenv('OS_PROJECT_DOMAIN_NAME')
52
53     return creds
54
55
56 def get_session_auth():
57     loader = loading.get_plugin_loader('password')
58     creds = get_credentials()
59     auth = loader.load_from_options(**creds)
60     return auth
61
62
63 def get_session():
64     auth = get_session_auth()
65     try:
66         cacert = os.environ['OS_CACERT']
67     except KeyError:
68         return session.Session(auth=auth)
69     else:
70         insecure = os.getenv('OS_INSECURE', '').lower() == 'true'
71         cacert = False if insecure else cacert
72         return session.Session(auth=auth, verify=cacert)
73
74
75 def get_endpoint(service_type, endpoint_type='publicURL'):
76     auth = get_session_auth()
77     # for multi-region, we need to specify region
78     # when finding the endpoint
79     return get_session().get_endpoint(auth=auth,
80                                       service_type=service_type,
81                                       endpoint_type=endpoint_type,
82                                       region_name=os.environ.get(
83                                           "OS_REGION_NAME"))
84
85
86 # *********************************************
87 #   CLIENTS
88 # *********************************************
89 def get_heat_api_version():     # pragma: no cover
90     try:
91         api_version = os.environ['HEAT_API_VERSION']
92     except KeyError:
93         return DEFAULT_HEAT_API_VERSION
94     else:
95         log.info("HEAT_API_VERSION is set in env as '%s'", api_version)
96         return api_version
97
98
99 def get_cinder_client_version():      # pragma: no cover
100     try:
101         api_version = os.environ['OS_VOLUME_API_VERSION']
102     except KeyError:
103         return DEFAULT_API_VERSION
104     else:
105         log.info("OS_VOLUME_API_VERSION is set in env as '%s'", api_version)
106         return api_version
107
108
109 def get_cinder_client():      # pragma: no cover
110     sess = get_session()
111     return cinderclient.Client(get_cinder_client_version(), session=sess)
112
113
114 def get_nova_client_version():      # pragma: no cover
115     try:
116         api_version = os.environ['OS_COMPUTE_API_VERSION']
117     except KeyError:
118         return DEFAULT_API_VERSION
119     else:
120         log.info("OS_COMPUTE_API_VERSION is set in env as '%s'", api_version)
121         return api_version
122
123
124 def get_nova_client():      # pragma: no cover
125     sess = get_session()
126     return novaclient.Client(get_nova_client_version(), session=sess)
127
128
129 def get_neutron_client_version():   # pragma: no cover
130     try:
131         api_version = os.environ['OS_NETWORK_API_VERSION']
132     except KeyError:
133         return DEFAULT_API_VERSION
134     else:
135         log.info("OS_NETWORK_API_VERSION is set in env as '%s'", api_version)
136         return api_version
137
138
139 def get_neutron_client():   # pragma: no cover
140     sess = get_session()
141     return neutronclient.Client(get_neutron_client_version(), session=sess)
142
143
144 def get_glance_client_version():    # pragma: no cover
145     try:
146         api_version = os.environ['OS_IMAGE_API_VERSION']
147     except KeyError:
148         return DEFAULT_API_VERSION
149     else:
150         log.info("OS_IMAGE_API_VERSION is set in env as '%s'", api_version)
151         return api_version
152
153
154 def get_glance_client():    # pragma: no cover
155     sess = get_session()
156     return glanceclient.Client(get_glance_client_version(), session=sess)
157
158
159 def get_shade_client():
160     return shade.openstack_cloud()
161
162
163 # *********************************************
164 #   NOVA
165 # *********************************************
166 def get_aggregates(nova_client):    # pragma: no cover
167     try:
168         return nova_client.aggregates.list()
169     except Exception:  # pylint: disable=broad-except
170         log.exception("Error [get_aggregates(nova_client)]")
171
172
173 def get_availability_zones(nova_client):    # pragma: no cover
174     try:
175         return nova_client.availability_zones.list()
176     except Exception:  # pylint: disable=broad-except
177         log.exception("Error [get_availability_zones(nova_client)]")
178
179
180 def get_availability_zone_names(nova_client):   # pragma: no cover
181     try:
182         return [az.zoneName for az in get_availability_zones(nova_client)]
183     except Exception:  # pylint: disable=broad-except
184         log.exception("Error [get_availability_zone_names(nova_client)]")
185
186
187 def create_aggregate(nova_client, aggregate_name, av_zone):  # pragma: no cover
188     try:
189         nova_client.aggregates.create(aggregate_name, av_zone)
190     except Exception:  # pylint: disable=broad-except
191         log.exception("Error [create_aggregate(nova_client, %s, %s)]",
192                       aggregate_name, av_zone)
193         return False
194     else:
195         return True
196
197
198 def get_aggregate_id(nova_client, aggregate_name):      # pragma: no cover
199     try:
200         aggregates = get_aggregates(nova_client)
201         _id = next((ag.id for ag in aggregates if ag.name == aggregate_name))
202     except Exception:  # pylint: disable=broad-except
203         log.exception("Error [get_aggregate_id(nova_client, %s)]",
204                       aggregate_name)
205     else:
206         return _id
207
208
209 def add_host_to_aggregate(nova_client, aggregate_name,
210                           compute_host):    # pragma: no cover
211     try:
212         aggregate_id = get_aggregate_id(nova_client, aggregate_name)
213         nova_client.aggregates.add_host(aggregate_id, compute_host)
214     except Exception:  # pylint: disable=broad-except
215         log.exception("Error [add_host_to_aggregate(nova_client, %s, %s)]",
216                       aggregate_name, compute_host)
217         return False
218     else:
219         return True
220
221
222 def create_aggregate_with_host(nova_client, aggregate_name, av_zone,
223                                compute_host):    # pragma: no cover
224     try:
225         create_aggregate(nova_client, aggregate_name, av_zone)
226         add_host_to_aggregate(nova_client, aggregate_name, compute_host)
227     except Exception:  # pylint: disable=broad-except
228         log.exception("Error [create_aggregate_with_host("
229                       "nova_client, %s, %s, %s)]",
230                       aggregate_name, av_zone, compute_host)
231         return False
232     else:
233         return True
234
235
236 def create_keypair(name, key_path=None):    # pragma: no cover
237     try:
238         with open(key_path) as fpubkey:
239             keypair = get_nova_client().keypairs.create(
240                 name=name, public_key=fpubkey.read())
241             return keypair
242     except Exception:  # pylint: disable=broad-except
243         log.exception("Error [create_keypair(nova_client)]")
244
245
246 def create_instance(json_body):    # pragma: no cover
247     try:
248         return get_nova_client().servers.create(**json_body)
249     except Exception:  # pylint: disable=broad-except
250         log.exception("Error create instance failed")
251         return None
252
253
254 def create_instance_and_wait_for_active(shade_client, name, image,
255                                         flavor, auto_ip=True, ips=None,
256                                         ip_pool=None, root_volume=None,
257                                         terminate_volume=False, wait=True,
258                                         timeout=180, reuse_ips=True,
259                                         network=None, boot_from_volume=False,
260                                         volume_size='20', boot_volume=None,
261                                         volumes=None, nat_destination=None,
262                                         **kwargs):
263     """Create a virtual server instance.
264
265     :param name:(string) Name of the server.
266     :param image:(dict) Image dict, name or ID to boot with. Image is required
267                  unless boot_volume is given.
268     :param flavor:(dict) Flavor dict, name or ID to boot onto.
269     :param auto_ip: Whether to take actions to find a routable IP for
270                     the server.
271     :param ips: List of IPs to attach to the server.
272     :param ip_pool:(string) Name of the network or floating IP pool to get an
273                    address from.
274     :param root_volume:(string) Name or ID of a volume to boot from.
275                        (defaults to None - deprecated, use boot_volume)
276     :param boot_volume:(string) Name or ID of a volume to boot from.
277     :param terminate_volume:(bool) If booting from a volume, whether it should
278                             be deleted when the server is destroyed.
279     :param volumes:(optional) A list of volumes to attach to the server.
280     :param wait:(optional) Wait for the address to appear as assigned to the server.
281     :param timeout: Seconds to wait, defaults to 60.
282     :param reuse_ips:(bool)Whether to attempt to reuse pre-existing
283                      floating ips should a floating IP be needed.
284     :param network:(dict) Network dict or name or ID to attach the server to.
285                    Mutually exclusive with the nics parameter. Can also be be
286                    a list of network names or IDs or network dicts.
287     :param boot_from_volume:(bool) Whether to boot from volume. 'boot_volume'
288                             implies True, but boot_from_volume=True with
289                             no boot_volume is valid and will create a
290                             volume from the image and use that.
291     :param volume_size: When booting an image from volume, how big should
292                         the created volume be?
293     :param nat_destination: Which network should a created floating IP
294                             be attached to, if it's not possible to infer from
295                             the cloud's configuration.
296     :param meta:(optional) A dict of arbitrary key/value metadata to store for
297                 this server. Both keys and values must be <=255 characters.
298     :param reservation_id: A UUID for the set of servers being requested.
299     :param min_count:(optional extension) The minimum number of servers to
300                      launch.
301     :param max_count:(optional extension) The maximum number of servers to
302                      launch.
303     :param security_groups: A list of security group names.
304     :param userdata: User data to pass to be exposed by the metadata server
305                      this can be a file type object as well or a string.
306     :param key_name:(optional extension) Name of previously created keypair to
307                     inject into the instance.
308     :param availability_zone: Name of the availability zone for instance
309                               placement.
310     :param block_device_mapping:(optional) A dict of block device mappings for
311                                 this server.
312     :param block_device_mapping_v2:(optional) A dict of block device mappings
313                                    for this server.
314     :param nics:(optional extension) An ordered list of nics to be added to
315                  this server, with information about connected networks, fixed
316                  IPs, port etc.
317     :param scheduler_hints:(optional extension) Arbitrary key-value pairs
318                            specified by the client to help boot an instance.
319     :param config_drive:(optional extension) Value for config drive either
320                          boolean, or volume-id.
321     :param disk_config:(optional extension) Control how the disk is partitioned
322                        when the server is created. Possible values are 'AUTO'
323                        or 'MANUAL'.
324     :param admin_pass:(optional extension) Add a user supplied admin password.
325
326     :returns: The created server.
327     """
328     try:
329         return shade_client.create_server(
330             name, image, flavor, auto_ip=auto_ip, ips=ips, ip_pool=ip_pool,
331             root_volume=root_volume, terminate_volume=terminate_volume,
332             wait=wait, timeout=timeout, reuse_ips=reuse_ips, network=network,
333             boot_from_volume=boot_from_volume, volume_size=volume_size,
334             boot_volume=boot_volume, volumes=volumes,
335             nat_destination=nat_destination, **kwargs)
336     except exc.OpenStackCloudException as o_exc:
337         log.error("Error [create_instance(shade_client)]. "
338                   "Exception message, '%s'", o_exc.orig_message)
339
340
341 def attach_server_volume(server_id, volume_id,
342                          device=None):    # pragma: no cover
343     try:
344         get_nova_client().volumes.create_server_volume(server_id,
345                                                        volume_id, device)
346     except Exception:  # pylint: disable=broad-except
347         log.exception("Error [attach_server_volume(nova_client, '%s', '%s')]",
348                       server_id, volume_id)
349         return False
350     else:
351         return True
352
353
354 def delete_instance(shade_client, name_or_id, wait=False, timeout=180,
355                     delete_ips=False, delete_ip_retry=1):
356     """Delete a server instance.
357
358     :param name_or_id: name or ID of the server to delete
359     :param wait:(bool) If true, waits for server to be deleted.
360     :param timeout:(int) Seconds to wait for server deletion.
361     :param delete_ips:(bool) If true, deletes any floating IPs associated with
362                       the instance.
363     :param delete_ip_retry:(int) Number of times to retry deleting
364                            any floating ips, should the first try be
365                            unsuccessful.
366     :returns: True if delete succeeded, False otherwise.
367     """
368     try:
369         return shade_client.delete_server(
370             name_or_id, wait=wait, timeout=timeout, delete_ips=delete_ips,
371             delete_ip_retry=delete_ip_retry)
372     except exc.OpenStackCloudException as o_exc:
373         log.error("Error [delete_instance(shade_client, '%s')]. "
374                   "Exception message: %s", name_or_id,
375                   o_exc.orig_message)
376         return False
377
378
379 def remove_host_from_aggregate(nova_client, aggregate_name,
380                                compute_host):  # pragma: no cover
381     try:
382         aggregate_id = get_aggregate_id(nova_client, aggregate_name)
383         nova_client.aggregates.remove_host(aggregate_id, compute_host)
384     except Exception:  # pylint: disable=broad-except
385         log.exception("Error remove_host_from_aggregate(nova_client, %s, %s)",
386                       aggregate_name, compute_host)
387         return False
388     else:
389         return True
390
391
392 def remove_hosts_from_aggregate(nova_client,
393                                 aggregate_name):   # pragma: no cover
394     aggregate_id = get_aggregate_id(nova_client, aggregate_name)
395     hosts = nova_client.aggregates.get(aggregate_id).hosts
396     assert(
397         all(remove_host_from_aggregate(nova_client, aggregate_name, host)
398             for host in hosts))
399
400
401 def delete_aggregate(nova_client, aggregate_name):  # pragma: no cover
402     try:
403         remove_hosts_from_aggregate(nova_client, aggregate_name)
404         nova_client.aggregates.delete(aggregate_name)
405     except Exception:  # pylint: disable=broad-except
406         log.exception("Error [delete_aggregate(nova_client, %s)]",
407                       aggregate_name)
408         return False
409     else:
410         return True
411
412
413 def get_server_by_name(name):   # pragma: no cover
414     try:
415         return get_nova_client().servers.list(search_opts={'name': name})[0]
416     except IndexError:
417         log.exception('Failed to get nova client')
418         raise
419
420
421 def create_flavor(name, ram, vcpus, disk, **kwargs):   # pragma: no cover
422     try:
423         return get_nova_client().flavors.create(name, ram, vcpus,
424                                                 disk, **kwargs)
425     except Exception:  # pylint: disable=broad-except
426         log.exception("Error [create_flavor(nova_client, %s, %s, %s, %s, %s)]",
427                       name, ram, disk, vcpus, kwargs['is_public'])
428         return None
429
430
431 def get_image_by_name(name):    # pragma: no cover
432     images = get_nova_client().images.list()
433     try:
434         return next((a for a in images if a.name == name))
435     except StopIteration:
436         log.exception('No image matched')
437
438
439 def get_flavor_id(nova_client, flavor_name):    # pragma: no cover
440     flavors = nova_client.flavors.list(detailed=True)
441     flavor_id = ''
442     for f in flavors:
443         if f.name == flavor_name:
444             flavor_id = f.id
445             break
446     return flavor_id
447
448
449 def get_flavor_by_name(name):   # pragma: no cover
450     flavors = get_nova_client().flavors.list()
451     try:
452         return next((a for a in flavors if a.name == name))
453     except StopIteration:
454         log.exception('No flavor matched')
455
456
457 def check_status(status, name, iterations, interval):   # pragma: no cover
458     for _ in range(iterations):
459         try:
460             server = get_server_by_name(name)
461         except IndexError:
462             log.error('Cannot found %s server', name)
463             raise
464
465         if server.status == status:
466             return True
467
468         time.sleep(interval)
469     return False
470
471
472 def delete_flavor(flavor_id):    # pragma: no cover
473     try:
474         get_nova_client().flavors.delete(flavor_id)
475     except Exception:  # pylint: disable=broad-except
476         log.exception("Error [delete_flavor(nova_client, %s)]", flavor_id)
477         return False
478     else:
479         return True
480
481
482 def delete_keypair(nova_client, key):     # pragma: no cover
483     try:
484         nova_client.keypairs.delete(key=key)
485         return True
486     except Exception:  # pylint: disable=broad-except
487         log.exception("Error [delete_keypair(nova_client)]")
488         return False
489
490
491 # *********************************************
492 #   NEUTRON
493 # *********************************************
494 def create_neutron_net(shade_client, network_name, shared=False,
495                        admin_state_up=True, external=False, provider=None,
496                        project_id=None):
497     """Create a neutron network.
498
499     :param network_name:(string) name of the network being created.
500     :param shared:(bool) whether the network is shared.
501     :param admin_state_up:(bool) set the network administrative state.
502     :param external:(bool) whether this network is externally accessible.
503     :param provider:(dict) a dict of network provider options.
504     :param project_id:(string) specify the project ID this network
505                       will be created on (admin-only).
506     :returns:(string) the network id.
507     """
508     try:
509         networks = shade_client.create_network(
510             name=network_name, shared=shared, admin_state_up=admin_state_up,
511             external=external, provider=provider, project_id=project_id)
512         return networks['id']
513     except exc.OpenStackCloudException as o_exc:
514         log.error("Error [create_neutron_net(shade_client)]."
515                   "Exception message, '%s'", o_exc.orig_message)
516         return None
517
518
519 def delete_neutron_net(shade_client, network_id):
520     try:
521         return shade_client.delete_network(network_id)
522     except exc.OpenStackCloudException:
523         log.error("Error [delete_neutron_net(shade_client, '%s')]", network_id)
524         return False
525
526
527 def create_neutron_subnet(shade_client, network_name_or_id, cidr=None,
528                           ip_version=4, enable_dhcp=False, subnet_name=None,
529                           tenant_id=None, allocation_pools=None,
530                           gateway_ip=None, disable_gateway_ip=False,
531                           dns_nameservers=None, host_routes=None,
532                           ipv6_ra_mode=None, ipv6_address_mode=None,
533                           use_default_subnetpool=False):
534     """Create a subnet on a specified network.
535
536     :param network_name_or_id:(string) the unique name or ID of the
537                               attached network. If a non-unique name is
538                               supplied, an exception is raised.
539     :param cidr:(string) the CIDR.
540     :param ip_version:(int) the IP version.
541     :param enable_dhcp:(bool) whether DHCP is enable.
542     :param subnet_name:(string) the name of the subnet.
543     :param tenant_id:(string) the ID of the tenant who owns the network.
544     :param allocation_pools: A list of dictionaries of the start and end
545                             addresses for the allocation pools.
546     :param gateway_ip:(string) the gateway IP address.
547     :param disable_gateway_ip:(bool) whether gateway IP address is enabled.
548     :param dns_nameservers: A list of DNS name servers for the subnet.
549     :param host_routes: A list of host route dictionaries for the subnet.
550     :param ipv6_ra_mode:(string) IPv6 Router Advertisement mode.
551                         Valid values are: 'dhcpv6-stateful',
552                         'dhcpv6-stateless', or 'slaac'.
553     :param ipv6_address_mode:(string) IPv6 address mode.
554                              Valid values are: 'dhcpv6-stateful',
555                              'dhcpv6-stateless', or 'slaac'.
556     :param use_default_subnetpool:(bool) use the default subnetpool for
557                                   ``ip_version`` to obtain a CIDR. It is
558                                   required to pass ``None`` to the ``cidr``
559                                   argument when enabling this option.
560     :returns:(string) the subnet id.
561     """
562     try:
563         subnet = shade_client.create_subnet(
564             network_name_or_id, cidr=cidr, ip_version=ip_version,
565             enable_dhcp=enable_dhcp, subnet_name=subnet_name,
566             tenant_id=tenant_id, allocation_pools=allocation_pools,
567             gateway_ip=gateway_ip, disable_gateway_ip=disable_gateway_ip,
568             dns_nameservers=dns_nameservers, host_routes=host_routes,
569             ipv6_ra_mode=ipv6_ra_mode, ipv6_address_mode=ipv6_address_mode,
570             use_default_subnetpool=use_default_subnetpool)
571         return subnet['id']
572     except exc.OpenStackCloudException as o_exc:
573         log.error("Error [create_neutron_subnet(shade_client)]. "
574                   "Exception message: %s", o_exc.orig_message)
575         return None
576
577
578 def create_neutron_router(shade_client, name=None, admin_state_up=True,
579                           ext_gateway_net_id=None, enable_snat=None,
580                           ext_fixed_ips=None, project_id=None):
581     """Create a logical router.
582
583     :param name:(string) the router name.
584     :param admin_state_up:(bool) the administrative state of the router.
585     :param ext_gateway_net_id:(string) network ID for the external gateway.
586     :param enable_snat:(bool) enable Source NAT (SNAT) attribute.
587     :param ext_fixed_ips: List of dictionaries of desired IP and/or subnet
588                           on the external network.
589     :param project_id:(string) project ID for the router.
590
591     :returns:(string) the router id.
592     """
593     try:
594         router = shade_client.create_router(
595             name, admin_state_up, ext_gateway_net_id, enable_snat,
596             ext_fixed_ips, project_id)
597         return router['id']
598     except exc.OpenStackCloudException as o_exc:
599         log.error("Error [create_neutron_router(shade_client)]. "
600                   "Exception message: %s", o_exc.orig_message)
601
602
603 def delete_neutron_router(shade_client, router_id):
604     try:
605         return shade_client.delete_router(router_id)
606     except exc.OpenStackCloudException as o_exc:
607         log.error("Error [delete_neutron_router(shade_client, '%s')]. "
608                   "Exception message: %s", router_id, o_exc.orig_message)
609         return False
610
611
612 def remove_gateway_router(neutron_client, router_id):      # pragma: no cover
613     try:
614         neutron_client.remove_gateway_router(router_id)
615         return True
616     except Exception:  # pylint: disable=broad-except
617         log.error("Error [remove_gateway_router(neutron_client, '%s')]",
618                   router_id)
619         return False
620
621
622 def remove_router_interface(shade_client, router, subnet_id=None,
623                             port_id=None):
624     """Detach a subnet from an internal router interface.
625
626     At least one of subnet_id or port_id must be supplied. If you specify both
627     subnet and port ID, the subnet ID must correspond to the subnet ID of the
628     first IP address on the port specified by the port ID.
629     Otherwise an error occurs.
630
631     :param router: The dict object of the router being changed
632     :param subnet_id:(string) The ID of the subnet to use for the interface
633     :param port_id:(string) The ID of the port to use for the interface
634     :returns: True on success
635     """
636     try:
637         shade_client.remove_router_interface(
638             router, subnet_id=subnet_id, port_id=port_id)
639         return True
640     except exc.OpenStackCloudException as o_exc:
641         log.error("Error [remove_interface_router(shade_client)]. "
642                   "Exception message: %s", o_exc.orig_message)
643         return False
644
645
646 def create_floating_ip(shade_client, network_name_or_id=None, server=None,
647                        fixed_address=None, nat_destination=None,
648                        port=None, wait=False, timeout=60):
649     """Allocate a new floating IP from a network or a pool.
650
651     :param network_name_or_id: Name or ID of the network
652                                that the floating IP should come from.
653     :param server: Server dict for the server to create
654                   the IP for and to which it should be attached.
655     :param fixed_address: Fixed IP to attach the floating ip to.
656     :param nat_destination: Name or ID of the network
657                            that the fixed IP to attach the floating
658                            IP to should be on.
659     :param port: The port ID that the floating IP should be
660                 attached to. Specifying a port conflicts with specifying a
661                 server,fixed_address or nat_destination.
662     :param wait: Whether to wait for the IP to be active.Only applies
663                 if a server is provided.
664     :param timeout: How long to wait for the IP to be active.Only
665                    applies if a server is provided.
666
667     :returns:Floating IP id and address
668     """
669     try:
670         fip = shade_client.create_floating_ip(
671             network=network_name_or_id, server=server,
672             fixed_address=fixed_address, nat_destination=nat_destination,
673             port=port, wait=wait, timeout=timeout)
674         return {'fip_addr': fip['floating_ip_address'], 'fip_id': fip['id']}
675     except exc.OpenStackCloudException as o_exc:
676         log.error("Error [create_floating_ip(shade_client)]. "
677                   "Exception message: %s", o_exc.orig_message)
678
679
680 def delete_floating_ip(shade_client, floating_ip_id, retry=1):
681     try:
682         return shade_client.delete_floating_ip(floating_ip_id=floating_ip_id,
683                                                retry=retry)
684     except exc.OpenStackCloudException as o_exc:
685         log.error("Error [delete_floating_ip(shade_client,'%s')]. "
686                   "Exception message: %s", floating_ip_id, o_exc.orig_message)
687         return False
688
689
690 def create_security_group_rule(shade_client, secgroup_name_or_id,
691                                port_range_min=None, port_range_max=None,
692                                protocol=None, remote_ip_prefix=None,
693                                remote_group_id=None, direction='ingress',
694                                ethertype='IPv4', project_id=None):
695     """Create a new security group rule
696
697     :param secgroup_name_or_id:(string) The security group name or ID to
698                                associate with this security group rule. If a
699                                non-unique group name is given, an exception is
700                                raised.
701     :param port_range_min:(int) The minimum port number in the range that is
702                           matched by the security group rule. If the protocol
703                           is TCP or UDP, this value must be less than or equal
704                           to the port_range_max attribute value. If nova is
705                           used by the cloud provider for security groups, then
706                           a value of None will be transformed to -1.
707     :param port_range_max:(int) The maximum port number in the range that is
708                           matched by the security group rule. The
709                           port_range_min attribute constrains the
710                           port_range_max attribute. If nova is used by the
711                           cloud provider for security groups, then a value of
712                           None will be transformed to -1.
713     :param protocol:(string) The protocol that is matched by the security group
714                     rule. Valid values are None, tcp, udp, and icmp.
715     :param remote_ip_prefix:(string) The remote IP prefix to be associated with
716                             this security group rule. This attribute matches
717                             the specified IP prefix as the source IP address of
718                             the IP packet.
719     :param remote_group_id:(string) The remote group ID to be associated with
720                            this security group rule.
721     :param direction:(string) Ingress or egress: The direction in which the
722                      security group rule is applied.
723     :param ethertype:(string) Must be IPv4 or IPv6, and addresses represented
724                      in CIDR must match the ingress or egress rules.
725     :param project_id:(string) Specify the project ID this security group will
726                       be created on (admin-only).
727
728     :returns: True on success.
729     """
730
731     try:
732         shade_client.create_security_group_rule(
733             secgroup_name_or_id, port_range_min=port_range_min,
734             port_range_max=port_range_max, protocol=protocol,
735             remote_ip_prefix=remote_ip_prefix, remote_group_id=remote_group_id,
736             direction=direction, ethertype=ethertype, project_id=project_id)
737         return True
738     except exc.OpenStackCloudException as op_exc:
739         log.error("Failed to create_security_group_rule(shade_client). "
740                   "Exception message: %s", op_exc.orig_message)
741         return False
742
743
744 def create_security_group_full(shade_client, sg_name,
745                                sg_description, project_id=None):
746     security_group = shade_client.get_security_group(sg_name)
747
748     if security_group:
749         log.info("Using existing security group '%s'...", sg_name)
750         return security_group['id']
751
752     log.info("Creating security group  '%s'...", sg_name)
753     try:
754         security_group = shade_client.create_security_group(
755             sg_name, sg_description, project_id=project_id)
756     except (exc.OpenStackCloudException,
757             exc.OpenStackCloudUnavailableFeature) as op_exc:
758         log.error("Error [create_security_group(shade_client, %s, %s)]. "
759                   "Exception message: %s", sg_name, sg_description,
760                   op_exc.orig_message)
761         return
762
763     log.debug("Security group '%s' with ID=%s created successfully.",
764               security_group['name'], security_group['id'])
765
766     log.debug("Adding ICMP rules in security group '%s'...", sg_name)
767     if not create_security_group_rule(shade_client, security_group['id'],
768                                       direction='ingress', protocol='icmp'):
769         log.error("Failed to create the security group rule...")
770         shade_client.delete_security_group(sg_name)
771         return
772
773     log.debug("Adding SSH rules in security group '%s'...", sg_name)
774     if not create_security_group_rule(shade_client, security_group['id'],
775                                       direction='ingress', protocol='tcp',
776                                       port_range_min='22',
777                                       port_range_max='22'):
778         log.error("Failed to create the security group rule...")
779         shade_client.delete_security_group(sg_name)
780         return
781
782     if not create_security_group_rule(shade_client, security_group['id'],
783                                       direction='egress', protocol='tcp',
784                                       port_range_min='22',
785                                       port_range_max='22'):
786         log.error("Failed to create the security group rule...")
787         shade_client.delete_security_group(sg_name)
788         return
789     return security_group['id']
790
791
792 # *********************************************
793 #   GLANCE
794 # *********************************************
795 def get_image_id(glance_client, image_name):    # pragma: no cover
796     images = glance_client.images.list()
797     return next((i.id for i in images if i.name == image_name), None)
798
799
800 def create_image(glance_client, image_name, file_path, disk_format,
801                  container_format, min_disk, min_ram, protected, tag,
802                  public, **kwargs):    # pragma: no cover
803     if not os.path.isfile(file_path):
804         log.error("Error: file %s does not exist.", file_path)
805         return None
806     try:
807         image_id = get_image_id(glance_client, image_name)
808         if image_id is not None:
809             log.info("Image %s already exists.", image_name)
810         else:
811             log.info("Creating image '%s' from '%s'...", image_name, file_path)
812
813             image = glance_client.images.create(
814                 name=image_name, visibility=public, disk_format=disk_format,
815                 container_format=container_format, min_disk=min_disk,
816                 min_ram=min_ram, tags=tag, protected=protected, **kwargs)
817             image_id = image.id
818             with open(file_path) as image_data:
819                 glance_client.images.upload(image_id, image_data)
820         return image_id
821     except Exception:  # pylint: disable=broad-except
822         log.error(
823             "Error [create_glance_image(glance_client, '%s', '%s', '%s')]",
824             image_name, file_path, public)
825         return None
826
827
828 def delete_image(glance_client, image_id):    # pragma: no cover
829     try:
830         glance_client.images.delete(image_id)
831
832     except Exception:  # pylint: disable=broad-except
833         log.exception("Error [delete_flavor(glance_client, %s)]", image_id)
834         return False
835     else:
836         return True
837
838
839 def list_images(shade_client=None):
840     if shade_client is None:
841         shade_client = get_shade_client()
842
843     try:
844         return shade_client.list_images()
845     except exc.OpenStackCloudException as o_exc:
846         log.error("Error [list_images(shade_client)]."
847                   "Exception message, '%s'", o_exc.orig_message)
848         return False
849
850
851 # *********************************************
852 #   CINDER
853 # *********************************************
854 def get_volume_id(volume_name):    # pragma: no cover
855     volumes = get_cinder_client().volumes.list()
856     return next((v.id for v in volumes if v.name == volume_name), None)
857
858
859 def create_volume(cinder_client, volume_name, volume_size,
860                   volume_image=False):    # pragma: no cover
861     try:
862         if volume_image:
863             volume = cinder_client.volumes.create(name=volume_name,
864                                                   size=volume_size,
865                                                   imageRef=volume_image)
866         else:
867             volume = cinder_client.volumes.create(name=volume_name,
868                                                   size=volume_size)
869         return volume
870     except Exception:  # pylint: disable=broad-except
871         log.exception("Error [create_volume(cinder_client, %s)]",
872                       (volume_name, volume_size))
873         return None
874
875
876 def delete_volume(cinder_client, volume_id,
877                   forced=False):      # pragma: no cover
878     try:
879         if forced:
880             try:
881                 cinder_client.volumes.detach(volume_id)
882             except Exception:  # pylint: disable=broad-except
883                 log.error(sys.exc_info()[0])
884             cinder_client.volumes.force_delete(volume_id)
885         else:
886             while True:
887                 volume = get_cinder_client().volumes.get(volume_id)
888                 if volume.status.lower() == 'available':
889                     break
890             cinder_client.volumes.delete(volume_id)
891         return True
892     except Exception:  # pylint: disable=broad-except
893         log.exception("Error [delete_volume(cinder_client, '%s')]", volume_id)
894         return False
895
896
897 def detach_volume(server_id, volume_id):      # pragma: no cover
898     try:
899         get_nova_client().volumes.delete_server_volume(server_id, volume_id)
900         return True
901     except Exception:  # pylint: disable=broad-except
902         log.exception("Error [detach_server_volume(nova_client, '%s', '%s')]",
903                       server_id, volume_id)
904         return False