Route exchange test with testcase3
[sdnvpn.git] / sdnvpn / test / functest / testcase_3.py
1 #!/usr/bin/env python
2 #
3 # Copyright (c) 2017 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 # Tests performed:
11 # - Peering OpenDaylight with Quagga:
12 #   - Set up a Quagga instance in the functest container
13 #   - Start a BGP router with OpenDaylight
14 #   - Add the functest Quagga as a neighbor
15 #   - Verify that the OpenDaylight and gateway Quagga peer
16
17 import logging
18 import os
19 import sys
20 import time
21
22 from sdnvpn.lib import quagga
23 from sdnvpn.lib import openstack_utils as os_utils
24 from sdnvpn.lib import utils as test_utils
25 from sdnvpn.lib import config as sdnvpn_config
26 from sdnvpn.lib.results import Results
27
28
29 logger = logging.getLogger(__name__)
30
31 COMMON_CONFIG = sdnvpn_config.CommonConfig()
32 TESTCASE_CONFIG = sdnvpn_config.TestcaseConfig(
33     "sdnvpn.test.functest.testcase_3")
34
35
36 def main():
37     results = Results(COMMON_CONFIG.line_length)
38     results.add_to_summary(0, "=")
39     results.add_to_summary(2, "STATUS", "SUBTEST")
40     results.add_to_summary(0, "=")
41
42     openstack_nodes = test_utils.get_nodes()
43
44     # node.is_odl() doesn't work in Apex
45     # https://jira.opnfv.org/browse/RELENG-192
46     controllers = [node for node in openstack_nodes
47                    if "running" in
48                    node.run_cmd("sudo systemctl status opendaylight")]
49     computes = [node for node in openstack_nodes if node.is_compute()]
50
51     msg = ("Verify that OpenDaylight can start/communicate with zrpcd/Quagga")
52     results.record_action(msg)
53     results.add_to_summary(0, "-")
54     if not controllers:
55         msg = ("Controller (ODL) list is empty. Skipping rest of tests.")
56         logger.info(msg)
57         results.add_failure(msg)
58         return results.compile_summary()
59     else:
60         msg = ("Controller (ODL) list is ready")
61         logger.info(msg)
62         results.add_success(msg)
63
64     logger.info("Checking if zrpcd is "
65                 "running on the controller nodes")
66
67     for controller in controllers:
68         output_zrpcd = controller.run_cmd("ps --no-headers -C "
69                                           "zrpcd -o state")
70         states = output_zrpcd.split()
71         running = any([s != 'Z' for s in states])
72         msg = ("zrpcd is running in {name}".format(name=controller.name))
73
74         if not running:
75             logger.info("zrpcd is not running on the controller node {name}"
76                         .format(name=controller.name))
77             results.add_failure(msg)
78         else:
79             logger.info("zrpcd is running on the controller node {name}"
80                         .format(name=controller.name))
81             results.add_success(msg)
82
83         results.add_to_summary(0, "-")
84
85     # Find the BGP entity owner in ODL because of this bug:
86     # https://jira.opendaylight.org/browse/NETVIRT-1308
87     msg = ("Found BGP entity owner")
88     controller = test_utils.get_odl_bgp_entity_owner(controllers)
89     if controller is None:
90         logger.error("Failed to find the BGP entity owner")
91         results.add_failure(msg)
92     else:
93         logger.info('BGP entity owner is {name}'
94                     .format(name=controller.name))
95         results.add_success(msg)
96     results.add_to_summary(0, "-")
97
98     get_ext_ip_cmd = "sudo ip a | grep br-ex | grep inet | awk '{print $2}'"
99     ext_net_cidr = controller.run_cmd(get_ext_ip_cmd).strip().split('\n')
100     ext_net_mask = ext_net_cidr[0].split('/')[1]
101     controller_ext_ip = ext_net_cidr[0].split('/')[0]
102
103     logger.info("Starting bgp speaker of controller at IP %s "
104                 % controller_ext_ip)
105
106     # Ensure that ZRPCD ip & port are well configured within ODL
107     add_client_conn_to_bgp = "bgp-connect -p 7644 -h 127.0.0.1 add"
108     test_utils.run_odl_cmd(controller, add_client_conn_to_bgp)
109
110     # Start bgp daemon
111     start_quagga = "odl:configure-bgp -op start-bgp-server " \
112                    "--as-num 100 --router-id {0}".format(controller_ext_ip)
113     test_utils.run_odl_cmd(controller, start_quagga)
114
115     # we need to wait a bit until the bgpd is up
116     time.sleep(5)
117
118     logger.info("Checking if bgpd is running"
119                 " on the controller node")
120
121     # Check if there is a non-zombie bgpd process
122     output_bgpd = controller.run_cmd("ps --no-headers -C "
123                                      "bgpd -o state")
124     states = output_bgpd.split()
125     running = any([s != 'Z' for s in states])
126
127     msg = ("bgpd is running")
128     if not running:
129         logger.info("bgpd is not running on the controller node")
130         results.add_failure(msg)
131     else:
132         logger.info("bgpd is running on the controller node")
133         results.add_success(msg)
134
135     results.add_to_summary(0, "-")
136
137     # We should be able to restart the speaker
138     # but the test is disabled because of buggy upstream
139     # https://github.com/6WIND/zrpcd/issues/15
140     # stop_quagga = 'odl:configure-bgp -op stop-bgp-server'
141     # test_utils.run_odl_cmd(controller, stop_quagga)
142
143     # logger.info("Checking if bgpd is still running"
144     #             " on the controller node")
145
146     # output_bgpd = controller.run_cmd("ps --no-headers -C " \
147     #                                  "bgpd -o state")
148     # states = output_bgpd.split()
149     # running = any([s != 'Z' for s in states])
150
151     # msg = ("bgpd is stopped")
152     # if not running:
153     #     logger.info("bgpd is not running on the controller node")
154     #     results.add_success(msg)
155     # else:
156     #     logger.info("bgpd is still running on the controller node")
157     #     results.add_failure(msg)
158
159     # Taken from the sfc tests
160     if not os.path.isfile(COMMON_CONFIG.ubuntu_image_path):
161         logger.info("Downloading image")
162         image_dest_path = '/'.join(
163                 COMMON_CONFIG.ubuntu_image_path.split('/')[:-1])
164         os_utils.download_url(
165             "http://artifacts.opnfv.org/sdnvpn/"
166             "ubuntu-16.04-server-cloudimg-amd64-disk1.img",
167             image_dest_path)
168     else:
169         logger.info("Using old image")
170
171     glance_client = os_utils.get_glance_client()
172     nova_client = os_utils.get_nova_client()
173     neutron_client = os_utils.get_neutron_client()
174
175     (floatingip_ids, instance_ids, router_ids, network_ids, image_ids,
176      subnet_ids, interfaces, bgpvpn_ids, flavor_ids) = ([] for i in range(9))
177
178     try:
179         _, flavor_id = test_utils.create_custom_flavor()
180         flavor_ids.append(flavor_id)
181
182         sg_id = os_utils.create_security_group_full(
183             neutron_client, TESTCASE_CONFIG.secgroup_name,
184             TESTCASE_CONFIG.secgroup_descr)
185         test_utils.open_icmp(neutron_client, sg_id)
186         test_utils.open_http_port(neutron_client, sg_id)
187
188         test_utils.open_bgp_port(neutron_client, sg_id)
189
190         image_id = os_utils.create_glance_image(
191             glance_client, TESTCASE_CONFIG.image_name,
192             COMMON_CONFIG.image_path, disk=COMMON_CONFIG.image_format,
193             container="bare", public='public')
194         image_ids.append(image_id)
195
196         net_1_id, subnet_1_id, router_1_id = test_utils.create_network(
197             neutron_client,
198             TESTCASE_CONFIG.net_1_name,
199             TESTCASE_CONFIG.subnet_1_name,
200             TESTCASE_CONFIG.subnet_1_cidr,
201             TESTCASE_CONFIG.router_1_name)
202
203         quagga_net_id, subnet_quagga_id, \
204             router_quagga_id = test_utils.create_network(
205                 neutron_client,
206                 TESTCASE_CONFIG.quagga_net_name,
207                 TESTCASE_CONFIG.quagga_subnet_name,
208                 TESTCASE_CONFIG.quagga_subnet_cidr,
209                 TESTCASE_CONFIG.quagga_router_name)
210
211         interfaces.append(tuple((router_1_id, subnet_1_id)))
212         interfaces.append(tuple((router_quagga_id, subnet_quagga_id)))
213         network_ids.extend([net_1_id, quagga_net_id])
214         router_ids.extend([router_1_id, router_quagga_id])
215         subnet_ids.extend([subnet_1_id, subnet_quagga_id])
216
217         installer_type = str(os.environ['INSTALLER_TYPE'].lower())
218         if installer_type == "fuel":
219             disk = 'raw'
220         elif installer_type == "apex":
221             disk = 'qcow2'
222         else:
223             logger.error("Incompatible installer type")
224
225         ubuntu_image_id = os_utils.create_glance_image(
226             glance_client,
227             COMMON_CONFIG.ubuntu_image_name,
228             COMMON_CONFIG.ubuntu_image_path,
229             disk,
230             container="bare",
231             public="public")
232
233         image_ids.append(ubuntu_image_id)
234
235         # NOTE(rski) The order of this seems a bit weird but
236         # there is a reason for this, namely
237         # https://jira.opnfv.org/projects/SDNVPN/issues/SDNVPN-99
238         # so we create the quagga instance using cloud-init
239         # and immediately give it a floating IP.
240         # The cloud-init script should contain a small sleep for
241         # this to work.
242         # We also create the FIP first because it is used in the
243         # cloud-init script.
244         # fake_fip is needed to bypass NAT
245         # see below for the reason why.
246         fake_fip = os_utils.create_floating_ip(neutron_client)
247         # pin quagga to some compute
248         floatingip_ids.append(fake_fip['fip_id'])
249         compute_node = nova_client.hypervisors.list()[0]
250         quagga_compute_node = "nova:" + compute_node.hypervisor_hostname
251         # Map the hypervisor used above to a compute handle
252         # returned by releng's manager
253         for comp in computes:
254             if compute_node.host_ip in comp.run_cmd("sudo ip a"):
255                 compute = comp
256                 break
257         quagga_bootstrap_script = quagga.gen_quagga_setup_script(
258             controller_ext_ip,
259             fake_fip['fip_addr'],
260             ext_net_mask,
261             TESTCASE_CONFIG.external_network_ip_prefix,
262             TESTCASE_CONFIG.route_distinguishers,
263             TESTCASE_CONFIG.import_targets,
264             TESTCASE_CONFIG.export_targets)
265
266         quagga_vm = test_utils.create_instance(
267             nova_client,
268             TESTCASE_CONFIG.quagga_instance_name,
269             ubuntu_image_id,
270             quagga_net_id,
271             sg_id,
272             fixed_ip=TESTCASE_CONFIG.quagga_instance_ip,
273             flavor=COMMON_CONFIG.custom_flavor_name,
274             userdata=quagga_bootstrap_script,
275             compute_node=quagga_compute_node)
276
277         instance_ids.append(quagga_vm)
278
279         quagga_vm_port = test_utils.get_port(neutron_client,
280                                              quagga_vm.id)
281         fip_added = os_utils.attach_floating_ip(neutron_client,
282                                                 quagga_vm_port['id'])
283
284         msg = ("Assign a Floating IP to %s " %
285                TESTCASE_CONFIG.quagga_instance_name)
286         if fip_added:
287             results.add_success(msg)
288             floatingip_ids.append(fip_added['floatingip']['id'])
289         else:
290             results.add_failure(msg)
291
292         test_utils.attach_instance_to_ext_br(quagga_vm, compute)
293
294         try:
295             testcase = "Bootstrap quagga inside an OpenStack instance"
296             cloud_init_success = test_utils.wait_for_cloud_init(quagga_vm)
297             if cloud_init_success:
298                 results.add_success(testcase)
299             else:
300                 results.add_failure(testcase)
301             results.add_to_summary(0, "=")
302
303             results.add_to_summary(0, '-')
304             results.add_to_summary(1, "Peer Quagga with OpenDaylight")
305             results.add_to_summary(0, '-')
306
307             neighbor = quagga.odl_add_neighbor(fake_fip['fip_addr'],
308                                                controller_ext_ip,
309                                                controller)
310             peer = quagga.check_for_peering(controller)
311
312         finally:
313             test_utils.detach_instance_from_ext_br(quagga_vm, compute)
314
315         if neighbor and peer:
316             results.add_success("Peering with quagga")
317         else:
318             results.add_failure("Peering with quagga")
319
320         test_utils.add_quagga_external_gre_end_point(controllers,
321                                                      fake_fip['fip_addr'])
322         test_utils.wait_before_subtest()
323
324         msg = ("Create VPN to define a VRF")
325         results.record_action(msg)
326         vpn_name = vpn_name = "sdnvpn-3"
327         kwargs = {
328             "import_targets": TESTCASE_CONFIG.import_targets,
329             "export_targets": TESTCASE_CONFIG.export_targets,
330             "route_targets": TESTCASE_CONFIG.route_targets,
331             "route_distinguishers": TESTCASE_CONFIG.route_distinguishers,
332             "name": vpn_name
333         }
334         bgpvpn = test_utils.create_bgpvpn(neutron_client, **kwargs)
335         bgpvpn_id = bgpvpn['bgpvpn']['id']
336         logger.debug("VPN1 created details: %s" % bgpvpn)
337         bgpvpn_ids.append(bgpvpn_id)
338
339         msg = ("Associate network '%s' to the VPN." %
340                TESTCASE_CONFIG.net_1_name)
341         results.record_action(msg)
342         results.add_to_summary(0, "-")
343
344         # create a vm and connect it with network1,
345         # which is going to be bgpvpn associated
346         userdata_common = test_utils.generate_ping_userdata(
347             [TESTCASE_CONFIG.external_network_ip])
348
349         compute_node = nova_client.hypervisors.list()[0]
350         av_zone_1 = "nova:" + compute_node.hypervisor_hostname
351         vm_bgpvpn = test_utils.create_instance(
352             nova_client,
353             TESTCASE_CONFIG.instance_1_name,
354             image_id,
355             net_1_id,
356             sg_id,
357             fixed_ip=TESTCASE_CONFIG.instance_1_ip,
358             secgroup_name=TESTCASE_CONFIG.secgroup_name,
359             compute_node=av_zone_1,
360             userdata=userdata_common)
361         instance_ids.append(vm_bgpvpn)
362
363         # wait for VM to get IP
364         instance_up = test_utils.wait_for_instances_up(vm_bgpvpn)
365         if not instance_up:
366             logger.error("One or more instances are down")
367
368         test_utils.create_network_association(
369             neutron_client, bgpvpn_id, net_1_id)
370
371         test_utils.wait_before_subtest()
372
373         msg = ("External IP prefix %s is exchanged with ODL"
374                % TESTCASE_CONFIG.external_network_ip_prefix)
375         fib_added = test_utils.is_fib_entry_present_on_odl(
376             controllers,
377             TESTCASE_CONFIG.external_network_ip_prefix,
378             TESTCASE_CONFIG.route_distinguishers)
379         if fib_added:
380             results.add_success(msg)
381         else:
382             results.add_failure(msg)
383
384         # TODO: uncomment the following once OVS is installed with > 2.8.3 and
385         # underlay connectivity is established between vxlan overlay and
386         # external network.
387         # results.get_ping_status_target_ip(
388         #    vm_bgpvpn,
389         #    TESTCASE_CONFIG.external_network_name,
390         #    TESTCASE_CONFIG.external_network_ip,
391         #    expected="PASS",
392         #    timeout=300)
393
394         results.add_to_summary(0, "=")
395         logger.info("\n%s" % results.summary)
396
397     except Exception as e:
398         logger.error("exception occurred while executing testcase_3: %s", e)
399         raise
400     finally:
401         test_utils.cleanup_nova(nova_client, instance_ids, flavor_ids)
402         test_utils.cleanup_glance(glance_client, image_ids)
403         test_utils.cleanup_neutron(neutron_client, floatingip_ids,
404                                    bgpvpn_ids, interfaces, subnet_ids,
405                                    router_ids, network_ids)
406         bgp_nbr_disconnect_cmd = ("bgp-nbr -i %s -a 200 del"
407                                   % fake_fip['fip_addr'])
408         bgp_server_stop_cmd = ("bgp-rtr -r %s -a 100 del"
409                                % controller_ext_ip)
410         odl_zrpc_disconnect_cmd = "bgp-connect -p 7644 -h 127.0.0.1 del"
411         test_utils.run_odl_cmd(controller, bgp_nbr_disconnect_cmd)
412         test_utils.run_odl_cmd(controller, bgp_server_stop_cmd)
413         test_utils.run_odl_cmd(controller, odl_zrpc_disconnect_cmd)
414
415     return results.compile_summary()
416
417
418 if __name__ == '__main__':
419     sys.exit(main())