diff options
-rw-r--r-- | docker/Dockerfile | 2 | ||||
-rw-r--r-- | nfvbench/cfg.default.yaml | 17 | ||||
-rw-r--r-- | nfvbench/cleanup.py | 2 | ||||
-rw-r--r-- | nfvbench/credentials.py | 117 | ||||
-rw-r--r-- | nfvbench/nfvbench.py | 10 | ||||
-rw-r--r-- | nfvbenchvm/README.rst | 26 | ||||
-rw-r--r-- | nfvbenchvm/dib/elements/nfvbenchvm/static/etc/rc.d/rc.local.generator | 9 | ||||
-rw-r--r-- | nfvbenchvm/dib/elements/nfvbenchvm/static/etc/rc.d/rc.local.loopvm | 3 | ||||
-rw-r--r-- | nfvbenchvm/dib/elements/nfvbenchvm/static/nfvbench/configure-nfvbench.sh | 23 | ||||
-rw-r--r-- | nfvbenchvm/dib/elements/nfvbenchvm/static/vpp/startup.conf | 2 | ||||
-rw-r--r-- | requirements.txt | 2 | ||||
-rw-r--r-- | test/test_nfvbench.py | 36 | ||||
-rw-r--r-- | xtesting/behaveframework.py | 123 |
13 files changed, 166 insertions, 206 deletions
diff --git a/docker/Dockerfile b/docker/Dockerfile index 4336261..e851c76 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -40,8 +40,6 @@ RUN apt-get update && apt-get install -y \ && wget -O nfvbenchvm-$VM_IMAGE_VER.qcow2 http://artifacts.opnfv.org/nfvbench/images/nfvbenchvm_centos-$VM_IMAGE_VER.qcow2 \ # Override Xtesting testcases.yaml file by NFVbench default one && cp xtesting/testcases.yaml /usr/local/lib/python3.8/dist-packages/xtesting/ci/testcases.yaml \ - # Temporary override waiting for PR approval : https://gerrit.opnfv.org/gerrit/c/functest-xtesting/+/72431 - && cp xtesting/behaveframework.py /usr/local/lib/python3.8/dist-packages/xtesting/core/behaveframework.py \ && python3 ./docker/cleanup_generators.py \ && rm -rf /opt/nfvbench/.git \ # Symlink for retrocompatibility 4.x diff --git a/nfvbench/cfg.default.yaml b/nfvbench/cfg.default.yaml index 8e822e5..c76e738 100644 --- a/nfvbench/cfg.default.yaml +++ b/nfvbench/cfg.default.yaml @@ -30,8 +30,25 @@ # - availability_zone # - hypervisor_hostname # - vlans +# WARNING: Not used if clouds_detail is sets openrc_file: +# The OpenStack clouds configuration from clouds.yaml file to use. +# clouds.yaml file must be in one of the following paths: +# - ~/.config/openstack +# - /etc/openstack +# Note: If running in a container, this path must be valid in the container. +# The only case where this field can be empty is when measuring a system that does not run +# OpenStack or when OpenStack APIs are not accessible or OpenStack APis use is not +# desirable. In that case the EXT service chain must be used. +# +# If user is not admin some parameters are mandatory and must be filled with valid values in config file such as : +# - availability_zone +# - hypervisor_hostname +# - vlans +# If a value is sets, this parameter disable the use of openrc file +clouds_detail: + # Forwarder to use in nfvbenchvm image. Available options: ['vpp', 'testpmd'] vm_forwarder: testpmd diff --git a/nfvbench/cleanup.py b/nfvbench/cleanup.py index 23cdf56..cefdcfa 100644 --- a/nfvbench/cleanup.py +++ b/nfvbench/cleanup.py @@ -235,7 +235,7 @@ class Cleaner(object): """Cleaner for all NFVbench resources.""" def __init__(self, config): - cred = credentials.Credentials(config.openrc_file, None, False) + cred = credentials.Credentials(config.openrc_file, config.clouds_detail, None, False) session = cred.get_session() self.neutron_client = nclient.Client('2.0', session=session) self.nova_client = Client(2, session=session) diff --git a/nfvbench/credentials.py b/nfvbench/credentials.py index 4e4985f..a707ba3 100644 --- a/nfvbench/credentials.py +++ b/nfvbench/credentials.py @@ -21,32 +21,40 @@ import getpass from keystoneauth1.identity import v2 from keystoneauth1.identity import v3 from keystoneauth1 import session +import openstack +from keystoneclient.exceptions import HTTPClientError + from .log import LOG class Credentials(object): def get_session(self): - dct = { - 'username': self.rc_username, - 'password': self.rc_password, - 'auth_url': self.rc_auth_url - } - auth = None - - if self.rc_identity_api_version == 3: - dct.update({ - 'project_name': self.rc_project_name, - 'project_domain_name': self.rc_project_domain_name, - 'user_domain_name': self.rc_user_domain_name - }) - auth = v3.Password(**dct) + + if self.clouds_detail: + connection = openstack.connect(cloud=self.clouds_detail) + cred_session = connection.session else: - dct.update({ - 'tenant_name': self.rc_tenant_name - }) - auth = v2.Password(**dct) - return session.Session(auth=auth, verify=self.rc_cacert) + dct = { + 'username': self.rc_username, + 'password': self.rc_password, + 'auth_url': self.rc_auth_url + } + + if self.rc_identity_api_version == 3: + dct.update({ + 'project_name': self.rc_project_name, + 'project_domain_name': self.rc_project_domain_name, + 'user_domain_name': self.rc_user_domain_name + }) + auth = v3.Password(**dct) + else: + dct.update({ + 'tenant_name': self.rc_tenant_name + }) + auth = v2.Password(**dct) + cred_session = session.Session(auth=auth, verify=self.rc_cacert) + return cred_session def __parse_openrc(self, file): export_re = re.compile('export OS_([A-Z_]*)="?(.*)') @@ -91,11 +99,28 @@ class Credentials(object): elif name == "PROJECT_DOMAIN_NAME": self.rc_project_domain_name = value + # /users URL returns exception (HTTP 403) if user is not admin. + # try first without the version in case session already has it in + # Return HTTP 200 if user is admin + def __user_is_admin(self, url): + is_admin = False + try: + # check if user has admin role in OpenStack project + filter = {'service_type': 'identity', + 'interface': 'public'} + self.get_session().get(url, endpoint_filter=filter) + is_admin = True + except HTTPClientError as exc: + if exc.http_status == 403: + LOG.warning( + "User is not admin, no permission to list user roles. Exception: %s", exc) + return is_admin + # # Read a openrc file and take care of the password # The 2 args are passed from the command line and can be None # - def __init__(self, openrc_file, pwd=None, no_env=False): + def __init__(self, openrc_file, clouds_detail, pwd=None, no_env=False): self.rc_password = None self.rc_username = None self.rc_tenant_name = None @@ -105,8 +130,9 @@ class Credentials(object): self.rc_user_domain_name = None self.rc_project_domain_name = None self.rc_project_name = None - self.rc_identity_api_version = 2 + self.rc_identity_api_version = 3 self.is_admin = False + self.clouds_detail = clouds_detail success = True if openrc_file: @@ -118,7 +144,7 @@ class Credentials(object): success = False else: self.__parse_openrc(openrc_file) - elif not no_env: + elif not clouds_detail and not no_env: # no openrc file passed - we assume the variables have been # sourced by the calling shell # just check that they are present @@ -153,34 +179,27 @@ class Credentials(object): # always override with CLI argument if provided - if pwd: - self.rc_password = pwd - # if password not know, check from env variable - elif self.rc_auth_url and not self.rc_password and success: - if 'OS_PASSWORD' in os.environ and not no_env: - self.rc_password = os.environ['OS_PASSWORD'] - else: - # interactively ask for password - self.rc_password = getpass.getpass( - 'Please enter your OpenStack Password: ') - if not self.rc_password: - self.rc_password = "" - - # check if user has admin role in OpenStack project - filter = {'service_type': 'identity', - 'interface': 'public', - 'region_name': self.rc_region_name} + if not clouds_detail: + if pwd: + self.rc_password = pwd + # if password not know, check from env variable + elif self.rc_auth_url and not self.rc_password and success: + if 'OS_PASSWORD' in os.environ and not no_env: + self.rc_password = os.environ['OS_PASSWORD'] + else: + # interactively ask for password + self.rc_password = getpass.getpass( + 'Please enter your OpenStack Password: ') + if not self.rc_password: + self.rc_password = "" + + try: # /users URL returns exception (HTTP 403) if user is not admin. # try first without the version in case session already has it in # Return HTTP 200 if user is admin - self.get_session().get('/users', endpoint_filter=filter) - self.is_admin = True - except Exception: - try: - # vX/users URL returns exception (HTTP 403) if user is not admin. - self.get_session().get('/v' + str(self.rc_identity_api_version) + '/users', - endpoint_filter=filter) - self.is_admin = True - except Exception as e: - LOG.warning("User is not admin, no permission to list user roles. Exception: %s", e) + self.is_admin = self.__user_is_admin('/users') or self.__user_is_admin( + '/v2/users') or self.__user_is_admin('/v3/users') + except Exception as e: + LOG.warning("Error occurred during Openstack API access. " + "Unable to check user is admin. Exception: %s", e) diff --git a/nfvbench/nfvbench.py b/nfvbench/nfvbench.py index 598247a..0719247 100644 --- a/nfvbench/nfvbench.py +++ b/nfvbench/nfvbench.py @@ -60,8 +60,8 @@ class NFVBench(object): self.config_plugin = config_plugin self.factory = factory self.notifier = notifier - self.cred = credentials.Credentials(config.openrc_file, None, False) \ - if config.openrc_file else None + self.cred = credentials.Credentials(config.openrc_file, config.clouds_detail, None, False) \ + if config.openrc_file or config.clouds_detail else None self.chain_runner = None self.specs = Specs() self.specs.set_openstack_spec(openstack_spec) @@ -96,8 +96,10 @@ class NFVBench(object): # check that an empty openrc file (no OpenStack) is only allowed # with EXT chain - if not self.config.openrc_file and self.config.service_chain != ChainType.EXT: - raise Exception("openrc_file in the configuration is required for PVP/PVVP chains") + if (not self.config.openrc_file and not self.config.clouds_detail) and \ + self.config.service_chain != ChainType.EXT: + raise Exception("openrc_file or clouds_detail in the configuration is required" + " for PVP/PVVP chains") self.specs.set_run_spec(self.config_plugin.get_run_spec(self.config, self.specs.openstack)) diff --git a/nfvbenchvm/README.rst b/nfvbenchvm/README.rst index 0274c75..dad9f46 100644 --- a/nfvbenchvm/README.rst +++ b/nfvbenchvm/README.rst @@ -95,6 +95,29 @@ Hardcoded Username and Password GENERATOR IMAGE INSTANCE AND CONFIG =================================== +Pre-requisites +-------------- +To use openstack APIs, NFVbench generator VM will use `clouds.yaml` file as openstack configuration. +The OpenStack clouds configuration from clouds.yaml file to use. +clouds.yaml file must be in one of the following paths: +- ~/.config/openstack +- /etc/openstack + +Example of `clouds.yaml`: + +.. code-block:: yaml + + clouds: + devstack: + auth: + auth_url: http://192.168.122.10:35357/ + project_name: demo + username: demo + password: 0penstack + region_name: RegionOne + +.. note:: Add `CLOUD_DETAIL` property with the accurate value for your openstack configuration (`devstack` in the above example) in ``/etc/nfvbenchvm.conf`` + Interface Requirements ---------------------- The instance must be launched using OpenStack with 2 network interfaces for dataplane traffic (using SR-IOV function) and 1 management interface to control nfvbench. @@ -135,6 +158,7 @@ Template of a genarator profile using CPU pinning: pci: "{{PCI_ADDRESS_2}}" switch: intf_speed: + .. note:: `CORE_THREADS` value is determined automatically based on the cores available on the VM starting from 2 to last worker core available. Auto-configuration @@ -155,6 +179,7 @@ Example of configuration: LOOPBACK_INTF_MAC2=FA:16:3E:10:DA:10 E2E_INTF_MAC1=FA:16:3E:B0:E2:43 E2E_INTF_MAC2=FA:16:3E:D3:6A:FC + .. note:: `ACTION` parameter is not mandatory but will permit to start NFVbench with the accurate ports (loopback or e2e). .. note:: Set of MAC parameters cannot be used in parallel as only one NFVbench/TRex process is running. .. note:: Switching from `loopback` to `e2e` action can be done manually using `/nfvbench/start-nfvbench.sh <action>` with the accurate keyword for `action` parameter. This script will restart NFVbench with the good set of MAC. @@ -185,6 +210,7 @@ Using pre-created direct-physical ports on openstack, mac addresses value are on INTF_MGMT_CIDR=172.20.56.228/2 INTF_MGMT_IP_GW=172.20.56.225 DNS_SERVERS=8.8.8.8,dns.server.com + .. note:: A management interface is required to automatically find the virtual interface to use according to the MAC address provided (see `INTF_MAC_MGMT` parameter). .. note:: NFVbench VM will call openstack API through the management interface to retrieve mac address for these ports .. note:: If openstack API required a host name resolution, add the parameter DNS_SERVERS to add IP or DNS server names (multiple servers can be added separated by a `,`) diff --git a/nfvbenchvm/dib/elements/nfvbenchvm/static/etc/rc.d/rc.local.generator b/nfvbenchvm/dib/elements/nfvbenchvm/static/etc/rc.d/rc.local.generator index 0746fd6..633403c 100644 --- a/nfvbenchvm/dib/elements/nfvbenchvm/static/etc/rc.d/rc.local.generator +++ b/nfvbenchvm/dib/elements/nfvbenchvm/static/etc/rc.d/rc.local.generator @@ -4,7 +4,6 @@ touch /var/lock/subsys/local # Waiting for cloud-init to generate $NFVBENCH_CONF, retry 60 seconds NFVBENCH_CONF=/etc/nfvbenchvm.conf -OPENRC=/etc/nfvbench/openrc retry=30 until [ $retry -eq 0 ]; do if [ -f $NFVBENCH_CONF ]; then break; fi @@ -20,10 +19,6 @@ echo "Generating configurations for NFVbench and TRex..." eval $(cat $NFVBENCH_CONF) touch /nfvbench_configured.flag -if [ -f $OPENRC ]; then - source $OPENRC -fi - # Add DNS entry if [ $DNS_SERVERS ]; then IFS="," read -a dns <<< $DNS_SERVERS @@ -80,8 +75,8 @@ get_eth_port() { if [ $INTF_MGMT_CIDR ] && [ $INTF_MGMT_IP_GW ]; then if [ $INTF_MAC_MGMT ]; then ETH_PORT=$(get_eth_port $INTF_MAC_MGMT) - elif [ -f $OPENRC ] && [ "$PORT_MGMT_NAME" ]; then - $INTF_MAC_MGMT=$(openstack port list | grep $PORT_MGMT_NAME | grep -o -Ei '([a-fA-F0-9:]{17}|[a-fA-F0-9]{12}$)' | head -1) + elif [ "$CLOUD_DETAIL" ] && [ "$PORT_MGMT_NAME" ]; then + $INTF_MAC_MGMT=$(openstack --os-cloud $CLOUD_DETAIL port list | grep $PORT_MGMT_NAME | grep -o -Ei '([a-fA-F0-9:]{17}|[a-fA-F0-9]{12}$)' | head -1) ETH_PORT=$(get_eth_port $INTF_MAC_MGMT) else ETH_PORT="" diff --git a/nfvbenchvm/dib/elements/nfvbenchvm/static/etc/rc.d/rc.local.loopvm b/nfvbenchvm/dib/elements/nfvbenchvm/static/etc/rc.d/rc.local.loopvm index bc14902..ecc8b05 100644 --- a/nfvbenchvm/dib/elements/nfvbenchvm/static/etc/rc.d/rc.local.loopvm +++ b/nfvbenchvm/dib/elements/nfvbenchvm/static/etc/rc.d/rc.local.loopvm @@ -167,10 +167,11 @@ fi if [ $PCI_ADDRESS_1 ] && [ $PCI_ADDRESS_2 ]; then logger "NFVBENCHVM: Using pci $PCI_ADDRESS_1 ($INTF_MAC1)" logger "NFVBENCHVM: Using pci $PCI_ADDRESS_2 ($INTF_MAC2)" + # active uio_pci_generic driver + modprobe uio_pci_generic # Configure the forwarder if [ "$FORWARDER" == "testpmd" ]; then echo "Configuring testpmd..." - modprobe uio_pci_generic mkdir /dpdk echo "set promisc all off" > /dpdk/testpmd_cmd.txt # Binding ports to DPDK VFIO or UIO diff --git a/nfvbenchvm/dib/elements/nfvbenchvm/static/nfvbench/configure-nfvbench.sh b/nfvbenchvm/dib/elements/nfvbenchvm/static/nfvbench/configure-nfvbench.sh index 5ec584b..3bf1d8d 100644 --- a/nfvbenchvm/dib/elements/nfvbenchvm/static/nfvbench/configure-nfvbench.sh +++ b/nfvbenchvm/dib/elements/nfvbenchvm/static/nfvbench/configure-nfvbench.sh @@ -6,15 +6,10 @@ NFVBENCH_CONF=/etc/nfvbenchvm.conf E2E_CFG=/etc/nfvbench/e2e.cfg LOOPBACK_CFG=/etc/nfvbench/loopback.cfg NFVBENCH_CFG=/etc/nfvbench/nfvbench.cfg -OPENRC=/etc/nfvbench/openrc # Parse and obtain all configurations eval $(cat $NFVBENCH_CONF) -if [ -f $OPENRC ]; then - source $OPENRC -fi - # WE assume there are at least 2 cores available for the VM CPU_CORES=$(grep -c ^processor /proc/cpuinfo) @@ -88,21 +83,21 @@ get_interfaces_mac_values(){ # Set dynamically interfaces mac values, if VM is spawn with SRIOV PF ports # and openstack API are accessible if [ -z "$LOOPBACK_INTF_MAC1" ] && [ -z "$LOOPBACK_INTF_MAC2" ]; then - if [ -f $OPENRC ] && [ "$LOOPBACK_PORT_NAME1" ] && [ "$LOOPBACK_PORT_NAME2" ]; then - LOOPBACK_INTF_MAC1=$(openstack port list | grep $LOOPBACK_PORT_NAME1 | grep -o -Ei '([a-fA-F0-9:]{17}|[a-fA-F0-9]{12}$)' | head -1) - LOOPBACK_INTF_MAC2=$(openstack port list | grep $LOOPBACK_PORT_NAME2 | grep -o -Ei '([a-fA-F0-9:]{17}|[a-fA-F0-9]{12}$)' | head -1) + if [ "$CLOUD_DETAIL" ] && [ "$LOOPBACK_PORT_NAME1" ] && [ "$LOOPBACK_PORT_NAME2" ]; then + LOOPBACK_INTF_MAC1=$(openstack --os-cloud $CLOUD_DETAIL port list | grep $LOOPBACK_PORT_NAME1 | grep -o -Ei '([a-fA-F0-9:]{17}|[a-fA-F0-9]{12}$)' | head -1) + LOOPBACK_INTF_MAC2=$(openstack --os-cloud $CLOUD_DETAIL port list | grep $LOOPBACK_PORT_NAME2 | grep -o -Ei '([a-fA-F0-9:]{17}|[a-fA-F0-9]{12}$)' | head -1) fi fi if [ -z "$E2E_INTF_MAC1" ] && [ -z "$E2E_INTF_MAC2" ]; then - if [ -f $OPENRC ] && [ "$E2E_PORT_NAME1" ] && [ "$E2E_PORT_NAME2" ]; then - E2E_INTF_MAC1=$(openstack port list | grep $E2E_PORT_NAME1 | grep -o -Ei '([a-fA-F0-9:]{17}|[a-fA-F0-9]{12}$)' | head -1) - E2E_INTF_MAC2=$(openstack port list | grep $E2E_PORT_NAME2 | grep -o -Ei '([a-fA-F0-9:]{17}|[a-fA-F0-9]{12}$)' | head -1) + if [ "$CLOUD_DETAIL" ] && [ "$E2E_PORT_NAME1" ] && [ "$E2E_PORT_NAME2" ]; then + E2E_INTF_MAC1=$(openstack --os-cloud $CLOUD_DETAIL port list | grep $E2E_PORT_NAME1 | grep -o -Ei '([a-fA-F0-9:]{17}|[a-fA-F0-9]{12}$)' | head -1) + E2E_INTF_MAC2=$(openstack --os-cloud $CLOUD_DETAIL port list | grep $E2E_PORT_NAME2 | grep -o -Ei '([a-fA-F0-9:]{17}|[a-fA-F0-9]{12}$)' | head -1) fi fi if [ -z "$INTF_MAC1" ] && [ -z "$INTF_MAC2" ]; then - if [ -f $OPENRC ] && [ "$PORT_NAME1" ] && [ "$PORT_NAME2" ]; then - INTF_MAC1=$(openstack port list | grep $PORT_NAME1 | grep -o -Ei '([a-fA-F0-9:]{17}|[a-fA-F0-9]{12}$)' | head -1) - INTF_MAC2=$(openstack port list | grep $PORT_NAME2 | grep -o -Ei '([a-fA-F0-9:]{17}|[a-fA-F0-9]{12}$)' | head -1) + if [ "$CLOUD_DETAIL" ] && [ "$PORT_NAME1" ] && [ "$PORT_NAME2" ]; then + INTF_MAC1=$(openstack --os-cloud $CLOUD_DETAIL port list | grep $PORT_NAME1 | grep -o -Ei '([a-fA-F0-9:]{17}|[a-fA-F0-9]{12}$)' | head -1) + INTF_MAC2=$(openstack --os-cloud $CLOUD_DETAIL port list | grep $PORT_NAME2 | grep -o -Ei '([a-fA-F0-9:]{17}|[a-fA-F0-9]{12}$)' | head -1) fi fi } diff --git a/nfvbenchvm/dib/elements/nfvbenchvm/static/vpp/startup.conf b/nfvbenchvm/dib/elements/nfvbenchvm/static/vpp/startup.conf index d174299..874f6cb 100644 --- a/nfvbenchvm/dib/elements/nfvbenchvm/static/vpp/startup.conf +++ b/nfvbenchvm/dib/elements/nfvbenchvm/static/vpp/startup.conf @@ -19,7 +19,7 @@ dpdk { socket-mem 1024 dev {{PCI_ADDRESS_1}} dev {{PCI_ADDRESS_2}} - uio-driver igb_uio + uio-driver uio_pci_generic num-mbufs {{NUM_MBUFS}} } diff --git a/requirements.txt b/requirements.txt index 7c74119..a333380 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,7 +22,7 @@ tabulate>=0.7.5 flask>=0.12 fluent-logger>=0.5.3 netaddr>=0.7.19 -xtesting>=0.87.0 +xtesting>=0.92.0 hdrhistogram>=0.8.0 behave>=1.2.6 retry>=0.9.2
\ No newline at end of file diff --git a/test/test_nfvbench.py b/test/test_nfvbench.py index e53a586..360e3bd 100644 --- a/test/test_nfvbench.py +++ b/test/test_nfvbench.py @@ -13,6 +13,8 @@ # License for the specific language governing permissions and limitations # under the License. # +import openstack +from keystoneauth1.exceptions import HTTPClientError from mock import patch import pytest @@ -139,13 +141,41 @@ def test_load_from_rate(): # ========================================================================= def test_no_credentials(): - cred = Credentials('/completely/wrong/path/openrc', None, False) - if cred.rc_auth_url: - # shouldn't get valid data unless user set environment variables + with patch.object(openstack, 'connect') as mock: + cred = Credentials('/completely/wrong/path/openrc', None, None, False) + if cred.rc_auth_url: + # shouldn't get valid data unless user set environment variables + assert False + else: + assert True + mock.assert_not_called() + + +def test_clouds_file_credentials(): + with patch.object(openstack, 'connect') as mock: + Credentials(None, 'openstack', None, False) + mock.assert_called_once() + + +@patch('nfvbench.nfvbench.credentials') +def test_is_not_admin(mock_session): + mock_session.Session.return_value.get.return_value.raiseError.side_effect = HTTPClientError + cred = Credentials(None, 'openstack', None, False) + if cred.is_admin: assert False else: assert True + +def test_is_admin(): + with patch.object(openstack, 'connect'): + cred = Credentials(None, 'openstack', None, False) + if cred.is_admin: + assert True + else: + assert False + + def test_ip_block(): ipb = IpBlock('10.0.0.0', '0.0.0.1', 256) assert ipb.get_ip() == '10.0.0.0' diff --git a/xtesting/behaveframework.py b/xtesting/behaveframework.py deleted file mode 100644 index 651240d..0000000 --- a/xtesting/behaveframework.py +++ /dev/null @@ -1,123 +0,0 @@ -#!/usr/bin/env python -# Copyright 2021 Orange -# -# 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. -# - -"""Define classes required to run any Behave test suites.""" - -from __future__ import division - -import logging -import os -import time - -import json -import six - -from behave.__main__ import main as behave_main - -from xtesting.core import testcase - -__author__ = "Deepak Chandella <deepak.chandella@orange.com>" - - -class BehaveFramework(testcase.TestCase): - """BehaveFramework runner.""" - # pylint: disable=too-many-instance-attributes - - __logger = logging.getLogger(__name__) - dir_results = "/var/lib/xtesting/results" - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.json_file = os.path.join(self.res_dir, 'output.json') - self.total_tests = 0 - self.pass_tests = 0 - self.fail_tests = 0 - self.skip_tests = 0 - self.response = None - - def parse_results(self): - """Parse output.json and get the details in it.""" - with open(self.json_file) as stream_: - self.response = json.load(stream_) - if self.response: - self.total_tests = len(self.response) - for item in self.response: - if item['status'] == 'passed': - self.pass_tests += 1 - elif item['status'] == 'failed': - self.fail_tests += 1 - elif item['status'] == 'skipped': - self.skip_tests += 1 - self.result = 100 * ( - self.pass_tests / self.total_tests) - self.details = {} - 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'] = self.response - - def run(self, **kwargs): - """Run the BehaveFramework feature files - - Here are the steps: - * create the output directories if required, - * run behave features with parameters - * get the results in output.json, - - Args: - kwargs: Arbitrary keyword arguments. - - Returns: - EX_OK if all suites ran well. - EX_RUN_ERROR otherwise. - """ - try: - suites = kwargs["suites"] - tags = kwargs.get("tags", []) - console = kwargs["console"] if "console" in kwargs else False - except KeyError: - self.__logger.exception("Mandatory args were not passed") - return self.EX_RUN_ERROR - if not os.path.exists(self.res_dir): - try: - os.makedirs(self.res_dir) - except Exception: # pylint: disable=broad-except - self.__logger.exception("Cannot create %s", self.res_dir) - return self.EX_RUN_ERROR - config = ['--tags=' + ','.join(tags), - '--junit', '--junit-directory={}'.format(self.res_dir), - '--format=json', '--outfile={}'.format(self.json_file)] - if six.PY3: - html_file = os.path.join(self.res_dir, 'output.html') - config += ['--format=behave_html_formatter:HTMLFormatter', - '--outfile={}'.format(html_file)] - if console: - config += ['--format=pretty', - '--outfile=-'] - for feature in suites: - config.append(feature) - self.start_time = time.time() - behave_main(config) - self.stop_time = time.time() - - try: - self.parse_results() - self.__logger.info("Results were successfully parsed") - except Exception: # pylint: disable=broad-except - self.__logger.exception("Cannot parse results") - return self.EX_RUN_ERROR - return self.EX_OK |