Add MTS driver 14/70214/20
authorVincent Mahe <v.mahe@orange.com>
Mon, 25 May 2020 16:32:58 +0000 (18:32 +0200)
committerCédric Ollivier <cedric.ollivier@orange.com>
Wed, 5 Aug 2020 11:21:12 +0000 (13:21 +0200)
It creates a new container xtesting-mts to avoid increase core
container size.

Signed-off-by: Vincent Mahe <v.mahe@orange.com>
Change-Id: I59544023e1235747e140a442815778a133bf6acf

15 files changed:
.gitignore
ansible/site.yml
build.sh
docker/core/Dockerfile [moved from docker/Dockerfile with 91% similarity]
docker/core/testcases.yaml [moved from docker/testcases.yaml with 100% similarity]
docker/mts/Dockerfile [new file with mode: 0644]
docker/mts/mts-installer.properties [new file with mode: 0644]
docker/mts/testcases.yaml [new file with mode: 0644]
requirements.txt
setup.cfg
tox.ini
xtesting/ci/testcases.yaml
xtesting/core/mts.py [new file with mode: 0644]
xtesting/samples/mts/pause.xml [new file with mode: 0644]
xtesting/samples/mts/test.xml [new file with mode: 0644]

index 69ce36c..c8e5057 100644 (file)
@@ -21,3 +21,5 @@ build
 dist
 AUTHORS
 ChangeLog
+.eggs/
+.vscode/
\ No newline at end of file
index ccc42b3..3f49628 100644 (file)
             containers:
               - name: xtesting
                 ref_arg: BRANCH
-                path: docker
+                path: docker/core
+          - name: opnfv/xtesting-mts
+            containers:
+              - name: xtesting-mts
+                ref_arg: BRANCH
+                path: docker/mts
       suites:
         - container: xtesting
           tests:
@@ -23,3 +28,6 @@
             - fourth
             - fifth
             - sixth
+        - container: xtesting-mts
+          tests:
+            - seventh
index 3c15559..f6aaa49 100644 (file)
--- a/build.sh
+++ b/build.sh
@@ -8,27 +8,51 @@
 set -xe
 
 repo=${REPO:-opnfv}
-tag=${BRANCH:-latest}
 arch=${arch-"\
 amd64 \
 arm64 \
 arm"}
+amd64_dirs=${amd64_dirs-"\
+docker/core \
+docker/mts"}
+arm_dirs=${arm_dirs-${amd64_dirs}}
+arm64_dirs=${arm64_dirs-${amd64_dirs}}
+tag=${BRANCH:-latest}
 image="xtesting"
 build_opts=(--pull=true --no-cache --force-rm=true)
 
-for arch in ${arch};do
+for arch in ${arch}; do
     if [[ ${arch} == arm64 ]]; then
         find . -name Dockerfile -exec sed -i \
             -e "s|alpine:3.12|arm64v8/alpine:3.12|g" {} +
+        find . -name Dockerfile -exec sed -i \
+            -e "s|opnfv/xtesting|${repo}/xtesting:arm64-${tag}|g" {} +
     elif [[ ${arch} == arm ]]; then
         find . -name Dockerfile -exec sed -i \
             -e "s|alpine:3.12|arm32v6/alpine:3.12|g" {} +
+        find . -name Dockerfile -exec sed -i \
+            -e "s|opnfv/xtesting|${repo}/xtesting:arm-${tag}|g" {} +
+    else
+        find . -name Dockerfile -exec sed -i \
+            -e "s|opnfv/xtesting|${repo}/xtesting:amd64-${tag}|g" {} +
     fi
