e79c988725268f5222a851effe50503f80872897
[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_server_volume(server_id, volume_id,
268                          device=None):    # pragma: no cover
269     try:
270         get_nova_client().volumes.create_server_volume(server_id,
271                                                        volume_id, device)
272     except Exception:  # pylint: disable=broad-except
273         log.exception("Error [attach_server_volume(nova_client, '%s', '%s')]",
274                       server_id, volume_id)
275         return False
276     else:
277         return True
278
279
280 def delete_instance(shade_client, name_or_id, wait=False, timeout=180,
281                     delete_ips=False, delete_ip_retry=1):
282     """Delete a server instance.
283
284     :param name_or_id: name or ID of the server to delete
285     :param wait:(bool) If true, waits for server to be deleted.
286     :param timeout:(int) Seconds to wait for server deletion.
287     :param delete_ips:(bool) If true, deletes any floating IPs associated with
288                       the instance.
289     :param delete_ip_retry:(int) Number of times to retry deleting
290                            any floating ips, should the first try be
291                            unsuccessful.
292     :returns: True if delete succeeded, False otherwise.
293     """
294     try:
295         return shade_client.delete_server(
296             name_or_id, wait=wait, timeout=timeout, delete_ips=delete_ips,
297             delete_ip_retry=delete_ip_retry)
298     except exc.OpenStackCloudException as o_exc:
299         log.error("Error [delete_instance(shade_client, '%s')]. "
300                   "Exception message: %s", name_or_id,
301                   o_exc.orig_message)
302         return False
303
304
305 def get_server_by_name(name):   # pragma: no cover
306     try:
307         return get_nova_client().servers.list(search_opts={'name': name})[0]
308     except IndexError:
309         log.exception('Failed to get nova client')
310         raise
311
312
313 def create_flavor(name, ram, vcpus, disk, **kwargs):   # pragma: no cover
314     try:
315         return get_nova_client().flavors.create(name, ram, vcpus,
316                                                 disk, **kwargs)
317     except Exception:  # pylint: disable=broad-except
318         log.exception("Error [create_flavor(nova_client, %s, %s, %s, %s, %s)]",
319                       name, ram, disk, vcpus, kwargs['is_public'])
320         return None
321
322
323 def get_flavor_id(nova_client, flavor_name):    # pragma: no cover
324     flavors = nova_client.flavors.list(detailed=True)
325     flavor_id = ''
326     for f in flavors:
327         if f.name == flavor_name:
328             flavor_id = f.id
329             break
330     return flavor_id
331
332
333 def get_flavor_by_name(name):   # pragma: no cover
334     flavors = get_nova_client().flavors.list()
335     try:
336         return next((a for a in flavors if a.name == name))
337     except StopIteration:
338         log.exception('No flavor matched')
339
340
341 def delete_flavor(flavor_id):    # pragma: no cover
342     try:
343         get_nova_client().flavors.delete(flavor_id)
344     except Exception:  # pylint: disable=broad-except
345         log.exception("Error [delete_flavor(nova_client, %s)]", flavor_id)
346         return False
347     else:
348         return True
349
350
351 def delete_keypair(nova_client, key):     # pragma: no cover
352     try:
353         nova_client.keypairs.delete(key=key)
354         return True
355     except Exception:  # pylint: disable=broad-except
356         log.exception("Error [delete_keypair(nova_client)]")
357         return False
358
359
360 # *********************************************
361 #   NEUTRON
362 # *********************************************
363 def create_neutron_net(shade_client, network_name, shared=False,
364                        admin_state_up=True, external=False, provider=None,
365                        project_id=None):
366     """Create a neutron network.
367
368     :param network_name:(string) name of the network being created.
369     :param shared:(bool) whether the network is shared.
370     :param admin_state_up:(bool) set the network administrative state.
371     :param external:(bool) whether this network is externally accessible.
372     :param provider:(dict) a dict of network provider options.
373     :param project_id:(string) specify the project ID this network
374                       will be created on (admin-only).
375     :returns:(string) the network id.
376     """
377     try:
378         networks = shade_client.create_network(
379             name=network_name, shared=shared, admin_state_up=admin_state_up,
380             external=external, provider=provider, project_id=project_id)
381         return networks['id']
382     except exc.OpenStackCloudException as o_exc:
383         log.error("Error [create_neutron_net(shade_client)]."
384                   "Exception message, '%s'", o_exc.orig_message)
385         return None
386
387
388 def delete_neutron_net(shade_client, network_id):
389     try:
390         return shade_client.delete_network(network_id)
391     except exc.OpenStackCloudException:
392         log.error("Error [delete_neutron_net(shade_client, '%s')]", network_id)
393         return False
394
395
396 def create_neutron_subnet(shade_client, network_name_or_id, cidr=None,
397                           ip_version=4, enable_dhcp=False, subnet_name=None,
398                           tenant_id=None, allocation_pools=None,
399                           gateway_ip=None, disable_gateway_ip=False,
400                           dns_nameservers=None, host_routes=None,
401                           ipv6_ra_mode=None, ipv6_address_mode=None,
402                           use_default_subnetpool=False):
403     """Create a subnet on a specified network.
404
405     :param network_name_or_id:(string) the unique name or ID of the
406                               attached network. If a non-unique name is
407                               supplied, an exception is raised.
408     :param cidr:(string) the CIDR.
409     :param ip_version:(int) the IP version.
410     :param enable_dhcp:(bool) whether DHCP is enable.
411     :param subnet_name:(string) the name of the subnet.
412     :param tenant_id:(string) the ID of the tenant who owns the network.
413     :param allocation_pools: A list of dictionaries of the start and end
414                             addresses for the allocation pools.
415     :param gateway_ip:(string) the gateway IP address.
416     :param disable_gateway_ip:(bool) whether gateway IP address is enabled.
417     :param dns_nameservers: A list of DNS name servers for the subnet.
418     :param host_routes: A list of host route dictionaries for the subnet.
419     :param ipv6_ra_mode:(string) IPv6 Router Advertisement mode.
420                         Valid values are: 'dhcpv6-stateful',
421                         'dhcpv6-stateless', or 'slaac'.
422     :param ipv6_address_mode:(string) IPv6 address mode.
423                              Valid values are: 'dhcpv6-stateful',
424                              'dhcpv6-stateless', or 'slaac'.
425     :param use_default_subnetpool:(bool) use the default subnetpool for
426                                   ``ip_version`` to obtain a CIDR. It is
427                                   required to pass ``None`` to the ``cidr``
428                                   argument when enabling this option.
429     :returns:(string) the subnet id.
430     """
431     try:
432         subnet = shade_client.create_subnet(
433             network_name_or_id, cidr=cidr, ip_version=ip_version,
434             enable_dhcp=enable_dhcp, subnet_name=subnet_name,
435             tenant_id=tenant_id, allocation_pools=allocation_pools,
436             gateway_ip=gateway_ip, disable_gateway_ip=disable_gateway_ip,
437             dns_nameservers=dns_nameservers, host_routes=host_routes,
438             ipv6_ra_mode=ipv6_ra_mode, ipv6_address_mode=ipv6_address_mode,
439             use_default_subnetpool=use_default_subnetpool)
440         return subnet['id']
441     except exc.OpenStackCloudException as o_exc:
442         log.error("Error [create_neutron_subnet(shade_client)]. "
443                   "Exception message: %s", o_exc.orig_message)
444         return None
445
446
447 def create_neutron_router(shade_client, name=None, admin_state_up=True,
448                           ext_gateway_net_id=None, enable_snat=None,
449                           ext_fixed_ips=None, project_id=None):
450     """Create a logical router.
451
452     :param name:(string) the router name.
453     :param admin_state_up:(bool) the administrative state of the router.
454     :param ext_gateway_net_id:(string) network ID for the external gateway.
455     :param enable_snat:(bool) enable Source NAT (SNAT) attribute.
456     :param ext_fixed_ips: List of dictionaries of desired IP and/or subnet
457                           on the external network.
458     :param project_id:(string) project ID for the router.
459
460     :returns:(string) the router id.
461     """
462     try:
463         router = shade_client.create_router(
464             name, admin_state_up, ext_gateway_net_id, enable_snat,
465             ext_fixed_ips, project_id)
466         return router['id']
467     except exc.OpenStackCloudException as o_exc:
468         log.error("Error [create_neutron_router(shade_client)]. "
469                   "Exception message: %s", o_exc.orig_message)
470
471
472 def delete_neutron_router(shade_client, router_id):
473     try:
474         return shade_client.delete_router(router_id)
475     except exc.OpenStackCloudException as o_exc:
476         log.error("Error [delete_neutron_router(shade_client, '%s')]. "
477                   "Exception message: %s", router_id, o_exc.orig_message)
478         return False
479
480
481 def remove_gateway_router(neutron_client, router_id):      # pragma: no cover
482     try:
483         neutron_client.remove_gateway_router(router_id)
484         return True
485     except Exception:  # pylint: disable=broad-except
486         log.error("Error [remove_gateway_router(neutron_client, '%s')]",
487                   router_id)
488         return False
489
490
491 def remove_router_interface(shade_client, router, subnet_id=None,
492                             port_id=None):
493     """Detach a subnet from an internal router interface.
494
495     At least one of subnet_id or port_id must be supplied. If you specify both
496     subnet and port ID, the subnet ID must correspond to the subnet ID of the
497     first IP address on the port specified by the port ID.
498     Otherwise an error occurs.
499
500     :param router: The dict object of the router being changed
501     :param subnet_id:(string) The ID of the subnet to use for the interface
502     :param port_id:(string) The ID of the port to use for the interface
503     :returns: True on success
504     """
505     try:
506         shade_client.remove_router_interface(
507             router, subnet_id=subnet_id, port_id=port_id)
508         return True
509     except exc.OpenStackCloudException as o_exc:
510         log.error("Error [remove_interface_router(shade_client)]. "
511                   "Exception message: %s", o_exc.orig_message)
512         return False
513
514
515 def create_floating_ip(shade_client, network_name_or_id=None, server=None,
516                        fixed_address=None, nat_destination=None,
517                        port=None, wait=False, timeout=60):
518     """Allocate a new floating IP from a network or a pool.
519
520     :param network_name_or_id: Name or ID of the network
521                                that the floating IP should come from.
522     :param server: Server dict for the server to create
523                   the IP for and to which it should be attached.
524     :param fixed_address: Fixed IP to attach the floating ip to.
525     :param nat_destination: Name or ID of the network
526                            that the fixed IP to attach the floating
527                            IP to should be on.
528     :param port: The port ID that the floating IP should be
529                 attached to. Specifying a port conflicts with specifying a
530                 server,fixed_address or nat_destination.
531     :param wait: Whether to wait for the IP to be active.Only applies
532                 if a server is provided.
533     :param timeout: How long to wait for the IP to be active.Only
534                    applies if a server is provided.
535
536     :returns:Floating IP id and address
537     """
538     try:
539         fip = shade_client.create_floating_ip(
540             network=network_name_or_id, server=server,
541             fixed_address=fixed_address, nat_destination=nat_destination,
542             port=port, wait=wait, timeout=timeout)
543         return {'fip_addr': fip['floating_ip_address'], 'fip_id': fip['id']}
544     except exc.OpenStackCloudException as o_exc:
545         log.error("Error [create_floating_ip(shade_client)]. "
546                   "Exception message: %s", o_exc.orig_message)
547
548
549 def delete_floating_ip(shade_client, floating_ip_id, retry=1):
550     try:
551         return shade_client.delete_floating_ip(floating_ip_id=floating_ip_id,
552                                                retry=retry)
553     except exc.OpenStackCloudException as o_exc:
554         log.error("Error [delete_floating_ip(shade_client,'%s')]. "
555                   "Exception message: %s", floating_ip_id, o_exc.orig_message)
556         return False
557
558
559 def create_security_group_rule(shade_client, secgroup_name_or_id,
560                                port_range_min=None, port_range_max=None,
561                                protocol=None, remote_ip_prefix=None,
562                                remote_group_id=None, direction='ingress',
563                                ethertype='IPv4', project_id=None):
564     """Create a new security group rule
565
566     :param secgroup_name_or_id:(string) The security group name or ID to
567                                associate with this security group rule. If a
568                                non-unique group name is given, an exception is
569                                raised.
570     :param port_range_min:(int) The minimum port number in the range that is
571                           matched by the security group rule. If the protocol
572                           is TCP or UDP, this value must be less than or equal
573                           to the port_range_max attribute value. If nova is
574                           used by the cloud provider for security groups, then
575                           a value of None will be transformed to -1.
576     :param port_range_max:(int) The maximum port number in the range that is
577                           matched by the security group rule. The
578                           port_range_min attribute constrains the
579                           port_range_max attribute. If nova is used by the
580                           cloud provider for security groups, then a value of
581                           None will be transformed to -1.
582     :param protocol:(string) The protocol that is matched by the security group
583                     rule. Valid values are None, tcp, udp, and icmp.
584     :param remote_ip_prefix:(string) The remote IP prefix to be associated with
585                             this security group rule. This attribute matches
586                             the specified IP prefix as the source IP address of
587                             the IP packet.
588     :param remote_group_id:(string) The remote group ID to be associated with
589                            this security group rule.
590     :param direction:(string) Ingress or egress: The direction in which the
591                      security group rule is applied.
592     :param ethertype:(string) Must be IPv4 or IPv6, and addresses represented
593                      in CIDR must match the ingress or egress rules.
594     :param project_id:(string) Specify the project ID this security group will
595                       be created on (admin-only).
596
597     :returns: True on success.
598     """
599
600     try:
601         shade_client.create_security_group_rule(
602             secgroup_name_or_id, port_range_min=port_range_min,
603             port_range_max=port_range_max, protocol=protocol,
604             remote_ip_prefix=remote_ip_prefix, remote_group_id=remote_group_id,
605             direction=direction, ethertype=ethertype, project_id=project_id)
606         return True
607     except exc.OpenStackCloudException as op_exc:
608         log.error("Failed to create_security_group_rule(shade_client). "
609                   "Exception message: %s", op_exc.orig_message)
610         return False
611
612
613 def create_security_group_full(shade_client, sg_name,
614                                sg_description, project_id=None):
615     security_group = shade_client.get_security_group(sg_name)
616
617     if security_group:
618         log.info("Using existing security group '%s'...", sg_name)
619         return security_group['id']
620
621     log.info("Creating security group  '%s'...", sg_name)
622     try:
623         security_group = shade_client.create_security_group(
624             sg_name, sg_description, project_id=project_id)
625     except (exc.OpenStackCloudException,
626             exc.OpenStackCloudUnavailableFeature) as op_exc:
627         log.error("Error [create_security_group(shade_client, %s, %s)]. "
628                   "Exception message: %s", sg_name, sg_description,
629                   op_exc.orig_message)
630         return
631
632     log.debug("Security group '%s' with ID=%s created successfully.",
633               security_group['name'], security_group['id'])
634
635     log.debug("Adding ICMP rules in security group '%s'...", sg_name)
636     if not create_security_group_rule(shade_client, security_group['id'],
637                                       direction='ingress', protocol='icmp'):
638         log.error("Failed to create the security group rule...")
639         shade_client.delete_security_group(sg_name)
640         return
641
642     log.debug("Adding SSH rules in security group '%s'...", sg_name)
643     if not create_security_group_rule(shade_client, security_group['id'],
644                                       direction='ingress', protocol='tcp',
645                                       port_range_min='22',
646                                       port_range_max='22'):
647         log.error("Failed to create the security group rule...")
648         shade_client.delete_security_group(sg_name)
649         return
650
651     if not create_security_group_rule(shade_client, security_group['id'],
652                                       direction='egress', protocol='tcp',
653                                       port_range_min='22',
654                                       port_range_max='22'):
655         log.error("Failed to create the security group rule...")
656         shade_client.delete_security_group(sg_name)
657         return
658     return security_group['id']
659
660
661 # *********************************************
662 #   GLANCE
663 # *********************************************
664 def get_image_id(glance_client, image_name):    # pragma: no cover
665     images = glance_client.images.list()
666     return next((i.id for i in images if i.name == image_name), None)
667
668
669 def create_image(glance_client, image_name, file_path, disk_format,
670                  container_format, min_disk, min_ram, protected, tag,
671                  public, **kwargs):    # pragma: no cover
672     if not os.path.isfile(file_path):
673         log.error("Error: file %s does not exist.", file_path)
674         return None
675     try:
676         image_id = get_image_id(glance_client, image_name)
677         if image_id is not None:
678             log.info("Image %s already exists.", image_name)
679         else:
680             log.info("Creating image '%s' from '%s'...", image_name, file_path)
681
682             image = glance_client.images.create(
683                 name=image_name, visibility=public, disk_format=disk_format,
684                 container_format=container_format, min_disk=min_disk,
685                 min_ram=min_ram, tags=tag, protected=protected, **kwargs)
686             image_id = image.id
687             with open(file_path) as image_data:
688                 glance_client.images.upload(image_id, image_data)
689         return image_id
690     except Exception:  # pylint: disable=broad-except
691         log.error(
692             "Error [create_glance_image(glance_client, '%s', '%s', '%s')]",
693             image_name, file_path, public)
694         return None
695
696
697 def delete_image(glance_client, image_id):    # pragma: no cover
698     try:
699         glance_client.images.delete(image_id)
700
701     except Exception:  # pylint: disable=broad-except
702         log.exception("Error [delete_flavor(glance_client, %s)]", image_id)
703         return False
704     else:
705         return True
706
707
708 def list_images(shade_client=None):
709     if shade_client is None:
710         shade_client = get_shade_client()
711
712     try:
713         return shade_client.list_images()
714     except exc.OpenStackCloudException as o_exc:
715         log.error("Error [list_images(shade_client)]."
716                   "Exception message, '%s'", o_exc.orig_message)
717         return False
718
719
720 # *********************************************
721 #   CINDER
722 # *********************************************
723 def get_volume_id(volume_name):    # pragma: no cover
724     volumes = get_cinder_client().volumes.list()
725     return next((v.id for v in volumes if v.name == volume_name), None)
726
727
728 def create_volume(cinder_client, volume_name, volume_size,
729                   volume_image=False):    # pragma: no cover
730     try:
731         if volume_image:
732             volume = cinder_client.volumes.create(name=volume_name,
733                                                   size=volume_size,
734                                                   imageRef=volume_image)
735         else:
736             volume = cinder_client.volumes.create(name=volume_name,
737                                                   size=volume_size)
738         return volume
739     except Exception:  # pylint: disable=broad-except
740         log.exception("Error [create_volume(cinder_client, %s)]",
741                       (volume_name, volume_size))
742         return None
743
744
745 def delete_volume(cinder_client, volume_id,
746                   forced=False):      # pragma: no cover
747     try:
748         if forced:
749             try:
750                 cinder_client.volumes.detach(volume_id)
751             except Exception:  # pylint: disable=broad-except
752                 log.error(sys.exc_info()[0])
753             cinder_client.volumes.force_delete(volume_id)
754         else:
755             while True:
756                 volume = get_cinder_client().volumes.get(volume_id)
757                 if volume.status.lower() == 'available':
758                     break
759             cinder_client.volumes.delete(volume_id)
760         return True
761     except Exception:  # pylint: disable=broad-except
762         log.exception("Error [delete_volume(cinder_client, '%s')]", volume_id)
763         return False
764
765
766 def detach_volume(server_id, volume_id):      # pragma: no cover
767     try:
768         get_nova_client().volumes.delete_server_volume(server_id, volume_id)
769         return True
770     except Exception:  # pylint: disable=broad-except
771         log.exception("Error [detach_server_volume(nova_client, '%s', '%s')]",
772                       server_id, volume_id)
773         return False