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