Fix most end-to-end issues with testcase 3 and reenable it
[sdnvpn.git] / sdnvpn / lib / utils.py
1 #!/usr/bin/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 import os
11 import sys
12 import time
13 import requests
14 import re
15 import subprocess
16
17 import functest.utils.functest_logger as ft_logger
18 import functest.utils.openstack_utils as os_utils
19 from opnfv.deployment.factory import Factory as DeploymentFactory
20
21 from sdnvpn.lib import config as sdnvpn_config
22
23 logger = ft_logger.Logger("sndvpn_test_utils").getLogger()
24
25 common_config = sdnvpn_config.CommonConfig()
26
27 ODL_USER = 'admin'
28 ODL_PASS = 'admin'
29
30
31 def create_net(neutron_client, name):
32     logger.debug("Creating network %s", name)
33     net_id = os_utils.create_neutron_net(neutron_client, name)
34     if not net_id:
35         logger.error(
36             "There has been a problem when creating the neutron network")
37         sys.exit(-1)
38     return net_id
39
40
41 def create_subnet(neutron_client, name, cidr, net_id):
42     logger.debug("Creating subnet %s in network %s with cidr %s",
43                  name, net_id, cidr)
44     subnet_id = os_utils.create_neutron_subnet(neutron_client,
45                                                name,
46                                                cidr,
47                                                net_id)
48     if not subnet_id:
49         logger.error(
50             "There has been a problem when creating the neutron subnet")
51         sys.exit(-1)
52     return subnet_id
53
54
55 def create_network(neutron_client, net, subnet1, cidr1,
56                    router, subnet2=None, cidr2=None):
57     """Network assoc will not work for networks/subnets created by this function.
58
59     It is an ODL limitation due to it handling routers as vpns.
60     See https://bugs.opendaylight.org/show_bug.cgi?id=6962"""
61     network_dic = os_utils.create_network_full(neutron_client,
62                                                net,
63                                                subnet1,
64                                                router,
65                                                cidr1)
66     if not network_dic:
67         logger.error(
68             "There has been a problem when creating the neutron network")
69         sys.exit(-1)
70     net_id = network_dic["net_id"]
71     subnet_id = network_dic["subnet_id"]
72     router_id = network_dic["router_id"]
73
74     if subnet2 is not None:
75         logger.debug("Creating and attaching a second subnet...")
76         subnet_id = os_utils.create_neutron_subnet(
77             neutron_client, subnet2, cidr2, net_id)
78         if not subnet_id:
79             logger.error(
80                 "There has been a problem when creating the second subnet")
81             sys.exit(-1)
82         logger.debug("Subnet '%s' created successfully" % subnet_id)
83     return net_id, subnet_id, router_id
84
85
86 def create_instance(nova_client,
87                     name,
88                     image_id,
89                     network_id,
90                     sg_id,
91                     secgroup_name=None,
92                     fixed_ip=None,
93                     compute_node='',
94                     userdata=None,
95                     files=None,
96                     **kwargs
97                     ):
98     if 'flavor' not in kwargs:
99         kwargs['flavor'] = common_config.default_flavor
100
101     logger.info("Creating instance '%s'..." % name)
102     logger.debug(
103         "Configuration:\n name=%s \n flavor=%s \n image=%s \n"
104         " network=%s\n secgroup=%s \n hypervisor=%s \n"
105         " fixed_ip=%s\n files=%s\n userdata=\n%s\n"
106         % (name, kwargs['flavor'], image_id, network_id, sg_id,
107            compute_node, fixed_ip, files, userdata))
108     instance = os_utils.create_instance_and_wait_for_active(
109         kwargs['flavor'],
110         image_id,
111         network_id,
112         name,
113         config_drive=True,
114         userdata=userdata,
115         av_zone=compute_node,
116         fixed_ip=fixed_ip,
117         files=files)
118
119     if instance is None:
120         logger.error("Error while booting instance.")
121         sys.exit(-1)
122     else:
123         logger.debug("Instance '%s' booted successfully. IP='%s'." %
124                      (name, instance.networks.itervalues().next()[0]))
125     # Retrieve IP of INSTANCE
126     # instance_ip = instance.networks.get(network_id)[0]
127
128     if secgroup_name:
129         logger.debug("Adding '%s' to security group '%s'..."
130                      % (name, secgroup_name))
131     else:
132         logger.debug("Adding '%s' to security group '%s'..."
133                      % (name, sg_id))
134     os_utils.add_secgroup_to_instance(nova_client, instance.id, sg_id)
135
136     return instance
137
138
139 def generate_ping_userdata(ips_array):
140     ips = ""
141     for ip in ips_array:
142         ips = ("%s %s" % (ips, ip))
143
144     ips = ips.replace('  ', ' ')
145     return ("#!/bin/sh\n"
146             "set%s\n"
147             "while true; do\n"
148             " for i do\n"
149             "  ip=$i\n"
150             "  ping -c 1 $ip 2>&1 >/dev/null\n"
151             "  RES=$?\n"
152             "  if [ \"Z$RES\" = \"Z0\" ] ; then\n"
153             "   echo ping $ip OK\n"
154             "  else echo ping $ip KO\n"
155             "  fi\n"
156             " done\n"
157             " sleep 1\n"
158             "done\n"
159             % ips)
160
161
162 def generate_userdata_common():
163     return ("#!/bin/sh\n"
164             "sudo mkdir -p /home/cirros/.ssh/\n"
165             "sudo chown cirros:cirros /home/cirros/.ssh/\n"
166             "sudo chown cirros:cirros /home/cirros/id_rsa\n"
167             "mv /home/cirros/id_rsa /home/cirros/.ssh/\n"
168             "sudo echo ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgnWtSS98Am516e"
169             "stBsq0jbyOB4eLMUYDdgzsUHsnxFQCtACwwAg9/2uq3FoGUBUWeHZNsT6jcK9"
170             "sCMEYiS479CUCzbrxcd8XaIlK38HECcDVglgBNwNzX/WDfMejXpKzZG61s98rU"
171             "ElNvZ0YDqhaqZGqxIV4ejalqLjYrQkoly3R+2k= "
172             "cirros@test1>/home/cirros/.ssh/authorized_keys\n"
173             "sudo chown cirros:cirros /home/cirros/.ssh/authorized_keys\n"
174             "chmod 700 /home/cirros/.ssh\n"
175             "chmod 644 /home/cirros/.ssh/authorized_keys\n"
176             "chmod 600 /home/cirros/.ssh/id_rsa\n"
177             )
178
179
180 def generate_userdata_with_ssh(ips_array):
181     u1 = generate_userdata_common()
182
183     ips = ""
184     for ip in ips_array:
185         ips = ("%s %s" % (ips, ip))
186
187     ips = ips.replace('  ', ' ')
188     u2 = ("#!/bin/sh\n"
189           "set%s\n"
190           "while true; do\n"
191           " for i do\n"
192           "  ip=$i\n"
193           "  hostname=$(ssh -y -i /home/cirros/.ssh/id_rsa "
194           "cirros@$ip 'hostname' </dev/zero 2>/dev/null)\n"
195           "  RES=$?\n"
196           "  if [ \"Z$RES\" = \"Z0\" ]; then echo $ip $hostname;\n"
197           "  else echo $ip 'not reachable';fi;\n"
198           " done\n"
199           " sleep 1\n"
200           "done\n"
201           % ips)
202     return (u1 + u2)
203
204
205 def get_installerHandler():
206     installer_type = str(os.environ['INSTALLER_TYPE'].lower())
207     installer_ip = get_installer_ip()
208
209     if installer_type not in ["fuel", "apex"]:
210         raise ValueError("%s is not supported" % installer_type)
211     else:
212         if installer_type in ["apex"]:
213             developHandler = DeploymentFactory.get_handler(
214                 installer_type,
215                 installer_ip,
216                 'root',
217                 pkey_file="/root/.ssh/id_rsa")
218
219         if installer_type in ["fuel"]:
220             developHandler = DeploymentFactory.get_handler(
221                 installer_type,
222                 installer_ip,
223                 'root',
224                 'r00tme')
225         return developHandler
226
227
228 def get_nodes():
229     developHandler = get_installerHandler()
230     return developHandler.get_nodes()
231
232
233 def get_installer_ip():
234     return str(os.environ['INSTALLER_IP'])
235
236
237 def wait_for_instance(instance):
238     logger.info("Waiting for instance %s to get a DHCP lease..." % instance.id)
239     # The sleep this function replaced waited for 80s
240     tries = 40
241     sleep_time = 2
242     pattern = "Lease of .* obtained, lease time"
243     expected_regex = re.compile(pattern)
244     console_log = ""
245     while tries > 0 and not expected_regex.search(console_log):
246         console_log = instance.get_console_output()
247         time.sleep(sleep_time)
248         tries -= 1
249
250     if not expected_regex.search(console_log):
251         logger.error("Instance %s seems to have failed leasing an IP."
252                      % instance.id)
253         return False
254     return True
255
256
257 def wait_for_instances_up(*args):
258     check = [wait_for_instance(instance) for instance in args]
259     return all(check)
260
261
262 def wait_for_bgp_net_assoc(neutron_client, bgpvpn_id, net_id):
263     tries = 30
264     sleep_time = 1
265     nets = []
266     logger.debug("Waiting for network %s to associate with BGPVPN %s "
267                  % (bgpvpn_id, net_id))
268
269     while tries > 0 and net_id not in nets:
270         nets = os_utils.get_bgpvpn_networks(neutron_client, bgpvpn_id)
271         time.sleep(sleep_time)
272         tries -= 1
273     if net_id not in nets:
274         logger.error("Association of network %s with BGPVPN %s failed" %
275                      (net_id, bgpvpn_id))
276         return False
277     return True
278
279
280 def wait_for_bgp_net_assocs(neutron_client, bgpvpn_id, *args):
281     check = [wait_for_bgp_net_assoc(neutron_client, bgpvpn_id, id)
282              for id in args]
283     # Return True if all associations succeeded
284     return all(check)
285
286
287 def wait_for_bgp_router_assoc(neutron_client, bgpvpn_id, router_id):
288     tries = 30
289     sleep_time = 1
290     routers = []
291     logger.debug("Waiting for router %s to associate with BGPVPN %s "
292                  % (bgpvpn_id, router_id))
293     while tries > 0 and router_id not in routers:
294         routers = os_utils.get_bgpvpn_routers(neutron_client, bgpvpn_id)
295         time.sleep(sleep_time)
296         tries -= 1
297     if router_id not in routers:
298         logger.error("Association of router %s with BGPVPN %s failed" %
299                      (router_id, bgpvpn_id))
300         return False
301     return True
302
303
304 def wait_for_bgp_router_assocs(neutron_client, bgpvpn_id, *args):
305     check = [wait_for_bgp_router_assoc(neutron_client, bgpvpn_id, id)
306              for id in args]
307     # Return True if all associations succeeded
308     return all(check)
309
310
311 def wait_before_subtest(*args, **kwargs):
312     ''' This is a placeholder.
313         TODO: Replace delay with polling logic. '''
314     time.sleep(30)
315
316
317 def assert_and_get_compute_nodes(nova_client, required_node_number=2):
318     """Get the compute nodes in the deployment
319
320     Exit if the deployment doesn't have enough compute nodes"""
321     compute_nodes = os_utils.get_hypervisors(nova_client)
322
323     num_compute_nodes = len(compute_nodes)
324     if num_compute_nodes < 2:
325         logger.error("There are %s compute nodes in the deployment. "
326                      "Minimum number of nodes to complete the test is 2."
327                      % num_compute_nodes)
328         sys.exit(-1)
329
330     logger.debug("Compute nodes: %s" % compute_nodes)
331     return compute_nodes
332
333
334 def open_icmp_ssh(neutron_client, security_group_id):
335     os_utils.create_secgroup_rule(neutron_client,
336                                   security_group_id,
337                                   'ingress',
338                                   'icmp')
339     os_utils.create_secgroup_rule(neutron_client,
340                                   security_group_id,
341                                   'tcp',
342                                   80, 80)
343
344
345 def open_bgp_port(neutron_client, security_group_id):
346     os_utils.create_secgroup_rule(neutron_client,
347                                   security_group_id,
348                                   'tcp',
349                                   179, 179)
350
351
352 def exec_cmd(cmd, verbose):
353     success = True
354     logger.debug("Executing '%s'" % cmd)
355     p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
356                          stderr=subprocess.STDOUT)
357     output = ""
358     for line in iter(p.stdout.readline, b''):
359         output += line
360
361     if verbose:
362         logger.debug(output)
363
364     p.stdout.close()
365     returncode = p.wait()
366     if returncode != 0:
367         logger.error("Command %s failed to execute." % cmd)
368         success = False
369
370     return output, success
371
372
373 def check_odl_fib(ip, controller_ip):
374     """Check that there is an entry in the ODL Fib for `ip`"""
375     url = "http://" + controller_ip + \
376           ":8181/restconf/config/odl-fib:fibEntries/"
377     logger.debug("Querring '%s' for FIB entries", url)
378     res = requests.get(url, auth=(ODL_USER, ODL_PASS))
379     if res.status_code != 200:
380         logger.error("OpenDaylight response status code: %s", res.status_code)
381         return False
382     logger.debug("Checking whether '%s' is in the OpenDaylight FIB"
383                  % controller_ip)
384     logger.debug("OpenDaylight FIB: \n%s" % res.text)
385     return ip in res.text
386
387
388 def run_odl_cmd(odl_node, cmd):
389     '''Run a command in the OpenDaylight Karaf shell
390
391     This is a bit flimsy because of shell quote escaping, make sure that
392     the cmd passed does not have any top level double quotes or this
393     function will break.
394
395     The /dev/null is used because client works, but outputs something
396     that contains "ERROR" and run_cmd doesn't like that.
397
398     '''
399     karaf_cmd = '/opt/opendaylight/bin/client "%s" 2>/dev/null' % cmd
400     return odl_node.run_cmd(karaf_cmd)
401
402
403 def wait_for_cloud_init(instance):
404     success = True
405     # ubuntu images take a long time to start
406     tries = 20
407     sleep_time = 30
408     while tries > 0:
409         instance_log = instance.get_console_output()
410         if "Failed to run module" in instance_log:
411             success = False
412             logger.error("Cloud init failed to run. Reason: %s",
413                          instance_log)
414             break
415         if re.search(r"Cloud-init v. .+ finished at" in instance_log):
416             success = True
417             break
418         time.sleep(sleep_time)
419         tries = tries - 1
420
421     if tries == 0:
422         logger.error("Cloud init timed out"
423                      ". Reason: %s",
424                      instance_log)
425         success = False
426
427     return success