blob: 5d1fc21ac1a5f30694703ddac733b3b93b7afc28 [file] [log] [blame]
#!/usr/bin/env python
#
# Copyright (c) 2017 Nest Labs, Inc.
# All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
#
# @file
# Tests the following scenarios:
# 1. Fault injection tests between the Border Gateway and Tunnel
# Front End with faults limited to tunnel establishment and
# teardown interactions. weave-ping is used to test tunnel
# connectivity while faults are injected.
#
import itertools
import os
import unittest
import set_test_path
import time
import json
import getopt
import sys
from happy.Utils import *
import happy.HappyNodeList
import plugins.weave.WeaveStateLoad as WeaveStateLoad
import plugins.weave.WeaveStateUnload as WeaveStateUnload
import plugin.WeavePing as WeavePing
import plugin.WeaveTunnelStart as WeaveTunnelStart
import plugin.WeaveTunnelStop as WeaveTunnelStop
import plugin.WeaveUtilities as WeaveUtilities
from plugin.WeaveTest import WeaveTest
import plugins.plaid.Plaid as Plaid
gFaultopts = WeaveUtilities.FaultInjectionOptions(nodes=["gateway", "service"])
gOptions = { 'case': True, "num_pings": "3", "restart": True }
class test_weave_tunnel_faults(unittest.TestCase):
def setUp(self):
self.tap = None
if "WEAVE_SYSTEM_CONFIG_USE_LWIP" in os.environ.keys() and os.environ["WEAVE_SYSTEM_CONFIG_USE_LWIP"] == "1":
self.topology_file = os.path.dirname(os.path.realpath(__file__)) + \
"/../topologies/thread_wifi_on_tap_ap_service.json"
self.tap = "wpan0"
else:
self.topology_file = os.path.dirname(os.path.realpath(__file__)) + \
"/../topologies/thread_wifi_ap_service.json"
self.show_strace = False
# setting Mesh for thread test
options = WeaveStateLoad.option()
options["json_file"] = self.topology_file
setup_network = WeaveStateLoad.WeaveStateLoad(options)
ret = setup_network.run()
# Wait for a second to ensure that Weave ULA addresses passed dad
# and are no longer tentative
time.sleep(2)
# set up Plaid for faster execution
plaid_opts = Plaid.default_options()
plaid_opts['num_clients'] = 3
plaid_opts['strace'] = self.show_strace
self.plaid = Plaid.Plaid(plaid_opts)
self.use_plaid = self.plaid.isPlaidConfigured()
def tearDown(self):
# cleaning up
options = WeaveStateUnload.option()
options["json_file"] = self.topology_file
teardown_network = WeaveStateUnload.WeaveStateUnload(options)
teardown_network.run()
def test_weave_tunnel(self):
# TODO: Once LwIP bugs are fix, enable this test on LwIP
if "WEAVE_SYSTEM_CONFIG_USE_LWIP" in os.environ.keys() and os.environ["WEAVE_SYSTEM_CONFIG_USE_LWIP"] == "1":
return
# topology has nodes: ThreadNode, BorderRouter, onhub and cloud
# we run tunnel between BorderRouter and cloud
use_case = gOptions["case"]
if self.use_plaid:
self.__start_plaid_server()
test_tag = "_HAPPY_SEQUENCE"
# Start tunnel
value_start, start_tunnel_data = self.__start_tunnel_between("BorderRouter", "cloud", use_case=use_case, test_tag=test_tag, use_plaid=self.use_plaid)
# Run ping test
for i in range(10):
print " running weave-ping between 'ThreadNode' and 'cloud' (iteration:%s/10)" %(str(i+1))
value_ping, data_ping = self.__run_ping_test_between("ThreadNode", "cloud", use_case=False, test_tag = test_tag, use_plaid=self.use_plaid)
if not value_ping: # 0 == SUCCESS:
# we're done, no need for more iterations.
break
# Stop tunnel
value_stop, stop_tunnel_data = self.__stop_tunnel_between("BorderRouter", "cloud", test_tag=test_tag)
if self.use_plaid:
self.__stop_plaid_server()
output_logs = {}
output_logs['gateway'] = stop_tunnel_data['gateway_output']
output_logs['service'] = stop_tunnel_data['service_output']
num_tests = 0
num_failed_tests = 0
failed_tests = []
for node in gFaultopts.nodes:
restart = gOptions["restart"]
if node == "service":
# TODO: due to a bug in mock-tunnel-service, tunnel data messages are not processed
# after restart. Hence, all restart cases fail. Re-enable after fixing
# mock-tunnel-service.
restart = False
fault_configs = gFaultopts.generate_fault_config_list(node, output_logs[node], restart)
for fault_config in fault_configs:
auth = "CASE" if use_case else "None"
test_tag = "_" + auth + "_" + str(num_tests) + "_" + node + "_" + fault_config
print "tag: " + test_tag
if self.use_plaid:
self.__start_plaid_server()
# Start tunnel
print " starting tunnel between 'BorderRouter' and 'cloud'"
value_start, data_start_tunnel = self.__start_tunnel_between("BorderRouter", "cloud", use_case=use_case, num_iterations = 3, faults = {node: fault_config}, test_tag = test_tag, use_plaid=self.use_plaid)
for i in range(15):
# Note that when running on plaid this loop only executes ~3 iterations in what is a minute of virtual time
# Without plaid, the number of iterations required is much more
print " running weave-ping between 'ThreadNode' and 'cloud' (iteration:%s/15)" %(str(i+1))
value_ping, data_ping = self.__run_ping_test_between("ThreadNode", "cloud", use_case=False, test_tag = test_tag, use_plaid=self.use_plaid)
if not value_ping: # 0 == SUCCESS:
# we're done, no need for more iterations.
break
# if tunnel goes down after ping succeeds, give it up to 15s to recover.
wt = WeaveTest()
for i in range(15):
gw_value, gw_output = wt.get_test_output("BorderRouter", "WEAVE-GATEWAY-TUNNEL" + test_tag, quiet=True)
if self.__is_tunnel_up(gw_output):
break
print " *** waiting for tunnel to recover *** (%s/15 seconds)" %(str(i+1))
time.sleep(1)
# Stop tunnel
print " stopping tunnel between 'BorderRouter' and 'cloud'"
value_stop, data_stop_tunnel = self.__stop_tunnel_between("BorderRouter", "cloud", test_tag = test_tag)
if self.use_plaid:
self.__stop_plaid_server()
results = self.__process_tunnel_results("BorderRouter", value_stop, data_stop_tunnel, value_ping, data_ping, test_tag=test_tag)
success = reduce(lambda x, y: x and y, results.values())
if not success:
print hred(" Failed + [" + test_tag + "]")
num_failed_tests += 1
failed_tests.append(test_tag)
else:
print hgreen(" Passed [" + test_tag + "]")
num_tests += 1
print " executed %d cases" % num_tests
print " failed %d cases:" % num_failed_tests
if num_failed_tests > 0:
for failed in failed_tests:
print " " + failed
self.assertEqual(num_failed_tests, 0, "The above tests failed")
def __start_tunnel_between(self, gateway, service, use_case=False, num_iterations=1, faults={}, test_tag="", block_until_tunnel_up=False, use_plaid=False):
options = WeaveTunnelStart.option()
options["border_gateway"] = gateway
options["service"] = service
options["case"] = use_case
options["case_cert_path"] = "default"
options["tap"] = self.tap
options["gateway_faults"] = faults.get("gateway")
options["service_faults"] = faults.get("service")
options["iterations"] = num_iterations
options["test_tag"] = test_tag
if use_plaid:
options["plaid_gateway_env"] = self.plaid.getPlaidClientLibEnv(gateway)
options["plaid_service_env"] = self.plaid.getPlaidClientLibEnv(service)
if block_until_tunnel_up:
# wait for tunnel to be established before saying you're done
options["sync_on_gateway_output"] = "ToState:PrimaryTunnelEstablished"
weave_tunnel = WeaveTunnelStart.WeaveTunnelStart(options)
ret = weave_tunnel.run()
value = ret.Value()
data = ret.Data()
return value, data
def __run_ping_test_between(self, nodeA, nodeB, use_case=False, test_tag="", use_plaid=False):
options = WeavePing.option()
options["clients"] = [nodeA]
options["server"] = nodeB
options["udp"] = True
options["endpoint"] = "Tunnel"
options["count"] = gOptions["num_pings"]
options["tap"] = self.tap
options["case"] = use_case
options["case_cert_path"] = "default"
options["test_tag"] = test_tag
if use_plaid:
options["plaid_client_env"] = self.plaid.getPlaidClientLibEnv(nodeA)
options["plaid_server_env"] = self.plaid.getPlaidClientLibEnv(nodeB)
weave_ping = WeavePing.WeavePing(options)
ret = weave_ping.run()
value = ret.Value()
data = ret.Data()
return value, data
def __process_ping_result(self, nodeA, nodeB, value, data):
num_pings = int(gOptions["num_pings"])
print "ping from " + nodeA + " to " + nodeB + " ",
data = data[0]
if value > num_pings + 1:
print hred("Failed")
else:
print hgreen("Passed")
try:
self.assertTrue(value < num_pings + 1, "%s < %s %%" % (str(value), num_pings))
except AssertionError, e:
print str(e)
print "Captured experiment result:"
print "Client Output: "
for line in data["client_output"].split("\n"):
print "\t" + line
print "Server Output: "
for line in data["server_output"].split("\n"):
print "\t" + line
if self.show_strace == True:
print "Server Strace: "
for line in data["server_strace"].split("\n"):
print "\t" + line
print "Client Strace: "
for line in data["client_strace"].split("\n"):
print "\t" + line
if value > num_pings + 1:
raise ValueError("Weave Ping over Weave Tunnel Failed")
def __process_tunnel_results(self, gw, gw_value, gw_data, ping_value, ping_data, test_tag):
gw_output = gw_data.get("gateway_output")
service_output = gw_data.get("service_output")
tunnel_up = self.__is_tunnel_up(gw_output)
gw_parser_error, gw_leak_detected = WeaveUtilities.scan_for_leaks_and_parser_errors(gw_output)
service_parser_error, service_leak_detected = WeaveUtilities.scan_for_leaks_and_parser_errors(service_output)
# at the end of the test, 'tunnel_up' should be True
if not tunnel_up:
print hred(" Tunnel start: Failed [" + test_tag + "]")
if ping_value != 0: # loss_percentage == 0
print hred(" Ping test: Failed [" + test_tag + "]")
if gw_parser_error is True:
print hred(" gateway parser error [" + test_tag + "]")
if gw_leak_detected is True:
print hred(" gateway leak detected [" + test_tag + "]+")
if service_parser_error is True:
print hred("service parser error")
if service_leak_detected is True:
print hred("service resource leak detected")
result = {}
result["tunnel_up"] = tunnel_up
result["ping_successful"] = ping_value == 0 #loss_percentage == 0
result["no_gw_parser_error"] = not gw_parser_error
result["no_gw_leak_detected"] = not gw_leak_detected
result["no_service_parser_error"] = not service_parser_error
result["no_service_leak_detected"] = not service_leak_detected
return result
def __is_tunnel_up(self, gw_output):
tunnel_up = False
post_fault_section = False
if not gw_output:
return False
gw_output = gw_output.split('\n')
for line in gw_output:
if "SIGUSR1 received: proceed to exit gracefully" in line:
break
if "***** Injecting fault" in line:
post_fault_section = True
if "ToState:PrimaryTunnelEstablished" in line:
tunnel_up = True
if "FromState:PrimaryTunnelEstablished" in line and "ToState:PrimaryTunnelEstablished" not in line:
tunnel_up = False
if "WEAVE:WT: Connection ABORTED" in line:
tunnel_up = False
if "WEAVE:WT: Tunnel connection not up" in line:
tunnel_up = False
return tunnel_up
def __stop_tunnel_between(self, gateway, service, num_iterations=1, faults={}, test_tag=""):
options = WeaveTunnelStop.option()
options["border_gateway"] = gateway
options["service"] = service
options["gateway_faults"] = faults.get("gateway")
options["service_faults"] = faults.get("service")
options["iterations"] = num_iterations
options["test_tag"] = test_tag
options["sync_on_gateway_output"] = "ToState:PrimaryTunnelEstablished"
weave_tunnel = WeaveTunnelStop.WeaveTunnelStop(options)
ret = weave_tunnel.run()
value = ret.Value()
data = ret.Data()
return value, data
def __start_plaid_server(self):
self.plaid.startPlaidServerProcess()
emsg = "plaid-server should be running."
print "test_weave_tunnel_faults: %s" % (emsg)
def __stop_plaid_server(self):
self.plaid.stopPlaidServerProcess()
emsg = "plaid-server should not be running any longer."
print "test_weave_tunnel_faults: %s" % (emsg)
if __name__ == "__main__":
help_str = """usage:
--help Print this usage info and exit
--num_pings N Number of echo packets sent during ping test (default: 3)
--disable-case Turn off CASE (CASE is enabled by default)\n"""
help_str += gFaultopts.help_string
longopts = ["help", "num_pings=","disable-case", "no-restart"]
longopts.extend(gFaultopts.getopt_config)
try:
opts, args = getopt.getopt(sys.argv[1:], "h", longopts)
except getopt.GetoptError as err:
print help_str
print hred(str(err))
sys.exit(hred("%s: Failed to parse arguments." % (__file__)))
opts = gFaultopts.process_opts(opts)
for o, a in opts:
if o in ("-h", "--help"):
print help_str
sys.exit(0)
elif o in ("--disable-case"):
gOptions["case"] = False
elif o in ("--num_pings"):
gOptions["num_pings"] = a
elif o in ("--no-restart"):
gOptions["restart"] = False
sys.argv = [sys.argv[0]]
WeaveUtilities.run_unittest()