From 0443951623b23abd9257876ab942443822427c57 Mon Sep 17 00:00:00 2001 From: Andrej Kozemcak Date: Thu, 24 Jan 2019 07:34:58 +0100 Subject: 1: Create Test enviroment. Change-Id: I79ea8d85312876af7fd389b4776f764c21345ff2 Signed-off-by: Andrej Kozemcak --- Makefile | 4 ++ test/conf/ietf-interfaces.xml | 14 +++++ test/conf/vpp.conf | 9 ++++ test/framewrok.py | 38 +++++++++++++ test/netopeer_controler.py | 70 ++++++++++++++++++++++++ test/run_test.py | 35 ++++++++++++ test/run_test.sh | 78 +++++++++++++++++++++++++++ test/test_ietf_interfaces.py | 80 +++++++++++++++++++++++++++ test/topology.py | 122 ++++++++++++++++++++++++++++++++++++++++++ test/util.py | 52 ++++++++++++++++++ test/vpp_controler.py | 67 +++++++++++++++++++++++ 11 files changed, 569 insertions(+) create mode 100644 test/conf/ietf-interfaces.xml create mode 100644 test/conf/vpp.conf create mode 100644 test/framewrok.py create mode 100644 test/netopeer_controler.py create mode 100755 test/run_test.py create mode 100755 test/run_test.sh create mode 100644 test/test_ietf_interfaces.py create mode 100644 test/topology.py create mode 100644 test/util.py create mode 100644 test/vpp_controler.py diff --git a/Makefile b/Makefile index 71b086a..079ba4a 100644 --- a/Makefile +++ b/Makefile @@ -72,6 +72,7 @@ help: @echo " build-plugins - build plugins" @echo " build-package - build rpm or deb package" @echo " docker - build sweetcomb in docker enviroment" + @echo " docker_test - run test in docker enviroment" @echo " clean - clean all build" @echo " distclean - remove all build directory" $(BR)/.deps.ok: @@ -203,6 +204,9 @@ build-plugins: docker: @build-root/scripts/docker.sh +docker_test: + @test/run_test.sh + build-package: @mkdir -p $(BR)/build-scvpp/;cd $(BR)/build-scvpp;cmake -DCMAKE_INSTALL_PREFIX:PATH=/usr $(WS_ROOT)/src/scvpp/;make install; @mkdir -p $(BR)/build-package/;cd $(BR)/build-package/;$(cmake) $(WS_ROOT)/src/;make package;rm -rf $(BR)/build-package/_CPack_Packages; diff --git a/test/conf/ietf-interfaces.xml b/test/conf/ietf-interfaces.xml new file mode 100644 index 0000000..3c8aa07 --- /dev/null +++ b/test/conf/ietf-interfaces.xml @@ -0,0 +1,14 @@ + + + local0 + Ethernet 0 + ianaift:ethernetCsmacd + false + + + host-vpp1 + Ethernet 1 + ianaift:ethernetCsmacd + false + + diff --git a/test/conf/vpp.conf b/test/conf/vpp.conf new file mode 100644 index 0000000..92d5241 --- /dev/null +++ b/test/conf/vpp.conf @@ -0,0 +1,9 @@ +unix { + log /var/log/vpp/vpp.log + cli-listen /run/vpp/cli.sock +} + +plugins { + plugin dpdk_plugin.so { disable } +} + diff --git a/test/framewrok.py b/test/framewrok.py new file mode 100644 index 0000000..9fbf240 --- /dev/null +++ b/test/framewrok.py @@ -0,0 +1,38 @@ +# +# Copyright (c) 2019 PANTHEON.tech. +# +# 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. +# + +import unittest + +from topology import Topology + +class SweetcombTestCase(unittest.TestCase): + + @classmethod + def instance(cls): + """Return the instance of this testcase""" + + return cls.instance + + @classmethod + def create_topoly(cls): + + cls.topology = Topology() + cls.topology.create_topology() + + cls.vpp = cls.topology.get_vpp() + cls.netopeer_cli = cls.topology.get_netopeer_cli() + + diff --git a/test/netopeer_controler.py b/test/netopeer_controler.py new file mode 100644 index 0000000..74c7dc6 --- /dev/null +++ b/test/netopeer_controler.py @@ -0,0 +1,70 @@ +# +# Copyright (c) 2019 PANTHEON.tech. +# +# 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. +# + +import pexpect + +class Netopeer_controler: + def __init__(self): + self.name = "netopeer2-cli" + self.password = "0000" + + def __del__(self): + self.kill() + + def kill(self): + if self.child is None: + return + + self.child.sendline("exit\r") + self.child.logfile.close() + #self.child.kill() + self.child = None + + def terminate(self): + self.kill() + + def set_password(self, password): + self.password = password; + + def spawn(self): + self.child = pexpect.spawn(self.name) + self.child.logfile = open('/var/log/Netopeer_controler.log', 'wb') + self.pid = self.child.pid + self.child.expect(">") + self.child.sendline("connect\r") + i = self.child.expect(["Password:", "Are you sure you want to continue connecting (yes/no)?"]) + if 0 == i: + self.child.sendline(self.password + '\r') + elif 1 == i: + self.child.sendline("yes\r") + self.child.expect("Password:") + self.child.sendline(self.password + '\r') + + self.child.expect(">") + + def get(self, msg): + self.child.sendline("get --filter-xpath {}\r".format(msg)) + self.child.expect("> ") + print(self.child.before.decode('ascii')) + + def edit_config(self, msg): + f = open("/tmp/tmp_example.xml", "w") + f.write(msg) + f.close() + + self.child.sendline("edit-config --target running --config=/tmp/tmp_example.xml\r") + self.child.expect("> ") + print(self.child.before.decode('ascii')) diff --git a/test/run_test.py b/test/run_test.py new file mode 100755 index 0000000..e3190c4 --- /dev/null +++ b/test/run_test.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2019 PANTHEON.tech. +# +# 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. +# +# + +import unittest +import util + +from framewrok import SweetcombTestCase +from test_ietf_interfaces import TestIetfInterfaces + +def suite(): + suite = unittest.TestSuite() + suite.addTest(TestIetfInterfaces('test_interface_up')) + suite.addTest(TestIetfInterfaces('test_ip_addr')) + return suite + +if __name__ == '__main__': + util.import_yang_modules() + runner = unittest.TextTestRunner() + runner.run(suite()) + diff --git a/test/run_test.sh b/test/run_test.sh new file mode 100755 index 0000000..aa28773 --- /dev/null +++ b/test/run_test.sh @@ -0,0 +1,78 @@ +#!/bin/bash + +# Copyright (c) 2019 PANTHEON.tech. +# +# 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. + + +IMAGE="sweetcomb_img" +CONTAINER="sweetcomb_test" + +function build_enviroment { + FIND=`docker images | grep ${IMAGE}` + + if [ -n "${FIND}" ]; then + return + fi + + docker build -t ${IMAGE} ./src/Docker/Build +} + +function create_container { + docker run -id --privileged --name ${CONTAINER} \ + -v $(pwd)/../sweetcomb:/root/src/sweetcomb ${IMAGE} +} + +function start_container { + FIND=`docker container ls -a | grep ${CONTAINER}` + + if [ -z "${FIND}" ]; then + create_container + else + FIND=`docker container ps | grep ${CONTAINER}` + if [ -z "${FIND}" ]; then + docker start ${CONTAINER} + fi + fi +} + +function build_sweetcomb { + docker exec -it ${CONTAINER} bash -c "/root/src/sweetcomb/build-root/scripts/de_build.sh" + docker exec -it ${CONTAINER} bash -c "apt-get install -y python3-pip && pip3 install pexpect && pip3 install pyroute2" +} + +function main { + build_enviroment + start_container + build_sweetcomb + + docker exec -it ${CONTAINER} bash -c " + + apt-get install -y python3-pip + + pip3 install pyroute2 pexpect psutil + + echo -e \"0000\n0000\" | passwd + " + + + docker exec -it ${CONTAINER} bash -c "/root/src/sweetcomb/test/run_test.py" + + echo "Wait 20s, then remove the container." + sleep 20 + + docker stop ${CONTAINER} + docker container rm ${CONTAINER} +} + +main $@ diff --git a/test/test_ietf_interfaces.py b/test/test_ietf_interfaces.py new file mode 100644 index 0000000..55ef029 --- /dev/null +++ b/test/test_ietf_interfaces.py @@ -0,0 +1,80 @@ +# +# Copyright (c) 2019 PANTHEON.tech. +# +# 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. +# + +import unittest + +import util +from framewrok import SweetcombTestCase + +class TestIetfInterfaces(SweetcombTestCase): + + def setUp(self): + super(TestIetfInterfaces, self).setUp() + + self.create_topoly() + + def tearDown(self): + super(TestIetfInterfaces, self).setUp() + + self.topology.close_topology() + + def test_interface_up(self): + + self.vpp.show_interface() + + self.netopeer_cli.get("/ietf-interfaces:*") + ts = ''' + + local0 + true + + + host-vpp1 + true + +''' + + self.netopeer_cli.edit_config(ts) + self.netopeer_cli.get("/ietf-interfaces:*") + + self.vpp.show_interface() + + def test_ip_addr(self): + self.vpp.show_address() + self.netopeer_cli.get("/ietf-interfaces:*") + + util.ping('192.168.0.1') + + ts = ''' + + host-vpp1 + true + + true + + 192.168.0.1 + 24 + + + +''' + + self.netopeer_cli.edit_config(ts) + self.netopeer_cli.get("/ietf-interfaces:*") + + self.vpp.show_address() + + util.ping('192.168.0.1') diff --git a/test/topology.py b/test/topology.py new file mode 100644 index 0000000..43f15e3 --- /dev/null +++ b/test/topology.py @@ -0,0 +1,122 @@ +# +# Copyright (c) 2019 PANTHEON.tech. +# +# 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. +# + +import os +import subprocess +from netopeer_controler import Netopeer_controler +from vpp_controler import Vpp_controler +from socket import AF_INET +from pyroute2 import IPRoute +import psutil +import time + +class Topology: + def __init__(self): + self.process = [] + + def __del__(self): + self._kill_process() + + def _kill_process(self): + if not self.process: + return + + for process in self.process: + process.terminate() + + for proc in psutil.process_iter(attrs=['pid', 'name']): + name = proc.info['name'] + if 'vpp' in name or 'sysrepo' in name or 'netopeer' in name: + proc.kill() + + self.process = [] + + def _prepare_linux_enviroment(self): + ip = IPRoute() + exist = ip.link_lookup(ifname='vpp1') + if exist: + return + + ip.link('add', ifname='vpp1', peer='virtual1', kind='veth') + ip.link('add', ifname='vpp2', peer='virtual2', kind='veth') + + vpp1 = ip.link_lookup(ifname='virtual1')[0] + vpp2 = ip.link_lookup(ifname='virtual2')[0] + + ip.link('set', index=vpp1, state='up') + ip.link('set', index=vpp2, state='up') + + ip.addr('add', index=vpp1, address='192.168.0.2', prefixlen=24) + ip.addr('add', index=vpp2, address='192.168.1.2', prefixlen=24) + + + def _start_sysrepo(self): + print("Start sysrepo deamon.") + #TODO: Need property close. + err = open("/var/log/sysrepod", 'wb') + self.sysrepo = subprocess.Popen(["sysrepod", "-d", "-l 3"], + stdout=subprocess.PIPE, stderr=err) + self.process.append(self.sysrepo) + + def _start_sysrepo_plugins(self): + print("Start sysrepo plugins.") + #TODO: Need property close. + err = open("/var/log/sysrepo-plugind", 'wb') + self.splugin = subprocess.Popen(["sysrepo-plugind", "-d", "-l 3"], + stdout=subprocess.PIPE, stderr=err) + self.process.append(self.splugin) + + def _start_netopeer_server(self): + print("Start netopeer server.") + self.netopeer_server = subprocess.Popen("netopeer2-server", + stdout=subprocess.PIPE) + self.process.append(self.netopeer_server) + + def _start_netopeer_cli(self): + print("Start netopeer client.") + self.netopeer_cli = Netopeer_controler() + self.process.append(self.netopeer_cli) + self.netopeer_cli.spawn() + + def _start_vpp(self): + print("Start VPP.") + self.vpp = Vpp_controler() + self.vpp.spawn() + self.process.append(self.vpp) + + def get_vpp(self): + return self.vpp + + def get_netopeer_cli(self): + return self.netopeer_cli + + def create_topology(self): + #try: + self._prepare_linux_enviroment() + self._start_vpp() + self._start_sysrepo() + time.sleep(1) + self._start_sysrepo_plugins() + self._start_netopeer_server() + + #Wait for netopeer server + time.sleep(1) + self._start_netopeer_cli() + #except: + #self._kill_process() + + def close_topology(self): + self._kill_process() diff --git a/test/util.py b/test/util.py new file mode 100644 index 0000000..6fe5427 --- /dev/null +++ b/test/util.py @@ -0,0 +1,52 @@ +# +# Copyright (c) 2019 PANTHEON.tech. +# +# 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. +# + +import subprocess +import os +import time + +def ping(ip): + subprocess.run("ping -c 4 " + ip, shell=True) + +def import_yang_modules(): + print("Import YANG modules to sysrepo.") + + directory = '/root/src/sweetcomb/' + os.chdir(directory + "src/plugins/yang/ietf/") + subprocess.run(["sysrepoctl", "--install", "-S", ".", + "--yang=./ietf-interfaces.yang"]) + + os.chdir(directory + "src/plugins/yang/openconfig/") + subprocess.run(["sysrepoctl", "--install", "-S", ".", + "--yang=./openconfig-interfaces.yang"]) + subprocess.run(["sysrepoctl", "--install", "-S", ".", + "--yang=./openconfig-if-ip.yang"]) + subprocess.run(["sysrepoctl", "--install", "-S", ".", + "--yang=./openconfig-local-routing.yang"]) + + time.sleep(5) + + os.chdir(directory + "test/conf/") + + print("Import configuration to sysrepo datastore.") + subprocess.run(["sysrepocfg", "--import=ietf-interfaces.xml", + "--datastore=startup", "--format=xml", "--leve=0", + "ietf-interfaces"]) + subprocess.run(["sysrepocfg", "--import=ietf-interfaces.xml", + "--datastore=running", "--format=xml", "--leve=0", + "ietf-interfaces"]) + os.chdir(directory) + time.sleep(2) diff --git a/test/vpp_controler.py b/test/vpp_controler.py new file mode 100644 index 0000000..561f5a0 --- /dev/null +++ b/test/vpp_controler.py @@ -0,0 +1,67 @@ +# +# Copyright (c) 2019 PANTHEON.tech. +# +# 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. +# + +import subprocess +import time +import psutil + +class Vpp_controler: + def __init__(self): + self.cmd = "vpp" + self.ccmd = "vppctl" + self.configuration = "/root/src/sweetcomb/test/conf/vpp.conf" + self.process = None + + def __del__(self): + #self.kill() + self.terminate() + + def _default_conf_vpp(self): + if self.process is None: + return + + subprocess.run(self.ccmd + " create host name vpp1", shell=True) + subprocess.run(self.ccmd + " create host name vpp2", shell=True) + + def spawn(self): + self.process = subprocess.Popen([self.cmd, "-c", self.configuration], + stdout=subprocess.PIPE) + time.sleep(4) + self._default_conf_vpp() + + def kill(self): + if self.process is None: + return + + self.process.kill() + self.process = None + + def terminate(self): + if self.process is None: + return + + self.process.terminate() + for proc in psutil.process_iter(attrs=['pid', 'name']): + if 'vpp' in proc.info['name']: + proc.kill() + + self.process = None + + def show_interface(self): + subprocess.run(self.ccmd + " show int", shell=True) + + def show_address(self): + subprocess.run(self.ccmd + " show int addr", shell=True) -- cgit 1.2.3-korg