From 831bfd57e9d046d38fb59d60c3c813eca7878d60 Mon Sep 17 00:00:00 2001 From: pmikus Date: Tue, 12 Apr 2016 09:58:59 +0200 Subject: [PATCH 01/16] Bootstrap verify perfomance fix - fix test tags to lowercase Change-Id: I6e01a677f15843105106300097b4d0b23e2cba5d Signed-off-by: pmikus --- bootstrap-verify-perf.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap-verify-perf.sh b/bootstrap-verify-perf.sh index c16240821c..70b235a6d9 100755 --- a/bootstrap-verify-perf.sh +++ b/bootstrap-verify-perf.sh @@ -81,7 +81,7 @@ if [ ! -z "$TEST_TAG" ]; then # run specific performance tests by tag if variable is set pybot -L TRACE \ -v TOPOLOGY_PATH:${WORKING_TOPOLOGY} \ - -i $TEST_TAG tests/ + -i "${TEST_TAG,,}" tests/ else # run full performance test suite pybot -L TRACE \ -- 2.16.6 From b5a17c8d29868a0521447176845d01d293ea585a Mon Sep 17 00:00:00 2001 From: pmikus Date: Tue, 12 Apr 2016 12:21:53 +0200 Subject: [PATCH 02/16] Bootstrap verify performance fix - fix installation directory create and remove Change-Id: I0acd47b886e869958a2075889af3bbe166d7808e Signed-off-by: pmikus --- bootstrap-verify-perf.sh | 22 ++++++++++++---------- resources/tools/topo_installation.py | 34 ++++++++++++++++++++++++---------- 2 files changed, 36 insertions(+), 20 deletions(-) diff --git a/bootstrap-verify-perf.sh b/bootstrap-verify-perf.sh index 70b235a6d9..a4d519441f 100755 --- a/bootstrap-verify-perf.sh +++ b/bootstrap-verify-perf.sh @@ -19,6 +19,7 @@ TOPOLOGIES="topologies/available/lf_testbed2-710-520.yaml" # Reservation dir RESERVATION_DIR="/tmp/reservation_dir" +INSTALLATION_DIR="/tmp/install_dir" # Jenkins VPP deb paths (convert to full path) VPP_DEBS="$( readlink -f $@ | tr '\n' ' ' )" @@ -59,24 +60,25 @@ while :; do sleep ${SLEEP_TIME} done +function cancel_all { + python ${CUR_DIR}/resources/tools/topo_installation.py -c -t $1 + python ${CUR_DIR}/resources/tools/topo_reservation.py -c -t $1 +} + +# On script exit we cancel the reservation and installation and delete all vpp +# packages +trap "cancel_all ${WORKING_TOPOLOGY}" EXIT + python ${CUR_DIR}/resources/tools/topo_installation.py -t ${WORKING_TOPOLOGY} \ - -d ${RESERVATION_DIR} \ + -d ${INSTALLATION_DIR} \ -p ${VPP_DEBS} if [ $? -eq 0 ]; then echo "VPP Installed on hosts from: ${WORKING_TOPOLOGY}" else echo "Failed to copy vpp deb files to DUTs" - exit $? + exit 1 fi -function cancel_reservation { - python ${CUR_DIR}/resources/tools/topo_reservation.py -c -t $1 -} - -# On script exit we cancel the reservation and delete all vpp packages -trap "cancel_reservation ${WORKING_TOPOLOGY}" EXIT - - if [ ! -z "$TEST_TAG" ]; then # run specific performance tests by tag if variable is set pybot -L TRACE \ diff --git a/resources/tools/topo_installation.py b/resources/tools/topo_installation.py index 8b596e1390..1dc818f113 100755 --- a/resources/tools/topo_installation.py +++ b/resources/tools/topo_installation.py @@ -30,12 +30,15 @@ def main(): help="Topology file") parser.add_argument("-d", "--directory", required=True, help="Installation directory") - parser.add_argument("-p", "--packages", required=True, nargs='+', + parser.add_argument("-p", "--packages", required=True, nargs='*', help="Packages paths to copy") + parser.add_argument("-c", "--cancel", help="Cancel installation", + action="store_true") args = parser.parse_args() topology_file = args.topo packages = args.packages install_dir = args.directory + cancel_installation = args.cancel work_file = open(topology_file) topology = load(work_file.read())['nodes'] @@ -45,16 +48,27 @@ def main(): ssh = SSH() ssh.connect(topology[node]) - # Copy packages from local path to installation dir - for deb in packages: - ssh.scp(local_path=deb, remote_path=install_dir) + if cancel_installation: + ret, _, err = ssh.exec_command("rm -r {}".format(install_dir)) + if ret != 0: + print "Cancel unsuccessful:\n{}".format(err) + return ret + else: + ret, _, err = ssh.exec_command("mkdir {}".format(install_dir)) + if ret != 0: + print "Mkdir unsuccessful:\n{}".format(err) + return ret - # Installation of VPP deb packages - ret, _, err = ssh.exec_command_sudo( - "dpkg -i {}/*.deb".format(install_dir)) - if ret != 0: - print "Installation unsuccessful:\n{}".format(err) - return ret + # Copy packages from local path to installation dir + for deb in packages: + ssh.scp(local_path=deb, remote_path=install_dir) + + # Installation of VPP deb packages + ret, _, err = ssh.exec_command_sudo( + "dpkg -i {}/*.deb".format(install_dir)) + if ret != 0: + print "Installation unsuccessful:\n{}".format(err) + return ret if __name__ == "__main__": sys.exit(main()) -- 2.16.6 From 7db13c550198c1954052ce51aae1283b3f277d05 Mon Sep 17 00:00:00 2001 From: pmikus Date: Tue, 12 Apr 2016 12:34:02 +0200 Subject: [PATCH 03/16] Bootstrap verify performance fix - Bootstrap verify performance fix Change-Id: I8f77c26aef805f5223d03e2a0bc4a97f88195ed8 Signed-off-by: pmikus --- resources/tools/topo_installation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/tools/topo_installation.py b/resources/tools/topo_installation.py index 1dc818f113..f445a965cf 100755 --- a/resources/tools/topo_installation.py +++ b/resources/tools/topo_installation.py @@ -30,7 +30,7 @@ def main(): help="Topology file") parser.add_argument("-d", "--directory", required=True, help="Installation directory") - parser.add_argument("-p", "--packages", required=True, nargs='*', + parser.add_argument("-p", "--packages", required=False, nargs='+', help="Packages paths to copy") parser.add_argument("-c", "--cancel", help="Cancel installation", action="store_true") -- 2.16.6 From dbb26bdddde2556a78a596f236783480dc60bc0f Mon Sep 17 00:00:00 2001 From: pmikus Date: Tue, 12 Apr 2016 13:38:20 +0200 Subject: [PATCH 04/16] Boostrap verify installation directory removal - Boostrap verify installation directory removal Change-Id: I091240f8a3f7e5074cdc2f069837963e4c8bd192 Signed-off-by: pmikus --- bootstrap-verify-perf.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap-verify-perf.sh b/bootstrap-verify-perf.sh index a4d519441f..042d44ed4b 100755 --- a/bootstrap-verify-perf.sh +++ b/bootstrap-verify-perf.sh @@ -61,7 +61,7 @@ while :; do done function cancel_all { - python ${CUR_DIR}/resources/tools/topo_installation.py -c -t $1 + python ${CUR_DIR}/resources/tools/topo_installation.py -c -d ${INSTALLATION_DIR} -t $1 python ${CUR_DIR}/resources/tools/topo_reservation.py -c -t $1 } -- 2.16.6 From e300d155302d493e0f4cf36b20081a1653909521 Mon Sep 17 00:00:00 2001 From: selias Date: Wed, 6 Apr 2016 16:49:15 +0200 Subject: [PATCH 05/16] Add keywords for Honeycomb tests -add interface dump and bridge-domain dump to Vat command templates -add low level keywords to implement vat dump commands The resulting data dumps will be used in Honeycomb testing, to verify data retrieved through Honeycomb API. Change-Id: I2a913eaf23aa13d7223576220681821007672812 Signed-off-by: selias --- resources/libraries/python/InterfaceUtil.py | 71 +++++++++++++++++++++-------- resources/libraries/python/L2Util.py | 27 +++++++++++ resources/templates/vat/interface_dump.vat | 1 + resources/templates/vat/l2_bd_dump.vat | 1 + 4 files changed, 80 insertions(+), 20 deletions(-) create mode 100644 resources/templates/vat/interface_dump.vat create mode 100644 resources/templates/vat/l2_bd_dump.vat diff --git a/resources/libraries/python/InterfaceUtil.py b/resources/libraries/python/InterfaceUtil.py index 58af5aad75..25503c08df 100644 --- a/resources/libraries/python/InterfaceUtil.py +++ b/resources/libraries/python/InterfaceUtil.py @@ -108,29 +108,28 @@ class InterfaceUtil(object): :raises: RuntimeError if the timeout period value has elapsed. """ if_ready = False - with VatTerminal(node) as vat: - not_ready = [] - start = time() - while not if_ready: - out = vat.vat_terminal_exec_cmd('sw_interface_dump') - if time() - start > timeout: - for interface in out: - if interface.get('admin_up_down') == 1: - if interface.get('link_up_down') != 1: - logger.debug('{0} link-down'.format( - interface.get('interface_name'))) - raise RuntimeError('timeout, not up {0}'.format(not_ready)) - not_ready = [] + not_ready = [] + start = time() + while not if_ready: + out = InterfaceUtil.vpp_get_interface_data(node) + if time() - start > timeout: for interface in out: if interface.get('admin_up_down') == 1: if interface.get('link_up_down') != 1: - not_ready.append(interface.get('interface_name')) - if not not_ready: - if_ready = True - else: - logger.debug('Interfaces still in link-down state: {0}, ' - 'waiting...'.format(not_ready)) - sleep(1) + logger.debug('{0} link-down'.format( + interface.get('interface_name'))) + raise RuntimeError('timeout, not up {0}'.format(not_ready)) + not_ready = [] + for interface in out: + if interface.get('admin_up_down') == 1: + if interface.get('link_up_down') != 1: + not_ready.append(interface.get('interface_name')) + if not not_ready: + if_ready = True + else: + logger.debug('Interfaces still in link-down state: {0}, ' + 'waiting...'.format(not_ready)) + sleep(1) @staticmethod def vpp_nodes_interfaces_ready_wait(nodes, timeout=10): @@ -160,3 +159,35 @@ class InterfaceUtil(object): for node in nodes.values(): if node['type'] == NodeType.DUT: InterfaceUtil.vpp_node_interfaces_ready_wait(node, timeout) + + @staticmethod + def vpp_get_interface_data(node, interface=None): + """Get all interface data from a VPP node. If a name or + sw_interface_index is provided, return only data for the matching + interface. + :param node: VPP node to get interface data from. + :param interface: Numeric index or name string of a specific interface. + :type node: dict + :type interface: int or str + :return: List of dictionaries containing data for each interface, or a + single dictionary for the specified interface. + :rtype: list or dict + """ + with VatTerminal(node) as vat: + response = vat.vat_terminal_exec_cmd_from_template( + "interface_dump.vat") + + data = response[0] + + if interface is not None: + if isinstance(interface, basestring): + sw_if_index = Topology.get_interface_sw_index(node, interface) + else: + sw_if_index = interface + + for data_if in data: + if data_if["sw_if_index"] == sw_if_index: + + return data_if + + return data diff --git a/resources/libraries/python/L2Util.py b/resources/libraries/python/L2Util.py index 0d34ce3e41..724ec0cdce 100644 --- a/resources/libraries/python/L2Util.py +++ b/resources/libraries/python/L2Util.py @@ -18,6 +18,7 @@ from resources.libraries.python.topology import Topology from resources.libraries.python.VatExecutor import VatExecutor, VatTerminal from resources.libraries.python.ssh import exec_cmd_no_error + class L2Util(object): """Utilities for l2 configuration""" @@ -218,3 +219,29 @@ class L2Util(object): """ cmd = 'brctl delbr {0}'.format(br_name) exec_cmd_no_error(node, cmd, sudo=True) + + @staticmethod + def vpp_get_bridge_domain_data(node, bd_id=None): + """Get all bridge domain data from a VPP node. If a domain ID number is + provided, return only data for the matching bridge domain. + + :param node: VPP node to get bridge domain data from. + :param bd_id: Numeric ID of a specific bridge domain. + :type node: dict + :type bd_id: int + :return: List of dictionaries containing data for each bridge domain, or + a single dictionary for the specified bridge domain. + :rtype: list or dict + """ + with VatTerminal(node) as vat: + response = vat.vat_terminal_exec_cmd_from_template("l2_bd_dump.vat") + + data = response[0] + + if bd_id is not None: + for bridge_domain in data: + if bridge_domain["bd_id"] == bd_id: + + return bridge_domain + + return data diff --git a/resources/templates/vat/interface_dump.vat b/resources/templates/vat/interface_dump.vat new file mode 100644 index 0000000000..850c348f69 --- /dev/null +++ b/resources/templates/vat/interface_dump.vat @@ -0,0 +1 @@ +sw_interface_dump diff --git a/resources/templates/vat/l2_bd_dump.vat b/resources/templates/vat/l2_bd_dump.vat new file mode 100644 index 0000000000..6d0b9d0778 --- /dev/null +++ b/resources/templates/vat/l2_bd_dump.vat @@ -0,0 +1 @@ +bridge_domain_dump -- 2.16.6 From d9c35ed4fe07f506a8146cfc295d96049d5a76e9 Mon Sep 17 00:00:00 2001 From: Miroslav Miklus Date: Tue, 12 Apr 2016 14:54:12 +0200 Subject: [PATCH 06/16] documentation: long_xconnect test suite documentation Generate testdoc by using: python -m robot.testdoc long_xconnect.robot perf_long_xconnect.html Change-Id: I12d83de4abb1af4d94e6fd78d05c46018ee5d7d7 Signed-off-by: Miroslav Miklus --- tests/suites/performance/long_xconnect.robot | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/tests/suites/performance/long_xconnect.robot b/tests/suites/performance/long_xconnect.robot index 773bd7cf98..4d441601e5 100644 --- a/tests/suites/performance/long_xconnect.robot +++ b/tests/suites/performance/long_xconnect.robot @@ -19,10 +19,19 @@ | Suite Teardown | 3-node Performance Suite Teardown | Test Setup | Setup all DUTs before test | Test Teardown | Run Keyword If Test Failed | Show statistics on all DUTs -| Documentation | Throughput search suite (long running test suite based on RFC2544). +| Documentation | *Throughput search suite (based on RFC2544).* +| ... +| ... | Test suite uses 3-node topology TG - DUT1 - DUT2 - TG, with one link +| ... | between nodes. Traffic profile contain 2 L2 streams (1 stream per +| ... | direction). Packets contain Ethernet header, IPv4 header, +| ... | IP protocol=61 and random payload. Ethernet header MAC addresses are +| ... | matching MAC addresses of the TG node. *** Test Cases *** | Find NDR by using linear search and 64B frames through L2 cross connect in 3-node topology +| | [Documentation] +| | ... | Find throughput with non drop rate for 64B frames by using +| | ... | linear search starting at 5Mpps, stepping down with step of 0.1Mpps | | ${framesize}= | Set Variable | 64 | | ${start_rate}= | Set Variable | 5000000 | | ${step_rate}= | Set Variable | 100000 @@ -33,6 +42,9 @@ | | ... | 3-node-xconnect | ${min_rate} | ${max_rate} | Find NDR by using linear search and 1518B frames through L2 cross connect in 3-node topology +| | [Documentation] +| | ... | Find throughput with non drop rate for 1518B frames by using +| | ... | linear search starting at 812,743pps, stepping down with step of 10,000pps | | ${framesize}= | Set Variable | 1518 | | ${start_rate}= | Set Variable | 812743 | | ${step_rate}= | Set Variable | 10000 @@ -43,6 +55,9 @@ | | ... | 3-node-xconnect | ${min_rate} | ${max_rate} | Find NDR by using linear search and 9000B frames through L2 cross connect in 3-node topology +| | [Documentation] +| | ... | Find throughput with non drop rate for 9000B frames by using +| | ... | linear search starting at 138,580pps, stepping down with step 5,000pps | | ${framesize}= | Set Variable | 9000 | | ${start_rate}= | Set Variable | 138580 | | ${step_rate}= | Set Variable | 5000 -- 2.16.6 From 3121b691debad27fcea1c6e2031e4a2544e42fbf Mon Sep 17 00:00:00 2001 From: Tibor Date: Wed, 6 Apr 2016 13:33:42 +0200 Subject: [PATCH 07/16] Honeycomb setup and utils - re-implement HTTPCodes as IntEnum rather then dictionary - add methods to manipulate data using honeycomb - get, set, delete - change the name of url file from vpp_version.url to oper_vpp_version.url - improve checking of startup and shutdown state of honeycomb - PEP8 fixes - add docstrings in all modules and classes - move logging to the lowest possible level - improve logging in exceptions - add method exec_command_sudo_log to resources.libraries.python.ssh module Change-Id: I54e0c6b45313e3a3c11bafa475488ae2b1e605c2 Signed-off-by: Tibor Frank --- docs/honeycomb_url_files.rst | 22 ++ resources/libraries/python/HTTPRequest.py | 178 +++++++++------ resources/libraries/python/HoneycombSetup.py | 244 ++++++++++----------- resources/libraries/python/HoneycombUtil.py | 210 ++++++++++++++---- .../{vpp_version.url => oper_vpp_version.url} | 0 5 files changed, 412 insertions(+), 242 deletions(-) create mode 100644 docs/honeycomb_url_files.rst rename resources/templates/honeycomb/{vpp_version.url => oper_vpp_version.url} (100%) diff --git a/docs/honeycomb_url_files.rst b/docs/honeycomb_url_files.rst new file mode 100644 index 0000000000..4dfa10c516 --- /dev/null +++ b/docs/honeycomb_url_files.rst @@ -0,0 +1,22 @@ +# Copyright (c) 2016 Cisco and/or its affiliates. +# 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. + +Documentation to files used to store URLs to resources in Honeycomb. +==================================================================== + +A URL file is a text file encoded in utf-8 with a path to a resource in +Honeycomb. There is only one line in each file. + +The URL is stored without host and port with leading slash. There is no slash at +the end, e.g.: + /restconf/config/v3po:vpp/bridge-domains diff --git a/resources/libraries/python/HTTPRequest.py b/resources/libraries/python/HTTPRequest.py index 7b21f5a761..fd2925cec4 100644 --- a/resources/libraries/python/HTTPRequest.py +++ b/resources/libraries/python/HTTPRequest.py @@ -11,69 +11,100 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Implements HTTP requests GET, PUT, POST, DELETE used in communication with -honeycomb. +"""Implementation of HTTP requests GET, PUT, POST and DELETE used in +communication with Honeycomb. + +The HTTP requests are implemented in the class HTTPRequest which uses +requests.request. """ +from enum import IntEnum, unique + +from robot.api.deco import keyword +from robot.api import logger + from requests import request, RequestException, Timeout, TooManyRedirects, \ HTTPError, ConnectionError from requests.auth import HTTPBasicAuth -from robot.api import logger -from robot.api.deco import keyword - -HTTP_CODES = {"OK": 200, - "UNAUTHORIZED": 401, - "FORBIDDEN": 403, - "NOT_FOUND": 404, - "SERVICE_UNAVAILABLE": 503} +@unique +class HTTPCodes(IntEnum): + """HTTP status codes""" + OK = 200 + UNAUTHORIZED = 401 + FORBIDDEN = 403 + NOT_FOUND = 404 + SERVICE_UNAVAILABLE = 503 class HTTPRequestError(Exception): - """Exception raised by HTTPRequest objects.""" + """Exception raised by HTTPRequest objects. + + When raising this exception, put this information to the message in this + order: + - short description of the encountered problem, + - relevant messages if there are any collected, e.g., from caught + exception, + - relevant data if there are any collected. + The logging is performed on two levels: 1. error - short description of the + problem; 2. debug - detailed information. + """ - def __init__(self, msg, enable_logging=True): - """Sets the exception message and enables / disables logging + def __init__(self, msg, details='', enable_logging=True): + """Sets the exception message and enables / disables logging. It is not wanted to log errors when using these keywords together - with keywords like "Wait until keyword succeeds". + with keywords like "Wait until keyword succeeds". So you can disable + logging by setting enable_logging to False. - :param msg: Message to be displayed and logged + :param msg: Message to be displayed and logged. :param enable_logging: When True, logging is enabled, otherwise logging is disabled. :type msg: str :type enable_logging: bool """ super(HTTPRequestError, self).__init__() - self._msg = msg - self._repr_msg = self.__module__ + '.' + \ - self.__class__.__name__ + ": " + self._msg - + self._msg = "{0}: {1}".format(self.__class__.__name__, msg) + self._details = details if enable_logging: logger.error(self._msg) - logger.debug(self._repr_msg) + logger.debug(self._details) def __repr__(self): - return repr(self._repr_msg) + return repr(self._msg) def __str__(self): - return str(self._repr_msg) + return str(self._msg) class HTTPRequest(object): - """A class implementing HTTP requests.""" + """A class implementing HTTP requests GET, PUT, POST and DELETE used in + communication with Honeycomb. + + The communication with Honeycomb and processing of all exceptions is done in + the method _http_request which uses requests.request to send requests and + receive responses. The received status code and content of response are + logged on the debug level. + All possible exceptions raised by requests.request are also processed there. + + The other methods (get, put, post and delete) use _http_request to send + corresponding request. + + These methods must not be used as keywords in tests. Use keywords + implemented in the module HoneycombAPIKeywords instead. + """ def __init__(self): pass @staticmethod def create_full_url(ip_addr, port, path): - """Creates full url including IP, port, and path to data. + """Creates full url including host, port, and path to data. - :param ip_addr: Server IP - :param port: Communication port - :param path: Path to data + :param ip_addr: Server IP. + :param port: Communication port. + :param path: Path to data. :type ip_addr: str :type port: str or int :type path: str @@ -85,16 +116,16 @@ class HTTPRequest(object): @staticmethod def _http_request(method, node, path, enable_logging=True, **kwargs): - """Sends specified HTTP request and returns status code and - response content + """Sends specified HTTP request and returns status code and response + content. :param method: The method to be performed on the resource identified by - the given request URI - :param node: honeycomb node - :param path: URL path, e.g. /index.html - :param enable_logging: used to suppress errors when checking - honeycomb state during suite setup and teardown - :param kwargs: named parameters accepted by request.request: + the given request URI. + :param node: Honeycomb node. + :param path: URL path, e.g. /index.html. + :param enable_logging: Used to suppress errors when checking Honeycomb + state during suite setup and teardown. + :param kwargs: Named parameters accepted by request.request: params -- (optional) Dictionary or bytes to be sent in the query string for the Request. data -- (optional) Dictionary, bytes, or file-like object to @@ -127,11 +158,11 @@ class HTTPRequest(object): :return: Status code and content of response :rtype: tuple :raises HTTPRequestError: If - 1. it is not possible to connect - 2. invalid HTTP response comes from server - 3. request exceeded the configured number of maximum re-directions - 4. request timed out - 5. there is any other unexpected HTTP request exception + 1. it is not possible to connect, + 2. invalid HTTP response comes from server, + 3. request exceeded the configured number of maximum re-directions, + 4. request timed out, + 5. there is any other unexpected HTTP request exception. """ timeout = kwargs["timeout"] url = HTTPRequest.create_full_url(node['host'], @@ -141,29 +172,30 @@ class HTTPRequest(object): auth = HTTPBasicAuth(node['honeycomb']['user'], node['honeycomb']['passwd']) rsp = request(method, url, auth=auth, **kwargs) + + logger.debug("Status code: {0}".format(rsp.status_code)) + logger.debug("Response: {0}".format(rsp.content)) + return rsp.status_code, rsp.content except ConnectionError as err: # Switching the logging on / off is needed only for # "requests.ConnectionError" - if enable_logging: - raise HTTPRequestError("Not possible to connect to {0}\n". - format(url) + repr(err)) - else: - raise HTTPRequestError("Not possible to connect to {0}\n". - format(url) + repr(err), - enable_logging=False) + raise HTTPRequestError("Not possible to connect to {0}:{1}.". + format(node['host'], + node['honeycomb']['port']), + repr(err), enable_logging=enable_logging) except HTTPError as err: - raise HTTPRequestError("Invalid HTTP response from {0}\n". - format(url) + repr(err)) + raise HTTPRequestError("Invalid HTTP response from {0}.". + format(node['host']), repr(err)) except TooManyRedirects as err: raise HTTPRequestError("Request exceeded the configured number " - "of maximum re-directions\n" + repr(err)) + "of maximum re-directions.", repr(err)) except Timeout as err: - raise HTTPRequestError("Request timed out. Timeout is set to " - "{0}\n".format(timeout) + repr(err)) + raise HTTPRequestError("Request timed out. Timeout is set to {0}.". + format(timeout), repr(err)) except RequestException as err: - raise HTTPRequestError("Unexpected HTTP request exception.\n" + + raise HTTPRequestError("Unexpected HTTP request exception.", repr(err)) @staticmethod @@ -171,60 +203,64 @@ class HTTPRequest(object): def get(node, path, headers=None, timeout=10, enable_logging=True): """Sends a GET request and returns the response and status code. - :param node: honeycomb node - :param path: URL path, e.g. /index.html + :param node: Honeycomb node. + :param path: URL path, e.g. /index.html. :param headers: Dictionary of HTTP Headers to send with the Request. :param timeout: How long to wait for the server to send data before giving up, as a float, or a (connect timeout, read timeout) tuple. - :param enable_logging: Used to suppress errors when checking - honeycomb state during suite setup and teardown. When True, logging - is enabled, otherwise logging is disabled. + :param enable_logging: Used to suppress errors when checking Honeycomb + state during suite setup and teardown. When True, logging is enabled, + otherwise logging is disabled. :type node: dict :type path: str :type headers: dict :type timeout: float or tuple :type enable_logging: bool - :return: Status code and content of response + :return: Status code and content of response. :rtype: tuple """ + return HTTPRequest._http_request('GET', node, path, enable_logging=enable_logging, headers=headers, timeout=timeout) @staticmethod @keyword(name="HTTP Put") - def put(node, path, headers=None, payload=None, timeout=10): + def put(node, path, headers=None, payload=None, json=None, timeout=10): """Sends a PUT request and returns the response and status code. - :param node: honeycomb node - :param path: URL path, e.g. /index.html + :param node: Honeycomb node. + :param path: URL path, e.g. /index.html. :param headers: Dictionary of HTTP Headers to send with the Request. :param payload: Dictionary, bytes, or file-like object to send in the body of the Request. + :param json: JSON formatted string to send in the body of the Request. :param timeout: How long to wait for the server to send data before giving up, as a float, or a (connect timeout, read timeout) tuple. :type node: dict :type path: str :type headers: dict :type payload: dict, bytes, or file-like object + :type json: str :type timeout: float or tuple - :return: Status code and content of response + :return: Status code and content of response. :rtype: tuple """ return HTTPRequest._http_request('PUT', node, path, headers=headers, - data=payload, timeout=timeout) + data=payload, json=json, + timeout=timeout) @staticmethod @keyword(name="HTTP Post") def post(node, path, headers=None, payload=None, json=None, timeout=10): """Sends a POST request and returns the response and status code. - :param node: honeycomb node - :param path: URL path, e.g. /index.html + :param node: Honeycomb node. + :param path: URL path, e.g. /index.html. :param headers: Dictionary of HTTP Headers to send with the Request. :param payload: Dictionary, bytes, or file-like object to send in the body of the Request. - :param json: json data to send in the body of the Request + :param json: JSON formatted string to send in the body of the Request. :param timeout: How long to wait for the server to send data before giving up, as a float, or a (connect timeout, read timeout) tuple. :type node: dict @@ -233,7 +269,7 @@ class HTTPRequest(object): :type payload: dict, bytes, or file-like object :type json: str :type timeout: float or tuple - :return: Status code and content of response + :return: Status code and content of response. :rtype: tuple """ return HTTPRequest._http_request('POST', node, path, headers=headers, @@ -245,14 +281,14 @@ class HTTPRequest(object): def delete(node, path, timeout=10): """Sends a DELETE request and returns the response and status code. - :param node: honeycomb node - :param path: URL path, e.g. /index.html + :param node: Honeycomb node. + :param path: URL path, e.g. /index.html. :param timeout: How long to wait for the server to send data before giving up, as a float, or a (connect timeout, read timeout) tuple. :type node: dict :type path: str :type timeout: float or tuple - :return: Status code and content of response + :return: Status code and content of response. :rtype: tuple """ return HTTPRequest._http_request('DELETE', node, path, timeout=timeout) diff --git a/resources/libraries/python/HoneycombSetup.py b/resources/libraries/python/HoneycombSetup.py index de05eff6ed..384c2949bb 100644 --- a/resources/libraries/python/HoneycombSetup.py +++ b/resources/libraries/python/HoneycombSetup.py @@ -11,220 +11,200 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Implements keywords for Honeycomb setup.""" +"""Implementation of keywords for Honeycomb setup.""" -import os.path from xml.etree import ElementTree as ET from robot.api import logger from resources.libraries.python.topology import NodeType from resources.libraries.python.ssh import SSH -from resources.libraries.python.HTTPRequest import HTTPRequest, \ - HTTPRequestError, HTTP_CODES -from resources.libraries.python.constants import Constants as C - - -class HoneycombError(Exception): - """Exception(s) raised by methods working with Honeycomb.""" - - def __init__(self, msg, enable_logging=True): - """Sets the exception message and enables / disables logging - - It is not wanted to log errors when using these keywords together - with keywords like "Wait until keyword succeeds". - - :param msg: Message to be displayed and logged - :param enable_logging: When True, logging is enabled, otherwise - logging is disabled. - :type msg: str - :type enable_logging: bool - """ - super(HoneycombError, self).__init__() - self._msg = msg - self._repr_msg = self.__module__ + '.' + \ - self.__class__.__name__ + ": " + self._msg - if enable_logging: - logger.error(self._msg) - logger.debug(self._repr_msg) - - def __repr__(self): - return repr(self._repr_msg) - - def __str__(self): - return str(self._repr_msg) +from resources.libraries.python.HTTPRequest import HTTPRequest, HTTPCodes, \ + HTTPRequestError +from resources.libraries.python.HoneycombUtil import HoneycombUtil as HcUtil +from resources.libraries.python.HoneycombUtil import HoneycombError +from resources.libraries.python.constants import Constants as Const class HoneycombSetup(object): - """Implements keywords for Honeycomb setup.""" + """Implements keywords for Honeycomb setup. + + The keywords implemented in this class make possible to: + - start Honeycomb, + - stop Honeycomb, + - check the Honeycomb start-up state, + - check the Honeycomb shutdown state, + - add VPP to the topology. + """ def __init__(self): pass @staticmethod def start_honeycomb_on_all_duts(nodes): - """Start honeycomb on all DUT nodes in topology. - - :param nodes: all nodes in topology + """Start Honeycomb on all DUT nodes in topology. + + This keyword starts the Honeycomb service on all DUTs. The keyword just + starts the Honeycomb and does not check its startup state. Use the + keyword "Check Honeycomb Startup State" to check if the Honeycomb is up + and running. + Honeycomb must be installed in "/opt" directory, otherwise the start + will fail. + :param nodes: All nodes in topology. :type nodes: dict + :raises HoneycombError: If Honeycomb fails to start. """ - logger.console("Starting honeycomb service") + logger.console("Starting Honeycomb service ...") + + cmd = "{0}/start".format(Const.REMOTE_HC_DIR) for node in nodes.values(): if node['type'] == NodeType.DUT: - HoneycombSetup.start_honeycomb(node) - - @staticmethod - def start_honeycomb(node): - """Start up honeycomb on DUT node. - - :param node: DUT node with honeycomb - :type node: dict - :return: ret_code, stdout, stderr - :rtype: tuple - :raises HoneycombError: if Honeycomb fails to start. - """ - - ssh = SSH() - ssh.connect(node) - cmd = os.path.join(C.REMOTE_HC_DIR, "start") - (ret_code, stdout, stderr) = ssh.exec_command_sudo(cmd) - if int(ret_code) != 0: - logger.debug('stdout: {0}'.format(stdout)) - logger.debug('stderr: {0}'.format(stderr)) - raise HoneycombError('Node {0} failed to start honeycomb'. - format(node['host'])) - return ret_code, stdout, stderr + ssh = SSH() + ssh.connect(node) + (ret_code, _, _) = ssh.exec_command_sudo(cmd) + if int(ret_code) != 0: + raise HoneycombError('Node {0} failed to start Honeycomb.'. + format(node['host'])) + else: + logger.info("Starting the Honeycomb service on node {0} is " + "in progress ...".format(node['host'])) @staticmethod def stop_honeycomb_on_all_duts(nodes): - """Stop the honeycomb service on all DUTs. + """Stop the Honeycomb service on all DUTs. - :param nodes: nodes in topology + This keyword stops the Honeycomb service on all nodes. It just stops the + Honeycomb and does not check its shutdown state. Use the keyword "Check + Honeycomb Shutdown State" to check if Honeycomb has stopped. + :param nodes: Nodes in topology. :type nodes: dict - :return: ret_code, stdout, stderr - :rtype: tuple - :raises HoneycombError: if Honeycomb failed to stop. + :raises HoneycombError: If Honeycomb failed to stop. """ - logger.console("Shutting down honeycomb service") + logger.console("Shutting down Honeycomb service ...") + + cmd = "{0}/stop".format(Const.REMOTE_HC_DIR) errors = [] + for node in nodes.values(): if node['type'] == NodeType.DUT: - ssh = SSH() ssh.connect(node) - cmd = os.path.join(C.REMOTE_HC_DIR, "stop") - (ret_code, stdout, stderr) = ssh.exec_command_sudo(cmd) + (ret_code, _, _) = ssh.exec_command_sudo(cmd) if int(ret_code) != 0: - logger.debug('stdout: {0}'.format(stdout)) - logger.debug('stderr: {0}'.format(stderr)) errors.append(node['host']) - continue - logger.info("Honeycomb was successfully stopped on node {0}.". - format(node['host'])) + else: + logger.info("Stopping the Honeycomb service on node {0} is " + "in progress ...".format(node['host'])) if errors: - raise HoneycombError('Node(s) {0} failed to stop honeycomb.'. + raise HoneycombError('Node(s) {0} failed to stop Honeycomb.'. format(errors)) @staticmethod def check_honeycomb_startup_state(nodes): - """Check state of honeycomb service during startup. + """Check state of Honeycomb service during startup. - Reads html path from template file vpp_version.url + Reads html path from template file oper_vpp_version.url. Honeycomb node replies with connection refused or the following status codes depending on startup progress: codes 200, 401, 403, 404, 503 - :param nodes: nodes in topology + :param nodes: Nodes in topology. :type nodes: dict - :return: True if all GETs returned code 200(OK) + :return: True if all GETs returned code 200(OK). :rtype bool """ - url_file = os.path.join(C.RESOURCES_TPL_HC, "vpp_version.url") - with open(url_file) as template: - data = template.readline() - - expected_status_codes = (HTTP_CODES["UNAUTHORIZED"], - HTTP_CODES["FORBIDDEN"], - HTTP_CODES["NOT_FOUND"], - HTTP_CODES["SERVICE_UNAVAILABLE"]) + path = HcUtil.read_path_from_url_file("oper_vpp_version") + expected_status_codes = (HTTPCodes.UNAUTHORIZED, + HTTPCodes.FORBIDDEN, + HTTPCodes.NOT_FOUND, + HTTPCodes.SERVICE_UNAVAILABLE) for node in nodes.values(): if node['type'] == NodeType.DUT: - status_code, _ = HTTPRequest.get(node, data, timeout=10, + status_code, _ = HTTPRequest.get(node, path, timeout=10, enable_logging=False) - if status_code == HTTP_CODES["OK"]: - pass + if status_code == HTTPCodes.OK: + logger.info("Honeycomb on node {0} is up and running". + format(node['host'])) elif status_code in expected_status_codes: - if status_code == HTTP_CODES["UNAUTHORIZED"]: + if status_code == HTTPCodes.UNAUTHORIZED: logger.info('Unauthorized. If this triggers keyword ' - 'timeout, verify honeycomb ' - 'username and password') + 'timeout, verify Honeycomb username and ' + 'password.') raise HoneycombError('Honeycomb on node {0} running but ' 'not yet ready.'.format(node['host']), enable_logging=False) else: - raise HoneycombError('Unexpected return code: {0}'. + raise HoneycombError('Unexpected return code: {0}.'. format(status_code)) return True @staticmethod def check_honeycomb_shutdown_state(nodes): - """Check state of honeycomb service during shutdown. + """Check state of Honeycomb service during shutdown. Honeycomb node replies with connection refused or the following status - codes depending on shutdown progress: codes 200, 404 + codes depending on shutdown progress: codes 200, 404. - :param nodes: nodes in topology + :param nodes: Nodes in topology. :type nodes: dict - :return: True if all GETs fail to connect + :return: True if all GETs fail to connect. :rtype bool """ + cmd = "ps -ef | grep -v grep | grep karaf" for node in nodes.values(): if node['type'] == NodeType.DUT: try: status_code, _ = HTTPRequest.get(node, '/index.html', timeout=5, enable_logging=False) - if status_code == HTTP_CODES["OK"]: + if status_code == HTTPCodes.OK: raise HoneycombError('Honeycomb on node {0} is still ' - 'running'.format(node['host']), + 'running.'.format(node['host']), enable_logging=False) - elif status_code == HTTP_CODES["NOT_FOUND"]: + elif status_code == HTTPCodes.NOT_FOUND: raise HoneycombError('Honeycomb on node {0} is shutting' - ' down'.format(node['host']), + ' down.'.format(node['host']), enable_logging=False) else: - raise HoneycombError('Unexpected return code: {' - '0}'.format(status_code)) + raise HoneycombError('Unexpected return code: {0}.'. + format(status_code)) except HTTPRequestError: - logger.debug('Connection refused') - + logger.debug('Connection refused, checking the process ' + 'state ...') + ssh = SSH() + ssh.connect(node) + (ret_code, _, _) = ssh.exec_command_sudo(cmd) + if ret_code == 0: + raise HoneycombError('Honeycomb on node {0} is still ' + 'running.'.format(node['host']), + enable_logging=False) + else: + logger.info("Honeycomb on node {0} has stopped". + format(node['host'])) return True - @staticmethod - def add_vpp_to_honeycomb_network_topology(nodes, headers): + def add_vpp_to_honeycomb_network_topology(nodes): """Add vpp node to Honeycomb network topology. - :param nodes: all nodes in test topology - :param headers: headers to be used with PUT requests + :param nodes: All nodes in test topology. :type nodes: dict - :type headers: dict - :return: status code and response from PUT requests + :return: Status code and response content from PUT requests. :rtype: tuple - :raises HoneycombError: if a node was not added to honeycomb topology + :raises HoneycombError: If a node was not added to Honeycomb topology. - Reads HTML path from template file config_topology_node.url + Reads HTML path from template file config_topology_node.url. Path to the node to be added, e.g.: ("/restconf/config/network-topology:network-topology" "/topology/topology-netconf/node/") - There must be "/" at the end, as generated node name is added - at the end. + There must be "/" at the end, as generated node name is added at the + end. - Reads payload data from template file add_vpp_to_topology.xml + Reads payload data from template file add_vpp_to_topology.xml. Information about node as XML structure, e.g.: @@ -258,17 +238,16 @@ class HoneycombSetup(object): MUST be there as they are replaced by correct values. """ - with open(os.path.join(C.RESOURCES_TPL_HC, "config_topology_node.url"))\ - as template: - path = template.readline() - + path = HcUtil.read_path_from_url_file("config_topology_node") try: - xml_data = ET.parse(os.path.join(C.RESOURCES_TPL_HC, - "add_vpp_to_topology.xml")) + xml_data = ET.parse("{0}/add_vpp_to_topology.xml". + format(Const.RESOURCES_TPL_HC)) except ET.ParseError as err: raise HoneycombError(repr(err)) data = ET.tostring(xml_data.getroot()) + headers = {"Content-Type": "application/xml"} + status_codes = [] responses = [] for node_name, node in nodes.items(): @@ -282,20 +261,19 @@ class HoneycombSetup(object): passwd=node['honeycomb']["passwd"]) status_code, resp = HTTPRequest.put( node=node, - path=path + '/' + node_name, + path="{0}/{1}".format(path, node_name), headers=headers, payload=payload) - if status_code != HTTP_CODES["OK"]: + if status_code != HTTPCodes.OK: raise HoneycombError( "VPP {0} was not added to topology. " - "Status code: {1}".format(node["host"], - status_code)) + "Status code: {1}.".format(node["host"], + status_code)) status_codes.append(status_code) responses.append(resp) except HTTPRequestError as err: - raise HoneycombError("VPP {0} was not added to topology.\n" - "{1}".format(node["host"], repr(err))) - + raise HoneycombError("VPP {0} was not added to topology.". + format(node["host"]), repr(err)) return status_codes, responses diff --git a/resources/libraries/python/HoneycombUtil.py b/resources/libraries/python/HoneycombUtil.py index c4dc3a067a..86c25adc38 100644 --- a/resources/libraries/python/HoneycombUtil.py +++ b/resources/libraries/python/HoneycombUtil.py @@ -11,76 +11,137 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Implements keywords used with Honeycomb.""" +"""Implementation of low level functionality used in communication with +Honeycomb. + +Exception HoneycombError is used in all methods and in all modules with +Honeycomb keywords. + +Class HoneycombUtil implements methods used by Honeycomb keywords. They must not +be used directly in tests. Use keywords implemented in the module +HoneycombAPIKeywords instead. +""" -import os.path from json import loads from robot.api import logger -from resources.libraries.python.topology import NodeType from resources.libraries.python.HTTPRequest import HTTPRequest -from resources.libraries.python.constants import Constants as C +from resources.libraries.python.constants import Constants as Const + + +class HoneycombError(Exception): + + """Exception(s) raised by methods working with Honeycomb. + + When raising this exception, put this information to the message in this + order: + - short description of the encountered problem (parameter msg), + - relevant messages if there are any collected, e.g., from caught + exception (optional parameter details), + - relevant data if there are any collected (optional parameter details). + The logging is performed on two levels: 1. error - short description of the + problem; 2. debug - detailed information. + """ + + def __init__(self, msg, details='', enable_logging=True): + """Sets the exception message and enables / disables logging. + + It is not wanted to log errors when using these keywords together + with keywords like "Wait until keyword succeeds". So you can disable + logging by setting enable_logging to False. + + :param msg: Message to be displayed and logged + :param enable_logging: When True, logging is enabled, otherwise + logging is disabled. + :type msg: str + :type enable_logging: bool + """ + super(HoneycombError, self).__init__() + self._msg = "{0}: {1}".format(self.__class__.__name__, msg) + self._details = details + if enable_logging: + logger.error(self._msg) + logger.debug(self._details) + + def __repr__(self): + return repr(self._msg) + + def __str__(self): + return str(self._msg) class HoneycombUtil(object): - """Implements keywords used with Honeycomb.""" + """Implements low level functionality used in communication with Honeycomb. + + There are implemented methods to get, put and delete data to/from Honeycomb. + They are based on functionality implemented in the module HTTPRequests which + uses HTTP requests GET, PUT, POST and DELETE to communicate with Honeycomb. + + It is possible to PUT the data represented as XML or JSON structures or as + plain text. + Data received in the response of GET are always represented as a JSON + structure. + + There are also two supportive methods implemented: + - read_path_from_url_file which reads URL file and returns a path (see + docs/honeycomb_url_files.rst). + - parse_json_response which parses data from response in JSON representation + according to given path. + """ def __init__(self): pass - def get_configured_topology(self, nodes): - """Retrieves topology node IDs from each honeycomb node. + @staticmethod + def read_path_from_url_file(url_file): + """Read path from *.url file. - :param nodes: all nodes in topology - :type nodes: dict - :return: list of string IDs such as ['vpp1', 'vpp2'] - :rtype list + For more information about *.url file see docs/honeycomb_url_files.rst + :param url_file: URL file. The argument contains only the name of file + without extension, not the full path. + :type url_file: str + :return: Requested path. + :rtype: str """ - url_file = os.path.join(C.RESOURCES_TPL_HC, "config_topology.url") + url_file = "{0}/{1}.url".format(Const.RESOURCES_TPL_HC, url_file) with open(url_file) as template: path = template.readline() + return path - data = [] - for node in nodes.values(): - if node['type'] == NodeType.DUT: - _, ret = HTTPRequest.get(node, path) - logger.debug('return: {0}'.format(ret)) - data.append(self.parse_json_response(ret, ("topology", - "node", "node-id"))) - - return data - - def parse_json_response(self, response, path=None): + @staticmethod + def parse_json_response(response, path=None): """Parse data from response string in JSON format according to given path. - :param response: JSON formatted string - :param path: Path to navigate down the data structure + :param response: JSON formatted string. + :param path: Path to navigate down the data structure. :type response: string :type path: tuple - :return: JSON dictionary/list tree - :rtype: dict + :return: JSON dictionary/list tree. + :rtype: list """ data = loads(response) if path: - data = self._parse_json_tree(data, path) - while isinstance(data, list) and len(data) == 1: - data = data[0] + data = HoneycombUtil._parse_json_tree(data, path) + if not isinstance(data, list): + data = [data, ] return data - def _parse_json_tree(self, data, path): - """Retrieve data from python representation of JSON object. + @staticmethod + def _parse_json_tree(data, path): + """Retrieve data addressed by path from python representation of JSON + object. - :param data: parsed JSON dictionary tree - :param path: Path to navigate down the dictionary tree + :param data: Parsed JSON dictionary tree. + :param path: Path to navigate down the dictionary tree. :type data: dict :type path: tuple - :return: data from specified path - :rtype: list or str + :return: Data from specified path. + :rtype: list, dict or str """ count = 0 @@ -91,7 +152,80 @@ class HoneycombUtil(object): elif isinstance(data, list): result = [] for item in data: - result.append(self._parse_json_tree(item, path[count:])) + result.append(HoneycombUtil._parse_json_tree(item, + path[count:])) return result - return data + + @staticmethod + def get_honeycomb_data(node, url_file): + """Retrieve data from Honeycomb according to given URL. + + :param node: Honeycomb node. + :param url_file: URL file. The argument contains only the name of file + without extension, not the full path. + :type node: dict + :type url_file: str + :return: Requested information. + :rtype list + """ + + path = HoneycombUtil.read_path_from_url_file(url_file) + status_code, resp = HTTPRequest.get(node, path) + return status_code, resp + + @staticmethod + def put_honeycomb_data(node, url_file, data, data_representation='json'): + """Send configuration data using PUT request and return the status code + and response. + + :param node: Honeycomb node. + :param url_file: URL file. The argument contains only the name of file + without extension, not the full path. + :param data: Configuration data to be sent to Honeycomb. + :param data_representation: How the data is represented. Supported types + of representation are: json, xml and txt. + :type node: dict + :type url_file: str + :type data: str + :type data_representation: str + :return: Status code and content of response. + :rtype: tuple + """ + + headers = {'json': + {"Content-Type": "application/json", + 'Accept': 'text/plain'}, + 'xml': + {"Content-Type": "application/xml", + 'Accept': 'text/plain'}, + 'txt': + {"Content-Type": "text/plain", + 'Accept': 'text/plain'} + } + try: + header = headers[data_representation] + except KeyError as err: + raise HoneycombError("Wrong data type: {0}.". + format(data_representation), repr(err)) + + path = HoneycombUtil.read_path_from_url_file(url_file) + status_code, resp = HTTPRequest.put(node=node, path=path, + headers=header, payload=data) + return status_code, resp + + @staticmethod + def delete_honeycomb_data(node, url_file): + """Delete data from Honeycomb according to given URL. + + :param node: Honeycomb node. + :param url_file: URL file. The argument contains only the name of file + without extension, not the full path. + :type node: dict + :type url_file: str + :return: Status code and response. + :rtype tuple + """ + + path = HoneycombUtil.read_path_from_url_file(url_file) + return HTTPRequest.delete(node, path) diff --git a/resources/templates/honeycomb/vpp_version.url b/resources/templates/honeycomb/oper_vpp_version.url similarity index 100% rename from resources/templates/honeycomb/vpp_version.url rename to resources/templates/honeycomb/oper_vpp_version.url -- 2.16.6 From 5a2fd159dce96a70f2e5157314391aceb6d80197 Mon Sep 17 00:00:00 2001 From: Matej Klotton Date: Wed, 23 Mar 2016 15:01:30 +0100 Subject: [PATCH 08/16] Move methods from topology.py to more appropriate place. Change-Id: I2612a9466095b3d79a4a9d938fcdbf9f93133f69 Signed-off-by: Matej Klotton --- resources/libraries/python/InterfaceSetup.py | 182 ---------------- resources/libraries/python/InterfaceUtil.py | 237 +++++++++++++++++++++ resources/libraries/python/TGSetup.py | 4 +- resources/libraries/python/VatConfigGenerator.py | 58 ----- resources/libraries/python/VatJsonUtil.py | 102 +++++++++ resources/libraries/python/topology.py | 176 --------------- resources/libraries/robot/default.robot | 5 +- resources/libraries/robot/vxlan.robot | 4 +- tests/suites/__init__.robot | 5 +- tests/suites/bridge_domain/test.robot | 2 - tests/suites/ipv4/ipv4_untagged.robot | 1 - .../suites/l2_xconnect/l2_xconnect_untagged.robot | 2 +- 12 files changed, 348 insertions(+), 430 deletions(-) delete mode 100644 resources/libraries/python/InterfaceSetup.py delete mode 100644 resources/libraries/python/VatConfigGenerator.py create mode 100644 resources/libraries/python/VatJsonUtil.py diff --git a/resources/libraries/python/InterfaceSetup.py b/resources/libraries/python/InterfaceSetup.py deleted file mode 100644 index 946c8fc8fd..0000000000 --- a/resources/libraries/python/InterfaceSetup.py +++ /dev/null @@ -1,182 +0,0 @@ -# Copyright (c) 2016 Cisco and/or its affiliates. -# 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. - -"""Interface setup library.""" - -from ssh import SSH -from robot.api.deco import keyword -from resources.libraries.python.VatExecutor import VatExecutor - - -class InterfaceSetup(object): - """Interface setup utilities.""" - - __UDEV_IF_RULES_FILE = '/etc/udev/rules.d/10-network.rules' - - @staticmethod - def tg_set_interface_driver(node, pci_addr, driver): - """Set interface driver on the TG node. - - :param node: Node to set interface driver on (must be TG node). - :param pci_addr: PCI address of the interface. - :param driver: Driver name. - :type node: dict - :type pci_addr: str - :type driver: str - """ - old_driver = InterfaceSetup.tg_get_interface_driver(node, pci_addr) - if old_driver == driver: - return - - ssh = SSH() - ssh.connect(node) - - # Unbind from current driver - if old_driver is not None: - cmd = 'sh -c "echo {0} > /sys/bus/pci/drivers/{1}/unbind"'.format( - pci_addr, old_driver) - (ret_code, _, _) = ssh.exec_command_sudo(cmd) - if int(ret_code) != 0: - raise Exception("'{0}' failed on '{1}'".format(cmd, - node['host'])) - - # Bind to the new driver - cmd = 'sh -c "echo {0} > /sys/bus/pci/drivers/{1}/bind"'.format( - pci_addr, driver) - (ret_code, _, _) = ssh.exec_command_sudo(cmd) - if int(ret_code) != 0: - raise Exception("'{0}' failed on '{1}'".format(cmd, node['host'])) - - @staticmethod - def tg_get_interface_driver(node, pci_addr): - """Get interface driver from the TG node. - - :param node: Node to get interface driver on (must be TG node). - :param pci_addr: PCI address of the interface. - :type node: dict - :type pci_addr: str - :return: Interface driver or None if not found. - :rtype: str - - .. note:: - # lspci -vmmks 0000:00:05.0 - Slot: 00:05.0 - Class: Ethernet controller - Vendor: Red Hat, Inc - Device: Virtio network device - SVendor: Red Hat, Inc - SDevice: Device 0001 - PhySlot: 5 - Driver: virtio-pci - """ - ssh = SSH() - ssh.connect(node) - - cmd = 'lspci -vmmks {0}'.format(pci_addr) - - (ret_code, stdout, _) = ssh.exec_command(cmd) - if int(ret_code) != 0: - raise Exception("'{0}' failed on '{1}'".format(cmd, node['host'])) - - for line in stdout.splitlines(): - if len(line) == 0: - continue - (name, value) = line.split("\t", 1) - if name == 'Driver:': - return value - - return None - - @staticmethod - def tg_set_interfaces_udev_rules(node): - """Set udev rules for interfaces. - - Create udev rules file in /etc/udev/rules.d where are rules for each - interface used by TG node, based on MAC interface has specific name. - So after unbind and bind again to kernel driver interface has same - name as before. This must be called after TG has set name for each - port in topology dictionary. - udev rule example - SUBSYSTEM=="net", ACTION=="add", ATTR{address}=="52:54:00:e1:8a:0f", - NAME="eth1" - - :param node: Node to set udev rules on (must be TG node). - :type node: dict - """ - ssh = SSH() - ssh.connect(node) - - cmd = 'rm -f {0}'.format(InterfaceSetup.__UDEV_IF_RULES_FILE) - (ret_code, _, _) = ssh.exec_command_sudo(cmd) - if int(ret_code) != 0: - raise Exception("'{0}' failed on '{1}'".format(cmd, node['host'])) - - for if_k, if_v in node['interfaces'].items(): - if if_k == 'mgmt': - continue - rule = 'SUBSYSTEM==\\"net\\", ACTION==\\"add\\", ATTR{address}' + \ - '==\\"' + if_v['mac_address'] + '\\", NAME=\\"' + \ - if_v['name'] + '\\"' - cmd = 'sh -c "echo \'{0}\' >> {1}"'.format( - rule, InterfaceSetup.__UDEV_IF_RULES_FILE) - (ret_code, _, _) = ssh.exec_command_sudo(cmd) - if int(ret_code) != 0: - raise Exception("'{0}' failed on '{1}'".format(cmd, - node['host'])) - - cmd = '/etc/init.d/udev restart' - ssh.exec_command_sudo(cmd) - - @staticmethod - def tg_set_interfaces_default_driver(node): - """Set interfaces default driver specified in topology yaml file. - - :param node: Node to setup interfaces driver on (must be TG node). - :type node: dict - """ - for if_k, if_v in node['interfaces'].items(): - if if_k == 'mgmt': - continue - InterfaceSetup.tg_set_interface_driver(node, if_v['pci_address'], - if_v['driver']) - - @staticmethod - def create_vxlan_interface(node, vni, source_ip, destination_ip): - """Create VXLAN interface and return index of created interface - - Executes "vxlan_add_del_tunnel src {src} dst {dst} vni {vni}" VAT - command on the node. - - :param node: Node where to create VXLAN interface - :param vni: VXLAN Network Identifier - :param source_ip: Source IP of a VXLAN Tunnel End Point - :param destination_ip: Destination IP of a VXLAN Tunnel End Point - :type node: dict - :type vni: int - :type source_ip: str - :type destination_ip: str - :return: SW IF INDEX of created interface - :rtype: int - """ - - output = VatExecutor.cmd_from_template(node, "vxlan_create.vat", - src=source_ip, - dst=destination_ip, - vni=vni) - output = output[0] - - if output["retval"] == 0: - return output["sw_if_index"] - else: - raise RuntimeError('Unable to create VXLAN interface on node {}'.\ - format(node)) diff --git a/resources/libraries/python/InterfaceUtil.py b/resources/libraries/python/InterfaceUtil.py index 25503c08df..4631ccce7a 100644 --- a/resources/libraries/python/InterfaceUtil.py +++ b/resources/libraries/python/InterfaceUtil.py @@ -14,15 +14,22 @@ """Interface util library""" from time import time, sleep + from robot.api import logger + +from resources.libraries.python.ssh import SSH from resources.libraries.python.ssh import exec_cmd_no_error from resources.libraries.python.topology import NodeType, Topology from resources.libraries.python.VatExecutor import VatExecutor, VatTerminal +from resources.libraries.python.VatJsonUtil import VatJsonUtil +from resources.libraries.python.parsers.JsonParser import JsonParser class InterfaceUtil(object): """General utilities for managing interfaces""" + __UDEV_IF_RULES_FILE = '/etc/udev/rules.d/10-network.rules' + @staticmethod def set_interface_state(node, interface, state): """Set interface state on a node. @@ -191,3 +198,233 @@ class InterfaceUtil(object): return data_if return data + + @staticmethod + def tg_set_interface_driver(node, pci_addr, driver): + """Set interface driver on the TG node. + + :param node: Node to set interface driver on (must be TG node). + :param pci_addr: PCI address of the interface. + :param driver: Driver name. + :type node: dict + :type pci_addr: str + :type driver: str + """ + old_driver = InterfaceUtil.tg_get_interface_driver(node, pci_addr) + if old_driver == driver: + return + + ssh = SSH() + ssh.connect(node) + + # Unbind from current driver + if old_driver is not None: + cmd = 'sh -c "echo {0} > /sys/bus/pci/drivers/{1}/unbind"'.format( + pci_addr, old_driver) + (ret_code, _, _) = ssh.exec_command_sudo(cmd) + if int(ret_code) != 0: + raise Exception("'{0}' failed on '{1}'".format(cmd, + node['host'])) + + # Bind to the new driver + cmd = 'sh -c "echo {0} > /sys/bus/pci/drivers/{1}/bind"'.format( + pci_addr, driver) + (ret_code, _, _) = ssh.exec_command_sudo(cmd) + if int(ret_code) != 0: + raise Exception("'{0}' failed on '{1}'".format(cmd, node['host'])) + + @staticmethod + def tg_get_interface_driver(node, pci_addr): + """Get interface driver from the TG node. + + :param node: Node to get interface driver on (must be TG node). + :param pci_addr: PCI address of the interface. + :type node: dict + :type pci_addr: str + :return: Interface driver or None if not found. + :rtype: str + + .. note:: + # lspci -vmmks 0000:00:05.0 + Slot: 00:05.0 + Class: Ethernet controller + Vendor: Red Hat, Inc + Device: Virtio network device + SVendor: Red Hat, Inc + SDevice: Device 0001 + PhySlot: 5 + Driver: virtio-pci + """ + ssh = SSH() + ssh.connect(node) + + cmd = 'lspci -vmmks {0}'.format(pci_addr) + + (ret_code, stdout, _) = ssh.exec_command(cmd) + if int(ret_code) != 0: + raise Exception("'{0}' failed on '{1}'".format(cmd, node['host'])) + + for line in stdout.splitlines(): + if len(line) == 0: + continue + (name, value) = line.split("\t", 1) + if name == 'Driver:': + return value + + return None + + @staticmethod + def tg_set_interfaces_udev_rules(node): + """Set udev rules for interfaces. + + Create udev rules file in /etc/udev/rules.d where are rules for each + interface used by TG node, based on MAC interface has specific name. + So after unbind and bind again to kernel driver interface has same + name as before. This must be called after TG has set name for each + port in topology dictionary. + udev rule example + SUBSYSTEM=="net", ACTION=="add", ATTR{address}=="52:54:00:e1:8a:0f", + NAME="eth1" + + :param node: Node to set udev rules on (must be TG node). + :type node: dict + """ + ssh = SSH() + ssh.connect(node) + + cmd = 'rm -f {0}'.format(InterfaceUtil.__UDEV_IF_RULES_FILE) + (ret_code, _, _) = ssh.exec_command_sudo(cmd) + if int(ret_code) != 0: + raise Exception("'{0}' failed on '{1}'".format(cmd, node['host'])) + + for interface in node['interfaces'].values(): + rule = 'SUBSYSTEM==\\"net\\", ACTION==\\"add\\", ATTR{address}' + \ + '==\\"' + interface['mac_address'] + '\\", NAME=\\"' + \ + interface['name'] + '\\"' + cmd = 'sh -c "echo \'{0}\' >> {1}"'.format( + rule, InterfaceUtil.__UDEV_IF_RULES_FILE) + (ret_code, _, _) = ssh.exec_command_sudo(cmd) + if int(ret_code) != 0: + raise Exception("'{0}' failed on '{1}'".format(cmd, + node['host'])) + + cmd = '/etc/init.d/udev restart' + ssh.exec_command_sudo(cmd) + + @staticmethod + def tg_set_interfaces_default_driver(node): + """Set interfaces default driver specified in topology yaml file. + + :param node: Node to setup interfaces driver on (must be TG node). + :type node: dict + """ + for interface in node['interfaces'].values(): + InterfaceUtil.tg_set_interface_driver(node, + interface['pci_address'], + interface['driver']) + + @staticmethod + def update_vpp_interface_data_on_node(node): + """Update vpp generated interface data for a given node in DICT__nodes + + Updates interface names, software if index numbers and any other details + generated specifically by vpp that are unknown before testcase run. + It does this by dumping interface list to JSON output from all + devices using vpp_api_test, and pairing known information from topology + (mac address/pci address of interface) to state from VPP. + + :param node: Node selected from DICT__nodes + :type node: dict + """ + vat_executor = VatExecutor() + vat_executor.execute_script_json_out("dump_interfaces.vat", node) + interface_dump_json = vat_executor.get_script_stdout() + VatJsonUtil.update_vpp_interface_data_from_json(node, + interface_dump_json) + + @staticmethod + def update_tg_interface_data_on_node(node): + """Update interface name for TG/linux node in DICT__nodes. + + :param node: Node selected from DICT__nodes. + :type node: dict + + .. note:: + # for dev in `ls /sys/class/net/`; + > do echo "\"`cat /sys/class/net/$dev/address`\": \"$dev\""; done + "52:54:00:9f:82:63": "eth0" + "52:54:00:77:ae:a9": "eth1" + "52:54:00:e1:8a:0f": "eth2" + "00:00:00:00:00:00": "lo" + + .. todo:: parse lshw -json instead + """ + # First setup interface driver specified in yaml file + InterfaceUtil.tg_set_interfaces_default_driver(node) + + # Get interface names + ssh = SSH() + ssh.connect(node) + + cmd = ('for dev in `ls /sys/class/net/`; do echo "\\"`cat ' + '/sys/class/net/$dev/address`\\": \\"$dev\\""; done;') + + (ret_code, stdout, _) = ssh.exec_command(cmd) + if int(ret_code) != 0: + raise Exception('Get interface name and MAC failed') + tmp = "{" + stdout.rstrip().replace('\n', ',') + "}" + interfaces = JsonParser().parse_data(tmp) + for interface in node['interfaces'].values(): + name = interfaces.get(interface['mac_address']) + if name is None: + continue + interface['name'] = name + + # Set udev rules for interfaces + InterfaceUtil.tg_set_interfaces_udev_rules(node) + + @staticmethod + def update_all_interface_data_on_all_nodes(nodes): + """Update interface names on all nodes in DICT__nodes. + + This method updates the topology dictionary by querying interface lists + of all nodes mentioned in the topology dictionary. + + :param nodes: Nodes in the topology. + :type nodes: dict + """ + for node_data in nodes.values(): + if node_data['type'] == NodeType.DUT: + InterfaceUtil.update_vpp_interface_data_on_node(node_data) + elif node_data['type'] == NodeType.TG: + InterfaceUtil.update_tg_interface_data_on_node(node_data) + + @staticmethod + def create_vxlan_interface(node, vni, source_ip, destination_ip): + """Create VXLAN interface and return sw if index of created interface. + + Executes "vxlan_add_del_tunnel src {src} dst {dst} vni {vni}" VAT + command on the node. + + :param node: Node where to create VXLAN interface. + :param vni: VXLAN Network Identifier. + :param source_ip: Source IP of a VXLAN Tunnel End Point. + :param destination_ip: Destination IP of a VXLAN Tunnel End Point. + :type node: dict + :type vni: int + :type source_ip: str + :type destination_ip: str + :return: SW IF INDEX of created interface. + :rtype: int + """ + output = VatExecutor.cmd_from_template(node, "vxlan_create.vat", + src=source_ip, + dst=destination_ip, + vni=vni) + output = output[0] + + if output["retval"] == 0: + return output["sw_if_index"] + else: + raise RuntimeError('Unable to create VXLAN interface on node {}' + .format(node)) diff --git a/resources/libraries/python/TGSetup.py b/resources/libraries/python/TGSetup.py index 3e372e9464..05c8b1d177 100644 --- a/resources/libraries/python/TGSetup.py +++ b/resources/libraries/python/TGSetup.py @@ -14,7 +14,7 @@ """TG Setup library.""" from topology import NodeType -from InterfaceSetup import InterfaceSetup +from InterfaceUtil import InterfaceUtil class TGSetup(object): @@ -29,4 +29,4 @@ class TGSetup(object): """ for node in nodes.values(): if node['type'] == NodeType.TG: - InterfaceSetup.tg_set_interfaces_default_driver(node) + InterfaceUtil.tg_set_interfaces_default_driver(node) diff --git a/resources/libraries/python/VatConfigGenerator.py b/resources/libraries/python/VatConfigGenerator.py deleted file mode 100644 index 98be9d3448..0000000000 --- a/resources/libraries/python/VatConfigGenerator.py +++ /dev/null @@ -1,58 +0,0 @@ -# Copyright (c) 2016 Cisco and/or its affiliates. -# 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. - -"""Can be used to generate VAT scripts from VAT template files.""" - -from robot.api import logger - - -class VatConfigGenerator(object): - """Generates VAT configuration scripts from VAT script template files. - """ - def __init__(self): - pass - - @staticmethod - def generate_vat_config_file(template_file, env_var_dict, out_file): - """ Write VAT configuration script to out file. - - Generates VAT configuration script from template using - dictionary containing environment variables - :param template_file: file that contains the VAT script template - :param env_var_dict: python dictionary that maps test - environment variables - """ - - template_data = open(template_file).read() - logger.trace("Loaded template file: \n '{0}'".format(template_data)) - generated_config = template_data.format(**env_var_dict) - logger.trace("Generated script file: \n '{0}'".format(generated_config)) - with open(out_file, 'w') as work_file: - work_file.write(generated_config) - - @staticmethod - def generate_vat_config_string(template_file, env_var_dict): - """ Return wat config string generated from template. - - Generates VAT configuration script from template using - dictionary containing environment variables - :param template_file: file that contains the VAT script template - :param env_var_dict: python dictionary that maps test - environment variables - """ - - template_data = open(template_file).read() - logger.trace("Loaded template file: \n '{0}'".format(template_data)) - generated_config = template_data.format(**env_var_dict) - logger.trace("Generated script file: \n '{0}'".format(generated_config)) - return generated_config diff --git a/resources/libraries/python/VatJsonUtil.py b/resources/libraries/python/VatJsonUtil.py new file mode 100644 index 0000000000..36c50533fc --- /dev/null +++ b/resources/libraries/python/VatJsonUtil.py @@ -0,0 +1,102 @@ +# Copyright (c) 2016 Cisco and/or its affiliates. +# 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. + +"""Utilities to work with JSON data format from VAT.""" + +from robot.api import logger + +from resources.libraries.python.parsers.JsonParser import JsonParser + + +class VatJsonUtil(object): + """Utilities to work with JSON data format from VAT.""" + + @staticmethod + def _convert_mac_to_number_list(mac_address): + """Convert MAC address string to list of decimal numbers. + + Converts a ":" separated MAC address to decimal number list as used + in JSON interface dump. + + :param mac_address: MAC address. + :type mac_address: str + :return: List representation of MAC address. + :rtype: list + """ + list_mac = [] + for num in mac_address.split(":"): + list_mac.append(int(num, 16)) + return list_mac + + @staticmethod + def get_vpp_interface_by_mac(interfaces_list, mac_address): + """Return interface dictionary from interface_list by MAC address. + + Extracts interface dictionary from all of the interfaces in interfaces + list parsed from json according to mac_address of the interface. + + :param interfaces_list: Interfaces parsed from JSON. + :param mac_address: MAC address of interface we are looking for. + :type interfaces_list: dict + :type mac_address: str + :return: Interface from JSON. + :rtype: dict + """ + interface_dict = {} + list_mac_address = VatJsonUtil._convert_mac_to_number_list(mac_address) + logger.trace("MAC address {0} converted to list {1}." + .format(mac_address, list_mac_address)) + for interface in interfaces_list: + # TODO: create vat json integrity checking and move there + if "l2_address" not in interface: + raise KeyError( + "key l2_address not found in interface dict." + "Probably input list is not parsed from correct VAT " + "json output.") + if "l2_address_length" not in interface: + raise KeyError( + "key l2_address_length not found in interface " + "dict. Probably input list is not parsed from correct " + "VAT json output.") + mac_from_json = interface["l2_address"][:6] + if mac_from_json == list_mac_address: + if interface["l2_address_length"] != 6: + raise ValueError("l2_address_length value is not 6.") + interface_dict = interface + break + return interface_dict + + @staticmethod + def update_vpp_interface_data_from_json(node, interface_dump_json): + """Update vpp node data in node__DICT from json interface dump. + + This method updates vpp interface names and sw if indexes according to + interface MAC addresses found in interface_dump_json. + + :param node: Node dictionary. + :param interface_dump_json: JSON output from dump_interface_list VAT + command. + :type node: dict + :type interface_dump_json: str + """ + interface_list = JsonParser().parse_data(interface_dump_json) + for ifc in node['interfaces'].values(): + if_mac = ifc['mac_address'] + interface_dict = VatJsonUtil.get_vpp_interface_by_mac( + interface_list, if_mac) + if not interface_dict: + raise Exception('Interface {0} not found by MAC {1}' + .format(ifc, if_mac)) + ifc['name'] = interface_dict["interface_name"] + ifc['vpp_sw_index'] = interface_dict["sw_if_index"] + ifc['mtu'] = interface_dict["mtu"] diff --git a/resources/libraries/python/topology.py b/resources/libraries/python/topology.py index 7a27173d40..d04d584623 100644 --- a/resources/libraries/python/topology.py +++ b/resources/libraries/python/topology.py @@ -13,10 +13,6 @@ """Defines nodes and topology structure.""" -from resources.libraries.python.parsers.JsonParser import JsonParser -from resources.libraries.python.VatExecutor import VatExecutor -from resources.libraries.python.ssh import SSH -from resources.libraries.python.InterfaceSetup import InterfaceSetup from robot.api import logger from robot.libraries.BuiltIn import BuiltIn from robot.api.deco import keyword @@ -137,8 +133,6 @@ class Topology(object): This method returns the interface names asociated with given links for a given node. - The resulting dictionary can be then used to with VatConfigGenerator - to generate a VAT script with proper interface names. :param link_names: list of names of the link that a interface is connected to. :param node: the node topology directory @@ -168,176 +162,6 @@ class Topology(object): return self._get_interface_by_key_value(node, "vpp_sw_index", sw_index) - @staticmethod - def convert_mac_to_number_list(mac_address): - """Convert mac address string to list of decimal numbers. - - Converts a : separated mac address to decimal number list as used - in json interface dump. - :param mac_address: string mac address - :return: list representation of mac address - """ - - list_mac = [] - for num in mac_address.split(":"): - list_mac.append(int(num, 16)) - return list_mac - - def _extract_vpp_interface_by_mac(self, interfaces_list, mac_address): - """Return interface dictionary from interface_list by mac address. - - Extracts interface dictionary from all of the interfaces in interfaces - list parsed from json according to mac_address of the interface - :param interfaces_list: dictionary of all interfaces parsed from json - :param mac_address: string mac address of interface we are looking for - :return: interface dictionary from json - """ - - interface_dict = {} - list_mac_address = self.convert_mac_to_number_list(mac_address) - logger.trace(str(list_mac_address)) - for interface in interfaces_list: - # TODO: create vat json integrity checking and move there - if "l2_address" not in interface: - raise KeyError( - "key l2_address not found in interface dict." - "Probably input list is not parsed from correct VAT " - "json output.") - if "l2_address_length" not in interface: - raise KeyError( - "key l2_address_length not found in interface " - "dict. Probably input list is not parsed from correct " - "VAT json output.") - mac_from_json = interface["l2_address"][:6] - if mac_from_json == list_mac_address: - if interface["l2_address_length"] != 6: - raise ValueError("l2_address_length value is not 6.") - interface_dict = interface - break - return interface_dict - - def vpp_interface_name_from_json_by_mac(self, json_data, mac_address): - """Return vpp interface name string from VAT interface dump json output - - Extracts the name given to an interface by VPP. - These interface names differ from what you would see if you - used the ipconfig or similar command. - Required json data can be obtained by calling : - VatExecutor.execute_script_json_out("dump_interfaces.vat", node) - :param json_data: string json data from sw_interface_dump VAT command - :param mac_address: string containing mac address of interface - whose vpp name we wish to discover. - :return: string vpp interface name - """ - - interfaces_list = JsonParser().parse_data(json_data) - # TODO: checking if json data is parsed correctly - interface_dict = self._extract_vpp_interface_by_mac(interfaces_list, - mac_address) - interface_name = interface_dict["interface_name"] - return interface_name - - def _update_node_interface_data_from_json(self, node, interface_dump_json): - """Update node vpp data in node__DICT from json interface dump. - - This method updates vpp interface names and sw indexexs according to - interface mac addresses found in interface_dump_json - :param node: node dictionary - :param interface_dump_json: json output from dump_interface_list VAT - command - """ - - interface_list = JsonParser().parse_data(interface_dump_json) - for ifc in node['interfaces'].values(): - if 'link' not in ifc: - continue - if_mac = ifc['mac_address'] - interface_dict = self._extract_vpp_interface_by_mac(interface_list, - if_mac) - if not interface_dict: - raise Exception('Interface {0} not found by MAC {1}'. - format(ifc, if_mac)) - ifc['name'] = interface_dict["interface_name"] - ifc['vpp_sw_index'] = interface_dict["sw_if_index"] - ifc['mtu'] = interface_dict["mtu"] - - def update_vpp_interface_data_on_node(self, node): - """Update vpp generated interface data for a given node in DICT__nodes - - Updates interface names, software index numbers and any other details - generated specifically by vpp that are unknown before testcase run. - :param node: Node selected from DICT__nodes - """ - - vat_executor = VatExecutor() - vat_executor.execute_script_json_out("dump_interfaces.vat", node) - interface_dump_json = vat_executor.get_script_stdout() - self._update_node_interface_data_from_json(node, - interface_dump_json) - - @staticmethod - def update_tg_interface_data_on_node(node): - """Update interface name for TG/linux node in DICT__nodes - - :param node: Node selected from DICT__nodes. - :type node: dict - - .. note:: - # for dev in `ls /sys/class/net/`; - > do echo "\"`cat /sys/class/net/$dev/address`\": \"$dev\""; done - "52:54:00:9f:82:63": "eth0" - "52:54:00:77:ae:a9": "eth1" - "52:54:00:e1:8a:0f": "eth2" - "00:00:00:00:00:00": "lo" - - .. todo:: parse lshw -json instead - """ - # First setup interface driver specified in yaml file - InterfaceSetup.tg_set_interfaces_default_driver(node) - - # Get interface names - ssh = SSH() - ssh.connect(node) - - cmd = 'for dev in `ls /sys/class/net/`; do echo "\\"`cat ' \ - '/sys/class/net/$dev/address`\\": \\"$dev\\""; done;' - - (ret_code, stdout, _) = ssh.exec_command(cmd) - if int(ret_code) != 0: - raise Exception('Get interface name and MAC failed') - tmp = "{" + stdout.rstrip().replace('\n', ',') + "}" - interfaces = JsonParser().parse_data(tmp) - for if_k, if_v in node['interfaces'].items(): - if if_k == 'mgmt': - continue - name = interfaces.get(if_v['mac_address']) - if name is None: - continue - if_v['name'] = name - - # Set udev rules for interfaces - InterfaceSetup.tg_set_interfaces_udev_rules(node) - - def update_all_interface_data_on_all_nodes(self, nodes): - """Update interface names on all nodes in DICT__nodes - - :param nodes: Nodes in the topology. - :type nodes: dict - - This method updates the topology dictionary by querying interface lists - of all nodes mentioned in the topology dictionary. - It does this by dumping interface list to json output from all devices - using vpp_api_test, and pairing known information from topology - (mac address/pci address of interface) to state from VPP. - For TG/linux nodes add interface name only. - """ - - for node_data in nodes.values(): - if node_data['type'] == NodeType.DUT: - self.update_vpp_interface_data_on_node(node_data) - elif node_data['type'] == NodeType.TG: - self.update_tg_interface_data_on_node(node_data) - @staticmethod def get_interface_sw_index(node, interface): """Get VPP sw_index for the interface. diff --git a/resources/libraries/robot/default.robot b/resources/libraries/robot/default.robot index d085825a04..dae515423b 100644 --- a/resources/libraries/robot/default.robot +++ b/resources/libraries/robot/default.robot @@ -13,8 +13,9 @@ *** Settings *** | Variables | resources/libraries/python/topology.py -| Library | resources/libraries/python/DUTSetup.py -| Library | resources/libraries/python/TGSetup.py +| Library | resources.libraries.python.topology.Topology +| Library | resources.libraries.python.DUTSetup +| Library | resources.libraries.python.TGSetup | Library | Collections *** Keywords *** diff --git a/resources/libraries/robot/vxlan.robot b/resources/libraries/robot/vxlan.robot index e855f2499d..1f3b141470 100644 --- a/resources/libraries/robot/vxlan.robot +++ b/resources/libraries/robot/vxlan.robot @@ -14,15 +14,13 @@ *** Settings *** | Library | Collections | Resource | resources/libraries/robot/default.robot +| Resource | resources/libraries/robot/interfaces.robot | Resource | resources/libraries/robot/bridge_domain.robot | Resource | resources/libraries/robot/l2_xconnect.robot | Library | resources.libraries.python.L2Util | Library | resources.libraries.python.IPUtil | Library | resources.libraries.python.IPv4Util | Library | resources.libraries.python.IPv4Setup -| Library | resources.libraries.python.InterfaceSetup -| Library | resources.libraries.python.InterfaceUtil -| Library | resources.libraries.python.topology.Topology | Library | resources.libraries.python.NodePath diff --git a/tests/suites/__init__.robot b/tests/suites/__init__.robot index e025575dd7..2016146e3b 100644 --- a/tests/suites/__init__.robot +++ b/tests/suites/__init__.robot @@ -13,9 +13,8 @@ *** Settings *** | Resource | resources/libraries/robot/default.robot -| Library | resources/libraries/python/SetupFramework.py -| Library | resources.libraries.python.topology.Topology +| Resource | resources/libraries/robot/interfaces.robot +| Library | resources.libraries.python.SetupFramework | Suite Setup | Run Keywords | Setup Framework | ${nodes} | ... | AND | Setup All DUTs | ${nodes} | ... | AND | Update All Interface Data On All Nodes | ${nodes} - diff --git a/tests/suites/bridge_domain/test.robot b/tests/suites/bridge_domain/test.robot index 9695b7fe88..c6a4cd1197 100644 --- a/tests/suites/bridge_domain/test.robot +++ b/tests/suites/bridge_domain/test.robot @@ -16,9 +16,7 @@ | Resource | resources/libraries/robot/interfaces.robot | Resource | resources/libraries/robot/bridge_domain.robot | Resource | resources/libraries/robot/l2_traffic.robot -| Library | resources.libraries.python.topology.Topology | Library | resources.libraries.python.NodePath -| Variables | resources/libraries/python/topology.py | Force Tags | HW_ENV | VM_ENV | Suite Setup | Setup all TGs before traffic script | Test Setup | Setup all DUTs before test diff --git a/tests/suites/ipv4/ipv4_untagged.robot b/tests/suites/ipv4/ipv4_untagged.robot index 47fe34acdf..45ab5768c7 100644 --- a/tests/suites/ipv4/ipv4_untagged.robot +++ b/tests/suites/ipv4/ipv4_untagged.robot @@ -12,7 +12,6 @@ # limitations under the License. *** Settings *** -| Library | resources.libraries.python.topology.Topology | Library | resources.libraries.python.NodePath | Library | resources.libraries.python.Trace | Resource | resources/libraries/robot/default.robot diff --git a/tests/suites/l2_xconnect/l2_xconnect_untagged.robot b/tests/suites/l2_xconnect/l2_xconnect_untagged.robot index cf32bfa076..3370cabaa4 100644 --- a/tests/suites/l2_xconnect/l2_xconnect_untagged.robot +++ b/tests/suites/l2_xconnect/l2_xconnect_untagged.robot @@ -15,7 +15,7 @@ | Resource | resources/libraries/robot/default.robot | Resource | resources/libraries/robot/l2_xconnect.robot | Resource | resources/libraries/robot/l2_traffic.robot -| Library | resources.libraries.python.InterfaceUtil +| Resource | resources/libraries/robot/interfaces.robot | Library | resources.libraries.python.NodePath | Force Tags | 3_NODE_SINGLE_LINK_TOPO | HW_ENV | VM_ENV | Test Setup | Setup all DUTs before test -- 2.16.6 From 6d9fe9bb4bd353b1d4fec3b2569bcd743f87fc4a Mon Sep 17 00:00:00 2001 From: Matej Klotton Date: Fri, 11 Mar 2016 13:56:39 +0100 Subject: [PATCH 09/16] VXLAN test with dot1q tagging. Change-Id: I3dbd12983736e338d757c580570d91680aedd83f Signed-off-by: Matej Klotton --- resources/libraries/python/InterfaceUtil.py | 32 +++++ resources/libraries/python/VatExecutor.py | 21 +++- resources/libraries/python/topology.py | 24 ++-- resources/libraries/robot/l2_traffic.robot | 8 ++ resources/libraries/robot/vxlan.robot | 138 ++++++++++++++------- resources/templates/vat/create_vlan_subif.vat | 1 + tests/suites/bridge_domain/test.robot | 9 +- .../suites/l2_xconnect/l2_xconnect_untagged.robot | 3 +- tests/suites/vxlan/vxlan_bd_dot1q.robot | 50 ++++++++ tests/suites/vxlan/vxlan_bd_untagged.robot | 34 +++-- tests/suites/vxlan/vxlan_xconnect_untagged.robot | 31 +++-- 11 files changed, 262 insertions(+), 89 deletions(-) create mode 100644 resources/templates/vat/create_vlan_subif.vat create mode 100644 tests/suites/vxlan/vxlan_bd_dot1q.robot diff --git a/resources/libraries/python/InterfaceUtil.py b/resources/libraries/python/InterfaceUtil.py index 4631ccce7a..6526fe83ca 100644 --- a/resources/libraries/python/InterfaceUtil.py +++ b/resources/libraries/python/InterfaceUtil.py @@ -399,6 +399,38 @@ class InterfaceUtil(object): elif node_data['type'] == NodeType.TG: InterfaceUtil.update_tg_interface_data_on_node(node_data) + @staticmethod + def create_vlan_subinterface(node, interface, vlan): + """Create VLAN subinterface on node. + + :param node: Node to add VLAN subinterface on. + :param interface: Interface name on which create VLAN subinterface. + :param vlan: VLAN ID of the subinterface to be created. + :type node: dict + :type interface: str + :type vlan: int + :return: Name and index of created subinterface. + :rtype: tuple + """ + sw_if_index = Topology.get_interface_sw_index(node, interface) + + output = VatExecutor.cmd_from_template(node, "create_vlan_subif.vat", + sw_if_index=sw_if_index, + vlan=vlan) + if output[0]["retval"] == 0: + sw_subif_index = output[0]["sw_if_index"] + logger.trace('VLAN subinterface with sw_if_index {} and VLAN ID {} ' + 'created on node {}'.format(sw_subif_index, + vlan, node['host'])) + else: + raise RuntimeError('Unable to create VLAN subinterface on node {}' + .format(node['host'])) + + with VatTerminal(node, False) as vat: + vat.vat_terminal_exec_cmd('exec show interfaces') + + return '{}.{}'.format(interface, vlan), sw_subif_index + @staticmethod def create_vxlan_interface(node, vni, source_ip, destination_ip): """Create VXLAN interface and return sw if index of created interface. diff --git a/resources/libraries/python/VatExecutor.py b/resources/libraries/python/VatExecutor.py index 136b7cc0ef..9851d93edd 100644 --- a/resources/libraries/python/VatExecutor.py +++ b/resources/libraries/python/VatExecutor.py @@ -142,18 +142,25 @@ class VatTerminal(object): """VAT interactive terminal :param node: Node to open VAT terminal on. + :param json_param: Defines if outputs from VAT are in JSON format. + Default is True. + :type node: dict + :type json_param: bool + """ __VAT_PROMPT = "vat# " __LINUX_PROMPT = ":~$ " - def __init__(self, node): + def __init__(self, node, json_param=True): + json_text = ' json' if json_param else '' + self.json = json_param self._ssh = SSH() self._ssh.connect(node) self._tty = self._ssh.interactive_terminal_open() self._ssh.interactive_terminal_exec_command( self._tty, - 'sudo -S {vat} json'.format(vat=Constants.VAT_BIN_NAME), + 'sudo -S {}{}'.format(Constants.VAT_BIN_NAME, json_text), self.__VAT_PROMPT) def __enter__(self): @@ -167,15 +174,19 @@ class VatTerminal(object): :param cmd: Command to be executed. - :return: Command output in python representation of JSON format. + :return: Command output in python representation of JSON format or + None if not in JSON mode. """ logger.debug("Executing command in VAT terminal: {}".format(cmd)) out = self._ssh.interactive_terminal_exec_command(self._tty, cmd, self.__VAT_PROMPT) logger.debug("VAT output: {}".format(out)) - json_out = json.loads(out) - return json_out + if self.json: + json_out = json.loads(out) + return json_out + else: + return None def vat_terminal_close(self): """Close VAT terminal.""" diff --git a/resources/libraries/python/topology.py b/resources/libraries/python/topology.py index d04d584623..0f26d2c6e9 100644 --- a/resources/libraries/python/topology.py +++ b/resources/libraries/python/topology.py @@ -164,20 +164,22 @@ class Topology(object): @staticmethod def get_interface_sw_index(node, interface): - """Get VPP sw_index for the interface. + """Get VPP sw_if_index for the interface. - :param node: Node to get interface sw_index on. - :param interface: Interface name. + :param node: Node to get interface sw_if_index on. + :param interface: Interface identifier. :type node: dict - :type interface: str - :return: Return sw_index or None if not found. + :type interface: str or int + :return: Return sw_if_index or None if not found. """ - for port in node['interfaces'].values(): - port_name = port.get('name') - if port_name == interface: - return port.get('vpp_sw_index') - - return None + try: + return int(interface) + except ValueError: + for port in node['interfaces'].values(): + port_name = port.get('name') + if port_name == interface: + return port.get('vpp_sw_index') + return None @staticmethod def get_interface_mtu(node, interface): diff --git a/resources/libraries/robot/l2_traffic.robot b/resources/libraries/robot/l2_traffic.robot index 2ba53d5821..bb66d9035d 100644 --- a/resources/libraries/robot/l2_traffic.robot +++ b/resources/libraries/robot/l2_traffic.robot @@ -27,3 +27,11 @@ | | ${args}= | Traffic Script Gen Arg | ${dst_int} | ${src_int} | ${src_mac} | | | ... | ${dst_mac} | ${src_ip} | ${dst_ip} | | Run Traffic Script On Node | send_ip_icmp.py | ${tg_node} | ${args} + +| Send and receive ICMPv4 bidirectionally +| | [Documentation] | Send ICMPv4 echo request from both directions, +| | ... | from interface1 to interface2 and +| | ... | from interface2 to interface1. +| | [Arguments] | ${tg_node} | ${int1} | ${int2} +| | Send and receive ICMPv4 | ${tg_node} | ${int1} | ${int2} +| | Send and receive ICMPv4 | ${tg_node} | ${int2} | ${int1} diff --git a/resources/libraries/robot/vxlan.robot b/resources/libraries/robot/vxlan.robot index 1f3b141470..ebf5d351ca 100644 --- a/resources/libraries/robot/vxlan.robot +++ b/resources/libraries/robot/vxlan.robot @@ -23,10 +23,22 @@ | Library | resources.libraries.python.IPv4Setup | Library | resources.libraries.python.NodePath - *** Keywords *** -| Setup VXLAN tunnel on nodes -| | [Arguments] | ${TG} | ${DUT1} | ${DUT2} | ${VNI} +| Path for VXLAN testing is set +| | [Documentation] | *Compute path for VXLAN testing on nodes.* +| | ... +| | ... | _Set testcase variables with interfaces and nodes:_ +| | ... | - ${tgs_to_dut1} +| | ... | - ${dut1s_to_tg} +| | ... | - ${tgs_to_dut2} +| | ... | - ${dut2s_to_tg} +| | ... | - ${dut1s_to_dut2} +| | ... | - ${dut2s_to_dut1} +| | ... | - ${tg} +| | ... | - ${dut1} +| | ... | - ${dut2} +| | ... +| | [Arguments] | ${TG} | ${DUT1} | ${DUT2} | | Append Nodes | ${TG} | ${DUT1} | ${DUT2} | ${TG} | | Compute Path | | ${tgs_to_dut1} | ${tg}= | Next Interface @@ -35,45 +47,85 @@ | | ${dut2s_to_dut1} | ${dut2}= | Next Interface | | ${dut2s_to_tg} | ${dut2}= | Next Interface | | ${tgs_to_dut2} | ${tg}= | Next Interface -| | Set Suite Variable | ${tgs_to_dut1} -| | Set Suite Variable | ${dut1s_to_tg} -| | Set Suite Variable | ${tgs_to_dut2} -| | Set Suite Variable | ${dut2s_to_tg} -| | Set Suite Variable | ${dut1s_to_dut2} -| | Set Suite Variable | ${dut2s_to_dut1} -# TODO: replace with address generator -| | Set Suite Variable | ${dut1s_ip_address} | 172.16.0.1 -| | Set Suite Variable | ${dut2s_ip_address} | 172.16.0.2 -| | Set Suite Variable | ${duts_ip_address_prefix} | 24 -| | Set Interface State | ${TG} | ${tgs_to_dut1} | up -| | Set Interface State | ${TG} | ${tgs_to_dut2} | up -| | ${vxlan_dut1}= | Setup VXLAN on DUT | ${DUT1} | ${VNI} | ${dut1s_ip_address} -| | ... | ${dut2s_ip_address} | ${dut1s_to_tg} -| | ... | ${dut1s_to_dut2} | ${dut1s_ip_address} -| | ... | ${duts_ip_address_prefix} -| | Set Suite Variable | ${vxlan_dut1} -| | ${vxlan_dut2}= | Setup VXLAN on DUT | ${DUT2} | ${VNI} | ${dut2s_ip_address} -| | ... | ${dut1s_ip_address} | ${dut2s_to_tg} -| | ... | ${dut2s_to_dut1} | ${dut2s_ip_address} -| | ... | ${duts_ip_address_prefix} -| | Set Suite Variable | ${vxlan_dut2} -| | @{test_nodes}= | Create list | ${DUT1} | ${DUT2} -| | Vpp Nodes Interfaces Ready Wait | ${test_nodes} -# ip arp table must be filled on both nodes with neighbors address -| | VPP IP Probe | ${DUT1} | ${dut1s_to_dut2} | ${dut2s_ip_address} +| | Set Test Variable | ${tgs_to_dut1} +| | Set Test Variable | ${dut1s_to_tg} +| | Set Test Variable | ${tgs_to_dut2} +| | Set Test Variable | ${dut2s_to_tg} +| | Set Test Variable | ${dut1s_to_dut2} +| | Set Test Variable | ${dut2s_to_dut1} +| | Set Test Variable | ${tg} +| | Set Test Variable | ${dut1} +| | Set Test Variable | ${dut2} + +| Interfaces in path are up +| | [Documentation] | *Set UP state on interfaces in path on nodes.* +| | ... +| | Set Interface State | ${tg} | ${tgs_to_dut1} | up +| | Set Interface State | ${tg} | ${tgs_to_dut2} | up +| | Set Interface State | ${dut1} | ${dut1s_to_tg} | up +| | Set Interface State | ${dut1} | ${dut1s_to_dut2} | up +| | Set Interface State | ${dut2} | ${dut2s_to_tg} | up +| | Set Interface State | ${dut2} | ${dut2s_to_dut1} | up +| | Vpp Node Interfaces Ready Wait | ${dut1} +| | Vpp Node Interfaces Ready Wait | ${dut2} + +| IP addresses are set on interfaces +| | [Documentation] | *Set IPv4 addresses on interfaces on DUTs.* +| | ... | If interface index is None then is determines with Get Interface Sw Index +| | ... | It also executes VPP IP Probe to determine MACs to IPs on DUTs +| | ... +| | ... | _Set testcase variables with IP addresses and prefix length:_ +| | ... | - ${dut1s_ip_address} +| | ... | - ${dut2s_ip_address} +| | ... | - ${duts_ip_address_prefix} +| | ... +| | [Arguments] | ${DUT1} | ${DUT1_INT_NAME} | ${DUT1_INT_INDEX} +| | ... | ${DUT2} | ${DUT2_INT_NAME} | ${DUT2_INT_INDEX} +| | Set Test Variable | ${dut1s_ip_address} | 172.16.0.1 +| | Set Test Variable | ${dut2s_ip_address} | 172.16.0.2 +| | Set Test Variable | ${duts_ip_address_prefix} | 24 +| | ${DUT1_INT_INDEX}= | Run Keyword If | ${DUT1_INT_INDEX} is None +| | | ... | Get Interface Sw Index | ${DUT1} | ${DUT1_INT_NAME} +| | | ... | ELSE | Set Variable | ${DUT1_INT_INDEX} +| | ${DUT2_INT_INDEX}= | Run Keyword If | ${DUT2_INT_INDEX} is None +| | | ... | Get Interface Sw Index | ${DUT2} | ${DUT2_INT_NAME} +| | | ... | ELSE | Set Variable | ${DUT2_INT_INDEX} +| | Set Interface Address | ${DUT1} | ${DUT1_INT_INDEX} +| | ... | ${dut1s_ip_address} | ${duts_ip_address_prefix} +| | Set Interface Address | ${DUT2} | ${DUT2_INT_INDEX} +| | ... | ${dut2s_ip_address} | ${duts_ip_address_prefix} +| | VPP IP Probe | ${DUT1} | ${DUT1_INT_NAME} | ${dut2s_ip_address} +| | VPP IP Probe | ${DUT2} | ${DUT2_INT_NAME} | ${dut1s_ip_address} + +| VXLAN interface is created +| | [Arguments] | ${DUT} | ${VNI} | ${SRC_IP} | ${DST_IP} +| | Create VXLAN interface | ${DUT} | ${VNI} | ${SRC_IP} | ${DST_IP} + +| Interfaces are added to BD +| | [Arguments] | ${DUT} | ${BID} | ${INTERFACE_1} | ${INTERFACE_2} +| | Vpp Add L2 Bridge Domain | ${DUT} | ${BID} | ${INTERFACE_1} | ${INTERFACE_2} -| Setup DUT for VXLAN using BD -| | [Arguments] | ${DUT} | ${VNI} | ${INGRESS} | ${vxlan_if_index} -| | Create L2 BD | ${DUT} | ${VNI} -| | Add sw if index To L2 BD | ${DUT} | ${vxlan_if_index} | ${VNI} -| | Add Interface To L2 BD | ${DUT} | ${INGRESS} | ${VNI} +| Interfaces are added to xconnect +| | [Arguments] | ${DUT} | ${INTERFACE_1} | ${INTERFACE_2} +| | L2 setup xconnect on DUT | ${DUT} | ${INTERFACE_1} | ${INTERFACE_2} -| Setup VXLAN on DUT -| | [Arguments] | ${DUT} | ${VNI} | ${SRC_IP} | ${DST_IP} | ${INGRESS} -| | ... | ${EGRESS} | ${IP} | ${PREFIX} -| | Set Interface State | ${DUT} | ${EGRESS} | up -| | Set Interface State | ${DUT} | ${INGRESS} | up -| | Set Interface Address | ${DUT} | ${EGRESS} | ${IP} | ${PREFIX} -| | ${vxlan_if_index}= | Create VXLAN interface | ${DUT} | ${VNI} | ${SRC_IP} -| | ... | ${DST_IP} -| | [Return] | ${vxlan_if_index} +| Vlan interfaces for VXLAN are created +| | [Documentation] | *Create VLAN subinterface on interfaces on DUTs with given VLAN ID.* +| | ... +| | ... | _Set testcase variables with name and index of created interfaces:_ +| | ... | - ${dut1s_vlan_name} +| | ... | - ${dut1s_vlan_index} +| | ... | - ${dut2s_vlan_name} +| | ... | - ${dut2s_vlan_index} +| | ... +| | [Arguments] | ${VLAN} | ${DUT1} | ${INT1} | ${DUT2} | ${INT2} +| | ${dut1s_vlan_name} | ${dut1s_vlan_index}= | Create Vlan Subinterface +| | | ... | ${DUT1} | ${INT1} | ${VLAN} +| | ${dut2s_vlan_name} | ${dut2s_vlan_index}= | Create Vlan Subinterface +| | | ... | ${DUT2} | ${INT2} | ${VLAN} +| | Set Interface State | ${DUT1} | ${dut1s_vlan_index} | up +| | Set Interface State | ${DUT2} | ${dut2s_vlan_index} | up +| | Set Test Variable | ${dut1s_vlan_name} +| | Set Test Variable | ${dut1s_vlan_index} +| | Set Test Variable | ${dut2s_vlan_name} +| | Set Test Variable | ${dut2s_vlan_index} diff --git a/resources/templates/vat/create_vlan_subif.vat b/resources/templates/vat/create_vlan_subif.vat new file mode 100644 index 0000000000..6520aef911 --- /dev/null +++ b/resources/templates/vat/create_vlan_subif.vat @@ -0,0 +1 @@ +create_vlan_subif sw_if_index {sw_if_index} vlan {vlan} \ No newline at end of file diff --git a/tests/suites/bridge_domain/test.robot b/tests/suites/bridge_domain/test.robot index c6a4cd1197..0f5b6977e9 100644 --- a/tests/suites/bridge_domain/test.robot +++ b/tests/suites/bridge_domain/test.robot @@ -34,8 +34,7 @@ | | ${bd_if1} | ${tmp}= | First Ingress Interface | | ${bd_if2} | ${tmp}= | Last Egress Interface | | Vpp l2bd forwarding setup | ${nodes['DUT1']} | ${bd_if1} | ${bd_if2} -| | Send and receive ICMPv4 | ${nodes['TG']} | ${tg_if1} | ${tg_if2} -| | Send and receive ICMPv4 | ${nodes['TG']} | ${tg_if2} | ${tg_if1} +| | Send and receive ICMPv4 bidirectionally | ${nodes['TG']} | ${tg_if1} | ${tg_if2} | Vpp forwards packets via L2 bridge domain in circular topology | | [Tags] | 3_NODE_SINGLE_LINK_TOPO @@ -50,8 +49,7 @@ | | ${tg_if2} | ${tg}= | Next Interface | | Vpp l2bd forwarding setup | ${dut1} | ${dut1_if1} | ${dut1_if2} | | Vpp l2bd forwarding setup | ${dut2} | ${dut2_if1} | ${dut2_if2} -| | Send and receive ICMPv4 | ${tg} | ${tg_if1} | ${tg_if2} -| | Send and receive ICMPv4 | ${tg} | ${tg_if2} | ${tg_if1} +| | Send and receive ICMPv4 bidirectionally | ${tg} | ${tg_if1} | ${tg_if2} | Vpp forwards packets via L2 bridge domain in circular topology with static L2FIB entries | | [Tags] | 3_NODE_SINGLE_LINK_TOPO @@ -69,5 +67,4 @@ | | ... | ${mac} | | Vpp l2bd forwarding setup | ${dut2} | ${dut2_if1} | ${dut2_if2} | ${FALSE} | | ... | ${mac} -| | Send and receive ICMPv4 | ${tg} | ${tg_if1} | ${tg_if2} -| | Send and receive ICMPv4 | ${tg} | ${tg_if2} | ${tg_if1} +| | Send and receive ICMPv4 bidirectionally | ${tg} | ${tg_if1} | ${tg_if2} diff --git a/tests/suites/l2_xconnect/l2_xconnect_untagged.robot b/tests/suites/l2_xconnect/l2_xconnect_untagged.robot index 3370cabaa4..a1682d1062 100644 --- a/tests/suites/l2_xconnect/l2_xconnect_untagged.robot +++ b/tests/suites/l2_xconnect/l2_xconnect_untagged.robot @@ -35,5 +35,4 @@ | | L2 setup xconnect on DUT | ${dut1} | ${dut1_if1} | ${dut1_if2} | | L2 setup xconnect on DUT | ${dut2} | ${dut2_if1} | ${dut2_if2} | | All Vpp Interfaces Ready Wait | ${nodes} -| | Send and receive ICMPv4 | ${tg} | ${tg_if1} | ${tg_if2} -| | Send and receive ICMPv4 | ${tg} | ${tg_if2} | ${tg_if1} +| | Send and receive ICMPv4 bidirectionally | ${tg} | ${tg_if1} | ${tg_if2} diff --git a/tests/suites/vxlan/vxlan_bd_dot1q.robot b/tests/suites/vxlan/vxlan_bd_dot1q.robot new file mode 100644 index 0000000000..8497d7851b --- /dev/null +++ b/tests/suites/vxlan/vxlan_bd_dot1q.robot @@ -0,0 +1,50 @@ +# Copyright (c) 2016 Cisco and/or its affiliates. +# 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. + +*** Settings *** +| Documentation | VXLAN tunnel over Dot1Q tagged IPv4 traffic tests using bridge domain. +| Resource | resources/libraries/robot/default.robot +| Resource | resources/libraries/robot/vxlan.robot +| Resource | resources/libraries/robot/l2_traffic.robot +| Library | resources.libraries.python.Trace +| Force Tags | 3_NODE_SINGLE_LINK_TOPO | VM_ENV | HW_ENV +| Test Setup | Run Keywords | Setup all DUTs before test +| ... | AND | Setup all TGs before traffic script +| Test Teardown | Show Packet Trace on All DUTs | ${nodes} + +*** Variables *** +| ${VNI}= | 23 +| ${BID}= | 23 +| ${VLAN}= | 10 + +*** Test Cases *** +| VPP can encapsulate L2 in VXLAN over IPv4 over Dot1Q +| | Given Path for VXLAN testing is set +| | ... | ${nodes['TG']} | ${nodes['DUT1']} | ${nodes['DUT2']} +| | And Interfaces in path are up +| | And Vlan interfaces for VXLAN are created | ${VLAN} +| | ... | ${dut1} | ${dut1s_to_dut2} +| | ... | ${dut2} | ${dut2s_to_dut1} +| | And IP addresses are set on interfaces +| | ... | ${dut1} | ${dut1s_vlan_name} | ${dut1s_vlan_index} +| | ... | ${dut2} | ${dut2s_vlan_name} | ${dut2s_vlan_index} +| | ${dut1s_vxlan}= | When Create VXLAN interface | ${dut1} | ${VNI} +| | | ... | ${dut1s_ip_address} | ${dut2s_ip_address} +| | And Interfaces are added to BD | ${dut1} | ${BID} +| | ... | ${dut1s_to_tg} | ${dut1s_vxlan} +| | ${dut2s_vxlan}= | And Create VXLAN interface | ${dut2} | ${VNI} +| | | ... | ${dut2s_ip_address} | ${dut1s_ip_address} +| | And Interfaces are added to BD | ${dut2} | ${BID} +| | ... | ${dut2s_to_tg} | ${dut2s_vxlan} +| | Then Send and receive ICMPv4 bidirectionally +| | ... | ${tg} | ${tgs_to_dut1} | ${tgs_to_dut2} diff --git a/tests/suites/vxlan/vxlan_bd_untagged.robot b/tests/suites/vxlan/vxlan_bd_untagged.robot index 2e5d27a721..1eea3e90fa 100644 --- a/tests/suites/vxlan/vxlan_bd_untagged.robot +++ b/tests/suites/vxlan/vxlan_bd_untagged.robot @@ -12,24 +12,34 @@ # limitations under the License. *** Settings *** -| Documentation | VXLAN tunnel untagged traffic tests using bridge domain. +| Documentation | VXLAN tunnel over untagged IPv4 traffic tests using bridge domain. | Resource | resources/libraries/robot/default.robot | Resource | resources/libraries/robot/vxlan.robot | Resource | resources/libraries/robot/l2_traffic.robot +| Library | resources.libraries.python.Trace | Force Tags | 3_NODE_SINGLE_LINK_TOPO | VM_ENV | HW_ENV -| Suite Setup | Run Keywords | Setup all DUTs before test -| ... | AND | Setup all TGs before traffic script -| ... | AND | Setup VXLAN tunnel on nodes | ${nodes['TG']} -| | ... | ${nodes['DUT1']} | ${nodes['DUT2']} | ${VNI} +| Test Setup | Run Keywords | Setup all DUTs before test +| ... | AND | Setup all TGs before traffic script +| Test Teardown | Show Packet Trace on All DUTs | ${nodes} *** Variables *** | ${VNI}= | 23 +| ${BID}= | 23 *** Test Cases *** -| VPP can encapsulate L2 in VXLAN over V4 -| | Setup DUT for VXLAN using BD | ${nodes['DUT1']} | ${23} | ${dut1s_to_tg} -| | ... | ${vxlan_dut1} -| | Setup DUT for VXLAN using BD | ${nodes['DUT2']} | ${23} | ${dut2s_to_tg} -| | ... | ${vxlan_dut2} -| | Send and receive ICMPv4 | ${nodes['TG']} | ${tgs_to_dut1} | ${tgs_to_dut2} -| | Send and receive ICMPv4 | ${nodes['TG']} | ${tgs_to_dut2} | ${tgs_to_dut1} +| VPP can pass IPv4 bidirectionally through VXLAN +| | Given Path for VXLAN testing is set +| | ... | ${nodes['TG']} | ${nodes['DUT1']} | ${nodes['DUT2']} +| | And Interfaces in path are up +| | And IP addresses are set on interfaces | ${dut1} | ${dut1s_to_dut2} | ${NONE} +| | ... | ${dut2} | ${dut2s_to_dut1} | ${NONE} +| | ${dut1s_vxlan}= | When Create VXLAN interface | ${dut1} | ${VNI} +| | | ... | ${dut1s_ip_address} | ${dut2s_ip_address} +| | And Interfaces are added to BD | ${dut1} | ${BID} +| | ... | ${dut1s_to_tg} | ${dut1s_vxlan} +| | ${dut2s_vxlan}= | And Create VXLAN interface | ${dut2} | ${VNI} +| | | ... | ${dut2s_ip_address} | ${dut1s_ip_address} +| | And Interfaces are added to BD | ${dut2} | ${BID} +| | ... | ${dut2s_to_tg} | ${dut2s_vxlan} +| | Then Send and receive ICMPv4 bidirectionally +| | ... | ${tg} | ${tgs_to_dut1} | ${tgs_to_dut2} diff --git a/tests/suites/vxlan/vxlan_xconnect_untagged.robot b/tests/suites/vxlan/vxlan_xconnect_untagged.robot index c3d2a3c0a4..2a57468b70 100644 --- a/tests/suites/vxlan/vxlan_xconnect_untagged.robot +++ b/tests/suites/vxlan/vxlan_xconnect_untagged.robot @@ -12,22 +12,33 @@ # limitations under the License. *** Settings *** -| Documentation | VXLAN tunnel untagged traffic tests using xconnect. +| Documentation | VXLAN tunnel over untagged IPv4 traffic tests using xconnect. | Resource | resources/libraries/robot/default.robot | Resource | resources/libraries/robot/vxlan.robot | Resource | resources/libraries/robot/l2_traffic.robot +| Library | resources.libraries.python.Trace | Force Tags | 3_NODE_SINGLE_LINK_TOPO | VM_ENV | HW_ENV -| Suite Setup | Run Keywords | Setup all DUTs before test -| ... | AND | Setup all TGs before traffic script -| ... | AND | Setup VXLAN tunnel on nodes | ${nodes['TG']} -| | ... | ${nodes['DUT1']} | ${nodes['DUT2']} | ${VNI} +| Test Setup | Run Keywords | Setup all DUTs before test +| ... | AND | Setup all TGs before traffic script +| Test Teardown | Show Packet Trace on All DUTs | ${nodes} *** Variables *** | ${VNI}= | 24 *** Test Cases *** -| VPP can pass IPv4 bidirectionally through VXLAN tunnel using l2-xconnect -| | L2 setup xconnect on DUT | ${nodes['DUT1']} | ${dut1s_to_tg} | ${vxlan_dut1} -| | L2 setup xconnect on DUT | ${nodes['DUT2']} | ${dut2s_to_tg} | ${vxlan_dut2} -| | Send and receive ICMPv4 | ${nodes['TG']} | ${tgs_to_dut1} | ${tgs_to_dut2} -| | Send and receive ICMPv4 | ${nodes['TG']} | ${tgs_to_dut2} | ${tgs_to_dut1} +| VPP can pass IPv4 bidirectionally through VXLAN +| | Given Path for VXLAN testing is set +| | ... | ${nodes['TG']} | ${nodes['DUT1']} | ${nodes['DUT2']} +| | And Interfaces in path are up +| | And IP addresses are set on interfaces | ${dut1} | ${dut1s_to_dut2} | ${NONE} +| | ... | ${dut2} | ${dut2s_to_dut1} | ${NONE} +| | ${dut1s_vxlan}= | When Create VXLAN interface | ${dut1} | ${VNI} +| | | ... | ${dut1s_ip_address} | ${dut2s_ip_address} +| | And Interfaces are added to xconnect | ${dut1} +| | ... | ${dut1s_to_tg} | ${dut1s_vxlan} +| | ${dut2s_vxlan}= | And Create VXLAN interface | ${dut2} | ${VNI} +| | | ... | ${dut2s_ip_address} | ${dut1s_ip_address} +| | And Interfaces are added to xconnect | ${dut2} +| | ... | ${dut2s_to_tg} | ${dut2s_vxlan} +| | Then Send and receive ICMPv4 bidirectionally +| | ... | ${tg} | ${tgs_to_dut1} | ${tgs_to_dut2} -- 2.16.6 From ee768ca7451bb49f0a591bedf3360b332d983f64 Mon Sep 17 00:00:00 2001 From: Jan Gelety Date: Mon, 11 Apr 2016 18:08:13 +0200 Subject: [PATCH 10/16] Update of bootstrap.sh file. - updated vpp build version - VIRL image switched to double-ring-nested topology - removed exclusion of 3_NODE_DOUBLE_LINK_TOPO test cases - included test cases with tags: - VM_ENV and 3_NODE_SINGLE_LINK_TOPO - VM_ENV and 3_NODE_DOUBLE_LINK_TOPO - added missing tags to TC VPP reports interfaces in Bridge Domain test suite Change-Id: Ia6a7a54ef9ad6245835b2762c303935fe39f4079 Signed-off-by: Jan Gelety --- bootstrap.sh | 18 +++++++++--------- tests/suites/bridge_domain/test.robot | 1 + 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/bootstrap.sh b/bootstrap.sh index 99458eb7fb..17d9e17e99 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -70,12 +70,12 @@ if [ "${#}" -ne "0" ]; then echo ${arr[0]} else rm -f *.deb - wget -q "https://nexus.fd.io/service/local/repositories/fd.io.dev/content/io/fd/vpp/vpp/1.0.0-229~gb1df169_amd64/vpp-1.0.0-229~gb1df169_amd64.deb" || exit - wget -q "https://nexus.fd.io/service/local/repositories/fd.io.dev/content/io/fd/vpp/vpp-dbg/1.0.0-229~gb1df169_amd64/vpp-dbg-1.0.0-229~gb1df169_amd64.deb" || exit - wget -q "https://nexus.fd.io/service/local/repositories/fd.io.dev/content/io/fd/vpp/vpp-dev/1.0.0-229~gb1df169_amd64/vpp-dev-1.0.0-229~gb1df169_amd64.deb" || exit - wget -q "https://nexus.fd.io/service/local/repositories/fd.io.dev/content/io/fd/vpp/vpp-dpdk-dev/1.0.0-229~gb1df169_amd64/vpp-dpdk-dev-1.0.0-229~gb1df169_amd64.deb" || exit - wget -q "https://nexus.fd.io/service/local/repositories/fd.io.dev/content/io/fd/vpp/vpp-dpdk-dkms/1.0.0-229~gb1df169_amd64/vpp-dpdk-dkms-1.0.0-229~gb1df169_amd64.deb" || exit - wget -q "https://nexus.fd.io/service/local/repositories/fd.io.dev/content/io/fd/vpp/vpp-lib/1.0.0-229~gb1df169_amd64/vpp-lib-1.0.0-229~gb1df169_amd64.deb" || exit + wget -q "https://nexus.fd.io/service/local/repositories/fd.io.dev/content/io/fd/vpp/vpp/1.0.0-304~gd530445_amd64/vpp-1.0.0-304~gd530445_amd64.deb" || exit + wget -q "https://nexus.fd.io/service/local/repositories/fd.io.dev/content/io/fd/vpp/vpp-dbg/1.0.0-304~gd530445_amd64/vpp-dbg-1.0.0-304~gd530445_amd64.deb" || exit + wget -q "https://nexus.fd.io/service/local/repositories/fd.io.dev/content/io/fd/vpp/vpp-dev/1.0.0-304~gd530445_amd64/vpp-dev-1.0.0-304~gd530445_amd64.deb" || exit + wget -q "https://nexus.fd.io/service/local/repositories/fd.io.dev/content/io/fd/vpp/vpp-dpdk-dev/1.0.0-304~gd530445_amd64/vpp-dpdk-dev-1.0.0-304~gd530445_amd64.deb" || exit + wget -q "https://nexus.fd.io/service/local/repositories/fd.io.dev/content/io/fd/vpp/vpp-dpdk-dkms/1.0.0-304~gd530445_amd64/vpp-dpdk-dkms-1.0.0-304~gd530445_amd64.deb" || exit + wget -q "https://nexus.fd.io/service/local/repositories/fd.io.dev/content/io/fd/vpp/vpp-lib/1.0.0-304~gd530445_amd64/vpp-lib-1.0.0-304~gd530445_amd64.deb" || exit fi VPP_DEBS=(*.deb) @@ -112,7 +112,7 @@ function stop_virl_simulation { VIRL_SID=$(ssh -i priv_key -o StrictHostKeyChecking=no \ ${VIRL_USERNAME}@${VIRL_SERVER} \ - "/home/jenkins-in/testcase-infra/bin/start-testcase -c simple-ring ${VPP_DEBS_FULL[@]}") + "/home/jenkins-in/testcase-infra/bin/start-testcase -c double-ring-nested ${VPP_DEBS_FULL[@]}") retval=$? if [ "$?" -ne "0" ]; then echo "VIRL simulation start failed" @@ -150,8 +150,8 @@ pip install -r requirements.txt PYTHONPATH=`pwd` pybot -L TRACE \ -v TOPOLOGY_PATH:topologies/enabled/topology.yaml \ - --exclude 3_node_double_link_topoNOT3_node_single_link_topo \ - --include VM_ENV \ + --include vm_envAND3_node_single_link_topo \ + --include vm_envAND3_node_double_link_topo \ --exclude PERFTEST \ --noncritical EXPECTED_FAILING \ tests/ diff --git a/tests/suites/bridge_domain/test.robot b/tests/suites/bridge_domain/test.robot index 0f5b6977e9..a2cf0c0ce3 100644 --- a/tests/suites/bridge_domain/test.robot +++ b/tests/suites/bridge_domain/test.robot @@ -23,6 +23,7 @@ *** Test Cases *** | VPP reports interfaces +| | [Tags] | 3_NODE_DOUBLE_LINK_TOPO | 3_NODE_SINGLE_LINK_TOPO | | VPP reports interfaces on | ${nodes['DUT1']} | Vpp forwards packets via L2 bridge domain 2 ports -- 2.16.6 From 60831dae7202e1a85fc0cc5037d72b25c09cf756 Mon Sep 17 00:00:00 2001 From: pmikus Date: Thu, 14 Apr 2016 13:30:12 +0200 Subject: [PATCH 11/16] Change the performance result message - change the result message of performance search to more verbose level (rate per stream, rate total, throughput in Gbps) Change-Id: I7562d6d33c525654131c75ebcc776cba438f68f4 Signed-off-by: pmikus --- resources/libraries/robot/performance.robot | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/resources/libraries/robot/performance.robot b/resources/libraries/robot/performance.robot index a3cb2e53af..e8a56adc0e 100644 --- a/resources/libraries/robot/performance.robot +++ b/resources/libraries/robot/performance.robot @@ -91,6 +91,7 @@ | | Teardown traffic generator | ${tg} | Find NDR using linear search and pps +| | [Documentation] | Find throughput by using RFC2544 linear search | | [Arguments] | ${framesize} | ${start_rate} | ${step_rate} | | ... | ${topology_type} | ${min_rate} | ${max_rate} | | Set Duration | 60 @@ -99,10 +100,11 @@ | | Set Search Frame Size | ${framesize} | | Set Search Rate Type pps | | Linear Search | ${start_rate} | ${topology_type} -| | ${result_rate}= | Verify Search Result -| | Set Test Message | FINAL_RATE: ${result_rate} pps +| | ${rate_per_stream}= | Verify Search Result +| | Display result of NDR search | ${rate_per_stream} | ${framesize} | 2 | Find NDR using binary search and pps +| | [Documentation] | Find throughput by using RFC2544 binary search | | [Arguments] | ${framesize} | ${binary_min} | ${binary_max} | | ... | ${topology_type} | ${min_rate} | ${max_rate} | ${threshold} | | Set Duration | 60 @@ -110,10 +112,12 @@ | | Set Search Rate Type pps | | Set Binary Convergence Threshold | ${threshold} | | Binary Search | ${binary_min} | ${binary_max} | ${topology_type} -| | ${result_rate}= | Verify Search Result -| | Set Test Message | FINAL_RATE: ${result_rate} pps +| | ${rate_per_stream}= | Verify Search Result +| | Display result of NDR search | ${rate_per_stream} | ${framesize} | 2 | Find NDR using combined search and pps +| | [Documentation] | Find throughput by using RFC2544 combined search +| | ... | (linear + binary) | | [Arguments] | ${framesize} | ${start_rate} | ${step_rate} | | ... | ${topology_type} | ${min_rate} | ${max_rate} | ${threshold} | | Set Duration | 60 @@ -123,8 +127,19 @@ | | Set Search Rate Type pps | | Set Binary Convergence Threshold | ${threshold} | | Combined Search | ${start_rate} | ${topology_type} -| | ${result_rate}= | Verify Search Result -| | Set Test Message | FINAL_RATE: ${result_rate} pps +| | ${rate_per_stream}= | Verify Search Result +| | Display result of NDR search | ${rate_per_stream} | ${framesize} | 2 + +| Display result of NDR search +| | [Documentation] | Display result of NDR search in packet per seconds (total +| | ... | and per stream) and Gbps +| | [Arguments] | ${rate_per_stream} | ${framesize} | ${nr_streams} +| | ${rate_total}= | Evaluate | ${rate_per_stream}*${nr_streams} +| | ${bandwidth_total}= | Evaluate | ${rate_total}*(${framesize}+20)*8/(10**9) +| | Set Test Message | FINAL_RATE: ${rate_total} pps +| | Set Test Message | (${nr_streams}x ${rate_per_stream} pps) | append=yes +| | Set Test Message | FINAL_BANDWIDTH: ${bandwidth_total} Gbps | append=yes + | Traffic should pass with no loss | | [Arguments] | ${duration} | ${rate} | ${framesize} | ${topology_type} -- 2.16.6 From 43277be7e77afe0363f62c97c687bcfa506ee4b8 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Mon, 4 Apr 2016 16:11:46 +0200 Subject: [PATCH 12/16] Update Qemu library Incorporated changes from nested VM on VIRL. Change-Id: I76ceeb2dde635c2cf0d3a53d29bf24fed72a5437 Signed-off-by: Matus Fabian --- docs/qemu_vpp_vm | 49 +++++++++++++++++++++++++++++ resources/libraries/bash/qemu_build.sh | 17 +++++++--- resources/libraries/python/QemuUtils.py | 56 ++++++++++++++++++++++++++++----- 3 files changed, 110 insertions(+), 12 deletions(-) create mode 100644 docs/qemu_vpp_vm diff --git a/docs/qemu_vpp_vm b/docs/qemu_vpp_vm new file mode 100644 index 0000000000..4b47f90009 --- /dev/null +++ b/docs/qemu_vpp_vm @@ -0,0 +1,49 @@ +# Copyright (c) 2016 Cisco and/or its affiliates. +# 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. + +QEMU is used for VPP-VM testing enviroment. You need to run qemu-2.2.1 or newer +in order to Vhos-user support. In CSIT VIRL setup DUT has preinstalled QEMU and +have small VM image "/var/lib/vm/vhost-nested.img". QEMU binary path must +be "/opt/qemu/bin/qemu-system-x86_64". You can use following script to replicate +QEMU setup on DUT "resources/libraries/bash/qemu_build.sh" for local testing, +out of LF's VM setup. VM image must have installed at least qemu-guest-agent, +sshd, bridge-utils and VirtIO support. Note that VPP must be installed before +starting QEMU, because VPP will do the hugepages configuration as part of it's +installation process. Username/password for the VM must be cisco/cisco and +NOPASSWD sudo access. The interface naming is based on driver (management +interface type is Intel E1000), all E1000 interfaces will be named mgmt and +all VirtIO interfaces will be named virtio. In VM +"/etc/init.d/qemu-guest-agent" you must set "TRANSPORT=isa-serial:/dev/ttyS1" +because ttyS0 is used by serial console and ttyS1 is dedicated for +qemu-guest-agent in QEMU setup. +There is python library for QEMU setup, start and some utilities +"resources/libraries/python/QemuUtils.py" and keyword "Stop and Clear QEMU" for +teardown in resources/libraries/robot/qemu.robot. "Qemu Start" setup one +management interface by default. You can add Vhost-user interfaces by +"Qemu Add Vhost User If" keyword. +Example usage in robot framework test: + +*** Settings *** +| Resource | resources/libraries/robot/qemu.robot + +*** Test Cases *** +| VM test +| | [Tags] | VPP_VM_ENV +| | Qemu Set Node | ${nodes['DUT1']} +| | Qemu Add Vhost User If | /tmp/vhost_sock +| | ${vm}= | Set Variable | ${None} +| | ${vm}= | Qemu Start +| | [Teardown] | Stop and Clear QEMU | ${dut1} | ${vm} + +Note that VPP must be started and cofigured before starting the VM when using +Vhost-user interfaces. diff --git a/resources/libraries/bash/qemu_build.sh b/resources/libraries/bash/qemu_build.sh index 449cce664f..7b8ac41251 100644 --- a/resources/libraries/bash/qemu_build.sh +++ b/resources/libraries/bash/qemu_build.sh @@ -12,27 +12,34 @@ # See the License for the specific language governing permissions and # limitations under the License. +QEMU_BUILD_DIR="/tmp/qemu-2.2.1" +QEMU_INSTALL_DIR="/opt/qemu" + echo echo Downloading QEMU source echo -sudo rm -rf /tmp/qemu-2.2.1 +sudo rm -rf ${QEMU_BUILD_DIR} +sudo rm -rf ${QEMU_INSTALL_DIR} cd /tmp wget -q http://wiki.qemu-project.org/download/qemu-2.2.1.tar.bz2 || exit echo echo Extracting QEMU echo -tar xjf qemu-2.2.1.tar.bz2 || exit -rm qemu-2.2.1.tar.bz2 +mkdir ${QEMU_BUILD_DIR} +tar --strip-components 1 -xjf qemu-2.2.1.tar.bz2 -C ${QEMU_BUILD_DIR} || exit +rm -f qemu-2.2.1.tar.bz2 echo echo Building QEMU echo -cd qemu-2.2.1 +cd ${QEMU_BUILD_DIR} +sudo mkdir ${QEMU_INSTALL_DIR} mkdir build cd build -../configure --target-list=x86_64-softmmu || exit +../configure --target-list=x86_64-softmmu --prefix=${QEMU_INSTALL_DIR} || exit make -j`nproc` || exit +sudo make install || exit echo echo QEMU ready diff --git a/resources/libraries/python/QemuUtils.py b/resources/libraries/python/QemuUtils.py index 7249b5e206..7f74106177 100644 --- a/resources/libraries/python/QemuUtils.py +++ b/resources/libraries/python/QemuUtils.py @@ -25,7 +25,7 @@ from resources.libraries.python.topology import NodeType class QemuUtils(object): """QEMU utilities.""" - __QEMU_BIN = '/tmp/qemu-2.2.1/build/x86_64-softmmu/qemu-system-x86_64' + __QEMU_BIN = '/opt/qemu/bin/qemu-system-x86_64' # QEMU Machine Protocol socket __QMP_SOCK = '/tmp/qmp.sock' # QEMU Guest Agent socket @@ -41,10 +41,14 @@ class QemuUtils(object): '-machine pc-1.0,accel=kvm,usb=off,mem-merge=off ' \ '-net nic,macaddr=52:54:00:00:02:01' self._qemu_opt['ssh_fwd_port'] = 10022 + # Default serial console port + self._qemu_opt['serial_port'] = 4556 # Default 512MB virtual RAM self._qemu_opt['mem_size'] = 512 # Default huge page mount point, required for Vhost-user interfaces. self._qemu_opt['huge_mnt'] = '/mnt/huge' + # Default image for CSIT virl setup + self._qemu_opt['disk_image'] = '/var/lib/vm/vhost-nested.img' # VM node info dict self._vm_info = { 'type': NodeType.VM, @@ -82,6 +86,14 @@ class QemuUtils(object): self._qemu_opt['ssh_fwd_port'] = fwd_port self._vm_info['port'] = fwd_port + def qemu_set_serial_port(self, port): + """Set serial console port. + + :param port: Serial console port. + :type port: int + """ + self._qemu_opt['serial_port'] = port + def qemu_set_mem_size(self, mem_size): """Set virtual RAM size. @@ -241,6 +253,8 @@ class QemuUtils(object): self._qemu_opt['disk_image'], self._node['host'])) # Create MAC-name dict for interface in interfaces: + if 'hardware-address' not in interface: + continue mac_name[interface['hardware-address']] = interface['name'] # Match interface by MAC and save interface name for interface in self._vm_info['interfaces'].values(): @@ -291,7 +305,8 @@ class QemuUtils(object): :return: VM node info :rtype: dict - .. note:: First set at least disk image and node to run QEMU on. + .. note:: First set at least node to run QEMU on. + .. warning:: Starts only one VM on the node. """ # SSH forwarding ssh_fwd = '-net user,hostfwd=tcp::{0}-:22'.format( @@ -303,15 +318,20 @@ class QemuUtils(object): self._huge_page_check() # Setup QMP via unix socket qmp = '-qmp unix:{0},server,nowait'.format(self.__QMP_SOCK) - # Setup QGA via chardev (unix socket) and virtio-serial channel + # Setup serial console + serial = '-chardev socket,host=127.0.0.1,port={0},id=gnc0,server,' \ + 'nowait -device isa-serial,chardev=gnc0'.format( + self._qemu_opt.get('serial_port')) + # Setup QGA via chardev (unix socket) and isa-serial channel qga = '-chardev socket,path=/tmp/qga.sock,server,nowait,id=qga0 ' \ - '-device virtio-serial ' \ - '-device virtserialport,chardev=qga0,name=org.qemu.guest_agent.0' + '-device isa-serial,chardev=qga0' + # Graphic setup + graphic = '-monitor none -display none -vga none' # Run QEMU - cmd = '{0} {1} {2} {3} {4} -hda {5} {6} {7}'.format( + cmd = '{0} {1} {2} {3} {4} -hda {5} {6} {7} {8} {9}'.format( self.__QEMU_BIN, self._qemu_opt.get('smp'), mem, ssh_fwd, self._qemu_opt.get('options'), - self._qemu_opt.get('disk_image'), qmp, qga) + self._qemu_opt.get('disk_image'), qmp, serial, qga, graphic) (ret_code, _, stderr) = self._ssh.exec_command_sudo(cmd, timeout=300) if int(ret_code) != 0: logger.debug('QEMU start failed {0}'.format(stderr)) @@ -358,6 +378,10 @@ class QemuUtils(object): def qemu_clear_socks(self): """Remove all sockets created by QEMU.""" + # If serial console port still open kill process + cmd = 'fuser -k {}/tcp'.format(self._qemu_opt.get('serial_port')) + self._ssh.exec_command_sudo(cmd) + # Delete all created sockets for sock in self._socks: cmd = 'rm -f {}'.format(sock) self._ssh.exec_command_sudo(cmd) @@ -365,6 +389,24 @@ class QemuUtils(object): def qemu_system_status(self): """Return current VM status. + VM should be in following status: + + - debug: QEMU running on a debugger + - finish-migrate: paused to finish the migration process + - inmigrate: waiting for an incoming migration + - internal-error: internal error has occurred + - io-error: the last IOP has failed + - paused: paused + - postmigrate: paused following a successful migrate + - prelaunch: QEMU was started with -S and guest has not started + - restore-vm: paused to restore VM state + - running: actively running + - save-vm: paused to save the VM state + - shutdown: shut down (and -no-shutdown is in use) + - suspended: suspended (ACPI S3) + - watchdog: watchdog action has been triggered + - guest-panicked: panicked as a result of guest OS panic + :return: VM status. :rtype: str """ -- 2.16.6 From c1bdb7115f12e7d4ec586ec0673fd19dce3a2414 Mon Sep 17 00:00:00 2001 From: pmikus Date: Thu, 7 Apr 2016 16:36:31 +0200 Subject: [PATCH 13/16] Multicore VPP setup for performance testing - add multithread TAGS documentation - add methods to VppConfigGenerator for RSS configuration - create KW for multithread setup - create sample test case using multithread vpp setup - add Documentation into TCs Change-Id: Id40862490d49380dc76d1d3ce39314603f983fd3 Signed-off-by: pmikus --- docs/tag_documentation.rst | 43 +++++++++ resources/libraries/python/VppConfigGenerator.py | 47 +++++++++- resources/libraries/robot/default.robot | 54 +++++++++++ tests/suites/performance/long_bridge_domain.robot | 100 +++++++++++++++++---- tests/suites/performance/long_ipv4.robot | 99 ++++++++++++++++---- tests/suites/performance/long_xconnect.robot | 86 +++++++++++++++--- tests/suites/performance/short_bridge_domain.robot | 81 ++++++++++++++--- tests/suites/performance/short_ipv4.robot | 81 ++++++++++++++--- tests/suites/performance/short_xconnect.robot | 82 ++++++++++++++--- 9 files changed, 596 insertions(+), 77 deletions(-) diff --git a/docs/tag_documentation.rst b/docs/tag_documentation.rst index 699337e7db..dd9e2ecc91 100644 --- a/docs/tag_documentation.rst +++ b/docs/tag_documentation.rst @@ -41,3 +41,46 @@ VM_ENV VPP_VM_ENV DUTs with VPP and capable of running Virtual Machine. + +DUT Setup TAGs +-------------- + +1_THREAD_NOHTT_RSS_1 + 1 worker thread pinned to dedicated core without use of Hyper-threading + technology with 1 thread per interface. Main thread pinned to core 0. + +2_THREAD_NOHTT_RSS_1 + 2 worker threads each pinned to dedicated core without use of Hyper-threading + technology with 1 thread per interface. Main thread pinned to core 0. + +4_THREAD_NOHTT_RSS_2 + 4 worker threads each pinned to dedicated core without use of Hyper-threading + technology with 2 threads per interface. Main thread pinned to core 0. + +6_THREAD_NOHTT_RSS_3 + 6 worker threads each pinned to dedicated core without use of Hyper-threading + technology with 3 threads per interface. Main thread pinned to core 0. + +8_THREAD_HTT_RSS_4 + 8 worker threads each pinned to dedicated core without use of Hyper-threading + technology with 4 threads per interface. Main thread pinned to core 0. + +SINGLE_THREAD + All single threaded test cases. + +MULTI_THREAD + All test cases with more then one thread. + +Performance testing TAGs +------------------------ + +PERFTEST + All performance test cases. + +PERFTEST_SHORT + Performance of DUT should pass specific value. Each test case run is + executed for 10 seconds. + +PERFTEST_LONG + Find performance of DUT based on RFC2544 with linear/binary/combined + search. Each test case run is executed for 60 seconds. diff --git a/resources/libraries/python/VppConfigGenerator.py b/resources/libraries/python/VppConfigGenerator.py index 14be90cd14..6085882e8e 100644 --- a/resources/libraries/python/VppConfigGenerator.py +++ b/resources/libraries/python/VppConfigGenerator.py @@ -45,14 +45,15 @@ unix {{ api-trace {{ on }} -{{heapsizeconfig}} +{heapsizeconfig} cpu {{ {cpuconfig} }} dpdk {{ - socket-mem {{socketmemconfig}} + socket-mem {socketmemconfig} {pciconfig} +{rssconfig} }} """ # End VPP configuration template. @@ -160,6 +161,26 @@ class VppConfigGenerator(object): logger.debug('Setting hostname {} Heap Size config to {}'.\ format(hostname, heapsize_config)) + def add_rss_config(self, node, rss_config): + """Add RSS configuration for node. + + :param node: DUT node + :param rss_config: RSS configuration, as a string + :type node: dict + :type rss_config: string + :return: nothing + """ + if node['type'] != NodeType.DUT: + raise ValueError('Node type is not a DUT') + hostname = Topology.get_node_hostname(node) + if not hostname in self._nodeconfig: + self._nodeconfig[hostname] = {} + if not 'rss_config' in self._nodeconfig[hostname]: + self._nodeconfig[hostname]['rss_config'] = [] + self._nodeconfig[hostname]['rss_config'].append(rss_config) + logger.debug('Setting hostname {} RSS config to {}'.\ + format(hostname, rss_config)) + def remove_all_pci_devices(self, node): """Remove PCI device configuration from node. @@ -220,6 +241,21 @@ class VppConfigGenerator(object): logger.debug('Clearing Heap Size config for hostname {}.'.\ format(hostname)) + def remove_rss_config(self, node): + """Remove RSS configuration from node. + + :param node: DUT node + :type: node: dict + :return: nothing + """ + if node['type'] != NodeType.DUT: + raise ValueError('Node type is not a DUT') + hostname = Topology.get_node_hostname(node) + if hostname in self._nodeconfig: + self._nodeconfig[hostname]['rss_config'] = [] + logger.debug('Clearing RSS config for hostname {}.'.\ + format(hostname)) + def apply_config(self, node, waittime=5, retries=12): """Generate and apply VPP configuration for node. @@ -242,6 +278,7 @@ class VppConfigGenerator(object): pciconfig = "" socketmemconfig = DEFAULT_SOCKETMEM_CONFIG heapsizeconfig = "" + rssconfig = "" if hostname in self._nodeconfig: cfg = self._nodeconfig[hostname] @@ -258,10 +295,14 @@ class VppConfigGenerator(object): heapsizeconfig = "\nheapsize {}\n".\ format(cfg['heapsize_config']) + if 'rss_config' in cfg: + rssconfig = " " + "\n ".join(cfg['rss_config']) + vppconfig = VPP_CONFIG_TEMPLATE.format(cpuconfig=cpuconfig, pciconfig=pciconfig, socketmemconfig=socketmemconfig, - heapsizeconfig=heapsizeconfig) + heapsizeconfig=heapsizeconfig, + rssconfig=rssconfig) logger.debug('Writing VPP config to host {}: "{}"'.format(hostname,\ vppconfig)) diff --git a/resources/libraries/robot/default.robot b/resources/libraries/robot/default.robot index dae515423b..51cafdff96 100644 --- a/resources/libraries/robot/default.robot +++ b/resources/libraries/robot/default.robot @@ -16,6 +16,7 @@ | Library | resources.libraries.python.topology.Topology | Library | resources.libraries.python.DUTSetup | Library | resources.libraries.python.TGSetup +| Library | resources/libraries/python/VppConfigGenerator.py | Library | Collections *** Keywords *** @@ -33,3 +34,56 @@ | | ${duts}= | Get Matches | ${nodes} | DUT* | | :FOR | ${dut} | IN | @{duts} | | | Vpp show stats | ${nodes['${dut}']} + +| Setup '${m}' worker threads and rss '${n}' without HTT on all DUTs +| | [Documentation] | Setup M worker threads without HTT and rss N in startup +| | ... | configuration of VPP on all DUTs +| | ${cpu}= | Catenate | main-core | 0 | corelist-workers +| | ${cpu}= | Run Keyword If | '${m}' == '1' | Catenate | ${cpu} | 1 +| | ... | ELSE IF | '${m}' == '2' | Catenate | ${cpu} | 1-2 +| | ... | ELSE IF | '${m}' == '4' | Catenate | ${cpu} | 1-4 +| | ... | ELSE IF | '${m}' == '6' | Catenate | ${cpu} | 1-6 +| | ... | ELSE | Fail | Not supported combination +| | ${rss}= | Catenate | rss | ${n} +| | Setup worker threads and rss on all DUTs | ${cpu} | ${rss} + +| Setup '${m}' worker threads and rss '${n}' with HTT on all DUTs +| | [Documentation] | Setup M worker threads with HTT and rss N in startup +| | ... | configuration of VPP on all DUTs +| | ${cpu}= | Catenate | main-core | 0 | corelist-workers +| | ${cpu}= | Run Keyword If | '${m}' == '2' | Catenate | ${cpu} | 1,10 +| | ... | ELSE IF | '${m}' == '4' | Catenate | ${cpu} | 1-2,10-11 +| | ... | ELSE IF | '${m}' == '6' | Catenate | ${cpu} | 1-3,10-12 +| | ... | ELSE IF | '${m}' == '8' | Catenate | ${cpu} | 1-4,10-13 +| | ... | ELSE | Fail | Not supported combination +| | ${rss}= | Catenate | rss | ${n} +| | Setup worker threads and rss on all DUTs | ${cpu} | ${rss} + +| Setup worker threads and rss on all DUTs +| | [Documentation] | Setup worker threads and rss in startup configuration of +| | ... | VPP on all DUTs +| | [Arguments] | ${cpu} | ${rss} +| | ${duts}= | Get Matches | ${nodes} | DUT* +| | :FOR | ${dut} | IN | @{duts} +| | | Add CPU config | ${nodes['${dut}']} +| | | ... | ${cpu} +| | | Add PCI device | ${nodes['${dut}']} +| | | Add RSS config | ${nodes['${dut}']} +| | | ... | ${rss} +| | | Apply config | ${nodes['${dut}']} + +| Reset startup configuration of VPP on all DUTs +| | [Documentation] | Reset startup configuration of VPP on all DUTs +| | ${cpu}= | Catenate | main-core | 1 +| | ${duts}= | Get Matches | ${nodes} | DUT* +| | :FOR | ${dut} | IN | @{duts} +| | | Remove All PCI Devices | ${nodes['${dut}']} +| | | Remove All CPU Config | ${nodes['${dut}']} +| | | Remove Socketmem Config | ${nodes['${dut}']} +| | | Remove Heapsize Config | ${nodes['${dut}']} +| | | Remove RSS Config | ${nodes['${dut}']} +| | | Add CPU Config | ${nodes['${dut}']} +| | | ... | ${cpu} +| | | Add PCI Device | ${nodes['${dut}']} +| | | Apply Config | ${nodes['${dut}']} + diff --git a/tests/suites/performance/long_bridge_domain.robot b/tests/suites/performance/long_bridge_domain.robot index 9938cce5d3..44cab87df4 100644 --- a/tests/suites/performance/long_bridge_domain.robot +++ b/tests/suites/performance/long_bridge_domain.robot @@ -17,36 +17,106 @@ | Suite Setup | 3-node Performance Suite Setup | L2 | Suite Teardown | 3-node Performance Suite Teardown | Test Setup | Setup all DUTs before test -| Test Teardown | Run Keyword If Test Failed | Show statistics on all DUTs -| Documentation | Throughput search suite (long running test suite based on RFC2544). +| Test Teardown | Run Keywords | Show statistics on all DUTs +| ... | AND | Reset startup configuration of VPP on all DUTs +| Documentation | *Throughput search suite (based on RFC2544).* *** Test Cases *** -| Find NDR by using linear search and 64B frames through bridge domain in 3-node topology +| Find NDR by using RFC2544 linear search and 64B frames through bridge domain in 3-node topology +| | [Documentation] +| | ... | Find throughput with non drop rate for 64B frames by using +| | ... | linear search starting at 4.1Mpps, stepping down with step of 0.1Mpps +| | [Tags] | 1_THREAD_NOHTT_RSS_1 | SINGLE_THREAD +| | # Variables | | ${framesize}= | Set Variable | 64 -| | ${start_rate}= | Set Variable | 5000000 +| | ${start_rate}= | Set Variable | 4100000 | | ${step_rate}= | Set Variable | 100000 | | ${min_rate}= | Set Variable | 100000 | | ${max_rate}= | Set Variable | 14880952 -| | Given L2 bridge domain initialized in a 3-node circular topology -| | Then Find NDR using linear search and pps | ${framesize} | ${start_rate} | ${step_rate} -| | ... | 3-node-bridge | ${min_rate} | ${max_rate} +| | # VPP setup +| | Given Setup '1' worker threads and rss '1' without HTT on all DUTs +| | AND L2 bridge domain initialized in a 3-node circular topology +| | # Linear search +| | Then Find NDR using linear search and pps | ${framesize} | ${start_rate} +| | ... | ${step_rate} | 3-node-bridge +| | ... | ${min_rate} | ${max_rate} -| Find NDR by using linear search and 1518B frames through bridge domain in 3-node topology +| Find NDR by using RFC2544 linear search and 1518B frames through bridge domain in 3-node topology +| | [Documentation] +| | ... | Find throughput with non drop rate for 1518B frames by using +| | ... | linear search starting at 812,743pps, stepping down with step of +| | ... | 10,000pps +| | [Tags] | 1_THREAD_NOHTT_RSS_1 | SINGLE_THREAD +| | # Variables | | ${framesize}= | Set Variable | 1518 | | ${start_rate}= | Set Variable | 812743 | | ${step_rate}= | Set Variable | 10000 | | ${min_rate}= | Set Variable | 10000 | | ${max_rate}= | Set Variable | 812743 -| | Given L2 bridge domain initialized in a 3-node circular topology -| | Then Find NDR using linear search and pps | ${framesize} | ${start_rate} | ${step_rate} -| | ... | 3-node-bridge | ${min_rate} | ${max_rate} +| | # VPP setup +| | Given Setup '1' worker threads and rss '1' without HTT on all DUTs +| | AND L2 bridge domain initialized in a 3-node circular topology +| | # Linear search +| | Then Find NDR using linear search and pps | ${framesize} | ${start_rate} +| | ... | ${step_rate} | 3-node-bridge +| | ... | ${min_rate} | ${max_rate} -| Find NDR by using linear search and 9000B frames through bridge domain in 3-node topology +| Find NDR by using RFC2544 linear search and 9000B frames through bridge domain in 3-node topology +| | [Documentation] +| | ... | Find throughput with non drop rate for 9000B frames by using +| | ... | linear search starting at 138,580pps, stepping down with step of +| | ... | 5,000pps +| | [Tags] | 1_THREAD_NOHTT_RSS_1 | SINGLE_THREAD +| | # Variables | | ${framesize}= | Set Variable | 9000 | | ${start_rate}= | Set Variable | 138580 | | ${step_rate}= | Set Variable | 5000 | | ${min_rate}= | Set Variable | 5000 | | ${max_rate}= | Set Variable | 138580 -| | Given L2 bridge domain initialized in a 3-node circular topology -| | Then Find NDR using linear search and pps | ${framesize} | ${start_rate} | ${step_rate} -| | ... | 3-node-bridge | ${min_rate} | ${max_rate} +| | # VPP setup +| | Given Setup '1' worker threads and rss '1' without HTT on all DUTs +| | AND L2 bridge domain initialized in a 3-node circular topology +| | # Linear search +| | Then Find NDR using linear search and pps | ${framesize} | ${start_rate} +| | ... | ${step_rate} | 3-node-bridge +| | ... | ${min_rate} | ${max_rate} + +| Find NDR with 2 cores and rss 1 by using RFC2544 linear search and 64B frames through bridge domain in 3-node topology +| | [Documentation] +| | ... | Find throughput on 2 cores with non drop rate for 64B frames by using +| | ... | linear search starting at 8.2Mpps, stepping down with step of 0.1Mpps +| | [Tags] | 2_THREAD_NOHTT_RSS_1 | MULTI_THREAD +| | # Variables +| | ${framesize}= | Set Variable | 64 +| | ${start_rate}= | Set Variable | 8200000 +| | ${step_rate}= | Set Variable | 100000 +| | ${min_rate}= | Set Variable | 100000 +| | ${max_rate}= | Set Variable | 14880952 +| | # VPP setup +| | Given Setup '2' worker threads and rss '1' without HTT on all DUTs +| | AND L2 bridge domain initialized in a 3-node circular topology +| | # Linear search +| | Then Find NDR using linear search and pps | ${framesize} | ${start_rate} +| | ... | ${step_rate} | 3-node-bridge +| | ... | ${min_rate} | ${max_rate} + +| Find NDR with 4 cores and rss 2 by using RFC2544 linear search and 64B frames through bridge domain in 3-node topology +| | [Documentation] +| | ... | Find throughput on 4 cores and rss 2 with non drop rate for 64B +| | ... | frames by using linear search starting at 9.8Mpps, stepping down +| | ... | with step of 0.1Mpps +| | [Tags] | 4_THREAD_NOHTT_RSS_2 | MULTI_THREAD +| | # Variables +| | ${framesize}= | Set Variable | 64 +| | ${start_rate}= | Set Variable | 9800000 +| | ${step_rate}= | Set Variable | 100000 +| | ${min_rate}= | Set Variable | 100000 +| | ${max_rate}= | Set Variable | 14880952 +| | # VPP setup +| | Given Setup '4' worker threads and rss '2' without HTT on all DUTs +| | AND L2 bridge domain initialized in a 3-node circular topology +| | # Linear search +| | Then Find NDR using linear search and pps | ${framesize} | ${start_rate} +| | ... | ${step_rate} | 3-node-bridge +| | ... | ${min_rate} | ${max_rate} + diff --git a/tests/suites/performance/long_ipv4.robot b/tests/suites/performance/long_ipv4.robot index 108fc903a8..7b2111ab63 100644 --- a/tests/suites/performance/long_ipv4.robot +++ b/tests/suites/performance/long_ipv4.robot @@ -21,36 +21,105 @@ | Suite Setup | 3-node Performance Suite Setup | L3 | Suite Teardown | 3-node Performance Suite Teardown | Test Setup | Setup all DUTs before test -| Test Teardown | Run Keyword If Test Failed | Show statistics on all DUTs -| Documentation | Throughput search suite (long running test suite based on RFC2544). +| Test Teardown | Run Keywords | Show statistics on all DUTs +| ... | AND | Reset startup configuration of VPP on all DUTs +| Documentation | *Throughput search suite (based on RFC2544).* *** Test Cases *** -| Find NDR by using linear search and 64B frames through IPv4 forwarding in 3-node topology +| Find NDR by using RFC2544 linear search and 64B frames through IPv4 forwarding in 3-node topology +| | [Documentation] +| | ... | Find throughput with non drop rate for 64B frames by using +| | ... | linear search starting at 4.1Mpps, stepping down with step of 0.1Mpps +| | [Tags] | 1_THREAD_NOHTT_RSS_1 | SINGLE_THREAD +| | # Variables | | ${framesize}= | Set Variable | 64 -| | ${start_rate}= | Set Variable | 5000000 +| | ${start_rate}= | Set Variable | 4400000 | | ${step_rate}= | Set Variable | 100000 | | ${min_rate}= | Set Variable | 100000 | | ${max_rate}= | Set Variable | 14880952 -| | Given IPv4 forwarding initialized in a 3-node circular topology -| | Then Find NDR using linear search and pps | ${framesize} | ${start_rate} | ${step_rate} -| | ... | 3-node-IPv4 | ${min_rate} | ${max_rate} +| | # VPP setup +| | Given Setup '1' worker threads and rss '1' without HTT on all DUTs +| | AND IPv4 forwarding initialized in a 3-node circular topology +| | # Linear search +| | Then Find NDR using linear search and pps | ${framesize} | ${start_rate} +| | ... | ${step_rate} | 3-node-IPv4 +| | ... | ${min_rate} | ${max_rate} -| Find NDR by using linear search and 1518B frames through IPv4 forwarding in 3-node topology +| Find NDR by using RFC2544 linear search and 1518B frames through IPv4 forwarding in 3-node topology +| | [Documentation] +| | ... | Find throughput with non drop rate for 1518B frames by using +| | ... | linear search starting at 812,743pps, stepping down with step of +| | ... | 10,000pps +| | [Tags] | 1_THREAD_NOHTT_RSS_1 | SINGLE_THREAD +| | # Variables | | ${framesize}= | Set Variable | 1518 | | ${start_rate}= | Set Variable | 812743 | | ${step_rate}= | Set Variable | 10000 | | ${min_rate}= | Set Variable | 10000 | | ${max_rate}= | Set Variable | 812743 -| | Given IPv4 forwarding initialized in a 3-node circular topology -| | Then Find NDR using linear search and pps | ${framesize} | ${start_rate} | ${step_rate} -| | ... | 3-node-IPv4 | ${min_rate} | ${max_rate} +| | # VPP setup +| | Given Setup '1' worker threads and rss '1' without HTT on all DUTs +| | AND IPv4 forwarding initialized in a 3-node circular topology +| | # Linear search +| | Then Find NDR using linear search and pps | ${framesize} | ${start_rate} +| | ... | ${step_rate} | 3-node-IPv4 +| | ... | ${min_rate} | ${max_rate} -| Find NDR by using linear search and 9000B frames through IPv4 forwarding in 3-node topology +| Find NDR by using RFC2544 linear search and 9000B frames through IPv4 forwarding in 3-node topology +| | [Documentation] +| | ... | Find throughput with non drop rate for 9000B frames by using +| | ... | linear search starting at 138,580pps, stepping down with step of +| | ... | 5,000pps +| | [Tags] | 1_THREAD_NOHTT_RSS_1 | SINGLE_THREAD +| | # Variables | | ${framesize}= | Set Variable | 9000 | | ${start_rate}= | Set Variable | 138580 | | ${step_rate}= | Set Variable | 5000 | | ${min_rate}= | Set Variable | 5000 | | ${max_rate}= | Set Variable | 138580 -| | Given IPv4 forwarding initialized in a 3-node circular topology -| | Then Find NDR using linear search and pps | ${framesize} | ${start_rate} | ${step_rate} -| | ... | 3-node-IPv4 | ${min_rate} | ${max_rate} +| | # VPP setup +| | Given Setup '1' worker threads and rss '1' without HTT on all DUTs +| | AND IPv4 forwarding initialized in a 3-node circular topology +| | # Linear search +| | Then Find NDR using linear search and pps | ${framesize} | ${start_rate} +| | ... | ${step_rate} | 3-node-IPv4 +| | ... | ${min_rate} | ${max_rate} + +| Find NDR with 2 cores and rss 1 by using RFC2544 linear search and 64B frames through IPv4 forwarding in 3-node topology +| | [Documentation] +| | ... | Find throughput on 2 cores with non drop rate for 64B frames by using +| | ... | linear search starting at 8.8Mpps, stepping down with step of 0.1Mpps +| | [Tags] | 2_THREAD_NOHTT_RSS_1 | MULTI_THREAD +| | # Variables +| | ${framesize}= | Set Variable | 64 +| | ${start_rate}= | Set Variable | 8800000 +| | ${step_rate}= | Set Variable | 100000 +| | ${min_rate}= | Set Variable | 100000 +| | ${max_rate}= | Set Variable | 14880952 +| | # VPP setup +| | Given Setup '2' worker threads and rss '1' without HTT on all DUTs +| | AND IPv4 forwarding initialized in a 3-node circular topology +| | # Linear search +| | Then Find NDR using linear search and pps | ${framesize} | ${start_rate} +| | ... | ${step_rate} | 3-node-IPv4 +| | ... | ${min_rate} | ${max_rate} + +| Find NDR with 4 cores and rss 2 by using RFC2544 linear search and 64B frames through IPv4 forwarding in 3-node topology +| | [Documentation] +| | ... | Find throughput on 2 cores with non drop rate for 64B frames by using +| | ... | linear search starting at 9.8Mpps, stepping down with step of 0.1Mpps +| | [Tags] | 4_THREAD_NOHTT_RSS_2 | MULTI_THREAD +| | # Variables +| | ${framesize}= | Set Variable | 64 +| | ${start_rate}= | Set Variable | 9800000 +| | ${step_rate}= | Set Variable | 100000 +| | ${min_rate}= | Set Variable | 100000 +| | ${max_rate}= | Set Variable | 14880952 +| | # VPP setup +| | Given Setup '4' worker threads and rss '2' without HTT on all DUTs +| | AND IPv4 forwarding initialized in a 3-node circular topology +| | # Linear search +| | Then Find NDR using linear search and pps | ${framesize} | ${start_rate} +| | ... | ${step_rate} | 3-node-IPv4 +| | ... | ${min_rate} | ${max_rate} + diff --git a/tests/suites/performance/long_xconnect.robot b/tests/suites/performance/long_xconnect.robot index 4d441601e5..fab92e6b54 100644 --- a/tests/suites/performance/long_xconnect.robot +++ b/tests/suites/performance/long_xconnect.robot @@ -18,7 +18,8 @@ | Suite Setup | 3-node Performance Suite Setup | L2 | Suite Teardown | 3-node Performance Suite Teardown | Test Setup | Setup all DUTs before test -| Test Teardown | Run Keyword If Test Failed | Show statistics on all DUTs +| Test Teardown | Run Keywords | Show statistics on all DUTs +| ... | AND | Reset startup configuration of VPP on all DUTs | Documentation | *Throughput search suite (based on RFC2544).* | ... | ... | Test suite uses 3-node topology TG - DUT1 - DUT2 - TG, with one link @@ -28,41 +29,98 @@ | ... | matching MAC addresses of the TG node. *** Test Cases *** -| Find NDR by using linear search and 64B frames through L2 cross connect in 3-node topology +| Find NDR by using RFC2544 linear search and 64B frames through L2 cross connect in 3-node topology | | [Documentation] | | ... | Find throughput with non drop rate for 64B frames by using | | ... | linear search starting at 5Mpps, stepping down with step of 0.1Mpps +| | [Tags] | 1_THREAD_NOHTT_RSS_1 | SINGLE_THREAD +| | # Variables | | ${framesize}= | Set Variable | 64 -| | ${start_rate}= | Set Variable | 5000000 +| | ${start_rate}= | Set Variable | 4800000 | | ${step_rate}= | Set Variable | 100000 | | ${min_rate}= | Set Variable | 100000 | | ${max_rate}= | Set Variable | 14880952 -| | Given L2 xconnect initialized in a 3-node circular topology -| | Then Find NDR using linear search and pps | ${framesize} | ${start_rate} | ${step_rate} -| | ... | 3-node-xconnect | ${min_rate} | ${max_rate} +| | Given Setup '1' worker threads and rss '1' without HTT on all DUTs +| | AND L2 xconnect initialized in a 3-node circular topology +| | # Linear search +| | Then Find NDR using linear search and pps | ${framesize} | ${start_rate} +| | ... | ${step_rate} | 3-node-xconnect +| | ... | ${min_rate} | ${max_rate} -| Find NDR by using linear search and 1518B frames through L2 cross connect in 3-node topology +| Find NDR by using RFC2544 linear search and 1518B frames through L2 cross connect in 3-node topology | | [Documentation] | | ... | Find throughput with non drop rate for 1518B frames by using | | ... | linear search starting at 812,743pps, stepping down with step of 10,000pps +| | [Tags] | 1_THREAD_NOHTT_RSS_1 | SINGLE_THREAD +| | # Variables | | ${framesize}= | Set Variable | 1518 | | ${start_rate}= | Set Variable | 812743 | | ${step_rate}= | Set Variable | 10000 | | ${min_rate}= | Set Variable | 10000 | | ${max_rate}= | Set Variable | 812743 -| | Given L2 xconnect initialized in a 3-node circular topology -| | Then Find NDR using linear search and pps | ${framesize} | ${start_rate} | ${step_rate} -| | ... | 3-node-xconnect | ${min_rate} | ${max_rate} +| | # VPP setup +| | Given Setup '1' worker threads and rss '1' without HTT on all DUTs +| | AND L2 xconnect initialized in a 3-node circular topology +| | # Linear search +| | Then Find NDR using linear search and pps | ${framesize} | ${start_rate} +| | ... | ${step_rate} | 3-node-xconnect +| | ... | ${min_rate} | ${max_rate} -| Find NDR by using linear search and 9000B frames through L2 cross connect in 3-node topology +| Find NDR by using RFC2544 linear search and 9000B frames through L2 cross connect in 3-node topology | | [Documentation] | | ... | Find throughput with non drop rate for 9000B frames by using | | ... | linear search starting at 138,580pps, stepping down with step 5,000pps +| | [Tags] | 1_THREAD_NOHTT_RSS_1 | SINGLE_THREAD +| | # Variables | | ${framesize}= | Set Variable | 9000 | | ${start_rate}= | Set Variable | 138580 | | ${step_rate}= | Set Variable | 5000 | | ${min_rate}= | Set Variable | 5000 | | ${max_rate}= | Set Variable | 138580 -| | Given L2 xconnect initialized in a 3-node circular topology -| | Then Find NDR using linear search and pps | ${framesize} | ${start_rate} | ${step_rate} -| | ... | 3-node-xconnect | ${min_rate} | ${max_rate} +| | # VPP setup +| | Given Setup '1' worker threads and rss '1' without HTT on all DUTs +| | AND L2 xconnect initialized in a 3-node circular topology +| | # Linear search +| | Then Find NDR using linear search and pps | ${framesize} | ${start_rate} +| | ... | ${step_rate} | 3-node-xconnect +| | ... | ${min_rate} | ${max_rate} + +| Find NDR with 2 cores and rss 1 by using RFC2544 linear search and 64B frames through L2 cross connect in 3-node topology +| | [Documentation] +| | ... | Find throughput on 2 cores with non drop rate for 64B frames by using +| | ... | linear search starting at 8.2Mpps, stepping down with step of 0.1Mpps +| | [Tags] | 2_THREAD_NOHTT_RSS_1 | MULTI_THREAD +| | # Variables +| | ${framesize}= | Set Variable | 64 +| | ${start_rate}= | Set Variable | 12000000 +| | ${step_rate}= | Set Variable | 100000 +| | ${min_rate}= | Set Variable | 100000 +| | ${max_rate}= | Set Variable | 14880952 +| | # VPP setup +| | Given Setup '2' worker threads and rss '1' without HTT on all DUTs +| | AND L2 xconnect initialized in a 3-node circular topology +| | # Linear search +| | Then Find NDR using linear search and pps | ${framesize} | ${start_rate} +| | ... | ${step_rate} | 3-node-xconnect +| | ... | ${min_rate} | ${max_rate} + +| Find NDR with 4 cores and rss 2 by using RFC2544 linear search and 64B frames through L2 cross connect in 3-node topology +| | [Documentation] +| | ... | Find throughput on 4 cores and rss 2 with non drop rate for 64B +| | ... | frames by using linear search starting at 8.2Mpps, stepping down +| | ... | with step of 0.1Mpps +| | [Tags] | 4_THREAD_NOHTT_RSS_2 | MULTI_THREAD +| | # Variables +| | ${framesize}= | Set Variable | 64 +| | ${start_rate}= | Set Variable | 12200000 +| | ${step_rate}= | Set Variable | 100000 +| | ${min_rate}= | Set Variable | 100000 +| | ${max_rate}= | Set Variable | 14880952 +| | # VPP setup +| | Given Setup '4' worker threads and rss '2' without HTT on all DUTs +| | AND L2 xconnect initialized in a 3-node circular topology +| | # Linear search +| | Then Find NDR using linear search and pps | ${framesize} | ${start_rate} +| | ... | ${step_rate} | 3-node-xconnect +| | ... | ${min_rate} | ${max_rate} + diff --git a/tests/suites/performance/short_bridge_domain.robot b/tests/suites/performance/short_bridge_domain.robot index 210664c38b..27671c1259 100644 --- a/tests/suites/performance/short_bridge_domain.robot +++ b/tests/suites/performance/short_bridge_domain.robot @@ -17,26 +17,87 @@ | Suite Setup | 3-node Performance Suite Setup | L2 | Suite Teardown | 3-node Performance Suite Teardown | Test Setup | Setup all DUTs before test -| Test Teardown | Run Keyword If Test Failed | Show statistics on all DUTs +| Test Teardown | Run Keywords | Show statistics on all DUTs +| ... | AND | Reset startup configuration of VPP on all DUTs +| Documentation | Minimal throughput acceptance test cases *** Test Cases *** -| 1core VPP passes 64B frames through bridge domain at 3.5mpps in 3-node topology +| 1core VPP passes 64B frames through bridge domain at 2x 3.5Mpps in 3-node topology +| | [Documentation] +| | ... | VPP with 1 core should pass 64B frames through bridge domain +| | ... | at 2x3.5Mpps in 3-node topology +| | [Tags] | 1_THREAD_NOHTT_RSS_1 | SINGLE_THREAD +| | # Variables | | ${framesize}= | Set Variable | 64 | | ${duration}= | Set Variable | 10 | | ${rate}= | Set Variable | 3.5mpps -| | Given L2 bridge domain initialized in a 3-node circular topology -| | Then Traffic should pass with no loss | ${duration} | ${rate} | ${framesize} | 3-node-bridge +| | # VPP setup +| | Given Setup '1' worker threads and rss '1' without HTT on all DUTs +| | AND L2 bridge domain initialized in a 3-node circular topology +| | # Sent traffic with specified rate +| | Then Traffic should pass with no loss | ${duration} | ${rate} +| | ... | ${framesize} | 3-node-bridge -| 1core VPP passes 1518B frames through bridge domain at 10gbps in 3-node topology +| 1core VPP passes 1518B frames through bridge domain at 2x 10Gbps in 3-node topology +| | [Documentation] +| | ... | VPP with 1 core should pass 1518B frames through bridge domain +| | ... | at 2x10Gbps in 3-node topology +| | [Tags] | 1_THREAD_NOHTT_RSS_1 | SINGLE_THREAD +| | # Variables | | ${framesize}= | Set Variable | 1518 | | ${duration}= | Set Variable | 10 | | ${rate}= | Set Variable | 10gbps -| | Given L2 bridge domain initialized in a 3-node circular topology -| | Then Traffic should pass with no loss | ${duration} | ${rate} | ${framesize} | 3-node-bridge +| | # VPP setup +| | Given Setup '1' worker threads and rss '1' without HTT on all DUTs +| | AND L2 bridge domain initialized in a 3-node circular topology +| | # Sent traffic with specified rate +| | Then Traffic should pass with no loss | ${duration} | ${rate} +| | ... | ${framesize} | 3-node-bridge -| 1core VPP passes 9000B frames through bridge domain at 10gbps in 3-node topology +| 1core VPP passes 9000B frames through bridge domain at 2x 10Gbps in 3-node topology +| | [Documentation] +| | ... | VPP with 1 core should pass 9000B frames through bridge domain +| | ... | at 2x10Gbps in 3-node topology +| | [Tags] | 1_THREAD_NOHTT_RSS_1 | SINGLE_THREAD +| | # Variables | | ${framesize}= | Set Variable | 9000 | | ${duration}= | Set Variable | 10 | | ${rate}= | Set Variable | 10gbps -| | Given L2 bridge domain initialized in a 3-node circular topology -| | Then Traffic should pass with no loss | ${duration} | ${rate} | ${framesize} | 3-node-bridge +| | # VPP setup +| | Given Setup '1' worker threads and rss '1' without HTT on all DUTs +| | AND L2 bridge domain initialized in a 3-node circular topology +| | # Sent traffic with specified rate +| | Then Traffic should pass with no loss | ${duration} | ${rate} +| | ... | ${framesize} | 3-node-bridge + +| 2core VPP with rss 1 passes 64B frames through bridge domain at 2x 7.6Mpps in 3-node topology +| | [Documentation] +| | ... | VPP with 2 cores should pass 64B frames through bridge domain +| | ... | at 2x7.6Mpps in 3-node topology +| | [Tags] | 2_THREAD_NOHTT_RSS_1 | MULTI_THREAD +| | # Variables +| | ${framesize}= | Set Variable | 64 +| | ${duration}= | Set Variable | 10 +| | ${rate}= | Set Variable | 7.6mpps +| | # VPP setup +| | Given Setup '2' worker threads and rss '1' without HTT on all DUTs +| | AND L2 bridge domain initialized in a 3-node circular topology +| | # Sent traffic with specified rate +| | Then Traffic should pass with no loss | ${duration} | ${rate} +| | ... | ${framesize} | 3-node-bridge + +| 4core VPP with rss 2 passes 64B frames through bridge domain at 2x 8.6Mpps in 3-node topology +| | [Documentation] +| | ... | VPP with 4 cores and rss 2 should pass 64B frames through bridge +| | ... | domain at 2x8.6Mpps in 3-node topology +| | [Tags] | 4_THREAD_NOHTT_RSS_2 | MULTI_THREAD +| | # Variables +| | ${framesize}= | Set Variable | 64 +| | ${duration}= | Set Variable | 10 +| | ${rate}= | Set Variable | 8.6mpps +| | # VPP setup +| | Given Setup '4' worker threads and rss '2' without HTT on all DUTs +| | AND L2 bridge domain initialized in a 3-node circular topology +| | # Sent traffic with specified rate +| | Then Traffic should pass with no loss | ${duration} | ${rate} +| | ... | ${framesize} | 3-node-bridge diff --git a/tests/suites/performance/short_ipv4.robot b/tests/suites/performance/short_ipv4.robot index 95e135147e..f96623beb6 100644 --- a/tests/suites/performance/short_ipv4.robot +++ b/tests/suites/performance/short_ipv4.robot @@ -21,26 +21,87 @@ | Suite Setup | 3-node Performance Suite Setup | L3 | Suite Teardown | 3-node Performance Suite Teardown | Test Setup | Setup all DUTs before test -| Test Teardown | Run Keyword If Test Failed | Show statistics on all DUTs +| Test Teardown | Run Keywords | Show statistics on all DUTs +| ... | AND | Reset startup configuration of VPP on all DUTs +| Documentation | Minimal throughput acceptance test cases *** Test Cases *** -| 1core VPP passes 64B frames through IPv4 forwarding at 3.5mpps in 3-node topology +| 1core VPP passes 64B frames through IPv4 forwarding at 2x 3.5mpps in 3-node topology +| | [Documentation] +| | ... | VPP with 1 core should pass 64B frames through IPv4 forwarding +| | ... | at 2x3.5mpps in 3-node topology +| | [Tags] | 1_THREAD_NOHTT_RSS_1 | SINGLE_THREAD +| | # Variables | | ${framesize}= | Set Variable | 64 | | ${duration}= | Set Variable | 10 | | ${rate}= | Set Variable | 3.5mpps -| | Given IPv4 forwarding initialized in a 3-node circular topology -| | Then Traffic should pass with no loss | ${duration} | ${rate} | ${framesize} | 3-node-IPv4 +| | # VPP setup +| | Given Setup '1' worker threads and rss '1' without HTT on all DUTs +| | AND IPv4 forwarding initialized in a 3-node circular topology +| | # Sent traffic with specified rate +| | Then Traffic should pass with no loss | ${duration} | ${rate} +| | ... | ${framesize} | 3-node-IPv4 -| 1core VPP passes 1518B frames through IPv4 forwarding at 10gbps in 3-node topology +| 1core VPP passes 1518B frames through IPv4 forwarding at 2x 10gbps in 3-node topology +| | [Documentation] +| | ... | VPP with 1 core should pass 1518B frames through IPv4 forwarding +| | ... | at 2x10gbps in 3-node topology +| | [Tags] | 1_THREAD_NOHTT_RSS_1 | SINGLE_THREAD +| | # Variables | | ${framesize}= | Set Variable | 1518 | | ${duration}= | Set Variable | 10 | | ${rate}= | Set Variable | 10gbps -| | Given IPv4 forwarding initialized in a 3-node circular topology -| | Then Traffic should pass with no loss | ${duration} | ${rate} | ${framesize} | 3-node-IPv4 +| | # VPP setup +| | Given Setup '1' worker threads and rss '1' without HTT on all DUTs +| | AND IPv4 forwarding initialized in a 3-node circular topology +| | # Sent traffic with specified rate +| | Then Traffic should pass with no loss | ${duration} | ${rate} +| | ... | ${framesize} | 3-node-IPv4 -| 1core VPP passes 9000B frames through IPv4 forwarding at 10gbps in 3-node topology +| 1core VPP passes 9000B frames through IPv4 forwarding at 2x 10gbps in 3-node topology +| | [Documentation] +| | ... | VPP with 1 core should pass 9000B frames through IPv4 forwarding +| | ... | at 2x10gbps in 3-node topology +| | [Tags] | 1_THREAD_NOHTT_RSS_1 | SINGLE_THREAD +| | # Variables | | ${framesize}= | Set Variable | 9000 | | ${duration}= | Set Variable | 10 | | ${rate}= | Set Variable | 10gbps -| | Given IPv4 forwarding initialized in a 3-node circular topology -| | Then Traffic should pass with no loss | ${duration} | ${rate} | ${framesize} | 3-node-IPv4 +| | # VPP setup +| | Given Setup '1' worker threads and rss '1' without HTT on all DUTs +| | AND IPv4 forwarding initialized in a 3-node circular topology +| | # Sent traffic with specified rate +| | Then Traffic should pass with no loss | ${duration} | ${rate} +| | ... | ${framesize} | 3-node-IPv4 + +| 2core VPP with rss 1 passes 64B frames through IPv4 forwarding at 2x 8.2mpps in 3-node topology +| | [Documentation] +| | ... | VPP with 2 cores should pass 64B frames through IPv4 forwarding +| | ... | at 2x8.2mpps in 3-node topology +| | [Tags] | 2_THREAD_NOHTT_RSS_1 | MULTI_THREAD +| | # Variables +| | ${framesize}= | Set Variable | 64 +| | ${duration}= | Set Variable | 10 +| | ${rate}= | Set Variable | 8.2mpps +| | # VPP setup +| | Given Setup '2' worker threads and rss '1' without HTT on all DUTs +| | AND IPv4 forwarding initialized in a 3-node circular topology +| | # Sent traffic with specified rate +| | Then Traffic should pass with no loss | ${duration} | ${rate} +| | ... | ${framesize} | 3-node-IPv4 + +| 4core VPP with rss 2 passes 64B frames through IPv4 forwarding at 2x 9.2mpps in 3-node topology +| | [Documentation] +| | ... | VPP with 4 cores and rss 2 should pass 64B frames through IPv4 +| | ... | forwarding at 2x9.2mpps in 3-node topology +| | [Tags] | 4_THREAD_NOHTT_RSS_2 | MULTI_THREAD +| | # Variables +| | ${framesize}= | Set Variable | 64 +| | ${duration}= | Set Variable | 10 +| | ${rate}= | Set Variable | 9.2mpps +| | # VPP setup +| | Given Setup '2' worker threads and rss '1' without HTT on all DUTs +| | AND IPv4 forwarding initialized in a 3-node circular topology +| | # Sent traffic with specified rate +| | Then Traffic should pass with no loss | ${duration} | ${rate} +| | ... | ${framesize} | 3-node-IPv4 diff --git a/tests/suites/performance/short_xconnect.robot b/tests/suites/performance/short_xconnect.robot index 55e05237b3..a909e8b8e7 100644 --- a/tests/suites/performance/short_xconnect.robot +++ b/tests/suites/performance/short_xconnect.robot @@ -18,26 +18,88 @@ | Suite Setup | 3-node Performance Suite Setup | L2 | Suite Teardown | 3-node Performance Suite Teardown | Test Setup | Setup all DUTs before test -| Test Teardown | Run Keyword If Test Failed | Show statistics on all DUTs +| Test Teardown | Run Keywords | Show statistics on all DUTs +| ... | AND | Reset startup configuration of VPP on all DUTs +| Documentation | Minimal throughput acceptance test cases *** Test Cases *** -| 1core VPP passes 64B frames through L2 cross connect at 3.5mpps in 3-node topology +| 1core VPP passes 64B frames through L2 cross connect at 2x 3.5mpps in 3-node topology +| | [Documentation] +| | ... | VPP with 1 core should pass 64B frames through L2 cross connect +| | ... | at 2x3.5mpps in 3-node topology +| | [Tags] | 1_THREAD_NOHTT_RSS_1 | SINGLE_THREAD +| | # Variables | | ${framesize}= | Set Variable | 64 | | ${duration}= | Set Variable | 10 | | ${rate}= | Set Variable | 3.5mpps -| | Given L2 xconnect initialized in a 3-node circular topology -| | Then Traffic should pass with no loss | ${duration} | ${rate} | ${framesize} | 3-node-xconnect +| | # VPP setup +| | Given Setup '1' worker threads and rss '1' without HTT on all DUTs +| | AND L2 xconnect initialized in a 3-node circular topology +| | # Sent traffic with specified rate +| | Then Traffic should pass with no loss | ${duration} | ${rate} +| | ... | ${framesize} | 3-node-xconnect -| 1core VPP passes 1518B frames through L2 cross connect at 10gbps in 3-node topology +| 1core VPP passes 1518B frames through L2 cross connect at 2x 10gbps in 3-node topology +| | [Documentation] +| | ... | VPP with 1 core should pass 1518B frames through L2 cross connect +| | ... | at 2x10gbps in 3-node topology +| | [Tags] | 1_THREAD_NOHTT_RSS_1 | SINGLE_THREAD +| | # Variables | | ${framesize}= | Set Variable | 1518 | | ${duration}= | Set Variable | 10 | | ${rate}= | Set Variable | 10gbps -| | Given L2 xconnect initialized in a 3-node circular topology -| | Then Traffic should pass with no loss | ${duration} | ${rate} | ${framesize} | 3-node-xconnect +| | # VPP setup +| | Given Setup '1' worker threads and rss '1' without HTT on all DUTs +| | AND L2 xconnect initialized in a 3-node circular topology +| | # Sent traffic with specified rate +| | Then Traffic should pass with no loss | ${duration} | ${rate} +| | ... | ${framesize} | 3-node-xconnect -| 1core VPP passes 9000B frames through L2 cross connect at 10gbps in 3-node topology +| 1core VPP passes 9000B frames through L2 cross connect at 2x 10gbps in 3-node topology +| | [Documentation] +| | ... | VPP with 1 core should pass 9000B frames through L2 cross connect +| | ... | at 2x10gbps in 3-node topology +| | [Tags] | 1_THREAD_NOHTT_RSS_1 | SINGLE_THREAD +| | # Variables | | ${framesize}= | Set Variable | 9000 | | ${duration}= | Set Variable | 10 | | ${rate}= | Set Variable | 10gbps -| | Given L2 xconnect initialized in a 3-node circular topology -| | Then Traffic should pass with no loss | ${duration} | ${rate} | ${framesize} | 3-node-xconnect +| | # VPP setup +| | Given Setup '1' worker threads and rss '1' without HTT on all DUTs +| | AND L2 xconnect initialized in a 3-node circular topology +| | # Sent traffic with specified rate +| | Then Traffic should pass with no loss | ${duration} | ${rate} +| | ... | ${framesize} | 3-node-xconnect + +| 2core VPP with rss 1 passes 64B frames through L2 cross connect at 2x 11.0mpps in 3-node topology +| | [Documentation] +| | ... | VPP with 2 cores should pass 64B frames through L2 cross connect +| | ... | at 2x11.0mpps in 3-node topology +| | [Tags] | 2_THREAD_NOHTT_RSS_1 | MULTI_THREAD +| | # Variables +| | ${framesize}= | Set Variable | 64 +| | ${duration}= | Set Variable | 10 +| | ${rate}= | Set Variable | 11.0mpps +| | # VPP setup +| | Given Setup '2' worker threads and rss '1' without HTT on all DUTs +| | AND L2 xconnect initialized in a 3-node circular topology +| | # Sent traffic with specified rate +| | Then Traffic should pass with no loss | ${duration} | ${rate} +| | ... | ${framesize} | 3-node-xconnect + +| 4core VPP with rss 2 passes 64B frames through L2 cross connect at 2x 11.6mpps in 3-node topology +| | [Documentation] +| | ... | VPP with 4 cores and rss 2 should pass 64B frames through L2 cross +| | ... | connect at 2x11.6mpps in 3-node topology +| | # Variables +| | [Tags] | 4_THREAD_NOHTT_RSS_2 | MULTI_THREAD +| | ${framesize}= | Set Variable | 64 +| | ${duration}= | Set Variable | 10 +| | ${rate}= | Set Variable | 11.6mpps +| | # VPP setup +| | Given Setup '4' worker threads and rss '2' without HTT on all DUTs +| | AND L2 xconnect initialized in a 3-node circular topology +| | # Sent traffic with specified rate +| | Then Traffic should pass with no loss | ${duration} | ${rate} +| | ... | ${framesize} | 3-node-xconnect + -- 2.16.6 From 8e37725ead0a812ae4474ffba4ef120fd5ca9662 Mon Sep 17 00:00:00 2001 From: Stefan Kobza Date: Tue, 19 Apr 2016 14:37:20 +0200 Subject: [PATCH 14/16] Make dut_setup.sh verbose; add sleep after vpp restart. - there's a race condition between VPP service restart and vpp_api_test execution; this patch addresses it with (for now) sleep after service vpp restart - above is just a workaround until VPP provides us with a notification of readiness.. Change-Id: I1f92c1d86e4f90c9432ea57c1747629f619f003e Signed-off-by: Stefan Kobza --- resources/libraries/bash/dut_setup.sh | 71 +++++++++++++++-------------------- 1 file changed, 31 insertions(+), 40 deletions(-) diff --git a/resources/libraries/bash/dut_setup.sh b/resources/libraries/bash/dut_setup.sh index c6bd0fbbe2..7069e5e608 100644 --- a/resources/libraries/bash/dut_setup.sh +++ b/resources/libraries/bash/dut_setup.sh @@ -12,50 +12,41 @@ # See the License for the specific language governing permissions and # limitations under the License. -echo -echo List vpp packages -echo -dpkg -l vpp\* +function cmd { + echo + echo "[Command_start_exec] '$1'" + echo -n "[Command_outputs] " + eval ${@} + echo "[Command_done_exec] '$1'" + echo +} echo -echo See vpp process -echo -ps aux | grep vpp +echo "[Command_desc] Starting ${0}" -echo -echo See /etc/vpp/startup.conf -echo -cat /etc/vpp/startup.conf +cmd 'dpkg -l vpp\*' -echo -echo Restart VPP -echo -sudo -S service vpp restart +cmd 'ps aux | grep vpp' -echo -echo List /proc/meminfo -echo -cat /proc/meminfo +cmd 'cat /etc/vpp/startup.conf' -echo -echo See free memory -echo -free -m +cmd 'sudo -S service vpp restart' + +echo "[Command_desc] SLEEP for three seconds, so that VPP is up for sure" +cmd 'sleep 3' + +cmd 'cat /proc/meminfo' + +cmd 'free -m' + +cmd 'ps aux | grep vpp' + +cmd 'sudo dmidecode | grep UUID' + +cmd 'lspci -Dnn' + +cmd 'tail -n 50 /var/log/syslog' + +echo "[Command_desc] Adding dpdk-input trace" +cmd 'sudo vpp_api_test <<< "exec trace add dpdk-input 100"' -echo -echo See vpp process -echo -ps aux | grep vpp - -echo UUID -sudo dmidecode | grep UUID - -echo Add dpdk-input trace -sudo vpp_api_test <<< "exec trace add dpdk-input 100" -RESULT=$? -if [ $RESULT -ne 0 ]; then - echo - echo See /var/log/syslog - sudo tail -n 200 /var/log/syslog - exit $RESULT -fi -- 2.16.6 From f4050fdb4d771e232f44a96e58a3c050bd99f5ca Mon Sep 17 00:00:00 2001 From: Stefan Kobza Date: Tue, 19 Apr 2016 22:53:23 +0200 Subject: [PATCH 15/16] Make VXLAN test dot1Q as noncritical for now. Change-Id: I0d41aad3fc1f96b58b83c6f0aa5a0e7606868ee4 Signed-off-by: Stefan Kobza --- tests/suites/vxlan/vxlan_bd_dot1q.robot | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/suites/vxlan/vxlan_bd_dot1q.robot b/tests/suites/vxlan/vxlan_bd_dot1q.robot index 8497d7851b..f570e5898f 100644 --- a/tests/suites/vxlan/vxlan_bd_dot1q.robot +++ b/tests/suites/vxlan/vxlan_bd_dot1q.robot @@ -29,6 +29,7 @@ *** Test Cases *** | VPP can encapsulate L2 in VXLAN over IPv4 over Dot1Q +| | [ Tags ] | EXPECTED_FAILING | | Given Path for VXLAN testing is set | | ... | ${nodes['TG']} | ${nodes['DUT1']} | ${nodes['DUT2']} | | And Interfaces in path are up -- 2.16.6 From 3765580c5c81cfa693b88daf8ba3180312c28575 Mon Sep 17 00:00:00 2001 From: pmikus Date: Fri, 15 Apr 2016 12:08:51 +0200 Subject: [PATCH 16/16] Include tags to CSIT performance boostrap script - include tags to CSIT performance boostrap script - allows future expansion of selective test execution Change-Id: I3870f52d86ff39234eb724500d3871fed0cbdb30 Signed-off-by: pmikus --- bootstrap-hw-tb2.sh | 52 ++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/bootstrap-hw-tb2.sh b/bootstrap-hw-tb2.sh index 1b889b3039..46cd3f0c84 100755 --- a/bootstrap-hw-tb2.sh +++ b/bootstrap-hw-tb2.sh @@ -30,7 +30,7 @@ virtualenv env echo pip install pip install -r requirements.txt -#we iterate over available topologies and wait until we reserve topology +# we iterate over available topologies and wait until we reserve topology while :; do for TOPOLOGY in ${TOPOLOGIES}; do @@ -43,11 +43,11 @@ while :; do done if [ ! -z "${WORKING_TOPOLOGY}" ]; then - #exit the infinite while loop if we made a reservation + # exit the infinite while loop if we made a reservation break fi - #wait 10 - 30 sec. before next try + # wait 10 - 30 sec. before next try SLEEP_TIME=$[ ( $RANDOM % 20 ) + 10 ]s echo "Sleeping ${SLEEP_TIME}" sleep ${SLEEP_TIME} @@ -57,10 +57,46 @@ function cancel_reservation { python ${CUR_DIR}/resources/tools/topo_reservation.py -c -t $1 } -#on script exit we cancel the reservation +# on script exit we cancel the reservation trap "cancel_reservation ${WORKING_TOPOLOGY}" EXIT -#run performance test suite -pybot -L TRACE \ - -v TOPOLOGY_PATH:${WORKING_TOPOLOGY} \ - -s performance tests/ +case "$TEST_TAG" in + # run specific performance tests based on jenkins job type variable + PERFTEST_LONG ) + pybot -L TRACE \ + -v TOPOLOGY_PATH:${WORKING_TOPOLOGY} \ + -i perftest_long \ + tests/ + ;; + PERFTEST_SHORT ) + pybot -L TRACE \ + -v TOPOLOGY_PATH:${WORKING_TOPOLOGY} \ + -i perftest_short \ + tests/ + ;; + PERFTEST_LONG_BRIDGE ) + pybot -L TRACE \ + -v TOPOLOGY_PATH:${WORKING_TOPOLOGY} \ + -s performance.long_bridge_domain \ + tests/ + ;; + PERFTEST_LONG_IPV4 ) + pybot -L TRACE \ + -v TOPOLOGY_PATH:${WORKING_TOPOLOGY} \ + -s performance.long_ipv4 \ + tests/ + ;; + PERFTEST_LONG_XCONNECT ) + pybot -L TRACE \ + -v TOPOLOGY_PATH:${WORKING_TOPOLOGY} \ + -s performance.long_xconnect \ + tests/ + ;; + * ) + # run full performance test suite + pybot -L TRACE \ + -v TOPOLOGY_PATH:${WORKING_TOPOLOGY} \ + -s performance \ + tests/ +esac + -- 2.16.6