Revert "Enable parser project"
[functest.git] / functest / utils / openstack_utils.py
1 #!/usr/bin/env python
2 #
3 # jose.lausuch@ericsson.com
4 # valentin.boucher@orange.com
5 # All rights reserved. This program and the accompanying materials
6 # are made available under the terms of the Apache License, Version 2.0
7 # which accompanies this distribution, and is available at
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10
11 import logging
12 import os.path
13 import re
14 import sys
15 import time
16
17 from keystoneauth1 import loading
18 from keystoneauth1 import session
19 from cinderclient import client as cinderclient
20 from glanceclient import client as glanceclient
21 from heatclient import client as heatclient
22 from novaclient import client as novaclient
23 from keystoneclient import client as keystoneclient
24 from neutronclient.neutron import client as neutronclient
25
26 import functest.utils.functest_utils as ft_utils
27
28 logger = logging.getLogger(__name__)
29
30 DEFAULT_API_VERSION = '2'
31 DEFAULT_HEAT_API_VERSION = '1'
32
33
34 # *********************************************
35 #   CREDENTIALS
36 # *********************************************
37 class MissingEnvVar(Exception):
38
39     def __init__(self, var):
40         self.var = var
41
42     def __str__(self):
43         return str.format("Please set the mandatory env var: {}", self.var)
44
45
46 def is_keystone_v3():
47     keystone_api_version = os.getenv('OS_IDENTITY_API_VERSION')
48     if (keystone_api_version is None or
49             keystone_api_version == '2'):
50         return False
51     else:
52         return True
53
54
55 def get_rc_env_vars():
56     env_vars = ['OS_AUTH_URL', 'OS_USERNAME', 'OS_PASSWORD']
57     if is_keystone_v3():
58         env_vars.extend(['OS_PROJECT_NAME',
59                          'OS_USER_DOMAIN_NAME',
60                          'OS_PROJECT_DOMAIN_NAME'])
61     else:
62         env_vars.extend(['OS_TENANT_NAME'])
63     return env_vars
64
65
66 def check_credentials():
67     """
68     Check if the OpenStack credentials (openrc) are sourced
69     """
70     env_vars = get_rc_env_vars()
71     return all(map(lambda v: v in os.environ and os.environ[v], env_vars))
72
73
74 def get_env_cred_dict():
75     env_cred_dict = {
76         'OS_USERNAME': 'username',
77         'OS_PASSWORD': 'password',
78         'OS_AUTH_URL': 'auth_url',
79         'OS_TENANT_NAME': 'tenant_name',
80         'OS_USER_DOMAIN_NAME': 'user_domain_name',
81         'OS_PROJECT_DOMAIN_NAME': 'project_domain_name',
82         'OS_PROJECT_NAME': 'project_name',
83         'OS_ENDPOINT_TYPE': 'endpoint_type',
84         'OS_REGION_NAME': 'region_name',
85         'OS_CACERT': 'https_cacert',
86         'OS_INSECURE': 'https_insecure'
87     }
88     return env_cred_dict
89
90
91 def get_credentials(other_creds={}):
92     """Returns a creds dictionary filled with parsed from env
93     """
94     creds = {}
95     env_vars = get_rc_env_vars()
96     env_cred_dict = get_env_cred_dict()
97
98     for envvar in env_vars:
99         if os.getenv(envvar) is None:
100             raise MissingEnvVar(envvar)
101         else:
102             creds_key = env_cred_dict.get(envvar)
103             creds.update({creds_key: os.getenv(envvar)})
104
105     if 'tenant' in other_creds.keys():
106         if is_keystone_v3():
107             tenant = 'project_name'
108         else:
109             tenant = 'tenant_name'
110         other_creds[tenant] = other_creds.pop('tenant')
111
112     creds.update(other_creds)
113
114     return creds
115
116
117 def source_credentials(rc_file):
118     with open(rc_file, "r") as f:
119         for line in f:
120             var = (line.rstrip('"\n').replace('export ', '').split("=")
121                    if re.search(r'(.*)=(.*)', line) else None)
122             # The two next lines should be modified as soon as rc_file
123             # conforms with common rules. Be aware that it could induce
124             # issues if value starts with '
125             if var:
126                 key = re.sub(r'^["\' ]*|[ \'"]*$', '', var[0])
127                 value = re.sub(r'^["\' ]*|[ \'"]*$', '', "".join(var[1:]))
128                 os.environ[key] = value
129
130
131 def get_credentials_for_rally():
132     creds = get_credentials()
133     env_cred_dict = get_env_cred_dict()
134     rally_conf = {"type": "ExistingCloud", "admin": {}}
135     for key in creds:
136         if key == 'auth_url':
137             rally_conf[key] = creds[key]
138         else:
139             rally_conf['admin'][key] = creds[key]
140
141     endpoint_types = [('internalURL', 'internal'),
142                       ('publicURL', 'public'), ('adminURL', 'admin')]
143
144     endpoint_type = get_endpoint_type_from_env()
145     if endpoint_type is not None:
146         cred_key = env_cred_dict.get('OS_ENDPOINT_TYPE')
147         for k, v in endpoint_types:
148             if endpoint_type == v:
149                 rally_conf[cred_key] = v
150
151     region_name = os.getenv('OS_REGION_NAME')
152     if region_name is not None:
153         cred_key = env_cred_dict.get('OS_REGION_NAME')
154         rally_conf[cred_key] = region_name
155
156     cred_key = env_cred_dict.get('OS_CACERT')
157     rally_conf[cred_key] = os.getenv('OS_CACERT', '')
158
159     insecure_key = env_cred_dict.get('OS_INSECURE')
160     rally_conf[insecure_key] = os.getenv('OS_INSECURE', '').lower() == 'true'
161
162     return rally_conf
163
164
165 def get_endpoint_type_from_env():
166     endpoint_type = os.environ.get("OS_ENDPOINT_TYPE",
167                                    os.environ.get("OS_INTERFACE"))
168     if endpoint_type and "URL" in endpoint_type:
169         endpoint_type = endpoint_type.replace("URL", "")
170     return endpoint_type
171
172
173 def get_session_auth(other_creds={}):
174     loader = loading.get_plugin_loader('password')
175     creds = get_credentials(other_creds)
176     auth = loader.load_from_options(**creds)
177     return auth
178
179
180 def get_endpoint(service_type, interface='public'):
181     auth = get_session_auth()
182     return get_session().get_endpoint(auth=auth,
183                                       service_type=service_type,
184                                       interface=interface)
185
186
187 def get_session(other_creds={}):
188     auth = get_session_auth(other_creds)
189     https_cacert = os.getenv('OS_CACERT', '')
190     https_insecure = os.getenv('OS_INSECURE', '').lower() == 'true'
191     return session.Session(auth=auth,
192                            verify=(https_cacert or not https_insecure))
193
194
195 # *********************************************
196 #   CLIENTS
197 # *********************************************
198 def get_keystone_client_version():
199     api_version = os.getenv('OS_IDENTITY_API_VERSION')
200     if api_version is not None:
201         logger.info("OS_IDENTITY_API_VERSION is set in env as '%s'",
202                     api_version)
203         return api_version
204     return DEFAULT_API_VERSION
205
206
207 def get_keystone_client(other_creds={}):
208     sess = get_session(other_creds)
209     return keystoneclient.Client(get_keystone_client_version(),
210                                  session=sess,
211                                  interface=os.getenv('OS_INTERFACE', 'admin'))
212
213
214 def get_nova_client_version():
215     api_version = os.getenv('OS_COMPUTE_API_VERSION')
216     if api_version is not None:
217         logger.info("OS_COMPUTE_API_VERSION is set in env as '%s'",
218                     api_version)
219         return api_version
220     return DEFAULT_API_VERSION
221
222
223 def get_nova_client(other_creds={}):
224     sess = get_session(other_creds)
225     return novaclient.Client(get_nova_client_version(), session=sess)
226
227
228 def get_cinder_client_version():
229     api_version = os.getenv('OS_VOLUME_API_VERSION')
230     if api_version is not None:
231         logger.info("OS_VOLUME_API_VERSION is set in env as '%s'",
232                     api_version)
233         return api_version
234     return DEFAULT_API_VERSION
235
236
237 def get_cinder_client(other_creds={}):
238     sess = get_session(other_creds)
239     return cinderclient.Client(get_cinder_client_version(), session=sess)
240
241
242 def get_neutron_client_version():
243     api_version = os.getenv('OS_NETWORK_API_VERSION')
244     if api_version is not None:
245         logger.info("OS_NETWORK_API_VERSION is set in env as '%s'",
246                     api_version)
247         return api_version
248     return DEFAULT_API_VERSION
249
250
251 def get_neutron_client(other_creds={}):
252     sess = get_session(other_creds)
253     return neutronclient.Client(get_neutron_client_version(), session=sess)
254
255
256 def get_glance_client_version():
257     api_version = os.getenv('OS_IMAGE_API_VERSION')
258     if api_version is not None:
259         logger.info("OS_IMAGE_API_VERSION is set in env as '%s'", api_version)
260         return api_version
261     return DEFAULT_API_VERSION
262
263
264 def get_glance_client(other_creds={}):
265     sess = get_session(other_creds)
266     return glanceclient.Client(get_glance_client_version(), session=sess)
267
268
269 def get_heat_client_version():
270     api_version = os.getenv('OS_ORCHESTRATION_API_VERSION')
271     if api_version is not None:
272         logger.info("OS_ORCHESTRATION_API_VERSION is set in env as '%s'",
273                     api_version)
274         return api_version
275     return DEFAULT_HEAT_API_VERSION
276
277
278 def get_heat_client(other_creds={}):
279     sess = get_session(other_creds)
280     return heatclient.Client(get_heat_client_version(), session=sess)
281
282
283 def download_and_add_image_on_glance(glance, image_name, image_url, data_dir):
284     try:
285         dest_path = data_dir
286         if not os.path.exists(dest_path):
287             os.makedirs(dest_path)
288         file_name = image_url.rsplit('/')[-1]
289         if not ft_utils.download_url(image_url, dest_path):
290             return False
291     except Exception:
292         raise Exception("Impossible to download image from {}".format(
293                         image_url))
294
295     try:
296         image = create_glance_image(
297             glance, image_name, dest_path + file_name)
298         if not image:
299             return False
300         else:
301             return image
302     except Exception:
303         raise Exception("Impossible to put image {} in glance".format(
304                         image_name))
305
306
307 # *********************************************
308 #   NOVA
309 # *********************************************
310 def get_instances(nova_client):
311     try:
312         instances = nova_client.servers.list(search_opts={'all_tenants': 1})
313         return instances
314     except Exception as e:
315         logger.error("Error [get_instances(nova_client)]: %s" % e)
316         return None
317
318
319 def get_instance_status(nova_client, instance):
320     try:
321         instance = nova_client.servers.get(instance.id)
322         return instance.status
323     except Exception as e:
324         logger.error("Error [get_instance_status(nova_client)]: %s" % e)
325         return None
326
327
328 def get_instance_by_name(nova_client, instance_name):
329     try:
330         instance = nova_client.servers.find(name=instance_name)
331         return instance
332     except Exception as e:
333         logger.error("Error [get_instance_by_name(nova_client, '%s')]: %s"
334                      % (instance_name, e))
335         return None
336
337
338 def get_flavor_id(nova_client, flavor_name):
339     flavors = nova_client.flavors.list(detailed=True)
340     id = ''
341     for f in flavors:
342         if f.name == flavor_name:
343             id = f.id
344             break
345     return id
346
347
348 def get_flavor_id_by_ram_range(nova_client, min_ram, max_ram):
349     flavors = nova_client.flavors.list(detailed=True)
350     id = ''
351     for f in flavors:
352         if min_ram <= f.ram and f.ram <= max_ram:
353             id = f.id
354             break
355     return id
356
357
358 def get_aggregates(nova_client):
359     try:
360         aggregates = nova_client.aggregates.list()
361         return aggregates
362     except Exception as e:
363         logger.error("Error [get_aggregates(nova_client)]: %s" % e)
364         return None
365
366
367 def get_aggregate_id(nova_client, aggregate_name):
368     try:
369         aggregates = get_aggregates(nova_client)
370         _id = [ag.id for ag in aggregates if ag.name == aggregate_name][0]
371         return _id
372     except Exception as e:
373         logger.error("Error [get_aggregate_id(nova_client, %s)]:"
374                      " %s" % (aggregate_name, e))
375         return None
376
377
378 def get_availability_zones(nova_client):
379     try:
380         availability_zones = nova_client.availability_zones.list()
381         return availability_zones
382     except Exception as e:
383         logger.error("Error [get_availability_zones(nova_client)]: %s" % e)
384         return None
385
386
387 def get_availability_zone_names(nova_client):
388     try:
389         az_names = [az.zoneName for az in get_availability_zones(nova_client)]
390         return az_names
391     except Exception as e:
392         logger.error("Error [get_availability_zone_names(nova_client)]:"
393                      " %s" % e)
394         return None
395
396
397 def create_flavor(nova_client, flavor_name, ram, disk, vcpus, public=True):
398     try:
399         flavor = nova_client.flavors.create(
400             flavor_name, ram, vcpus, disk, is_public=public)
401         try:
402             extra_specs = ft_utils.get_functest_config(
403                 'general.flavor_extra_specs')
404             flavor.set_keys(extra_specs)
405         except ValueError:
406             # flavor extra specs are not configured, therefore skip the update
407             pass
408
409     except Exception as e:
410         logger.error("Error [create_flavor(nova_client, '%s', '%s', '%s', "
411                      "'%s')]: %s" % (flavor_name, ram, disk, vcpus, e))
412         return None
413     return flavor.id
414
415
416 def get_or_create_flavor(flavor_name, ram, disk, vcpus, public=True):
417     flavor_exists = False
418     nova_client = get_nova_client()
419
420     flavor_id = get_flavor_id(nova_client, flavor_name)
421     if flavor_id != '':
422         logger.info("Using existing flavor '%s'..." % flavor_name)
423         flavor_exists = True
424     else:
425         logger.info("Creating flavor '%s' with '%s' RAM, '%s' disk size, "
426                     "'%s' vcpus..." % (flavor_name, ram, disk, vcpus))
427         flavor_id = create_flavor(
428             nova_client, flavor_name, ram, disk, vcpus, public=public)
429         if not flavor_id:
430             raise Exception("Failed to create flavor '%s'..." % (flavor_name))
431         else:
432             logger.debug("Flavor '%s' with ID=%s created successfully."
433                          % (flavor_name, flavor_id))
434
435     return flavor_exists, flavor_id
436
437
438 def get_floating_ips(neutron_client):
439     try:
440         floating_ips = neutron_client.list_floatingips()
441         return floating_ips['floatingips']
442     except Exception as e:
443         logger.error("Error [get_floating_ips(neutron_client)]: %s" % e)
444         return None
445
446
447 def get_hypervisors(nova_client):
448     try:
449         nodes = []
450         hypervisors = nova_client.hypervisors.list()
451         for hypervisor in hypervisors:
452             if hypervisor.state == "up":
453                 nodes.append(hypervisor.hypervisor_hostname)
454         return nodes
455     except Exception as e:
456         logger.error("Error [get_hypervisors(nova_client)]: %s" % e)
457         return None
458
459
460 def create_aggregate(nova_client, aggregate_name, av_zone):
461     try:
462         nova_client.aggregates.create(aggregate_name, av_zone)
463         return True
464     except Exception as e:
465         logger.error("Error [create_aggregate(nova_client, %s, %s)]: %s"
466                      % (aggregate_name, av_zone, e))
467         return None
468
469
470 def add_host_to_aggregate(nova_client, aggregate_name, compute_host):
471     try:
472         aggregate_id = get_aggregate_id(nova_client, aggregate_name)
473         nova_client.aggregates.add_host(aggregate_id, compute_host)
474         return True
475     except Exception as e:
476         logger.error("Error [add_host_to_aggregate(nova_client, %s, %s)]: %s"
477                      % (aggregate_name, compute_host, e))
478         return None
479
480
481 def create_aggregate_with_host(
482         nova_client, aggregate_name, av_zone, compute_host):
483     try:
484         create_aggregate(nova_client, aggregate_name, av_zone)
485         add_host_to_aggregate(nova_client, aggregate_name, compute_host)
486         return True
487     except Exception as e:
488         logger.error("Error [create_aggregate_with_host("
489                      "nova_client, %s, %s, %s)]: %s"
490                      % (aggregate_name, av_zone, compute_host, e))
491         return None
492
493
494 def create_instance(flavor_name,
495                     image_id,
496                     network_id,
497                     instance_name="functest-vm",
498                     confdrive=True,
499                     userdata=None,
500                     av_zone='',
501                     fixed_ip=None,
502                     files=None):
503     nova_client = get_nova_client()
504     try:
505         flavor = nova_client.flavors.find(name=flavor_name)
506     except:
507         flavors = nova_client.flavors.list()
508         logger.error("Error: Flavor '%s' not found. Available flavors are: "
509                      "\n%s" % (flavor_name, flavors))
510         return None
511     if fixed_ip is not None:
512         nics = {"net-id": network_id, "v4-fixed-ip": fixed_ip}
513     else:
514         nics = {"net-id": network_id}
515     if userdata is None:
516         instance = nova_client.servers.create(
517             name=instance_name,
518             flavor=flavor,
519             image=image_id,
520             nics=[nics],
521             availability_zone=av_zone,
522             files=files
523         )
524     else:
525         instance = nova_client.servers.create(
526             name=instance_name,
527             flavor=flavor,
528             image=image_id,
529             nics=[nics],
530             config_drive=confdrive,
531             userdata=userdata,
532             availability_zone=av_zone,
533             files=files
534         )
535     return instance
536
537
538 def create_instance_and_wait_for_active(flavor_name,
539                                         image_id,
540                                         network_id,
541                                         instance_name="",
542                                         config_drive=False,
543                                         userdata="",
544                                         av_zone='',
545                                         fixed_ip=None,
546                                         files=None):
547     SLEEP = 3
548     VM_BOOT_TIMEOUT = 180
549     nova_client = get_nova_client()
550     instance = create_instance(flavor_name,
551                                image_id,
552                                network_id,
553                                instance_name,
554                                config_drive,
555                                userdata,
556                                av_zone=av_zone,
557                                fixed_ip=fixed_ip,
558                                files=files)
559     count = VM_BOOT_TIMEOUT / SLEEP
560     for n in range(count, -1, -1):
561         status = get_instance_status(nova_client, instance)
562         if status.lower() == "active":
563             return instance
564         elif status.lower() == "error":
565             logger.error("The instance %s went to ERROR status."
566                          % instance_name)
567             return None
568         time.sleep(SLEEP)
569     logger.error("Timeout booting the instance %s." % instance_name)
570     return None
571
572
573 def create_floating_ip(neutron_client):
574     extnet_id = get_external_net_id(neutron_client)
575     props = {'floating_network_id': extnet_id}
576     try:
577         ip_json = neutron_client.create_floatingip({'floatingip': props})
578         fip_addr = ip_json['floatingip']['floating_ip_address']
579         fip_id = ip_json['floatingip']['id']
580     except Exception as e:
581         logger.error("Error [create_floating_ip(neutron_client)]: %s" % e)
582         return None
583     return {'fip_addr': fip_addr, 'fip_id': fip_id}
584
585
586 def add_floating_ip(nova_client, server_id, floatingip_addr):
587     try:
588         nova_client.servers.add_floating_ip(server_id, floatingip_addr)
589         return True
590     except Exception as e:
591         logger.error("Error [add_floating_ip(nova_client, '%s', '%s')]: %s"
592                      % (server_id, floatingip_addr, e))
593         return False
594
595
596 def delete_instance(nova_client, instance_id):
597     try:
598         nova_client.servers.force_delete(instance_id)
599         return True
600     except Exception as e:
601         logger.error("Error [delete_instance(nova_client, '%s')]: %s"
602                      % (instance_id, e))
603         return False
604
605
606 def delete_floating_ip(neutron_client, floatingip_id):
607     try:
608         neutron_client.delete_floatingip(floatingip_id)
609         return True
610     except Exception as e:
611         logger.error("Error [delete_floating_ip(neutron_client, '%s')]: %s"
612                      % (floatingip_id, e))
613         return False
614
615
616 def remove_host_from_aggregate(nova_client, aggregate_name, compute_host):
617     try:
618         aggregate_id = get_aggregate_id(nova_client, aggregate_name)
619         nova_client.aggregates.remove_host(aggregate_id, compute_host)
620         return True
621     except Exception as e:
622         logger.error("Error [remove_host_from_aggregate(nova_client, %s, %s)]:"
623                      " %s" % (aggregate_name, compute_host, e))
624         return False
625
626
627 def remove_hosts_from_aggregate(nova_client, aggregate_name):
628     aggregate_id = get_aggregate_id(nova_client, aggregate_name)
629     hosts = nova_client.aggregates.get(aggregate_id).hosts
630     assert(
631         all(remove_host_from_aggregate(nova_client, aggregate_name, host)
632             for host in hosts))
633
634
635 def delete_aggregate(nova_client, aggregate_name):
636     try:
637         remove_hosts_from_aggregate(nova_client, aggregate_name)
638         nova_client.aggregates.delete(aggregate_name)
639         return True
640     except Exception as e:
641         logger.error("Error [delete_aggregate(nova_client, %s)]: %s"
642                      % (aggregate_name, e))
643         return False
644
645
646 # *********************************************
647 #   NEUTRON
648 # *********************************************
649 def get_network_list(neutron_client):
650     network_list = neutron_client.list_networks()['networks']
651     if len(network_list) == 0:
652         return None
653     else:
654         return network_list
655
656
657 def get_router_list(neutron_client):
658     router_list = neutron_client.list_routers()['routers']
659     if len(router_list) == 0:
660         return None
661     else:
662         return router_list
663
664
665 def get_port_list(neutron_client):
666     port_list = neutron_client.list_ports()['ports']
667     if len(port_list) == 0:
668         return None
669     else:
670         return port_list
671
672
673 def get_network_id(neutron_client, network_name):
674     networks = neutron_client.list_networks()['networks']
675     id = ''
676     for n in networks:
677         if n['name'] == network_name:
678             id = n['id']
679             break
680     return id
681
682
683 def get_subnet_id(neutron_client, subnet_name):
684     subnets = neutron_client.list_subnets()['subnets']
685     id = ''
686     for s in subnets:
687         if s['name'] == subnet_name:
688             id = s['id']
689             break
690     return id
691
692
693 def get_router_id(neutron_client, router_name):
694     routers = neutron_client.list_routers()['routers']
695     id = ''
696     for r in routers:
697         if r['name'] == router_name:
698             id = r['id']
699             break
700     return id
701
702
703 def get_private_net(neutron_client):
704     # Checks if there is an existing shared private network
705     networks = neutron_client.list_networks()['networks']
706     if len(networks) == 0:
707         return None
708     for net in networks:
709         if (net['router:external'] is False) and (net['shared'] is True):
710             return net
711     return None
712
713
714 def get_external_net(neutron_client):
715     for network in neutron_client.list_networks()['networks']:
716         if network['router:external']:
717             return network['name']
718     return None
719
720
721 def get_external_net_id(neutron_client):
722     for network in neutron_client.list_networks()['networks']:
723         if network['router:external']:
724             return network['id']
725     return None
726
727
728 def check_neutron_net(neutron_client, net_name):
729     for network in neutron_client.list_networks()['networks']:
730         if network['name'] == net_name:
731             for subnet in network['subnets']:
732                 return True
733     return False
734
735
736 def create_neutron_net(neutron_client, name):
737     json_body = {'network': {'name': name,
738                              'admin_state_up': True}}
739     try:
740         network = neutron_client.create_network(body=json_body)
741         network_dict = network['network']
742         return network_dict['id']
743     except Exception as e:
744         logger.error("Error [create_neutron_net(neutron_client, '%s')]: %s"
745                      % (name, e))
746         return None
747
748
749 def create_neutron_subnet(neutron_client, name, cidr, net_id,
750                           dns=['8.8.8.8', '8.8.4.4']):
751     json_body = {'subnets': [{'name': name, 'cidr': cidr,
752                               'ip_version': 4, 'network_id': net_id,
753                               'dns_nameservers': dns}]}
754
755     try:
756         subnet = neutron_client.create_subnet(body=json_body)
757         return subnet['subnets'][0]['id']
758     except Exception as e:
759         logger.error("Error [create_neutron_subnet(neutron_client, '%s', "
760                      "'%s', '%s')]: %s" % (name, cidr, net_id, e))
761         return None
762
763
764 def create_neutron_router(neutron_client, name):
765     json_body = {'router': {'name': name, 'admin_state_up': True}}
766     try:
767         router = neutron_client.create_router(json_body)
768         return router['router']['id']
769     except Exception as e:
770         logger.error("Error [create_neutron_router(neutron_client, '%s')]: %s"
771                      % (name, e))
772         return None
773
774
775 def create_neutron_port(neutron_client, name, network_id, ip):
776     json_body = {'port': {
777                  'admin_state_up': True,
778                  'name': name,
779                  'network_id': network_id,
780                  'fixed_ips': [{"ip_address": ip}]
781                  }}
782     try:
783         port = neutron_client.create_port(body=json_body)
784         return port['port']['id']
785     except Exception as e:
786         logger.error("Error [create_neutron_port(neutron_client, '%s', '%s', "
787                      "'%s')]: %s" % (name, network_id, ip, e))
788         return None
789
790
791 def update_neutron_net(neutron_client, network_id, shared=False):
792     json_body = {'network': {'shared': shared}}
793     try:
794         neutron_client.update_network(network_id, body=json_body)
795         return True
796     except Exception as e:
797         logger.error("Error [update_neutron_net(neutron_client, '%s', '%s')]: "
798                      "%s" % (network_id, str(shared), e))
799         return False
800
801
802 def update_neutron_port(neutron_client, port_id, device_owner):
803     json_body = {'port': {
804                  'device_owner': device_owner,
805                  }}
806     try:
807         port = neutron_client.update_port(port=port_id,
808                                           body=json_body)
809         return port['port']['id']
810     except Exception as e:
811         logger.error("Error [update_neutron_port(neutron_client, '%s', '%s')]:"
812                      " %s" % (port_id, device_owner, e))
813         return None
814
815
816 def add_interface_router(neutron_client, router_id, subnet_id):
817     json_body = {"subnet_id": subnet_id}
818     try:
819         neutron_client.add_interface_router(router=router_id, body=json_body)
820         return True
821     except Exception as e:
822         logger.error("Error [add_interface_router(neutron_client, '%s', "
823                      "'%s')]: %s" % (router_id, subnet_id, e))
824         return False
825
826
827 def add_gateway_router(neutron_client, router_id):
828     ext_net_id = get_external_net_id(neutron_client)
829     router_dict = {'network_id': ext_net_id}
830     try:
831         neutron_client.add_gateway_router(router_id, router_dict)
832         return True
833     except Exception as e:
834         logger.error("Error [add_gateway_router(neutron_client, '%s')]: %s"
835                      % (router_id, e))
836         return False
837
838
839 def delete_neutron_net(neutron_client, network_id):
840     try:
841         neutron_client.delete_network(network_id)
842         return True
843     except Exception as e:
844         logger.error("Error [delete_neutron_net(neutron_client, '%s')]: %s"
845                      % (network_id, e))
846         return False
847
848
849 def delete_neutron_subnet(neutron_client, subnet_id):
850     try:
851         neutron_client.delete_subnet(subnet_id)
852         return True
853     except Exception as e:
854         logger.error("Error [delete_neutron_subnet(neutron_client, '%s')]: %s"
855                      % (subnet_id, e))
856         return False
857
858
859 def delete_neutron_router(neutron_client, router_id):
860     try:
861         neutron_client.delete_router(router=router_id)
862         return True
863     except Exception as e:
864         logger.error("Error [delete_neutron_router(neutron_client, '%s')]: %s"
865                      % (router_id, e))
866         return False
867
868
869 def delete_neutron_port(neutron_client, port_id):
870     try:
871         neutron_client.delete_port(port_id)
872         return True
873     except Exception as e:
874         logger.error("Error [delete_neutron_port(neutron_client, '%s')]: %s"
875                      % (port_id, e))
876         return False
877
878
879 def remove_interface_router(neutron_client, router_id, subnet_id):
880     json_body = {"subnet_id": subnet_id}
881     try:
882         neutron_client.remove_interface_router(router=router_id,
883                                                body=json_body)
884         return True
885     except Exception as e:
886         logger.error("Error [remove_interface_router(neutron_client, '%s', "
887                      "'%s')]: %s" % (router_id, subnet_id, e))
888         return False
889
890
891 def remove_gateway_router(neutron_client, router_id):
892     try:
893         neutron_client.remove_gateway_router(router_id)
894         return True
895     except Exception as e:
896         logger.error("Error [remove_gateway_router(neutron_client, '%s')]: %s"
897                      % (router_id, e))
898         return False
899
900
901 def create_network_full(neutron_client,
902                         net_name,
903                         subnet_name,
904                         router_name,
905                         cidr,
906                         dns=['8.8.8.8', '8.8.4.4']):
907
908     # Check if the network already exists
909     network_id = get_network_id(neutron_client, net_name)
910     subnet_id = get_subnet_id(neutron_client, subnet_name)
911     router_id = get_router_id(neutron_client, router_name)
912
913     if network_id != '' and subnet_id != '' and router_id != '':
914         logger.info("A network with name '%s' already exists..." % net_name)
915     else:
916         neutron_client.format = 'json'
917         logger.info('Creating neutron network %s...' % net_name)
918         network_id = create_neutron_net(neutron_client, net_name)
919
920         if not network_id:
921             return False
922
923         logger.debug("Network '%s' created successfully" % network_id)
924         logger.debug('Creating Subnet....')
925         subnet_id = create_neutron_subnet(neutron_client, subnet_name,
926                                           cidr, network_id, dns)
927         if not subnet_id:
928             return None
929
930         logger.debug("Subnet '%s' created successfully" % subnet_id)
931         logger.debug('Creating Router...')
932         router_id = create_neutron_router(neutron_client, router_name)
933
934         if not router_id:
935             return None
936
937         logger.debug("Router '%s' created successfully" % router_id)
938         logger.debug('Adding router to subnet...')
939
940         if not add_interface_router(neutron_client, router_id, subnet_id):
941             return None
942
943         logger.debug("Interface added successfully.")
944
945         logger.debug('Adding gateway to router...')
946         if not add_gateway_router(neutron_client, router_id):
947             return None
948
949         logger.debug("Gateway added successfully.")
950
951     network_dic = {'net_id': network_id,
952                    'subnet_id': subnet_id,
953                    'router_id': router_id}
954     return network_dic
955
956
957 def create_shared_network_full(net_name, subnt_name, router_name, subnet_cidr):
958     neutron_client = get_neutron_client()
959
960     network_dic = create_network_full(neutron_client,
961                                       net_name,
962                                       subnt_name,
963                                       router_name,
964                                       subnet_cidr)
965     if network_dic:
966         if not update_neutron_net(neutron_client,
967                                   network_dic['net_id'],
968                                   shared=True):
969             logger.error("Failed to update network %s..." % net_name)
970             return None
971         else:
972             logger.debug("Network '%s' is available..." % net_name)
973     else:
974         logger.error("Network %s creation failed" % net_name)
975         return None
976     return network_dic
977
978
979 # *********************************************
980 #   SEC GROUPS
981 # *********************************************
982
983
984 def get_security_groups(neutron_client):
985     try:
986         security_groups = neutron_client.list_security_groups()[
987             'security_groups']
988         return security_groups
989     except Exception as e:
990         logger.error("Error [get_security_groups(neutron_client)]: %s" % e)
991         return None
992
993
994 def get_security_group_id(neutron_client, sg_name):
995     security_groups = get_security_groups(neutron_client)
996     id = ''
997     for sg in security_groups:
998         if sg['name'] == sg_name:
999             id = sg['id']
1000             break
1001     return id
1002
1003
1004 def create_security_group(neutron_client, sg_name, sg_description):
1005     json_body = {'security_group': {'name': sg_name,
1006                                     'description': sg_description}}
1007     try:
1008         secgroup = neutron_client.create_security_group(json_body)
1009         return secgroup['security_group']
1010     except Exception as e:
1011         logger.error("Error [create_security_group(neutron_client, '%s', "
1012                      "'%s')]: %s" % (sg_name, sg_description, e))
1013         return None
1014
1015
1016 def create_secgroup_rule(neutron_client, sg_id, direction, protocol,
1017                          port_range_min=None, port_range_max=None):
1018     # We create a security group in 2 steps
1019     # 1 - we check the format and set the json body accordingly
1020     # 2 - we call neturon client to create the security group
1021
1022     # Format check
1023     json_body = {'security_group_rule': {'direction': direction,
1024                                          'security_group_id': sg_id,
1025                                          'protocol': protocol}}
1026     # parameters may be
1027     # - both None => we do nothing
1028     # - both Not None => we add them to the json description
1029     # but one cannot be None is the other is not None
1030     if (port_range_min is not None and port_range_max is not None):
1031         # add port_range in json description
1032         json_body['security_group_rule']['port_range_min'] = port_range_min
1033         json_body['security_group_rule']['port_range_max'] = port_range_max
1034         logger.debug("Security_group format set (port range included)")
1035     else:
1036         # either both port range are set to None => do nothing
1037         # or one is set but not the other => log it and return False
1038         if port_range_min is None and port_range_max is None:
1039             logger.debug("Security_group format set (no port range mentioned)")
1040         else:
1041             logger.error("Bad security group format."
1042                          "One of the port range is not properly set:"
1043                          "range min: {},"
1044                          "range max: {}".format(port_range_min,
1045                                                 port_range_max))
1046             return False
1047
1048     # Create security group using neutron client
1049     try:
1050         neutron_client.create_security_group_rule(json_body)
1051         return True
1052     except:
1053         logger.exception("Impossible to create_security_group_rule,"
1054                          "security group rule probably already exists")
1055         return False
1056
1057
1058 def get_security_group_rules(neutron_client, sg_id):
1059     try:
1060         security_rules = neutron_client.list_security_group_rules()[
1061             'security_group_rules']
1062         security_rules = [rule for rule in security_rules
1063                           if rule["security_group_id"] == sg_id]
1064         return security_rules
1065     except Exception as e:
1066         logger.error("Error [get_security_group_rules(neutron_client, sg_id)]:"
1067                      " %s" % e)
1068         return None
1069
1070
1071 def check_security_group_rules(neutron_client, sg_id, direction, protocol,
1072                                port_min=None, port_max=None):
1073     try:
1074         security_rules = get_security_group_rules(neutron_client, sg_id)
1075         security_rules = [rule for rule in security_rules
1076                           if (rule["direction"].lower() == direction and
1077                               rule["protocol"].lower() == protocol and
1078                               rule["port_range_min"] == port_min and
1079                               rule["port_range_max"] == port_max)]
1080         if len(security_rules) == 0:
1081             return True
1082         else:
1083             return False
1084     except Exception as e:
1085         logger.error("Error [check_security_group_rules("
1086                      " neutron_client, sg_id, direction,"
1087                      " protocol, port_min=None, port_max=None)]: "
1088                      "%s" % e)
1089         return None
1090
1091
1092 def create_security_group_full(neutron_client,
1093                                sg_name, sg_description):
1094     sg_id = get_security_group_id(neutron_client, sg_name)
1095     if sg_id != '':
1096         logger.info("Using existing security group '%s'..." % sg_name)
1097     else:
1098         logger.info("Creating security group  '%s'..." % sg_name)
1099         SECGROUP = create_security_group(neutron_client,
1100                                          sg_name,
1101                                          sg_description)
1102         if not SECGROUP:
1103             logger.error("Failed to create the security group...")
1104             return None
1105
1106         sg_id = SECGROUP['id']
1107
1108         logger.debug("Security group '%s' with ID=%s created successfully."
1109                      % (SECGROUP['name'], sg_id))
1110
1111         logger.debug("Adding ICMP rules in security group '%s'..."
1112                      % sg_name)
1113         if not create_secgroup_rule(neutron_client, sg_id,
1114                                     'ingress', 'icmp'):
1115             logger.error("Failed to create the security group rule...")
1116             return None
1117
1118         logger.debug("Adding SSH rules in security group '%s'..."
1119                      % sg_name)
1120         if not create_secgroup_rule(
1121                 neutron_client, sg_id, 'ingress', 'tcp', '22', '22'):
1122             logger.error("Failed to create the security group rule...")
1123             return None
1124
1125         if not create_secgroup_rule(
1126                 neutron_client, sg_id, 'egress', 'tcp', '22', '22'):
1127             logger.error("Failed to create the security group rule...")
1128             return None
1129     return sg_id
1130
1131
1132 def add_secgroup_to_instance(nova_client, instance_id, secgroup_id):
1133     try:
1134         nova_client.servers.add_security_group(instance_id, secgroup_id)
1135         return True
1136     except Exception as e:
1137         logger.error("Error [add_secgroup_to_instance(nova_client, '%s', "
1138                      "'%s')]: %s" % (instance_id, secgroup_id, e))
1139         return False
1140
1141
1142 def update_sg_quota(neutron_client, tenant_id, sg_quota, sg_rule_quota):
1143     json_body = {"quota": {
1144         "security_group": sg_quota,
1145         "security_group_rule": sg_rule_quota
1146     }}
1147
1148     try:
1149         neutron_client.update_quota(tenant_id=tenant_id,
1150                                     body=json_body)
1151         return True
1152     except Exception as e:
1153         logger.error("Error [update_sg_quota(neutron_client, '%s', '%s', "
1154                      "'%s')]: %s" % (tenant_id, sg_quota, sg_rule_quota, e))
1155         return False
1156
1157
1158 def delete_security_group(neutron_client, secgroup_id):
1159     try:
1160         neutron_client.delete_security_group(secgroup_id)
1161         return True
1162     except Exception as e:
1163         logger.error("Error [delete_security_group(neutron_client, '%s')]: %s"
1164                      % (secgroup_id, e))
1165         return False
1166
1167
1168 # *********************************************
1169 #   GLANCE
1170 # *********************************************
1171 def get_images(glance_client):
1172     try:
1173         images = glance_client.images.list()
1174         return images
1175     except Exception as e:
1176         logger.error("Error [get_images]: %s" % e)
1177         return None
1178
1179
1180 def get_image_id(glance_client, image_name):
1181     images = glance_client.images.list()
1182     id = ''
1183     for i in images:
1184         if i.name == image_name:
1185             id = i.id
1186             break
1187     return id
1188
1189
1190 def create_glance_image(glance_client, image_name, file_path, disk="qcow2",
1191                         container="bare", public="public"):
1192     if not os.path.isfile(file_path):
1193         logger.error("Error: file %s does not exist." % file_path)
1194         return None
1195     try:
1196         image_id = get_image_id(glance_client, image_name)
1197         if image_id != '':
1198             logger.info("Image %s already exists." % image_name)
1199         else:
1200             logger.info("Creating image '%s' from '%s'..." % (image_name,
1201                                                               file_path))
1202
1203             image = glance_client.images.create(name=image_name,
1204                                                 visibility=public,
1205                                                 disk_format=disk,
1206                                                 container_format=container)
1207             image_id = image.id
1208             with open(file_path) as image_data:
1209                 glance_client.images.upload(image_id, image_data)
1210         return image_id
1211     except Exception as e:
1212         logger.error("Error [create_glance_image(glance_client, '%s', '%s', "
1213                      "'%s')]: %s" % (image_name, file_path, public, e))
1214         return None
1215
1216
1217 def get_or_create_image(name, path, format):
1218     image_exists = False
1219     glance_client = get_glance_client()
1220
1221     image_id = get_image_id(glance_client, name)
1222     if image_id != '':
1223         logger.info("Using existing image '%s'..." % name)
1224         image_exists = True
1225     else:
1226         logger.info("Creating image '%s' from '%s'..." % (name, path))
1227         image_id = create_glance_image(glance_client, name, path, format)
1228         if not image_id:
1229             logger.error("Failed to create a Glance image...")
1230         else:
1231             logger.debug("Image '%s' with ID=%s created successfully."
1232                          % (name, image_id))
1233
1234     return image_exists, image_id
1235
1236
1237 def delete_glance_image(glance_client, image_id):
1238     try:
1239         glance_client.images.delete(image_id)
1240         return True
1241     except Exception as e:
1242         logger.error("Error [delete_glance_image(glance_client, '%s')]: %s"
1243                      % (image_id, e))
1244         return False
1245
1246
1247 # *********************************************
1248 #   CINDER
1249 # *********************************************
1250 def get_volumes(cinder_client):
1251     try:
1252         volumes = cinder_client.volumes.list(search_opts={'all_tenants': 1})
1253         return volumes
1254     except Exception as e:
1255         logger.error("Error [get_volumes(cinder_client)]: %s" % e)
1256         return None
1257
1258
1259 def list_volume_types(cinder_client, public=True, private=True):
1260     try:
1261         volume_types = cinder_client.volume_types.list()
1262         if not public:
1263             volume_types = [vt for vt in volume_types if not vt.is_public]
1264         if not private:
1265             volume_types = [vt for vt in volume_types if vt.is_public]
1266         return volume_types
1267     except Exception as e:
1268         logger.error("Error [list_volume_types(cinder_client)]: %s" % e)
1269         return None
1270
1271
1272 def create_volume_type(cinder_client, name):
1273     try:
1274         volume_type = cinder_client.volume_types.create(name)
1275         return volume_type
1276     except Exception as e:
1277         logger.error("Error [create_volume_type(cinder_client, '%s')]: %s"
1278                      % (name, e))
1279         return None
1280
1281
1282 def update_cinder_quota(cinder_client, tenant_id, vols_quota,
1283                         snapshots_quota, gigabytes_quota):
1284     quotas_values = {"volumes": vols_quota,
1285                      "snapshots": snapshots_quota,
1286                      "gigabytes": gigabytes_quota}
1287
1288     try:
1289         cinder_client.quotas.update(tenant_id, **quotas_values)
1290         return True
1291     except Exception as e:
1292         logger.error("Error [update_cinder_quota(cinder_client, '%s', '%s', "
1293                      "'%s' '%s')]: %s" % (tenant_id, vols_quota,
1294                                           snapshots_quota, gigabytes_quota, e))
1295         return False
1296
1297
1298 def delete_volume(cinder_client, volume_id, forced=False):
1299     try:
1300         if forced:
1301             try:
1302                 cinder_client.volumes.detach(volume_id)
1303             except:
1304                 logger.error(sys.exc_info()[0])
1305             cinder_client.volumes.force_delete(volume_id)
1306         else:
1307             cinder_client.volumes.delete(volume_id)
1308         return True
1309     except Exception as e:
1310         logger.error("Error [delete_volume(cinder_client, '%s', '%s')]: %s"
1311                      % (volume_id, str(forced), e))
1312         return False
1313
1314
1315 def delete_volume_type(cinder_client, volume_type):
1316     try:
1317         cinder_client.volume_types.delete(volume_type)
1318         return True
1319     except Exception as e:
1320         logger.error("Error [delete_volume_type(cinder_client, '%s')]: %s"
1321                      % (volume_type, e))
1322         return False
1323
1324
1325 # *********************************************
1326 #   KEYSTONE
1327 # *********************************************
1328 def get_tenants(keystone_client):
1329     try:
1330         if is_keystone_v3():
1331             tenants = keystone_client.projects.list()
1332         else:
1333             tenants = keystone_client.tenants.list()
1334         return tenants
1335     except Exception as e:
1336         logger.error("Error [get_tenants(keystone_client)]: %s" % e)
1337         return None
1338
1339
1340 def get_users(keystone_client):
1341     try:
1342         users = keystone_client.users.list()
1343         return users
1344     except Exception as e:
1345         logger.error("Error [get_users(keystone_client)]: %s" % e)
1346         return None
1347
1348
1349 def get_tenant_id(keystone_client, tenant_name):
1350     tenants = get_tenants(keystone_client)
1351     id = ''
1352     for t in tenants:
1353         if t.name == tenant_name:
1354             id = t.id
1355             break
1356     return id
1357
1358
1359 def get_user_id(keystone_client, user_name):
1360     users = get_users(keystone_client)
1361     id = ''
1362     for u in users:
1363         if u.name == user_name:
1364             id = u.id
1365             break
1366     return id
1367
1368
1369 def get_role_id(keystone_client, role_name):
1370     roles = keystone_client.roles.list()
1371     id = ''
1372     for r in roles:
1373         if r.name == role_name:
1374             id = r.id
1375             break
1376     return id
1377
1378
1379 def create_tenant(keystone_client, tenant_name, tenant_description):
1380     try:
1381         if is_keystone_v3():
1382             tenant = keystone_client.projects.create(
1383                 name=tenant_name,
1384                 description=tenant_description,
1385                 domain="default",
1386                 enabled=True)
1387         else:
1388             tenant = keystone_client.tenants.create(tenant_name,
1389                                                     tenant_description,
1390                                                     enabled=True)
1391         return tenant.id
1392     except Exception as e:
1393         logger.error("Error [create_tenant(keystone_client, '%s', '%s')]: %s"
1394                      % (tenant_name, tenant_description, e))
1395         return None
1396
1397
1398 def get_or_create_tenant(keystone_client, tenant_name, tenant_description):
1399     tenant_id = get_tenant_id(keystone_client, tenant_name)
1400     if not tenant_id:
1401         tenant_id = create_tenant(keystone_client, tenant_name,
1402                                   tenant_description)
1403
1404     return tenant_id
1405
1406
1407 def get_or_create_tenant_for_vnf(keystone_client, tenant_name,
1408                                  tenant_description):
1409     """Get or Create a Tenant
1410
1411         Args:
1412             keystone_client: keystone client reference
1413             tenant_name: the name of the tenant
1414             tenant_description: the description of the tenant
1415
1416         return False if tenant retrieved though get
1417         return True if tenant created
1418         raise Exception if error during processing
1419     """
1420     try:
1421         tenant_id = get_tenant_id(keystone_client, tenant_name)
1422         if not tenant_id:
1423             tenant_id = create_tenant(keystone_client, tenant_name,
1424                                       tenant_description)
1425             return True
1426         else:
1427             return False
1428     except:
1429         raise Exception("Impossible to create a Tenant for the VNF {}".format(
1430                             tenant_name))
1431
1432
1433 def create_user(keystone_client, user_name, user_password,
1434                 user_email, tenant_id):
1435     try:
1436         if is_keystone_v3():
1437             user = keystone_client.users.create(name=user_name,
1438                                                 password=user_password,
1439                                                 email=user_email,
1440                                                 project_id=tenant_id,
1441                                                 enabled=True)
1442         else:
1443             user = keystone_client.users.create(user_name,
1444                                                 user_password,
1445                                                 user_email,
1446                                                 tenant_id,
1447                                                 enabled=True)
1448         return user.id
1449     except Exception as e:
1450         logger.error("Error [create_user(keystone_client, '%s', '%s', '%s'"
1451                      "'%s')]: %s" % (user_name, user_password,
1452                                      user_email, tenant_id, e))
1453         return None
1454
1455
1456 def get_or_create_user(keystone_client, user_name, user_password,
1457                        tenant_id, user_email=None):
1458     user_id = get_user_id(keystone_client, user_name)
1459     if not user_id:
1460         user_id = create_user(keystone_client, user_name, user_password,
1461                               user_email, tenant_id)
1462     return user_id
1463
1464
1465 def get_or_create_user_for_vnf(keystone_client, vnf_ref):
1466     """Get or Create user for VNF
1467
1468         Args:
1469             keystone_client: keystone client reference
1470             vnf_ref: VNF reference used as user name & password, tenant name
1471
1472         return False if user retrieved through get
1473         return True if user created
1474         raise Exception if error during processing
1475     """
1476     try:
1477         user_id = get_user_id(keystone_client, vnf_ref)
1478         tenant_id = get_tenant_id(keystone_client, vnf_ref)
1479         created = False
1480         if not user_id:
1481             user_id = create_user(keystone_client, vnf_ref, vnf_ref,
1482                                   "", tenant_id)
1483             created = True
1484         try:
1485             role_id = get_role_id(keystone_client, 'admin')
1486             tenant_id = get_tenant_id(keystone_client, vnf_ref)
1487             add_role_user(keystone_client, user_id, role_id, tenant_id)
1488         except:
1489             logger.warn("Cannot associate user to role admin on tenant")
1490         return created
1491     except:
1492         raise Exception("Impossible to create a user for the VNF {}".format(
1493             vnf_ref))
1494
1495
1496 def add_role_user(keystone_client, user_id, role_id, tenant_id):
1497     try:
1498         if is_keystone_v3():
1499             keystone_client.roles.grant(role=role_id,
1500                                         user=user_id,
1501                                         project=tenant_id)
1502         else:
1503             keystone_client.roles.add_user_role(user_id, role_id, tenant_id)
1504         return True
1505     except Exception as e:
1506         logger.error("Error [add_role_user(keystone_client, '%s', '%s'"
1507                      "'%s')]: %s " % (user_id, role_id, tenant_id, e))
1508         return False
1509
1510
1511 def delete_tenant(keystone_client, tenant_id):
1512     try:
1513         if is_keystone_v3():
1514             keystone_client.projects.delete(tenant_id)
1515         else:
1516             keystone_client.tenants.delete(tenant_id)
1517         return True
1518     except Exception as e:
1519         logger.error("Error [delete_tenant(keystone_client, '%s')]: %s"
1520                      % (tenant_id, e))
1521         return False
1522
1523
1524 def delete_user(keystone_client, user_id):
1525     try:
1526         keystone_client.users.delete(user_id)
1527         return True
1528     except Exception as e:
1529         logger.error("Error [delete_user(keystone_client, '%s')]: %s"
1530                      % (user_id, e))
1531         return False
1532
1533
1534 # *********************************************
1535 #   HEAT
1536 # *********************************************
1537 def get_resource(heat_client, stack_id, resource):
1538     try:
1539         resources = heat_client.resources.get(stack_id, resource)
1540         return resources
1541     except Exception as e:
1542         logger.error("Error [get_resource]: %s" % e)
1543         return None