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