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