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