add yardstick iruya 9.0.0 release notes
[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 def get_shade_operator_client(**os_cloud_config):
176     """Get Shade Operator cloud client
177
178     :return: ``shade.OperatorCloud`` object.
179     """
180     params = copy.deepcopy(constants.OS_CLOUD_DEFAULT_CONFIG)
181     params.update(os_cloud_config)
182     return shade.operator_cloud(**params)
183
184
185 # *********************************************
186 #   NOVA
187 # *********************************************
188 def create_keypair(shade_client, name, public_key=None):
189     """Create a new keypair.
190
191     :param name: Name of the keypair being created.
192     :param public_key: Public key for the new keypair.
193
194     :return: Created keypair.
195     """
196     try:
197         return shade_client.create_keypair(name, public_key=public_key)
198     except exc.OpenStackCloudException as o_exc:
199         log.error("Error [create_keypair(shade_client)]. "
200                   "Exception message, '%s'", o_exc.orig_message)
201
202
203 def create_instance_and_wait_for_active(shade_client, name, image,
204                                         flavor, auto_ip=True, ips=None,
205                                         ip_pool=None, root_volume=None,
206                                         terminate_volume=False, wait=True,
207                                         timeout=180, reuse_ips=True,
208                                         network=None, boot_from_volume=False,
209                                         volume_size='20', boot_volume=None,
210                                         volumes=None, nat_destination=None,
211                                         **kwargs):
212     """Create a virtual server instance.
213
214     :param name:(string) Name of the server.
215     :param image:(dict) Image dict, name or ID to boot with. Image is required
216                  unless boot_volume is given.
217     :param flavor:(dict) Flavor dict, name or ID to boot onto.
218     :param auto_ip: Whether to take actions to find a routable IP for
219                     the server.
220     :param ips: List of IPs to attach to the server.
221     :param ip_pool:(string) Name of the network or floating IP pool to get an
222                    address from.
223     :param root_volume:(string) Name or ID of a volume to boot from.
224                        (defaults to None - deprecated, use boot_volume)
225     :param boot_volume:(string) Name or ID of a volume to boot from.
226     :param terminate_volume:(bool) If booting from a volume, whether it should
227                             be deleted when the server is destroyed.
228     :param volumes:(optional) A list of volumes to attach to the server.
229     :param wait:(optional) Wait for the address to appear as assigned to the server.
230     :param timeout: Seconds to wait, defaults to 60.
231     :param reuse_ips:(bool)Whether to attempt to reuse pre-existing
232                      floating ips should a floating IP be needed.
233     :param network:(dict) Network dict or name or ID to attach the server to.
234                    Mutually exclusive with the nics parameter. Can also be be
235                    a list of network names or IDs or network dicts.
236     :param boot_from_volume:(bool) Whether to boot from volume. 'boot_volume'
237                             implies True, but boot_from_volume=True with
238                             no boot_volume is valid and will create a
239                             volume from the image and use that.
240     :param volume_size: When booting an image from volume, how big should
241                         the created volume be?
242     :param nat_destination: Which network should a created floating IP
243                             be attached to, if it's not possible to infer from
244                             the cloud's configuration.
245     :param meta:(optional) A dict of arbitrary key/value metadata to store for
246                 this server. Both keys and values must be <=255 characters.
247     :param reservation_id: A UUID for the set of servers being requested.
248     :param min_count:(optional extension) The minimum number of servers to
249                      launch.
250     :param max_count:(optional extension) The maximum number of servers to
251                      launch.
252     :param security_groups: A list of security group names.
253     :param userdata: User data to pass to be exposed by the metadata server
254                      this can be a file type object as well or a string.
255     :param key_name:(optional extension) Name of previously created keypair to
256                     inject into the instance.
257     :param availability_zone: Name of the availability zone for instance
258                               placement.
259     :param block_device_mapping:(optional) A dict of block device mappings for
260                                 this server.
261     :param block_device_mapping_v2:(optional) A dict of block device mappings
262                                    for this server.
263     :param nics:(optional extension) An ordered list of nics to be added to
264                  this server, with information about connected networks, fixed
265                  IPs, port etc.
266     :param scheduler_hints:(optional extension) Arbitrary key-value pairs
267                            specified by the client to help boot an instance.
268     :param config_drive:(optional extension) Value for config drive either
269                          boolean, or volume-id.
270     :param disk_config:(optional extension) Control how the disk is partitioned
271                        when the server is created. Possible values are 'AUTO'
272                        or 'MANUAL'.
273     :param admin_pass:(optional extension) Add a user supplied admin password.
274
275     :returns: The created server.
276     """
277     try:
278         return shade_client.create_server(
279             name, image, flavor, auto_ip=auto_ip, ips=ips, ip_pool=ip_pool,
280             root_volume=root_volume, terminate_volume=terminate_volume,
281             wait=wait, timeout=timeout, reuse_ips=reuse_ips, network=network,
282             boot_from_volume=boot_from_volume, volume_size=volume_size,
283             boot_volume=boot_volume, volumes=volumes,
284             nat_destination=nat_destination, **kwargs)
285     except exc.OpenStackCloudException as o_exc:
286         log.error("Error [create_instance(shade_client)]. "
287                   "Exception message, '%s'", o_exc.orig_message)
288
289
290 def attach_volume_to_server(shade_client, server_name_or_id, volume_name_or_id,
291                             device=None, wait=True, timeout=None):
292     """Attach a volume to a server.
293
294     This will attach a volume, described by the passed in volume
295     dict, to the server described by the passed in server dict on the named
296     device on the server.
297
298     If the volume is already attached to the server, or generally not
299     available, then an exception is raised. To re-attach to a server,
300     but under a different device, the user must detach it first.
301
302     :param server_name_or_id:(string) The server name or id to attach to.
303     :param volume_name_or_id:(string) The volume name or id to attach.
304     :param device:(string) The device name where the volume will attach.
305     :param wait:(bool) If true, waits for volume to be attached.
306     :param timeout: Seconds to wait for volume attachment. None is forever.
307
308     :returns: True if attached successful, False otherwise.
309     """
310     try:
311         server = shade_client.get_server(name_or_id=server_name_or_id)
312         volume = shade_client.get_volume(volume_name_or_id)
313         shade_client.attach_volume(
314             server, volume, device=device, wait=wait, timeout=timeout)
315         return True
316     except exc.OpenStackCloudException as o_exc:
317         log.error("Error [attach_volume_to_server(shade_client)]. "
318                   "Exception message: %s", o_exc.orig_message)
319         return False
320
321
322 def delete_instance(shade_client, name_or_id, wait=False, timeout=180,
323                     delete_ips=False, delete_ip_retry=1):
324     """Delete a server instance.
325
326     :param name_or_id: name or ID of the server to delete
327     :param wait:(bool) If true, waits for server to be deleted.
328     :param timeout:(int) Seconds to wait for server deletion.
329     :param delete_ips:(bool) If true, deletes any floating IPs associated with
330                       the instance.
331     :param delete_ip_retry:(int) Number of times to retry deleting
332                            any floating ips, should the first try be
333                            unsuccessful.
334     :returns: True if delete succeeded, False otherwise.
335     """
336     try:
337         return shade_client.delete_server(
338             name_or_id, wait=wait, timeout=timeout, delete_ips=delete_ips,
339             delete_ip_retry=delete_ip_retry)
340     except exc.OpenStackCloudException as o_exc:
341         log.error("Error [delete_instance(shade_client, '%s')]. "
342                   "Exception message: %s", name_or_id,
343                   o_exc.orig_message)
344         return False
345
346
347 def get_server(shade_client, name_or_id=None, filters=None, detailed=False,
348                bare=False):
349     """Get a server by name or ID.
350
351     :param name_or_id: Name or ID of the server.
352     :param filters:(dict) A dictionary of meta data to use for further
353                    filtering.
354     :param detailed:(bool) Whether or not to add detailed additional
355                     information.
356     :param bare:(bool) Whether to skip adding any additional information to the
357                 server record.
358
359     :returns: A server ``munch.Munch`` or None if no matching server is found.
360     """
361     try:
362         return shade_client.get_server(name_or_id=name_or_id, filters=filters,
363                                        detailed=detailed, bare=bare)
364     except exc.OpenStackCloudException as o_exc:
365         log.error("Error [get_server(shade_client, '%s')]. "
366                   "Exception message: %s", name_or_id, o_exc.orig_message)
367
368
369 def create_flavor(name, ram, vcpus, disk, **kwargs):   # pragma: no cover
370     try:
371         return get_nova_client().flavors.create(name, ram, vcpus,
372                                                 disk, **kwargs)
373     except Exception:  # pylint: disable=broad-except
374         log.exception("Error [create_flavor(nova_client, %s, %s, %s, %s, %s)]",
375                       name, ram, disk, vcpus, kwargs['is_public'])
376         return None
377
378
379 def get_flavor_id(nova_client, flavor_name):    # pragma: no cover
380     flavors = nova_client.flavors.list(detailed=True)
381     flavor_id = ''
382     for f in flavors:
383         if f.name == flavor_name:
384             flavor_id = f.id
385             break
386     return flavor_id
387
388
389 def get_flavor(shade_client, name_or_id, filters=None, get_extra=True):
390     """Get a flavor by name or ID.
391
392     :param name_or_id: Name or ID of the flavor.
393     :param filters: A dictionary of meta data to use for further filtering.
394     :param get_extra: Whether or not the list_flavors call should get the extra
395     flavor specs.
396
397     :returns: A flavor ``munch.Munch`` or None if no matching flavor is found.
398     """
399     try:
400         return shade_client.get_flavor(name_or_id, filters=filters,
401                                        get_extra=get_extra)
402     except exc.OpenStackCloudException as o_exc:
403         log.error("Error [get_flavor(shade_client, '%s')]. "
404                   "Exception message: %s", name_or_id, o_exc.orig_message)
405
406
407 def delete_flavor(flavor_id):    # pragma: no cover
408     try:
409         get_nova_client().flavors.delete(flavor_id)
410     except Exception:  # pylint: disable=broad-except
411         log.exception("Error [delete_flavor(nova_client, %s)]", flavor_id)
412         return False
413     else:
414         return True
415
416
417 def delete_keypair(shade_client, name):
418     """Delete a keypair.
419
420     :param name: Name of the keypair to delete.
421
422     :returns: True if delete succeeded, False otherwise.
423     """
424     try:
425         return shade_client.delete_keypair(name)
426     except exc.OpenStackCloudException as o_exc:
427         log.error("Error [delete_neutron_router(shade_client, '%s')]. "
428                   "Exception message: %s", name, o_exc.orig_message)
429         return False
430
431
432 # *********************************************
433 #   NEUTRON
434 # *********************************************
435 def create_neutron_net(shade_client, network_name, shared=False,
436                        admin_state_up=True, external=False, provider=None,
437                        project_id=None):
438     """Create a neutron network.
439
440     :param network_name:(string) name of the network being created.
441     :param shared:(bool) whether the network is shared.
442     :param admin_state_up:(bool) set the network administrative state.
443     :param external:(bool) whether this network is externally accessible.
444     :param provider:(dict) a dict of network provider options.
445     :param project_id:(string) specify the project ID this network
446                       will be created on (admin-only).
447     :returns:(string) the network id.
448     """
449     try:
450         networks = shade_client.create_network(
451             name=network_name, shared=shared, admin_state_up=admin_state_up,
452             external=external, provider=provider, project_id=project_id)
453         return networks['id']
454     except exc.OpenStackCloudException as o_exc:
455         log.error("Error [create_neutron_net(shade_client)]."
456                   "Exception message, '%s'", o_exc.orig_message)
457         return None
458
459
460 def delete_neutron_net(shade_client, network_id):
461     try:
462         return shade_client.delete_network(network_id)
463     except exc.OpenStackCloudException:
464         log.error("Error [delete_neutron_net(shade_client, '%s')]", network_id)
465         return False
466
467
468 def create_neutron_subnet(shade_client, network_name_or_id, cidr=None,
469                           ip_version=4, enable_dhcp=False, subnet_name=None,
470                           tenant_id=None, allocation_pools=None,
471                           gateway_ip=None, disable_gateway_ip=False,
472                           dns_nameservers=None, host_routes=None,
473                           ipv6_ra_mode=None, ipv6_address_mode=None,
474                           use_default_subnetpool=False):
475     """Create a subnet on a specified network.
476
477     :param network_name_or_id:(string) the unique name or ID of the
478                               attached network. If a non-unique name is
479                               supplied, an exception is raised.
480     :param cidr:(string) the CIDR.
481     :param ip_version:(int) the IP version.
482     :param enable_dhcp:(bool) whether DHCP is enable.
483     :param subnet_name:(string) the name of the subnet.
484     :param tenant_id:(string) the ID of the tenant who owns the network.
485     :param allocation_pools: A list of dictionaries of the start and end
486                             addresses for the allocation pools.
487     :param gateway_ip:(string) the gateway IP address.
488     :param disable_gateway_ip:(bool) whether gateway IP address is enabled.
489     :param dns_nameservers: A list of DNS name servers for the subnet.
490     :param host_routes: A list of host route dictionaries for the subnet.
491     :param ipv6_ra_mode:(string) IPv6 Router Advertisement mode.
492                         Valid values are: 'dhcpv6-stateful',
493                         'dhcpv6-stateless', or 'slaac'.
494     :param ipv6_address_mode:(string) IPv6 address mode.
495                              Valid values are: 'dhcpv6-stateful',
496                              'dhcpv6-stateless', or 'slaac'.
497     :param use_default_subnetpool:(bool) use the default subnetpool for
498                                   ``ip_version`` to obtain a CIDR. It is
499                                   required to pass ``None`` to the ``cidr``
500                                   argument when enabling this option.
501     :returns:(string) the subnet id.
502     """
503     try:
504         subnet = shade_client.create_subnet(
505             network_name_or_id, cidr=cidr, ip_version=ip_version,
506             enable_dhcp=enable_dhcp, subnet_name=subnet_name,
507             tenant_id=tenant_id, allocation_pools=allocation_pools,
508             gateway_ip=gateway_ip, disable_gateway_ip=disable_gateway_ip,
509             dns_nameservers=dns_nameservers, host_routes=host_routes,
510             ipv6_ra_mode=ipv6_ra_mode, ipv6_address_mode=ipv6_address_mode,
511             use_default_subnetpool=use_default_subnetpool)
512         return subnet['id']
513     except exc.OpenStackCloudException as o_exc:
514         log.error("Error [create_neutron_subnet(shade_client)]. "
515                   "Exception message: %s", o_exc.orig_message)
516         return None
517
518
519 def create_neutron_router(shade_client, name=None, admin_state_up=True,
520                           ext_gateway_net_id=None, enable_snat=None,
521                           ext_fixed_ips=None, project_id=None):
522     """Create a logical router.
523
524     :param name:(string) the router name.
525     :param admin_state_up:(bool) the administrative state of the router.
526     :param ext_gateway_net_id:(string) network ID for the external gateway.
527     :param enable_snat:(bool) enable Source NAT (SNAT) attribute.
528     :param ext_fixed_ips: List of dictionaries of desired IP and/or subnet
529                           on the external network.
530     :param project_id:(string) project ID for the router.
531
532     :returns:(string) the router id.
533     """
534     try:
535         router = shade_client.create_router(
536             name, admin_state_up, ext_gateway_net_id, enable_snat,
537             ext_fixed_ips, project_id)
538         return router['id']
539     except exc.OpenStackCloudException as o_exc:
540         log.error("Error [create_neutron_router(shade_client)]. "
541                   "Exception message: %s", o_exc.orig_message)
542
543
544 def delete_neutron_router(shade_client, router_id):
545     try:
546         return shade_client.delete_router(router_id)
547     except exc.OpenStackCloudException as o_exc:
548         log.error("Error [delete_neutron_router(shade_client, '%s')]. "
549                   "Exception message: %s", router_id, o_exc.orig_message)
550         return False
551
552
553 def remove_gateway_router(neutron_client, router_id):      # pragma: no cover
554     try:
555         neutron_client.remove_gateway_router(router_id)
556         return True
557     except Exception:  # pylint: disable=broad-except
558         log.error("Error [remove_gateway_router(neutron_client, '%s')]",
559                   router_id)
560         return False
561
562
563 def remove_router_interface(shade_client, router, subnet_id=None,
564                             port_id=None):
565     """Detach a subnet from an internal router interface.
566
567     At least one of subnet_id or port_id must be supplied. If you specify both
568     subnet and port ID, the subnet ID must correspond to the subnet ID of the
569     first IP address on the port specified by the port ID.
570     Otherwise an error occurs.
571
572     :param router: The dict object of the router being changed
573     :param subnet_id:(string) The ID of the subnet to use for the interface
574     :param port_id:(string) The ID of the port to use for the interface
575     :returns: True on success
576     """
577     try:
578         shade_client.remove_router_interface(
579             router, subnet_id=subnet_id, port_id=port_id)
580         return True
581     except exc.OpenStackCloudException as o_exc:
582         log.error("Error [remove_interface_router(shade_client)]. "
583                   "Exception message: %s", o_exc.orig_message)
584         return False
585
586
587 def create_floating_ip(shade_client, network_name_or_id=None, server=None,
588                        fixed_address=None, nat_destination=None,
589                        port=None, wait=False, timeout=60):
590     """Allocate a new floating IP from a network or a pool.
591
592     :param network_name_or_id: Name or ID of the network
593                                that the floating IP should come from.
594     :param server: Server dict for the server to create
595                   the IP for and to which it should be attached.
596     :param fixed_address: Fixed IP to attach the floating ip to.
597     :param nat_destination: Name or ID of the network
598                            that the fixed IP to attach the floating
599                            IP to should be on.
600     :param port: The port ID that the floating IP should be
601                 attached to. Specifying a port conflicts with specifying a
602                 server,fixed_address or nat_destination.
603     :param wait: Whether to wait for the IP to be active.Only applies
604                 if a server is provided.
605     :param timeout: How long to wait for the IP to be active.Only
606                    applies if a server is provided.
607
608     :returns:Floating IP id and address
609     """
610     try:
611         fip = shade_client.create_floating_ip(
612             network=network_name_or_id, server=server,
613             fixed_address=fixed_address, nat_destination=nat_destination,
614             port=port, wait=wait, timeout=timeout)
615         return {'fip_addr': fip['floating_ip_address'], 'fip_id': fip['id']}
616     except exc.OpenStackCloudException as o_exc:
617         log.error("Error [create_floating_ip(shade_client)]. "
618                   "Exception message: %s", o_exc.orig_message)
619
620
621 def delete_floating_ip(shade_client, floating_ip_id, retry=1):
622     try:
623         return shade_client.delete_floating_ip(floating_ip_id=floating_ip_id,
624                                                retry=retry)
625     except exc.OpenStackCloudException as o_exc:
626         log.error("Error [delete_floating_ip(shade_client,'%s')]. "
627                   "Exception message: %s", floating_ip_id, o_exc.orig_message)
628         return False
629
630
631 def create_security_group_rule(shade_client, secgroup_name_or_id,
632                                port_range_min=None, port_range_max=None,
633                                protocol=None, remote_ip_prefix=None,
634                                remote_group_id=None, direction='ingress',
635                                ethertype='IPv4', project_id=None):
636     """Create a new security group rule
637
638     :param secgroup_name_or_id:(string) The security group name or ID to
639                                associate with this security group rule. If a
640                                non-unique group name is given, an exception is
641                                raised.
642     :param port_range_min:(int) The minimum port number in the range that is
643                           matched by the security group rule. If the protocol
644                           is TCP or UDP, this value must be less than or equal
645                           to the port_range_max attribute value. If nova is
646                           used by the cloud provider for security groups, then
647                           a value of None will be transformed to -1.
648     :param port_range_max:(int) The maximum port number in the range that is
649                           matched by the security group rule. The
650                           port_range_min attribute constrains the
651                           port_range_max attribute. If nova is used by the
652                           cloud provider for security groups, then a value of
653                           None will be transformed to -1.
654     :param protocol:(string) The protocol that is matched by the security group
655                     rule. Valid values are None, tcp, udp, and icmp.
656     :param remote_ip_prefix:(string) The remote IP prefix to be associated with
657                             this security group rule. This attribute matches
658                             the specified IP prefix as the source IP address of
659                             the IP packet.
660     :param remote_group_id:(string) The remote group ID to be associated with
661                            this security group rule.
662     :param direction:(string) Ingress or egress: The direction in which the
663                      security group rule is applied.
664     :param ethertype:(string) Must be IPv4 or IPv6, and addresses represented
665                      in CIDR must match the ingress or egress rules.
666     :param project_id:(string) Specify the project ID this security group will
667                       be created on (admin-only).
668
669     :returns: True on success.
670     """
671
672     try:
673         shade_client.create_security_group_rule(
674             secgroup_name_or_id, port_range_min=port_range_min,
675             port_range_max=port_range_max, protocol=protocol,
676             remote_ip_prefix=remote_ip_prefix, remote_group_id=remote_group_id,
677             direction=direction, ethertype=ethertype, project_id=project_id)
678         return True
679     except exc.OpenStackCloudException as op_exc:
680         log.error("Failed to create_security_group_rule(shade_client). "
681                   "Exception message: %s", op_exc.orig_message)
682         return False
683
684
685 def create_security_group_full(shade_client, sg_name,
686                                sg_description, project_id=None):
687     security_group = shade_client.get_security_group(sg_name)
688
689     if security_group:
690         log.info("Using existing security group '%s'...", sg_name)
691         return security_group['id']
692
693     log.info("Creating security group  '%s'...", sg_name)
694     try:
695         security_group = shade_client.create_security_group(
696             sg_name, sg_description, project_id=project_id)
697     except (exc.OpenStackCloudException,
698             exc.OpenStackCloudUnavailableFeature) as op_exc:
699         log.error("Error [create_security_group(shade_client, %s, %s)]. "
700                   "Exception message: %s", sg_name, sg_description,
701                   op_exc.orig_message)
702         return
703
704     log.debug("Security group '%s' with ID=%s created successfully.",
705               security_group['name'], security_group['id'])
706
707     log.debug("Adding ICMP rules in security group '%s'...", sg_name)
708     if not create_security_group_rule(shade_client, security_group['id'],
709                                       direction='ingress', protocol='icmp'):
710         log.error("Failed to create the security group rule...")
711         shade_client.delete_security_group(sg_name)
712         return
713
714     log.debug("Adding SSH rules in security group '%s'...", sg_name)
715     if not create_security_group_rule(shade_client, security_group['id'],
716                                       direction='ingress', 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
723     if not create_security_group_rule(shade_client, security_group['id'],
724                                       direction='egress', protocol='tcp',
725                                       port_range_min='22',
726                                       port_range_max='22'):
727         log.error("Failed to create the security group rule...")
728         shade_client.delete_security_group(sg_name)
729         return
730     return security_group['id']
731
732
733 # *********************************************
734 #   GLANCE
735 # *********************************************
736 def create_image(shade_client, name, filename=None, container='images',
737                  md5=None, sha256=None, disk_format=None,
738                  container_format=None, disable_vendor_agent=True,
739                  wait=False, timeout=3600, allow_duplicates=False, meta=None,
740                  volume=None, **kwargs):
741     """Upload an image.
742
743     :param name:(str) Name of the image to create. If it is a pathname of an
744                 image, the name will be constructed from the extensionless
745                 basename of the path.
746     :param filename:(str) The path to the file to upload, if needed.
747     :param container:(str) Name of the container in swift where images should
748                      be uploaded for import if the cloud requires such a thing.
749     :param md5:(str) md5 sum of the image file. If not given, an md5 will
750             be calculated.
751     :param sha256:(str) sha256 sum of the image file. If not given, an md5
752                   will be calculated.
753     :param disk_format:(str) The disk format the image is in.
754     :param container_format:(str) The container format the image is in.
755     :param disable_vendor_agent:(bool) Whether or not to append metadata
756                                 flags to the image to inform the cloud in
757                                 question to not expect a vendor agent to be running.
758     :param wait:(bool) If true, waits for image to be created.
759     :param timeout:(str) Seconds to wait for image creation.
760     :param allow_duplicates:(bool) If true, skips checks that enforce unique
761                             image name.
762     :param meta:(dict) A dict of key/value pairs to use for metadata that
763                 bypasses automatic type conversion.
764     :param volume:(str) Name or ID or volume object of a volume to create an
765                   image from.
766     Additional kwargs will be passed to the image creation as additional
767     metadata for the image and will have all values converted to string
768     except for min_disk, min_ram, size and virtual_size which will be
769     converted to int.
770     If you are sure you have all of your data types correct or have an
771     advanced need to be explicit, use meta. If you are just a normal
772     consumer, using kwargs is likely the right choice.
773     If a value is in meta and kwargs, meta wins.
774     :returns: Image id
775     """
776     try:
777         image_id = shade_client.get_image_id(name)
778         if image_id is not None:
779             log.info("Image %s already exists.", name)
780             return image_id
781         log.info("Creating image '%s'", name)
782         image = shade_client.create_image(
783             name, filename=filename, container=container, md5=md5, sha256=sha256,
784             disk_format=disk_format, container_format=container_format,
785             disable_vendor_agent=disable_vendor_agent, wait=wait, timeout=timeout,
786             allow_duplicates=allow_duplicates, meta=meta, volume=volume, **kwargs)
787         image_id = image["id"]
788         return image_id
789     except exc.OpenStackCloudException as op_exc:
790         log.error("Failed to create_image(shade_client). "
791                   "Exception message: %s", op_exc.orig_message)
792
793
794 def delete_image(shade_client, name_or_id, wait=False, timeout=3600,
795                  delete_objects=True):
796     try:
797         return shade_client.delete_image(name_or_id, wait=wait,
798                                          timeout=timeout,
799                                          delete_objects=delete_objects)
800
801     except exc.OpenStackCloudException as op_exc:
802         log.error("Failed to delete_image(shade_client). "
803                   "Exception message: %s", op_exc.orig_message)
804         return False
805
806
807 def list_images(shade_client=None):
808     if shade_client is None:
809         shade_client = get_shade_client()
810
811     try:
812         return shade_client.list_images()
813     except exc.OpenStackCloudException as o_exc:
814         log.error("Error [list_images(shade_client)]."
815                   "Exception message, '%s'", o_exc.orig_message)
816         return False
817
818
819 # *********************************************
820 #   CINDER
821 # *********************************************
822 def get_volume_id(shade_client, volume_name):
823     return shade_client.get_volume_id(volume_name)
824
825
826 def get_volume(shade_client, name_or_id, filters=None):
827     """Get a volume by name or ID.
828
829     :param name_or_id: Name or ID of the volume.
830     :param filters: A dictionary of meta data to use for further filtering.
831
832     :returns: A volume ``munch.Munch`` or None if no matching volume is found.
833     """
834     return shade_client.get_volume(name_or_id, filters=filters)
835
836
837 def create_volume(shade_client, size, wait=True, timeout=None,
838                   image=None, **kwargs):
839     """Create a volume.
840
841     :param size: Size, in GB of the volume to create.
842     :param name: (optional) Name for the volume.
843     :param description: (optional) Name for the volume.
844     :param wait: If true, waits for volume to be created.
845     :param timeout: Seconds to wait for volume creation. None is forever.
846     :param image: (optional) Image name, ID or object from which to create
847                   the volume.
848
849     :returns: The created volume object.
850
851     """
852     try:
853         return shade_client.create_volume(size, wait=wait, timeout=timeout,
854                                           image=image, **kwargs)
855     except (exc.OpenStackCloudException, exc.OpenStackCloudTimeout) as op_exc:
856         log.error("Failed to create_volume(shade_client). "
857                   "Exception message: %s", op_exc.orig_message)
858
859
860 def delete_volume(shade_client, name_or_id=None, wait=True, timeout=None):
861     """Delete a volume.
862
863     :param name_or_id:(string) Name or unique ID of the volume.
864     :param wait:(bool) If true, waits for volume to be deleted.
865     :param timeout:(string) Seconds to wait for volume deletion. None is forever.
866
867     :return:  True on success, False otherwise.
868     """
869     try:
870         return shade_client.delete_volume(name_or_id=name_or_id,
871                                           wait=wait, timeout=timeout)
872     except (exc.OpenStackCloudException, exc.OpenStackCloudTimeout) as o_exc:
873         log.error("Error [delete_volume(shade_client,'%s')]. "
874                   "Exception message: %s", name_or_id, o_exc.orig_message)
875         return False
876
877
878 def detach_volume(shade_client, server_name_or_id, volume_name_or_id,
879                   wait=True, timeout=None):
880     """Detach a volume from a server.
881
882     :param server_name_or_id: The server name or id to detach from.
883     :param volume_name_or_id: The volume name or id to detach.
884     :param wait: If true, waits for volume to be detached.
885     :param timeout: Seconds to wait for volume detachment. None is forever.
886
887     :return: True on success.
888     """
889     try:
890         volume = shade_client.get_volume(volume_name_or_id)
891         server = get_server(shade_client, name_or_id=server_name_or_id)
892         shade_client.detach_volume(server, volume, wait=wait, timeout=timeout)
893         return True
894     except (exc.OpenStackCloudException, exc.OpenStackCloudTimeout) as o_exc:
895         log.error("Error [detach_volume(shade_client)]. "
896                   "Exception message: %s", o_exc.orig_message)
897         return False