-    (cd docker &&   docker build "${build_opts[@]}" \
-        -t "${repo}/${image}:${arch}-${tag}" .)
-    docker push "${repo}/${image}:${arch}-${tag}"
+    dirs=${arch}_dirs
+    for dir in ${!dirs}; do
+        if [[ ${dir} == docker/core ]]; then
+            image=xtesting
+        else
+            image=xtesting-${dir##**/}
+        fi
+        (cd "${dir}" &&
+            docker build "${build_opts[@]}" \
+                -t "${repo}/${image}:${arch}-${tag}" . &&
+            docker push "${repo}/${image}:${arch}-${tag}")
+        [ "${dir}" != "docker/core" ] &&
+            (docker rmi \
+                "${repo}/${image}:${arch}-${tag}" || true)
+    done
     [ "$?" == "0" ] &&
-        (sudo docker rmi "${repo}/${image}:${arch}-${tag}"|| true)
+        (sudo docker rmi "${repo}/xtesting:${arch}-${tag}"|| true)
     find . -name Dockerfile -exec git checkout \{\} +;
 done
 exit $?
similarity index 91%
rename from docker/Dockerfile
rename to docker/core/Dockerfile
index a5e3801..5838973 100644 (file)
@@ -3,9 +3,9 @@ FROM alpine:3.12
 ARG BRANCH=master
 ARG OPENSTACK_TAG=master
 
-RUN apk --no-cache add --update python3 py3-pip bash git mailcap && \
+RUN apk --no-cache add --update python3 py3-pip bash git mailcap libxml2 libxslt && \
     apk --no-cache add --virtual .build-deps --update \
-        python3-dev build-base && \
+        python3-dev build-base libxml2-dev libxslt-dev && \
     git init /src/functest-xtesting && \
     (cd /src/functest-xtesting && \
         git fetch --tags https://gerrit.opnfv.org/gerrit/functest-xtesting $BRANCH && \
diff --git a/docker/mts/Dockerfile b/docker/mts/Dockerfile
new file mode 100644 (file)
index 0000000..597e4f4
--- /dev/null
@@ -0,0 +1,24 @@
+FROM opnfv/xtesting
+
+ARG MTS_TAG=6.6.3
+ARG APP_FOLDER=/opt/mts
+ARG MAVEN_OPTS=
+ENV JAVA_HOME=/usr/lib/jvm/java-1.8-openjdk
+ENV NGN_JAVA_HOME=${JAVA_HOME}/bin
+ENV MAVEN_OPTS=$MAVEN_OPTS
+
+COPY mts-installer.properties /src/mts-installer.properties
+RUN apk --no-cache add --update openjdk8-jre lksctp-tools libpcap && \
+    apk --no-cache add --virtual .build-deps --update \
+        libpcap-dev openjdk8 maven git && \
+    git init /src/git-mts && \
+    (cd /src/git-mts && \
+        git fetch --tags https://github.com/ericsson-mts/mts $MTS_TAG && \
+        git checkout FETCH_HEAD && \
+        echo ${NGN_JAVA_HOME} > src/main/bin/java_home.release && \
+        mvn versions:set -DnewVersion=${MTS_TAG} && mvn package && mvn install && \
+        java -jar target/mts-${MTS_TAG}-installer.jar -options /src/mts-installer.properties) && \
+    rm -rf /root/.m2/ ${APP_FOLDER}/tutorial /src/mts-installer.properties /src/git-mts && \
+    apk del .build-deps
+COPY testcases.yaml /usr/lib/python3.8/site-packages/xtesting/ci/testcases.yaml
+CMD ["run_tests", "-t", "all"]
diff --git a/docker/mts/mts-installer.properties b/docker/mts/mts-installer.properties
new file mode 100644 (file)
index 0000000..fe4e030
--- /dev/null
@@ -0,0 +1,2 @@
+INSTALL_PATH=/opt/mts
+java_memory=1024
diff --git a/docker/mts/testcases.yaml b/docker/mts/testcases.yaml
new file mode 100644 (file)
index 0000000..40bb46d
--- /dev/null
@@ -0,0 +1,26 @@
+---
+tiers:
+    -
+        name: samples
+        order: 1
+        description: ''
+        testcases:
+            -
+                case_name: seventh
+                project_name: xtesting
+                enabled: true
+                criteria: 100
+                blocking: true
+                clean_flag: false
+                description: 'Some MTS tests'
+                run:
+                    name: 'mts'
+                    args:
+                        test_file: /usr/lib/python3.8/site-packages/xtesting/samples/mts/test.xml
+                        testcases:
+                            - Pause_5_sec
+                        max_duration: 2 # in seconds
+                        log_level: INFO
+                        store_method: FILE
+                        java_memory: 2048
+                        console: true
index d301949..b3e0a62 100644 (file)
@@ -17,3 +17,4 @@ os-testr<2.0.0;python_version=='2.7' # Apache-2.0
 os-testr;python_version>='3.6' # Apache-2.0
 junitxml
 boto3 # Apache-2.0
+lxml!=3.7.0 # BSD
index 9b8dc92..755d107 100644 (file)
--- a/setup.cfg
+++ b/setup.cfg
@@ -31,6 +31,7 @@ xtesting.testcase =
     unit = xtesting.core.unit:Suite
     first = xtesting.samples.first:Test
     second = xtesting.samples.second:Test
+    mts = xtesting.core.mts:MTSLauncher
 
 [build_sphinx]
 all_files = 1
diff --git a/tox.ini b/tox.ini
index e23387e..5fca42a 100644 (file)
--- a/tox.ini
+++ b/tox.ini
@@ -33,7 +33,7 @@ basepython = python3.8
 whitelist_externals = bash
 commands =
   pylint --min-similarity-lines=10 \
-    --disable=locally-disabled --ignore-imports=y --reports=n xtesting
+    --disable=locally-disabled --ignore-imports=y --reports=n --extension-pkg-whitelist=lxml xtesting
 
 [testenv:yamllint]
 basepython = python3.8
index 9de9c4b..802708d 100644 (file)
@@ -81,3 +81,23 @@ tiers:
                             - /usr/lib/python3.6/site-packages/xtesting/samples/features/
                         tags:
                             - foo
+
+            -
+                case_name: seventh
+                project_name: xtesting
+                enabled: true
+                criteria: 100
+                blocking: true
+                clean_flag: false
+                description: ''
+                run:
+                    name: 'mts'
+                    args:
+                        test_file: /opt/mts/bin/test/test.xml
+                        testcases:
+                            - Pause_5_sec
+                        max_duration: 2  # in seconds
+                        log_level: INFO
+                        store_method: FILE
+                        java_memory: 2048
+                        console: true
diff --git a/xtesting/core/mts.py b/xtesting/core/mts.py
new file mode 100644 (file)
index 0000000..a5cc6a1
--- /dev/null
@@ -0,0 +1,287 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2020 Orange and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# pylint: disable=too-many-instance-attributes
+
+"""Define the parent classes of all Xtesting Features.
+
+Feature is considered as TestCase offered by Third-party. It offers
+helpers to run any python method or any bash command.
+"""
+
+import csv
+import logging
+import os
+import subprocess
+import sys
+import time
+
+from lxml import etree
+import prettytable
+
+from xtesting.core import testcase
+
+
+__author__ = ("Vincent Mahe <v.mahe@orange.com>, "
+              "Cedric Ollivier <cedric.ollivier@orange.com>")
+
+
+class MTSLauncher(testcase.TestCase):
+    """Class designed to run MTS tests."""
+
+    __logger = logging.getLogger(__name__)
+    mts_install_dir = "/opt/mts"
+
+    def __init__(self, **kwargs):
+        super(MTSLauncher, self).__init__(**kwargs)
+        self.result_file = "{}/{}.log".format(self.res_dir, self.case_name)
+        # Location of the HTML report generated by MTS
+        self.mts_stats_dir = os.path.join(self.res_dir, 'mts_stats_report')
+        # Location of the log files generated by MTS for each test.
+        # Need to end path with a separator because of a bug in MTS.
+        self.mts_logs_dir = os.path.join(self.res_dir,
+                                         'mts_logs' + os.path.sep)
+        # The location of file named testPlan.csv
+        # that it always in $MTS_HOME/logs
+        self.mts_result_csv_file = self.mts_install_dir + os.path.sep
+        self.mts_result_csv_file += ("logs" + os.path.sep + "testPlan.csv")
+        self.total_tests = 0
+        self.pass_tests = 0
+        self.fail_tests = 0
+        self.skip_tests = 0
+        self.response = None
+        self.testcases = []
+
+    def parse_results(self):
+        """Parse testPlan.csv containing the status of each testcase of the test file.
+        See sample file in `xtesting/samples/mts/output/testPlan.csv`
+        """
+        with open(self.mts_result_csv_file) as stream_:
+            self.__logger.info("Parsing file : %s", self.mts_result_csv_file)
+            reader = csv.reader(stream_, delimiter=';')
+            rownum = 0
+            _tests_data = []
+            msg = prettytable.PrettyTable(
+                header_style='upper', padding_width=5,
+                field_names=['MTS test', 'MTS test case',
+                             'status'])
+            for row in reader:
+                _test_dict = {}
+                nb_values = len(row)
+                if rownum > 0:
+                    # If there's only one delimiter,
+                    # it is the name of the <test> elt
+                    if nb_values == 2:
+                        test_name = row[0]
+                        _test_dict['parent'] = test_name
+                    elif nb_values == 3:
+                        testcase_name = row[0].lstrip()
+                        testcase_status = row[2]
+                        self.total_tests += 1
+                        if testcase_status == 'OK':
+                            self.pass_tests += 1
+                        elif testcase_status == 'Failed':
+                            self.fail_tests += 1
+                        elif testcase_status == '?':
+                            self.skip_tests += 1
+                        _test_dict['status'] = testcase_status
+                        _test_dict['name'] = testcase_name
+                        msg.add_row(
+                            [test_name,
+                             _test_dict['name'],
+                             _test_dict['status']])
+                rownum += 1
+                _tests_data.append(_test_dict)
+            try:
+                self.result = 100 * (
+                    self.pass_tests / self.total_tests)
+            except ZeroDivisionError:
+                self.__logger.error("No test has been run")
+            self.__logger.info("MTS Test result:\n\n%s\n", msg.get_string())
+            self.details = {}
+            self.details['description'] = "Execution of some MTS tests"
+            self.details['total_tests'] = self.total_tests
+            self.details['pass_tests'] = self.pass_tests
+            self.details['fail_tests'] = self.fail_tests
+            self.details['skip_tests'] = self.skip_tests
+            self.details['tests'] = _tests_data
+
+    def parse_xml_test_file(self, xml_test_file):
+        """Parse the XML file containing the test definition for MTS.
+        See sample file in `xtesting/samples/mts/test.xml`
+        """
+        nb_testcases = -1
+        self.__logger.info(
+            "Parsing XML test file %s containing the MTS tests definitions.",
+            xml_test_file)
+        try:
+            parser = etree.XMLParser(load_dtd=True, resolve_entities=True)
+            self.__logger.info("XML test file %s successfully parsed.",
+                               xml_test_file)
+            root = etree.parse(xml_test_file, parser=parser)
+            # Need to look at all child nodes because there may be
+            # some <for> elt between <test> and <testcase> elt
+            self.testcases = root.xpath('//test//testcase/@name')
+            nb_testcases = len(self.testcases)
+            if nb_testcases == 0:
+                self.__logger.warning("Found no MTS testcase !")
+            elif nb_testcases == 1:
+                self.__logger.info("Found only one MTS testcase: %s",
+                                   self.testcases[0])
+            else:
+                self.__logger.info("Found %d MTS testcases :", nb_testcases)
+                for mts_testcase in self.testcases:
+                    self.__logger.info("    - %s", mts_testcase)
+        except etree.XMLSyntaxError as xml_err:
+            self.__logger.error("Error while parsing XML test file: %s",
+                                str(xml_err))
+        return nb_testcases
+
+    def check_enabled_mts_test_cases(self, enabled_testcases):
+        """Make sure that all required MTS test cases exist
+        in the XML test file.
+        """
+        if len(enabled_testcases) > 0:
+            # Verify if the MTS test case exists in the whole list of test
+            # cases declared in the test XML file
+            for enabled_testcase in enabled_testcases:
+                if enabled_testcase not in self.testcases:
+                    self.__logger.error(
+                        "The required MTS testcase named `%s` does not exist"
+                        " !", enabled_testcase)
+                    return False
+        return True
+
+    def execute(self, **kwargs):  # pylint: disable=too-many-locals
+        """Execute the cmd passed as arg
+
+        Args:
+            kwargs: Arbitrary keyword arguments.
+
+        Returns:
+            0 if cmd returns 0,
+            -1 otherwise.
+        """
+        try:
+            console = kwargs["console"] if "console" in kwargs else False
+            # Read specific parameters for MTS
+            test_file = kwargs["test_file"]
+            log_level = kwargs[
+                "log_level"] if "log_level" in kwargs else "INFO"
+
+            # For some MTS tests, we need to force stop after N sec
+            max_duration = kwargs[
+                "max_duration"] if "max_duration" in kwargs else None
+            store_method = kwargs[
+                "store_method"] if "store_method" in kwargs else "FILE"
+            # Must use the $HOME_MTS/bin as current working dir
+            cwd = self.mts_install_dir + os.path.sep + "bin"
+
+            # Get the list of enabled MTS testcases, if any
+            enabled_testcases = kwargs[
+                "testcases"] if "testcases" in kwargs else []
+            enabled_testcases_str = ''
+            if len(enabled_testcases) > 0:
+                enabled_testcases_str = ' '.join(enabled_testcases)
+                check_ok = self.check_enabled_mts_test_cases(enabled_testcases)
+                if not check_ok:
+                    return -2
+
+            # Build command line to launch for MTS
+            cmd = ("cd {} && ./startCmd.sh {} {} -sequential -levelLog:{}"
+                   " -storageLog:{}"
+                   " -config:stats.REPORT_DIRECTORY+{}"
+                   " -config:logs.STORAGE_DIRECTORY+{}"
+                   " -genReport:true"
+                   " -showRep:false").format(cwd,
+                                             test_file,
+                                             enabled_testcases_str,
+                                             log_level,
+                                             store_method,
+                                             self.mts_stats_dir,
+                                             self.mts_logs_dir)
+
+            # Make sure to create the necessary output sub-folders for MTS
+            if not os.path.isdir(self.mts_stats_dir):
+                os.makedirs(self.mts_stats_dir)
+            if not os.path.isdir(self.mts_logs_dir):
+                os.makedirs(self.mts_logs_dir)
+            self.__logger.info(
+                "MTS statistics output dir: %s ", self.mts_stats_dir)
+            self.__logger.info("MTS logs output dir: %s ", self.mts_logs_dir)
+
+            # Launch MTS as a sub-process
+            # and save its standard output to a file
+            with open(self.result_file, 'w') as f_stdout:
+                self.__logger.info("Calling %s", cmd)
+                process = subprocess.Popen(
+                    cmd, shell=True, stdout=subprocess.PIPE,
+                    stderr=subprocess.STDOUT)
+                for line in iter(process.stdout.readline, b''):
+                    if console:
+                        sys.stdout.write(line.decode("utf-8"))
+                    f_stdout.write(line.decode("utf-8"))
+                try:
+                    process.wait(timeout=max_duration)
+                except subprocess.TimeoutExpired:
+                    process.kill()
+                    self.__logger.info(
+                        "Killing MTS process after %d second(s).",
+                        max_duration)
+                    return 3
+            with open(self.result_file, 'r') as f_stdin:
+                self.__logger.debug("$ %s\n%s", cmd, f_stdin.read().rstrip())
+            return process.returncode
+        except KeyError:
+            self.__logger.error("Missing mandatory arg for MTS. kwargs: %s",
+                                kwargs)
+        return -1
+
+    def run(self, **kwargs):
+        """Run the feature.
+
+        It allows executing any Python method by calling execute().
+
+        It sets the following attributes required to push the results
+        to DB:
+
+            * result,
+            * start_time,
+            * stop_time.
+
+        It doesn't fulfill details when pushing the results to the DB.
+
+        Args:
+            kwargs: Arbitrary keyword arguments.
+
+        Returns:
+            TestCase.EX_OK if execute() returns 0,
+            TestCase.EX_RUN_ERROR otherwise.
+        """
+        self.start_time = time.time()
+        exit_code = testcase.TestCase.EX_RUN_ERROR
+        self.result = 0
+        try:
+            nb_testcases = self.parse_xml_test_file(kwargs["test_file"])
+            # Do something only if there are some MTS test cases in the test
+            # file
+            if nb_testcases > 0:
+                if self.execute(**kwargs) == 0:
+                    exit_code = testcase.TestCase.EX_OK
+                    try:
+                        self.parse_results()
+                    except Exception:  # pylint: disable=broad-except
+                        self.__logger.exception(
+                            "Cannot parse result file "
+                            "$MTS_HOME/logs/testPlan.csv")
+                        exit_code = testcase.TestCase.EX_RUN_ERROR
+        except Exception:  # pylint: disable=broad-except
+            self.__logger.exception("%s FAILED", self.project_name)
+        self.stop_time = time.time()
+        return exit_code
diff --git a/xtesting/samples/mts/pause.xml b/xtesting/samples/mts/pause.xml
new file mode 100644 (file)
index 0000000..34f7b52
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Related XMLSchema file: conf/schemas/scenario.xsd -->
+
+<scenario>
+  <log>Duree de pause demandee : [pauseDuration]</log>
+  <pause name="Pause" seconds="[pauseDuration]"/>
+</scenario>
diff --git a/xtesting/samples/mts/test.xml b/xtesting/samples/mts/test.xml
new file mode 100644 (file)
index 0000000..5901f5b
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<test name="Commissioning MTS">
+    <testcase name="Pause_5_sec" state="true">
+        <parameter name="[pauseDuration]" operation="set" value="5"/>
+
+        <scenario name="[testcasename]" file="pause.xml" />
+    </testcase>
+</test>