85b6f43dcbb82357e44219fc4f16d201988db169
[functest.git] / testcases / config_functest.py
1 #!/usr/bin/env python
2 #
3 # Copyright (c) 2015 Ericsson
4 # jose.lausuch@ericsson.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 re, json, os, urllib2, argparse, logging, shutil, subprocess, yaml
12 from git import Repo
13
14 from neutronclient.v2_0 import client
15
16 actions = ['start', 'check', 'clean']
17
18
19
20 parser = argparse.ArgumentParser()
21 parser.add_argument("action", help="Possible actions are: '{d[0]}|{d[1]}|{d[2]}' ".format(d=actions))
22 parser.add_argument("-d", "--debug", help="Debug mode",  action="store_true")
23 args = parser.parse_args()
24
25
26 """ logging configuration """
27 logger = logging.getLogger('config_functest')
28 logger.setLevel(logging.DEBUG)
29
30 ch = logging.StreamHandler()
31 if args.debug:
32     ch.setLevel(logging.DEBUG)
33 else:
34     ch.setLevel(logging.INFO)
35
36 formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
37 ch.setFormatter(formatter)
38 logger.addHandler(ch)
39
40
41
42 yaml_url = 'https://git.opnfv.org/cgit/functest/plain/testcases/functest.yaml'
43 name = yaml_url.rsplit('/')[-1]
44 dest = "./" + name
45 if not os.path.exists(dest):
46     logger.info("Downloading functest.yaml...")
47     try:
48         response = urllib2.urlopen(yaml_url)
49     except (urllib2.HTTPError, urllib2.URLError):
50         logger.error("Error in fetching %s" %yaml_url)
51         exit(-1)
52     with open(dest, 'wb') as f:
53         f.write(response.read())
54     logger.info("functest.yaml stored in %s" % dest)
55 else:
56     logger.info("functest.yaml found in %s" % dest)
57
58
59 with open('./functest.yaml') as f:
60     functest_yaml = yaml.safe_load(f)
61 f.close()
62
63
64 """ global variables """
65 # Directories
66 HOME = os.environ['HOME']+"/"
67 FUNCTEST_BASE_DIR = HOME + functest_yaml.get("general").get("directories").get("dir_functest")
68 RALLY_REPO_DIR = HOME + functest_yaml.get("general").get("directories").get("dir_rally_repo")
69 RALLY_TEST_DIR = HOME + functest_yaml.get("general").get("directories").get("dir_rally")
70 RALLY_INSTALLATION_DIR = HOME + functest_yaml.get("general").get("directories").get("dir_rally_inst")
71 BENCH_TESTS_DIR = HOME + functest_yaml.get("general").get("directories").get("dir_rally_scn")
72 VPING_DIR = HOME + functest_yaml.get("general").get("directories").get("dir_vping")
73 ODL_DIR = HOME + functest_yaml.get("general").get("directories").get("dir_odl")
74
75
76 # NEUTRON Private Network parameters
77 NEUTRON_PRIVATE_NET_NAME = functest_yaml.get("general").get("openstack").get("neutron_private_net_name")
78 NEUTRON_PRIVATE_SUBNET_NAME = functest_yaml.get("general").get("openstack").get("neutron_private_subnet_name")
79 NEUTRON_PRIVATE_SUBNET_CIDR = functest_yaml.get("general").get("openstack").get("neutron_private_subnet_cidr")
80 ROUTER_NAME = functest_yaml.get("general").get("openstack").get("neutron_router_name")
81
82 #GLANCE image parameters
83 IMAGE_URL = functest_yaml.get("general").get("openstack").get("image_url")
84 IMAGE_DISK_FORMAT = functest_yaml.get("general").get("openstack").get("image_disk_format")
85 IMAGE_NAME = functest_yaml.get("general").get("openstack").get("image_name")
86 IMAGE_FILE_NAME = IMAGE_URL.rsplit('/')[-1]
87 IMAGE_DOWNLOAD_PATH = FUNCTEST_BASE_DIR + IMAGE_FILE_NAME
88
89 credentials = None
90 neutron_client = None
91
92 def config_functest_start():
93     """
94     Start the functest environment installation
95     """
96     if not check_internet_connectivity():
97         logger.error("There is no Internet connectivity. Please check the network configuration.")
98         exit(-1)
99
100     if config_functest_check():
101         logger.info("Functest environment already installed in %s. Nothing to do." %FUNCTEST_BASE_DIR)
102         exit(0)
103
104     else:
105         # Clean in case there are left overs
106         logger.debug("Functest environment not found or faulty. Cleaning in case of leftovers.")
107         config_functest_clean()
108
109         logger.info("Starting installation of functest environment in %s" % FUNCTEST_BASE_DIR)
110         os.makedirs(FUNCTEST_BASE_DIR)
111         if not os.path.exists(FUNCTEST_BASE_DIR):
112             logger.error("There has been a problem while creating the environment directory.")
113             exit(-1)
114
115         logger.info("Downloading test scripts and scenarios...")
116         if not download_tests():
117             logger.error("There has been a problem while downloading the test scripts and scenarios.")
118             config_functest_clean()
119             exit(-1)
120
121         logger.info("Installing Rally...")
122         if not install_rally():
123             logger.error("There has been a problem while installing Rally.")
124             config_functest_clean()
125             exit(-1)
126
127         logger.info("Installing ODL environment...")
128         if not install_odl():
129             logger.error("There has been a problem while installing Robot.")
130             config_functest_clean()
131             exit(-1)
132
133         credentials = get_credentials()
134         neutron_client = client.Client(**credentials)
135
136         logger.info("Configuring Neutron...")
137         logger.info("Checking if private network '%s' exists..." % NEUTRON_PRIVATE_NET_NAME)
138         #Now: if exists we don't create it again (the clean command does not clean the neutron networks yet)
139         if check_neutron_net(neutron_client, NEUTRON_PRIVATE_NET_NAME):
140             logger.info("Private network '%s' found. No need to create another one." % NEUTRON_PRIVATE_NET_NAME)
141         else:
142             logger.info("Private network '%s' not found. Creating..." % NEUTRON_PRIVATE_NET_NAME)
143             if not create_private_neutron_net(neutron_client):
144                 logger.error("There has been a problem while creating the Neutron network.")
145                 #config_functest_clean()
146                 exit(-1)
147
148
149         logger.info("Donwloading image...")
150         if not download_url_with_progress(IMAGE_URL, FUNCTEST_BASE_DIR):
151             logger.error("There has been a problem while downloading the image.")
152             config_functest_clean()
153             exit(-1)
154
155         logger.info("Creating Glance image: %s ..." %IMAGE_NAME)
156         if not create_glance_image(IMAGE_DOWNLOAD_PATH,IMAGE_NAME,IMAGE_DISK_FORMAT):
157             logger.error("There has been a problem while creating the Glance image.")
158             config_functest_clean()
159             exit(-1)
160
161         exit(0)
162
163
164
165 def config_functest_check():
166     """
167     Check if the functest environment is properly installed
168     """
169     errors_all = False
170
171     logger.info("Checking current functest configuration...")
172     credentials = get_credentials()
173     neutron_client = client.Client(**credentials)
174
175     logger.debug("Checking directories...")
176     errors = False
177     dirs = [FUNCTEST_BASE_DIR, RALLY_INSTALLATION_DIR, RALLY_REPO_DIR, RALLY_TEST_DIR, BENCH_TESTS_DIR, VPING_DIR, ODL_DIR]
178     for dir in dirs:
179         if not os.path.exists(dir):
180             logger.debug("The directory '%s' does NOT exist." % dir)
181             errors = True
182             errors_all = True
183         else:
184             logger.debug("   %s found" % dir)
185     if not errors:
186         logger.debug("...OK")
187     else:
188         logger.debug("...FAIL")
189
190
191     logger.debug("Checking Rally deployment...")
192     if not check_rally():
193         logger.debug("   Rally deployment NOT found.")
194         errors_all = True
195         logger.debug("...FAIL")
196     else:
197         logger.debug("...OK")
198
199
200     logger.debug("Checking Neutron...")
201     if not check_neutron_net(neutron_client, NEUTRON_PRIVATE_NET_NAME):
202         logger.debug("   Private network '%s' NOT found." % NEUTRON_PRIVATE_NET_NAME)
203         logger.debug("...FAIL")
204         errors_all = True
205     else:
206         logger.debug("   Private network '%s' found." % NEUTRON_PRIVATE_NET_NAME)
207         logger.debug("...OK")
208
209
210     logger.debug("Checking Image...")
211     errors = False
212     if not os.path.isfile(IMAGE_DOWNLOAD_PATH):
213         logger.debug("   Image file '%s' NOT found." % IMAGE_DOWNLOAD_PATH)
214         errors = True
215         errors_all = True
216     else:
217         logger.debug("   Image file found in %s" % IMAGE_DOWNLOAD_PATH)
218
219     cmd="glance image-list | grep " + IMAGE_NAME
220     FNULL = open(os.devnull, 'w');
221     logger.debug('   Executing command : {}'.format(cmd))
222     p=subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=FNULL);
223     #if the command does not exist or there is no glance image
224     line = p.stdout.readline()
225     if line == "":
226         logger.debug("   Glance image NOT found.")
227         errors = True
228         errors_all = True
229     else:
230         logger.debug("   Glance image found.")
231
232     if not errors:
233         logger.debug("...OK")
234     else:
235         logger.debug("...FAIL")
236
237     #TODO: check OLD environment setup
238     if errors_all:
239         return False
240     else:
241         return True
242
243
244
245
246 def config_functest_clean():
247     """
248     Clean the existing functest environment
249     """
250     logger.info("Removing current functest environment...")
251     if os.path.exists(RALLY_INSTALLATION_DIR):
252         logger.debug("Removing rally installation directory %s" % RALLY_INSTALLATION_DIR)
253         shutil.rmtree(RALLY_INSTALLATION_DIR,ignore_errors=True)
254
255     if os.path.exists(FUNCTEST_BASE_DIR):
256         logger.debug("Removing functest directory %s" % FUNCTEST_BASE_DIR)
257         cmd = "sudo rm -rf " + FUNCTEST_BASE_DIR #need to be sudo, not possible with rmtree
258         execute_command(cmd)
259
260     #logger.debug("Deleting Neutron network %s" % NEUTRON_PRIVATE_NET_NAME)
261     #if not delete_neutron_net() :
262     #    logger.error("Error deleting the network. Remove it manually.")
263
264     logger.debug("Deleting glance images")
265     cmd = "glance image-list | grep "+IMAGE_NAME+" | cut -c3-38"
266     p = os.popen(cmd,"r")
267
268     #while image_id = p.readline()
269     for image_id in p.readlines():
270         cmd = "glance image-delete " + image_id
271         execute_command(cmd)
272
273     return True
274
275
276
277
278
279 def install_rally():
280     if check_rally():
281         logger.info("Rally is already installed.")
282     else:
283         logger.debug("Cloning repository...")
284         url = "https://git.openstack.org/openstack/rally"
285         Repo.clone_from(url, RALLY_REPO_DIR)
286
287         logger.debug("Executing %s./install_rally.sh..." %RALLY_REPO_DIR)
288         install_script = RALLY_REPO_DIR + "install_rally.sh"
289         cmd = 'sudo ' + install_script
290         execute_command(cmd)
291         #subprocess.call(['sudo', install_script])
292
293         logger.debug("Creating Rally environment...")
294         cmd = "rally deployment create --fromenv --name=opnfv-arno-rally"
295         execute_command(cmd)
296
297         logger.debug("Installing tempest...")
298         cmd = "rally-manage tempest install"
299         execute_command(cmd)
300
301         cmd = "rally deployment check"
302         execute_command(cmd)
303         #TODO: check that everything is 'Available' and warn if not
304
305         cmd = "rally show images"
306         execute_command(cmd)
307
308         cmd = "rally show flavors"
309         execute_command(cmd)
310
311     return True
312
313
314
315 def check_rally():
316     """
317     Check if Rally is installed and properly configured
318     """
319     if os.path.exists(RALLY_INSTALLATION_DIR):
320         logger.debug("   Rally installation directory found in %s" % RALLY_INSTALLATION_DIR)
321         FNULL = open(os.devnull, 'w');
322         cmd="rally deployment list | grep opnfv";
323         logger.debug('   Executing command : {}'.format(cmd))
324         p=subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=FNULL);
325         #if the command does not exist or there is no deployment
326         line = p.stdout.readline()
327         if line == "":
328             logger.debug("   Rally deployment not found")
329             return False
330         logger.debug("   Rally deployment found")
331         return True
332     else:
333         logger.debug("   Rally installation directory not found")
334         return False
335
336
337 def install_odl():
338     cmd = "chmod +x " + ODL_DIR + "create_venv.sh"
339     execute_command(cmd)
340     cmd = ODL_DIR + "create_venv.sh"
341     execute_command(cmd)
342     return True
343
344
345 def check_credentials():
346     """
347     Check if the OpenStack credentials (openrc) are sourced
348     """
349     #TODO: there must be a short way to do this, doing if os.environ["something"] == "" throws an error
350     try:
351        os.environ['OS_AUTH_URL']
352     except KeyError:
353         return False
354     try:
355        os.environ['OS_USERNAME']
356     except KeyError:
357         return False
358     try:
359        os.environ['OS_PASSWORD']
360     except KeyError:
361         return False
362     try:
363        os.environ['OS_TENANT_NAME']
364     except KeyError:
365         return False
366     try:
367        os.environ['OS_REGION_NAME']
368     except KeyError:
369         return False
370     return True
371
372
373 def get_credentials():
374     d = {}
375     d['username'] = os.environ['OS_USERNAME']
376     d['password'] = os.environ['OS_PASSWORD']
377     d['auth_url'] = os.environ['OS_AUTH_URL']
378     d['tenant_name'] = os.environ['OS_TENANT_NAME']
379     return d
380
381 def get_nova_credentials():
382     d = {}
383     d['username'] = os.environ['OS_USERNAME']
384     d['api_key'] = os.environ['OS_PASSWORD']
385     d['auth_url'] = os.environ['OS_AUTH_URL']
386     d['project_id'] = os.environ['OS_TENANT_NAME']
387     return d
388
389
390 def download_tests():
391     os.makedirs(VPING_DIR)
392     os.makedirs(ODL_DIR)
393     os.makedirs(BENCH_TESTS_DIR)
394
395     logger.info("Copying functest.yaml to functest environment...")
396     try:
397         shutil.copy("./functest.yaml", FUNCTEST_BASE_DIR+"functest.yaml")
398     except:
399         print "Error copying the file:", sys.exc_info()[0]
400         return False
401
402     logger.info("Downloading vPing test...")
403     vPing_url = 'https://git.opnfv.org/cgit/functest/plain/testcases/vPing/CI/libraries/vPing.py'
404     if not download_url(vPing_url,VPING_DIR):
405         return False
406
407
408     logger.info("Downloading Rally bench tests...")
409     run_rally_url = 'https://git.opnfv.org/cgit/functest/plain/testcases/VIM/OpenStack/CI/libraries/run_rally.py'
410     if not download_url(run_rally_url,RALLY_TEST_DIR):
411         return False
412
413     rally_bench_base_url = 'https://git.opnfv.org/cgit/functest/plain/testcases/VIM/OpenStack/CI/suites/'
414     bench_tests = ['authenticate', 'cinder', 'glance', 'heat', 'keystone', 'neutron', 'nova', 'quotas', 'requests', 'tempest', 'vm']
415     for i in bench_tests:
416         rally_bench_url = rally_bench_base_url + "opnfv-" + i + ".json"
417         logger.debug("Downloading %s" %rally_bench_url)
418         if not download_url(rally_bench_url,BENCH_TESTS_DIR):
419             return False
420
421     logger.info("Downloading OLD tests...")
422     odl_base_url = 'https://git.opnfv.org/cgit/functest/plain/testcases/Controllers/ODL/CI/'
423     odl_tests = ['create_venv.sh', 'requirements.pip', 'start_tests.sh', 'test_list.txt']
424     for i in odl_tests:
425         odl_url = odl_base_url + i
426         logger.debug("Downloading %s" %odl_url)
427         if not download_url(odl_url,ODL_DIR):
428             return False
429
430     return True
431
432
433
434 def create_private_neutron_net(neutron):
435     try:
436         neutron.format = 'json'
437         logger.debug('Creating Neutron network %s...' % NEUTRON_PRIVATE_NET_NAME)
438         json_body = {'network': {'name': NEUTRON_PRIVATE_NET_NAME,
439                     'admin_state_up': True}}
440         netw = neutron.create_network(body=json_body)
441         net_dict = netw['network']
442         network_id = net_dict['id']
443         logger.debug("Network '%s' created successfully" % network_id)
444
445         logger.debug('Creating Subnet....')
446         json_body = {'subnets': [{'name': NEUTRON_PRIVATE_SUBNET_NAME, 'cidr': NEUTRON_PRIVATE_SUBNET_CIDR,
447                            'ip_version': 4, 'network_id': network_id}]}
448
449         subnet = neutron.create_subnet(body=json_body)
450         subnet_id = subnet['subnets'][0]['id']
451         logger.debug("Subnet '%s' created successfully" % subnet_id)
452
453
454         logger.debug('Creating Router...')
455         json_body = {'router': {'name': ROUTER_NAME, 'admin_state_up': True}}
456         router = neutron.create_router(json_body)
457         router_id = router['router']['id']
458         logger.debug("Router '%s' created successfully" % router_id)
459
460         logger.debug('Adding router to subnet...')
461         json_body = {"subnet_id": subnet_id}
462         neutron.add_interface_router(router=router_id, body=json_body)
463         logger.debug("Interface added successfully.")
464
465     except:
466         print "Error:", sys.exc_info()[0]
467         return False
468
469     logger.info("Private Neutron network created successfully.")
470     return True
471
472 def get_network_id(neutron, network_name):
473     networks = neutron.list_networks()['networks']
474     id  = ''
475     for n in networks:
476         if n['name'] == network_name:
477             id = n['id']
478             break
479     return id
480
481 def check_neutron_net(neutron, net_name):
482     for network in neutron.list_networks()['networks']:
483         if network['name'] == net_name :
484             for subnet in network['subnets']:
485                 return True
486     return False
487
488 def delete_neutron_net(neutron):
489     #TODO: remove router, ports
490     try:
491         #https://github.com/isginf/openstack_tools/blob/master/openstack_remove_tenant.py
492         for network in neutron.list_networks()['networks']:
493             if network['name'] == NEUTRON_PRIVATE_NET_NAME :
494                 for subnet in network['subnets']:
495                     print "Deleting subnet " + subnet
496                     neutron.delete_subnet(subnet)
497                 print "Deleting network " + network['name']
498                 neutron.delete_neutron_net(network['id'])
499     finally:
500         return True
501     return False
502
503
504
505
506
507 def create_glance_image(path,name,disk_format):
508     """
509     Create a glance image given the absolute path of the image, its name and the disk format
510     """
511     cmd = "glance image-create --name "+name+" --is-public true --disk-format "+disk_format+" --container-format bare --file "+path
512     execute_command(cmd)
513     return True
514
515
516
517
518
519 def download_url(url, dest_path):
520     """
521     Download a file to a destination path given a URL
522     """
523     name = url.rsplit('/')[-1]
524     dest = dest_path + name
525     try:
526         response = urllib2.urlopen(url)
527     except (urllib2.HTTPError, urllib2.URLError):
528         logger.error("Error in fetching %s" %url)
529         return False
530
531     with open(dest, 'wb') as f:
532         f.write(response.read())
533     return True
534
535
536 def download_url_with_progress(url, dest_path):
537     """
538     Download a file to a destination path given a URL showing the progress
539     """
540     name = url.rsplit('/')[-1]
541     dest = dest_path + name
542     try:
543         response = urllib2.urlopen(url)
544     except (urllib2.HTTPError, urllib2.URLError):
545         logger.error("Error in fetching %s" %url)
546         return False
547
548     f = open(dest, 'wb')
549     meta = response.info()
550     file_size = int(meta.getheaders("Content-Length")[0])
551     logger.info("Downloading: %s Bytes: %s" %(dest, file_size))
552
553     file_size_dl = 0
554     block_sz = 8192
555     while True:
556         buffer = response.read(block_sz)
557         if not buffer:
558             break
559
560         file_size_dl += len(buffer)
561         f.write(buffer)
562         status = r"%10d  [%3.2f%%]" % (file_size_dl, file_size_dl * 100. / file_size)
563         status = status + chr(8)*(len(status)+1)
564         print status,
565
566     f.close()
567     print("\n")
568     return True
569
570
571 def check_internet_connectivity(url='http://www.google.com/'):
572     """
573     Check if there is access to the internet
574     """
575     try:
576         urllib2.urlopen(url, timeout=5)
577         return True
578     except urllib.request.URLError:
579         return False
580
581 def execute_command(cmd):
582     """
583     Execute Linux command
584     """
585     logger.debug('Executing command : {}'.format(cmd))
586     #p = os.popen(cmd,"r")
587     #logger.debug(p.read())
588     output_file = "output.txt"
589     f = open(output_file, 'w+')
590     p = subprocess.call(cmd,shell=True, stdout=f, stderr=subprocess.STDOUT)
591     f.close()
592     f = open(output_file, 'r')
593     logger.debug(f.read())
594     #p = subprocess.call(cmd,shell=True);
595     if p == 0 :
596         return True
597     else:
598         logger.error("Error when executing command %s" %cmd)
599         exit(-1)
600
601
602
603
604 def main():
605     if not (args.action in actions):
606         logger.error('argument not valid')
607         exit(-1)
608
609     if not check_credentials():
610         logger.error("Please source the openrc credentials and run the script again.")
611         #TODO: source the credentials in this script
612         exit(-1)
613
614     if args.action == "start":
615         config_functest_start()
616
617     if args.action == "check":
618         if config_functest_check():
619             logger.info("Functest environment correctly installed")
620         else:
621             logger.info("Functest environment not found or faulty")
622
623     if args.action == "clean":
624         while True:
625             print("Are you sure? [y|n]")
626             answer = raw_input("")
627             if answer == "y":
628                 config_functest_clean()
629                 break
630             elif answer == "n":
631                 break
632             else:
633                 print("Invalid option.")
634     exit(0)
635
636
637 if __name__ == '__main__':
638     main()
639