Report overall status of the test results to DB
[sdnvpn.git] / test / functest / testcase_2.py
1 #!/usr/bin/python
2 #
3 # Copyright (c) 2015 All rights reserved
4 # This program and the accompanying materials
5 # are made available under the terms of the Apache License, Version 2.0
6 # which accompanies this distribution, and is available at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10
11 import argparse
12 import os
13 from random import randint
14 import sys
15 import time
16
17 import functest.utils.functest_logger as ft_logger
18 import functest.utils.functest_utils as ft_utils
19 import functest.utils.openstack_utils as os_utils
20
21
22 parser = argparse.ArgumentParser()
23
24 parser.add_argument("-r", "--report",
25                     help="Create json result file",
26                     action="store_true")
27
28 args = parser.parse_args()
29
30 logger = ft_logger.Logger("sdnvpn-testcase-2").getLogger()
31
32 REPO_PATH = os.environ['repos_dir'] + '/sdnvpn/'
33
34 VM_BOOT_TIMEOUT = 180
35
36 config_file = REPO_PATH + 'test/functest/config.yaml'
37
38 INSTANCE_1_NAME = ft_utils.get_parameter_from_yaml(
39     "testcases.testcase_2.instance_1_name", config_file)
40 INSTANCE_1_IP = "10.10.10.11"
41 INSTANCE_2_NAME = ft_utils.get_parameter_from_yaml(
42     "testcases.testcase_2.instance_2_name", config_file)
43 INSTANCE_2_IP = "10.10.10.12"
44 INSTANCE_3_NAME = ft_utils.get_parameter_from_yaml(
45     "testcases.testcase_2.instance_3_name", config_file)
46 INSTANCE_3_IP = "10.10.11.13"
47 INSTANCE_4_NAME = ft_utils.get_parameter_from_yaml(
48     "testcases.testcase_2.instance_4_name", config_file)
49 INSTANCE_4_IP = "10.10.10.12"
50 INSTANCE_5_NAME = ft_utils.get_parameter_from_yaml(
51     "testcases.testcase_2.instance_5_name", config_file)
52 INSTANCE_5_IP = "10.10.11.13"
53 FLAVOR = ft_utils.get_parameter_from_yaml(
54     "testcases.testcase_2.flavor", config_file)
55 IMAGE_NAME = ft_utils.get_parameter_from_yaml(
56     "testcases.testcase_2.image_name", config_file)
57 IMAGE_FILENAME = ft_utils.get_parameter_from_yaml(
58     "general.openstack.image_file_name")
59 IMAGE_FORMAT = ft_utils.get_parameter_from_yaml(
60     "general.openstack.image_disk_format")
61 IMAGE_PATH = ft_utils.get_parameter_from_yaml(
62     "general.directories.dir_functest_data") + "/" + IMAGE_FILENAME
63
64 KEYFILE_PATH = REPO_PATH + 'test/functest/id_rsa'
65
66 # NEUTRON Private Network parameters
67
68 NET_1_NAME = ft_utils.get_parameter_from_yaml(
69     "testcases.testcase_2.net_1_name", config_file)
70 SUBNET_1a_NAME = ft_utils.get_parameter_from_yaml(
71     "testcases.testcase_2.subnet_1a_name", config_file)
72 SUBNET_1a_CIDR = ft_utils.get_parameter_from_yaml(
73     "testcases.testcase_2.subnet_1a_cidr", config_file)
74 SUBNET_1b_NAME = ft_utils.get_parameter_from_yaml(
75     "testcases.testcase_2.subnet_1b_name", config_file)
76 SUBNET_1b_CIDR = ft_utils.get_parameter_from_yaml(
77     "testcases.testcase_2.subnet_1b_cidr", config_file)
78 ROUTER_1_NAME = ft_utils.get_parameter_from_yaml(
79     "testcases.testcase_2.router_1_name", config_file)
80 NET_2_NAME = ft_utils.get_parameter_from_yaml(
81     "testcases.testcase_2.net_2_name", config_file)
82 SUBNET_2a_NAME = ft_utils.get_parameter_from_yaml(
83     "testcases.testcase_2.subnet_2a_name", config_file)
84 SUBNET_2a_CIDR = ft_utils.get_parameter_from_yaml(
85     "testcases.testcase_2.subnet_2a_cidr", config_file)
86 SUBNET_2b_NAME = ft_utils.get_parameter_from_yaml(
87     "testcases.testcase_2.subnet_2b_name", config_file)
88 SUBNET_2b_CIDR = ft_utils.get_parameter_from_yaml(
89     "testcases.testcase_2.subnet_2b_cidr", config_file)
90 ROUTER_1_NAME = ft_utils.get_parameter_from_yaml(
91     "testcases.testcase_2.router_1_name", config_file)
92 ROUTER_2_NAME = ft_utils.get_parameter_from_yaml(
93     "testcases.testcase_2.router_2_name", config_file)
94 SECGROUP_NAME = ft_utils.get_parameter_from_yaml(
95     "testcases.testcase_2.sdnvpn_sg_name", config_file)
96 SECGROUP_DESCR = ft_utils.get_parameter_from_yaml(
97     "testcases.testcase_2.sdnvpn_sg_descr", config_file)
98 TARGETS_1 = ft_utils.get_parameter_from_yaml(
99     "testcases.testcase_2.targets1", config_file)
100 TARGETS_2 = ft_utils.get_parameter_from_yaml(
101     "testcases.testcase_2.targets2", config_file)
102 SUCCESS_CRITERIA = ft_utils.get_parameter_from_yaml(
103     "testcases.testcase_1.succes_criteria", config_file)
104 TEST_DB = ft_utils.get_parameter_from_yaml("results.test_db_url")
105
106 TEST_RESULT = "PASS"
107 SUMMARY = ""
108 LINE_LENGTH = 90  # length for the summary table
109 DETAILS = []
110 NUM_TESTS = 0
111 NUM_TESTS_FAILED = 0
112
113
114 def create_network(neutron_client, net, subnet1, cidr1,
115                    router, subnet2=None, cidr2=None):
116     network_dic = os_utils.create_network_full(logger,
117                                                neutron_client,
118                                                net,
119                                                subnet1,
120                                                router,
121                                                cidr1)
122     if not network_dic:
123         logger.error(
124             "There has been a problem when creating the neutron network")
125         sys.exit(-1)
126     net_id = network_dic["net_id"]
127     if subnet2 is not None:
128         logger.debug("Creating and attaching a second subnet...")
129         subnet_id = os_utils.create_neutron_subnet(
130             neutron_client, subnet2, cidr2, net_id)
131         if not subnet_id:
132             logger.error(
133                 "There has been a problem when creating the second subnet")
134             sys.exit(-1)
135         logger.debug("Subnet '%s' created successfully" % subnet_id)
136     return net_id
137
138
139 def create_instance(nova_client,
140                     name,
141                     flavor,
142                     image_id,
143                     network_id,
144                     sg_id,
145                     fixed_ip,
146                     compute_node='',
147                     userdata=None,
148                     files=None):
149     logger.info("Creating instance '%s'..." % name)
150     logger.debug(
151         "Configuration:\n name=%s \n flavor=%s \n image=%s \n"
152         " network=%s\n secgroup=%s \n hypervisor=%s \n"
153         " fixed_ip=%s\n files=%s\n userdata=\n%s\n"
154         % (name, flavor, image_id, network_id, sg_id,
155            compute_node, fixed_ip, files, userdata))
156     instance = os_utils.create_instance_and_wait_for_active(
157         flavor,
158         image_id,
159         network_id,
160         name,
161         config_drive=True,
162         userdata=userdata,
163         av_zone=compute_node,
164         fixed_ip=fixed_ip,
165         files=files)
166
167     if instance is None:
168         logger.error("Error while booting instance.")
169         sys.exit(-1)
170     # Retrieve IP of INSTANCE
171     # instance_ip = instance.networks.get(network_id)[0]
172
173     logger.debug("Adding '%s' to security group '%s'..."
174                  % (name, SECGROUP_NAME))
175     os_utils.add_secgroup_to_instance(nova_client, instance.id, sg_id)
176
177     return instance
178
179
180 def generate_userdata_common():
181     return ("#!/bin/sh\n"
182             "sudo mkdir -p /home/cirros/.ssh/\n"
183             "sudo chown cirros:cirros /home/cirros/.ssh/\n"
184             "sudo chown cirros:cirros /home/cirros/id_rsa\n"
185             "mv /home/cirros/id_rsa /home/cirros/.ssh/\n"
186             "sudo echo ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgnWtSS98Am516e"
187             "stBsq0jbyOB4eLMUYDdgzsUHsnxFQCtACwwAg9/2uq3FoGUBUWeHZNsT6jcK9"
188             "sCMEYiS479CUCzbrxcd8XaIlK38HECcDVglgBNwNzX/WDfMejXpKzZG61s98rU"
189             "ElNvZ0YDqhaqZGqxIV4ejalqLjYrQkoly3R+2k= "
190             "cirros@test1>/home/cirros/.ssh/authorized_keys\n"
191             "sudo chown cirros:cirros /home/cirros/.ssh/authorized_keys\n"
192             "chmod 700 /home/cirros/.ssh\n"
193             "chmod 644 /home/cirros/.ssh/authorized_keys\n"
194             "chmod 600 /home/cirros/.ssh/id_rsa\n"
195             )
196
197
198 def generate_userdata_with_ssh(ips_array):
199     u1 = generate_userdata_common()
200
201     ips = ""
202     for ip in ips_array:
203         ips = ("%s %s" % (ips, ip))
204
205     ips = ips.replace('  ', ' ')
206     u2 = ("#!/bin/sh\n"
207           "set%s\n"
208           "while true; do\n"
209           " for i do\n"
210           "  ip=$i\n"
211           "  hostname=$(ssh -y -i /home/cirros/.ssh/id_rsa "
212           "cirros@$ip 'hostname' </dev/zero 2>/dev/null)\n"
213           "  RES=$?\n"
214           "  if [ \"Z$RES\" = \"Z0\" ]; then echo $ip $hostname;\n"
215           "  else echo $ip 'not reachable';fi;\n"
216           " done\n"
217           " sleep 1\n"
218           "done\n"
219           % ips)
220     return (u1 + u2)
221
222
223 def check_ssh_output(vm_source, ip_source,
224                      vm_target, ip_target,
225                      expected, timeout=30):
226     console_log = vm_source.get_console_output()
227
228     global TEST_RESULT
229
230     if "request failed" in console_log:
231         # Normally, cirros displays this message when userdata fails
232         logger.debug("It seems userdata is not supported in "
233                      "nova boot...")
234         return False
235     else:
236         tab = ("%s" % (" " * 53))
237         test_case_name = ("[%s] returns 'I am %s' to '%s'[%s]" %
238                           (ip_target, expected,
239                            vm_source.name, ip_source))
240         logger.debug("%sSSH\n%sfrom '%s' (%s)\n%sto '%s' (%s).\n"
241                      "%s-->Expected result: %s.\n"
242                      % (tab, tab, vm_source.name, ip_source,
243                         tab, vm_target.name, ip_target,
244                         tab, expected))
245         while True:
246             console_log = vm_source.get_console_output()
247             # the console_log is a long string, we want to take
248             # the last 4 lines (for example)
249             lines = console_log.split('\n')
250             last_n_lines = lines[-5:]
251             if ("%s %s" % (ip_target, expected)) in last_n_lines:
252                 logger.debug("[PASS] %s" % test_case_name)
253                 add_to_summary(2, "PASS", test_case_name)
254                 break
255             elif ("%s not reachable" % ip_target) in last_n_lines:
256                 logger.debug("[FAIL] %s" % test_case_name)
257                 add_to_summary(2, "FAIL", test_case_name)
258                 TEST_RESULT = "FAIL"
259                 break
260             time.sleep(1)
261             timeout -= 1
262             if timeout == 0:
263                 TEST_RESULT = "FAIL"
264                 logger.debug("[FAIL] Timeout reached for '%s'. No ping output "
265                              "captured in the console log" % vm_source.name)
266                 add_to_summary(2, "FAIL", test_case_name)
267                 break
268
269
270 def add_to_summary(num_cols, col1, col2=""):
271     global SUMMARY, LINE_LENGTH, NUM_TESTS, NUM_TESTS_FAILED
272     if num_cols == 0:
273         SUMMARY += ("+%s+\n" % (col1 * (LINE_LENGTH - 2)))
274     elif num_cols == 1:
275         SUMMARY += ("| " + col1.ljust(LINE_LENGTH - 3) + "|\n")
276     elif num_cols == 2:
277         SUMMARY += ("| %s" % col1.ljust(7) + "| ")
278         SUMMARY += (col2.ljust(LINE_LENGTH - 12) + "|\n")
279         if col1 in ("FAIL", "PASS"):
280             DETAILS.append({col2: col1})
281             NUM_TESTS += 1
282             if col1 == "FAIL":
283                 NUM_TESTS_FAILED += 1
284
285
286 def main():
287     global TEST_RESULT, SUMMARY
288
289     add_to_summary(0, "=")
290     add_to_summary(2, "STATUS", "SUBTEST")
291     add_to_summary(0, "=")
292
293     nova_client = os_utils.get_nova_client()
294     neutron_client = os_utils.get_neutron_client()
295     glance_client = os_utils.get_glance_client()
296
297     logger.debug("Using private key %s injected to the VMs." % KEYFILE_PATH)
298     keyfile = open(KEYFILE_PATH, 'r')
299     key = keyfile.read()
300     keyfile.close()
301     files = {"/home/cirros/id_rsa": key}
302
303     image_id = os_utils.create_glance_image(glance_client,
304                                             IMAGE_NAME,
305                                             IMAGE_PATH,
306                                             disk=IMAGE_FORMAT,
307                                             container="bare",
308                                             public=True,
309                                             logger=logger)
310     network_1_id = create_network(neutron_client,
311                                   NET_1_NAME,
312                                   SUBNET_1a_NAME,
313                                   SUBNET_1a_CIDR,
314                                   ROUTER_1_NAME,
315                                   SUBNET_1b_NAME,
316                                   SUBNET_1b_CIDR)
317     network_2_id = create_network(neutron_client,
318                                   NET_2_NAME,
319                                   SUBNET_2a_NAME,
320                                   SUBNET_2a_CIDR,
321                                   ROUTER_2_NAME,
322                                   SUBNET_2b_NAME,
323                                   SUBNET_2b_CIDR)
324     sg_id = os_utils.create_security_group_full(logger, neutron_client,
325                                                 SECGROUP_NAME, SECGROUP_DESCR)
326
327     # Get hypervisors zones
328     compute_nodes = os_utils.get_hypervisors(nova_client)
329     num_compute_nodes = len(compute_nodes)
330     if num_compute_nodes < 2:
331         logger.error("There are %s compute nodes in the deployment. "
332                      "Minimum number of nodes to complete the test is 2."
333                      % num_compute_nodes)
334         sys.exit(-1)
335
336     logger.debug("Compute nodes: %s" % compute_nodes)
337     av_zone_1 = "nova:" + compute_nodes[0]
338     av_zone_2 = "nova:" + compute_nodes[1]
339
340     # boot INTANCES
341     userdata_common = generate_userdata_common()
342     vm_2 = create_instance(nova_client,
343                            INSTANCE_2_NAME,
344                            FLAVOR,
345                            image_id,
346                            network_1_id,
347                            sg_id,
348                            INSTANCE_2_IP,
349                            compute_node=av_zone_1,
350                            userdata=userdata_common)
351     vm_2_ip = vm_2.networks.itervalues().next()[0]
352     logger.debug("Instance '%s' booted successfully. IP='%s'." %
353                  (INSTANCE_2_NAME, vm_2_ip))
354
355     vm_3 = create_instance(nova_client,
356                            INSTANCE_3_NAME,
357                            FLAVOR, image_id,
358                            network_1_id,
359                            sg_id,
360                            INSTANCE_3_IP,
361                            compute_node=av_zone_2,
362                            userdata=userdata_common)
363     vm_3_ip = vm_3.networks.itervalues().next()[0]
364     logger.debug("Instance '%s' booted successfully. IP='%s'." %
365                  (INSTANCE_3_NAME, vm_3_ip))
366
367     vm_5 = create_instance(nova_client,
368                            INSTANCE_5_NAME,
369                            FLAVOR,
370                            image_id,
371                            network_2_id,
372                            sg_id,
373                            fixed_ip=INSTANCE_5_IP,
374                            compute_node=av_zone_2,
375                            userdata=userdata_common)
376     vm_5_ip = vm_5.networks.itervalues().next()[0]
377     logger.debug("Instance '%s' booted successfully. IP='%s'." %
378                  (INSTANCE_5_NAME, vm_5_ip))
379
380     # We boot vm5 first because we need vm5_ip for vm4 userdata
381     u4 = generate_userdata_with_ssh(
382         [INSTANCE_1_IP, INSTANCE_3_IP, INSTANCE_5_IP])
383     vm_4 = create_instance(nova_client,
384                            INSTANCE_4_NAME,
385                            FLAVOR,
386                            image_id,
387                            network_2_id,
388                            sg_id,
389                            fixed_ip=INSTANCE_4_IP,
390                            compute_node=av_zone_1,
391                            userdata=u4,
392                            files=files)
393     vm_4_ip = vm_4.networks.itervalues().next()[0]
394     logger.debug("Instance '%s' booted successfully. IP='%s'." %
395                  (INSTANCE_4_NAME, vm_4_ip))
396
397     # We boot VM1 at the end because we need to get the IPs first to generate
398     # the userdata
399     u1 = generate_userdata_with_ssh(
400         [INSTANCE_2_IP, INSTANCE_3_IP, INSTANCE_4_IP, INSTANCE_5_IP])
401     vm_1 = create_instance(nova_client,
402                            INSTANCE_1_NAME,
403                            FLAVOR,
404                            image_id,
405                            network_1_id,
406                            sg_id,
407                            fixed_ip=INSTANCE_1_IP,
408                            compute_node=av_zone_1,
409                            userdata=u1,
410                            files=files)
411     vm_1_ip = vm_1.networks.itervalues().next()[0]
412     logger.debug("Instance '%s' booted successfully. IP='%s'." %
413                  (INSTANCE_1_NAME, vm_1_ip))
414
415     msg = ("Create VPN1 with eRT=iRT")
416     logger.info(msg)
417     add_to_summary(1, msg)
418     vpn1_name = "sdnvpn-1-" + str(randint(100000, 999999))
419     kwargs = {"import_targets": TARGETS_2,
420               "export_targets": TARGETS_2,
421               "route_targets": TARGETS_2,
422               "name": vpn1_name}
423     bgpvpn1 = os_utils.create_bgpvpn(neutron_client, **kwargs)
424     bgpvpn1_id = bgpvpn1['bgpvpn']['id']
425     logger.debug("VPN1 created details: %s" % bgpvpn1)
426
427     msg = ("Associate network '%s' to the VPN." % NET_1_NAME)
428     logger.info(msg)
429     add_to_summary(1, msg)
430     add_to_summary(0, "-")
431
432     os_utils.create_network_association(
433         neutron_client, bgpvpn1_id, network_1_id)
434
435     # Wait for VMs to get ips.
436     time.sleep(80)
437
438     # 10.10.10.12 should return sdnvpn-2 to sdnvpn-1
439     check_ssh_output(
440         vm_1, vm_1_ip, vm_2, vm_2_ip, expected=INSTANCE_2_NAME, timeout=200)
441     # 10.10.11.13 should return sdnvpn-3 to sdnvpn-1
442     check_ssh_output(
443         vm_1, vm_1_ip, vm_3, vm_3_ip, expected=INSTANCE_3_NAME, timeout=30)
444
445     add_to_summary(0, "-")
446     msg = ("Create VPN2 with eRT=iRT")
447     logger.info(msg)
448     add_to_summary(1, msg)
449     vpn2_name = "sdnvpn-2-" + str(randint(100000, 999999))
450     kwargs = {"import_targets": TARGETS_1,
451               "export_targets": TARGETS_1,
452               "route_targets": TARGETS_1,
453               "name": vpn2_name}
454     bgpvpn2 = os_utils.create_bgpvpn(neutron_client, **kwargs)
455     bgpvpn2_id = bgpvpn2['bgpvpn']['id']
456     logger.debug("VPN created details: %s" % bgpvpn2)
457
458     msg = ("Associate network '%s' to the VPN2." % NET_2_NAME)
459     logger.info(msg)
460     add_to_summary(1, msg)
461     add_to_summary(0, "-")
462
463     os_utils.create_network_association(
464         neutron_client, bgpvpn2_id, network_2_id)
465
466     # Wait a bit for this to take effect
467     time.sleep(80)
468
469     # 10.10.11.13 should return sdnvpn-5 to sdnvpn-4
470     check_ssh_output(
471         vm_4, vm_4_ip, vm_5, vm_5_ip, expected=INSTANCE_5_NAME, timeout=30)
472
473     # 10.10.10.11 should return "not reachable" to sdnvpn-4
474     check_ssh_output(
475         vm_4, vm_4_ip, vm_1, vm_1_ip, expected="not reachable", timeout=30)
476
477     add_to_summary(0, "=")
478     logger.info("\n%s" % SUMMARY)
479
480     if TEST_RESULT == "PASS":
481         logger.info("All the sub tests have passed as expected.")
482     else:
483         logger.info("One or more sub tests have failed.")
484
485     status = "PASS"
486     success = 100 - (100 * int(NUM_TESTS_FAILED) / int(NUM_TESTS))
487     if success < int(SUCCESS_CRITERIA):
488         status = "FAILED"
489
490     return {"status": status, "details": DETAILS}
491
492
493 if __name__ == '__main__':
494     main()