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