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