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