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