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