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