a047269aa40f67d9ff07b6326ef018c382a28676
[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 won't 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 get_instance_ip(instance):
238     instance_ip = instance.networks.itervalues().next()[0]
239     return instance_ip
240
241
242 def wait_for_instance(instance):
243     logger.info("Waiting for instance %s to get a DHCP lease..." % instance.id)
244     # The sleep this function replaced waited for 80s
245     tries = 40
246     sleep_time = 2
247     pattern = "Lease of .* obtained, lease time"
248     expected_regex = re.compile(pattern)
249     console_log = ""
250     while tries > 0 and not expected_regex.search(console_log):
251         console_log = instance.get_console_output()
252         time.sleep(sleep_time)
253         tries -= 1
254
255     if not expected_regex.search(console_log):
256         logger.error("Instance %s seems to have failed leasing an IP."
257                      % instance.id)
258         return False
259     return True
260
261
262 def wait_for_instances_up(*args):
263     check = [wait_for_instance(instance) for instance in args]
264     return all(check)
265
266
267 def wait_for_bgp_net_assoc(neutron_client, bgpvpn_id, net_id):
268     tries = 30
269     sleep_time = 1
270     nets = []
271     logger.debug("Waiting for network %s to associate with BGPVPN %s "
272                  % (bgpvpn_id, net_id))
273
274     while tries > 0 and net_id not in nets:
275         nets = os_utils.get_bgpvpn_networks(neutron_client, bgpvpn_id)
276         time.sleep(sleep_time)
277         tries -= 1
278     if net_id not in nets:
279         logger.error("Association of network %s with BGPVPN %s failed" %
280                      (net_id, bgpvpn_id))
281         return False
282     return True
283
284
285 def wait_for_bgp_net_assocs(neutron_client, bgpvpn_id, *args):
286     check = [wait_for_bgp_net_assoc(neutron_client, bgpvpn_id, id)
287              for id in args]
288     # Return True if all associations succeeded
289     return all(check)
290
291
292 def wait_for_bgp_router_assoc(neutron_client, bgpvpn_id, router_id):
293     tries = 30
294     sleep_time = 1
295     routers = []
296     logger.debug("Waiting for router %s to associate with BGPVPN %s "
297                  % (bgpvpn_id, router_id))
298     while tries > 0 and router_id not in routers:
299         routers = os_utils.get_bgpvpn_routers(neutron_client, bgpvpn_id)
300         time.sleep(sleep_time)
301         tries -= 1
302     if router_id not in routers:
303         logger.error("Association of router %s with BGPVPN %s failed" %
304                      (router_id, bgpvpn_id))
305         return False
306     return True
307
308
309 def wait_for_bgp_router_assocs(neutron_client, bgpvpn_id, *args):
310     check = [wait_for_bgp_router_assoc(neutron_client, bgpvpn_id, id)
311              for id in args]
312     # Return True if all associations succeeded
313     return all(check)
314
315
316 def wait_before_subtest(*args, **kwargs):
317     ''' This is a placeholder.
318         TODO: Replace delay with polling logic. '''
319     time.sleep(30)
320
321
322 def assert_and_get_compute_nodes(nova_client, required_node_number=2):
323     """Get the compute nodes in the deployment
324
325     Exit if the deployment doesn't have enough compute nodes"""
326     compute_nodes = os_utils.get_hypervisors(nova_client)
327
328     num_compute_nodes = len(compute_nodes)
329     if num_compute_nodes < 2:
330         logger.error("There are %s compute nodes in the deployment. "
331                      "Minimum number of nodes to complete the test is 2."
332                      % num_compute_nodes)
333         sys.exit(-1)
334
335     logger.debug("Compute nodes: %s" % compute_nodes)
336     return compute_nodes
337
338
339 def open_icmp_ssh(neutron_client, security_group_id):
340     os_utils.create_secgroup_rule(neutron_client,
341                                   security_group_id,
342                                   'ingress',
343                                   'icmp')
344     os_utils.create_secgroup_rule(neutron_client,
345                                   security_group_id,
346                                   'tcp',
347                                   80, 80)
348
349
350 def open_bgp_port(neutron_client, security_group_id):
351     os_utils.create_secgroup_rule(neutron_client,
352                                   security_group_id,
353                                   'tcp',
354                                   179, 179)
355
356
357 def exec_cmd(cmd, verbose):
358     success = True
359     logger.debug("Executing '%s'" % cmd)
360     p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
361                          stderr=subprocess.STDOUT)
362     output = ""
363     for line in iter(p.stdout.readline, b''):
364         output += line
365
366     if verbose:
367         logger.debug(output)
368
369     p.stdout.close()
370     returncode = p.wait()
371     if returncode != 0:
372         logger.error("Command %s failed to execute." % cmd)
373         success = False
374
375     return output, success
376
377
378 def check_odl_fib(ip, controller_ip):
379     """Check that there is an entry in the ODL Fib for `ip`"""
380     url = "http://" + controller_ip + \
381           ":8181/restconf/config/odl-fib:fibEntries/"
382     logger.debug("Querring '%s' for FIB entries", url)
383     res = requests.get(url, auth=(ODL_USER, ODL_PASS))
384     if res.status_code != 200:
385         logger.error("OpenDaylight response status code: %s", res.status_code)
386         return False
387     logger.debug("Checking whether '%s' is in the OpenDaylight FIB"
388                  % controller_ip)
389     logger.debug("OpenDaylight FIB: \n%s" % res.text)
390     return ip in res.text
391
392
393 def run_odl_cmd(odl_node, cmd):
394     '''Run a command in the OpenDaylight Karaf shell
395
396     This is a bit flimsy because of shell quote escaping, make sure that
397     the cmd passed does not have any top level double quotes or this
398     function will break.
399
400     The /dev/null is used because client works, but outputs something
401     that contains "ERROR" and run_cmd doesn't like that.
402
403     '''
404     karaf_cmd = '/opt/opendaylight/bin/client "%s" 2>/dev/null' % cmd
405     return odl_node.run_cmd(karaf_cmd)
406
407
408 def wait_for_cloud_init(instance):
409     success = True
410     # ubuntu images take a long time to start
411     tries = 20
412     sleep_time = 30
413     while tries > 0:
414         instance_log = instance.get_console_output()
415         if "Failed to run module" in instance_log:
416             success = False
417             logger.error("Cloud init failed to run. Reason: %s",
418                          instance_log)
419             break
420         if re.search(r"Cloud-init v. .+ finished at" in instance_log):
421             success = True
422             break
423         time.sleep(sleep_time)
424         tries = tries - 1
425
426     if tries == 0:
427         logger.error("Cloud init timed out"
428                      ". Reason: %s",
429                      instance_log)
430         success = False
431
432     return success