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