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