From 0bbc7e2674bd06921e9fe4a0b3318724327d3ff2 Mon Sep 17 00:00:00 2001 From: opensource-tnbt Date: Wed, 25 Nov 2020 19:30:04 +0530 Subject: [PATCH] [WIP]: VSPERF-Xtesting Integration Support. This patch adds support for integrationg with Xtesting project There are two variations - baremetal and openstack. Update-1: Add Download-result feature to baremetal Update-2: Update Dockerfile following successful compilations. Update-3: Add Baremetal-Advanced to Migrate to driver version Update-4: Remove Bashfeature approach of Baremetal. Update-5: Update documentation, Baremetal container name and openstack. Update-6: Update Openstack after successful testing Signed-off-by: Sridhar K. N. Rao Change-Id: Idcf9f533a0cc6760ca3e56355e2ff60d41616f0f --- docs/index.rst | 1 + docs/xtesting/index.rst | 85 +++++ docs/xtesting/vsperf-xtesting.png | Bin 0 -> 93202 bytes xtesting/baremetal/Dockerfile | 36 +++ xtesting/baremetal/exceptions.py | 65 ++++ xtesting/baremetal/requirements.txt | 2 + xtesting/baremetal/setup.cfg | 10 + xtesting/baremetal/setup.py | 9 + xtesting/baremetal/site.yml | 13 + xtesting/baremetal/ssh.py | 546 ++++++++++++++++++++++++++++++++ xtesting/baremetal/testcases.yaml | 16 + xtesting/baremetal/utils.py | 41 +++ xtesting/baremetal/vsperf.conf | 21 ++ xtesting/baremetal/vsperf_controller.py | 194 ++++++++++++ xtesting/openstack/Dockerfile | 61 ++++ xtesting/openstack/cloud.rc | 10 + xtesting/openstack/setup.cfg | 10 + xtesting/openstack/setup.py | 9 + xtesting/openstack/site.yml | 13 + xtesting/openstack/testcases.yaml | 19 ++ xtesting/openstack/vsperfostack.conf | 80 +++++ xtesting/openstack/vsperfostack.py | 85 +++++ 22 files changed, 1326 insertions(+) create mode 100644 docs/xtesting/index.rst create mode 100755 docs/xtesting/vsperf-xtesting.png create mode 100644 xtesting/baremetal/Dockerfile create mode 100644 xtesting/baremetal/exceptions.py create mode 100644 xtesting/baremetal/requirements.txt create mode 100644 xtesting/baremetal/setup.cfg create mode 100644 xtesting/baremetal/setup.py create mode 100644 xtesting/baremetal/site.yml create mode 100644 xtesting/baremetal/ssh.py create mode 100644 xtesting/baremetal/testcases.yaml create mode 100644 xtesting/baremetal/utils.py create mode 100644 xtesting/baremetal/vsperf.conf create mode 100644 xtesting/baremetal/vsperf_controller.py create mode 100644 xtesting/openstack/Dockerfile create mode 100644 xtesting/openstack/cloud.rc create mode 100644 xtesting/openstack/setup.cfg create mode 100644 xtesting/openstack/setup.py create mode 100644 xtesting/openstack/site.yml create mode 100644 xtesting/openstack/testcases.yaml create mode 100644 xtesting/openstack/vsperfostack.conf create mode 100755 xtesting/openstack/vsperfostack.py diff --git a/docs/index.rst b/docs/index.rst index d688c752..c8a400f8 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -20,4 +20,5 @@ OPNFV Vswitchperf lma/index openstack/index k8s/index + xtesting/index diff --git a/docs/xtesting/index.rst b/docs/xtesting/index.rst new file mode 100644 index 00000000..9259a12a --- /dev/null +++ b/docs/xtesting/index.rst @@ -0,0 +1,85 @@ +.. This work is licensed under a Creative Commons Attribution 4.0 International License. +.. http://creativecommons.org/licenses/by/4.0 +.. (c) OPNFV, Spirent, AT&T, Ixia and others. + +.. OPNFV VSPERF Documentation master file. + +******************************** +OPNFV VSPERF with OPNFV Xtesting +******************************** + +============ +Introduction +============ +User can use VSPERF with Xtesting for two different usecases. + +1. Baremetal Dataplane Testing/Benchmarking. +2. Openstack Dataplane Testing/Benchmarking. + +The Baremetal usecase is the legacy usecase of OPNFV VSPERF. + +The below figure summarizes both the usecases. + +.. image:: ./vsperf-xtesting.png + :width: 400 + +=========== +How to Use? +=========== + +Step-1: Build the container +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Go the xtesting/baremetal or xtesting/openstack and run the following command. + +.. code-block:: console + + docker build -t 127.0.0.1:5000/vsperfbm + + +Step-2: Install and run Xtesting Playbook +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +These commands are described in OPNFV Xtesting Documentation. Please refere to OPNFV Xtesting wiki for description of these commands. + +.. code-block:: console + + virtualenv xtesting + . xtesting/bin/activate + ansible-galaxy install collivier.xtesting + ansible-playbook site.yml + +====================== +Accessing the Results? +====================== + +VSPERF automatically publishes the results to any OPNFV Testapi deployment. +User has to configure following two parameters in VSPERF. + +1. OPNFVPOD - The name of the pod. +2. OPNFV_URL - The endpoint serving testapi. + +As Xtesting runs its own testapi, user should point to this (testapi endpoint of Xtesting) using the above two configuration. + +The above two configurations should be done wherever VSPERF is running (refer to the figure above) + +NOTE: Before running the test, it would help if user can prepre the testapi of Xtesting (if needed). The preparation include setting up the following: + +1. Projects +2. Testcases. +3. Pods. + +Please refer to the documentation of testapi for more details. + +======================================= +Accessing other components of Xtesting? +======================================= + +Please refer to the documentation of Xtesting in OPNFV Wiki. + +=========== +Limitations +=========== +For Jerma Release, following limitations apply: + +1. For both baremetal and openstack, only phy2phy_tput testcase is supported. +2. For openstack, only Spirent's STCv and Keysight's Ixnet-Virtual is supported. diff --git a/docs/xtesting/vsperf-xtesting.png b/docs/xtesting/vsperf-xtesting.png new file mode 100755 index 0000000000000000000000000000000000000000..64cad722e88ede4299867b349e69cf5a53f76321 GIT binary patch literal 93202 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 M+vUsBFZ{pxf0hTPng9R* literal 0 HcmV?d00001 diff --git a/xtesting/baremetal/Dockerfile b/xtesting/baremetal/Dockerfile new file mode 100644 index 00000000..b78594b5 --- /dev/null +++ b/xtesting/baremetal/Dockerfile @@ -0,0 +1,36 @@ +# Copyright 2020 Spirent Communications. +# +# 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. + +FROM opnfv/xtesting +LABEL maintainer="sridhar.rao@spirent.com" + +ADD . /src/ +RUN apk add --no-cache --update --virtual .build-deps python3 \ + py3-pip py3-wheel git python3-dev linux-headers libffi-dev \ + make openssl-dev gcc musl-dev && \ + pip3 install --upgrade pip chainmap oslo.utils \ + paramiko scp && \ + git init /src && pip3 install /src + +ENV DUT_IP_ADDRESS=10.10.120.24 +ENV DUT_USERNAME=opnfv +ENV DUT_PASSWORD=opnfv +ENV VSPERF_TESTS=phy2phy_tput +ENV VSPERF_CONFFILE=/vsperf.conf +ENV RES_PATH=/tmp +ENV VSPERF_TRAFFICGEN_MODE=NO + +COPY vsperf.conf /vsperf.conf +COPY testcases.yaml /usr/lib/python3.8/site-packages/xtesting/ci/testcases.yaml +CMD ["run_tests", "-t", "all"] diff --git a/xtesting/baremetal/exceptions.py b/xtesting/baremetal/exceptions.py new file mode 100644 index 00000000..c4e0e097 --- /dev/null +++ b/xtesting/baremetal/exceptions.py @@ -0,0 +1,65 @@ +""" +# Copyright (c) 2017 Intel Corporation +# +# 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. +""" +#pylint: disable=import-error +from oslo_utils import excutils + + +class VsperfCException(Exception): + """Base VSPERF-C Exception. + + To correctly use this class, inherit from it and define + a 'message' property. That message will get printf'd + with the keyword arguments provided to the constructor. + + Based on NeutronException class. + """ + message = "An unknown exception occurred." + + def __init__(self, **kwargs): + try: + super(VsperfCException, self).__init__(self.message % kwargs) + self.msg = self.message % kwargs + except Exception: # pylint: disable=broad-except + with excutils.save_and_reraise_exception() as ctxt: + if not self.use_fatal_exceptions(): + ctxt.reraise = False + # at least get the core message out if something happened + super(VsperfCException, self).__init__(self.message) + + def __str__(self): + return self.msg + + def use_fatal_exceptions(self): + """Is the instance using fatal exceptions. + + :returns: Always returns False. + """ #pylint: disable=no-self-use + return False + + +class InvalidType(VsperfCException): + """Invalid type""" + message = 'Type "%(type_to_convert)s" is not valid' + + +class SSHError(VsperfCException): + """ssh error""" + message = '%(error_msg)s' + + +class SSHTimeout(SSHError): + """ssh timeout""" #pylint: disable=unnecessary-pass + pass diff --git a/xtesting/baremetal/requirements.txt b/xtesting/baremetal/requirements.txt new file mode 100644 index 00000000..f2da6ad5 --- /dev/null +++ b/xtesting/baremetal/requirements.txt @@ -0,0 +1,2 @@ +xtesting +requests!=2.20.0,!=2.24.0 # Apache-2.0 diff --git a/xtesting/baremetal/setup.cfg b/xtesting/baremetal/setup.cfg new file mode 100644 index 00000000..9ca38236 --- /dev/null +++ b/xtesting/baremetal/setup.cfg @@ -0,0 +1,10 @@ +[metadata] +name = vsperf +version = 1 + +[files] +packages = . + +[entry_points] +xtesting.testcase = + vsperf_controller = vsperf_controller:VsperfBm diff --git a/xtesting/baremetal/setup.py b/xtesting/baremetal/setup.py new file mode 100644 index 00000000..fa9d59ac --- /dev/null +++ b/xtesting/baremetal/setup.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python + +# pylint: disable=missing-docstring + +import setuptools + +setuptools.setup( + setup_requires=['pbr>=2.0.0'], + pbr=True) diff --git a/xtesting/baremetal/site.yml b/xtesting/baremetal/site.yml new file mode 100644 index 00000000..06f8c2e2 --- /dev/null +++ b/xtesting/baremetal/site.yml @@ -0,0 +1,13 @@ +--- +- hosts: + - 127.0.0.1 + roles: + - role: collivier.xtesting + project: vsperf + repo: 127.0.0.1 + dport: 5000 + gerrit: + suites: + - container: vsperfbm + tests: + - phy2phy_tput diff --git a/xtesting/baremetal/ssh.py b/xtesting/baremetal/ssh.py new file mode 100644 index 00000000..ce560c49 --- /dev/null +++ b/xtesting/baremetal/ssh.py @@ -0,0 +1,546 @@ +# Copyright 2020: Mirantis Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +#pylint: disable=I,C,R,locally-disabled +#pylint: disable=import-error,arguments-differ + +# this is a modified copy of rally/rally/common/sshutils.py + +"""High level ssh library. + +Usage examples: + +Execute command and get output: + + ssh = sshclient.SSH("root", "example.com", port=33) + status, stdout, stderr = ssh.execute("ps ax") + if status: + raise Exception("Command failed with non-zero status.") + print(stdout.splitlines()) + +Execute command with huge output: + + class PseudoFile(io.RawIOBase): + def write(chunk): + if "error" in chunk: + email_admin(chunk) + + ssh = SSH("root", "example.com") + with PseudoFile() as p: + ssh.run("tail -f /var/log/syslog", stdout=p, timeout=False) + +Execute local script on remote side: + + ssh = sshclient.SSH("user", "example.com") + + with open("~/myscript.sh", "r") as stdin_file: + status, out, err = ssh.execute('/bin/sh -s "arg1" "arg2"', + stdin=stdin_file) + +Upload file: + + ssh = SSH("user", "example.com") + # use rb for binary files + with open("/store/file.gz", "rb") as stdin_file: + ssh.run("cat > ~/upload/file.gz", stdin=stdin_file) + +Eventlet: + + eventlet.monkey_patch(select=True, time=True) + or + eventlet.monkey_patch() + or + sshclient = eventlet.import_patched("vsperf.ssh") + +""" +from __future__ import print_function +import io +import logging +import os +import re +import select +import socket +import time + +import paramiko +from chainmap import ChainMap +from oslo_utils import encodeutils +from scp import SCPClient +import six + +# When building container change this to +import exceptions as exceptions +#else keep it as +#import exceptions +# When building container change this to +from utils import try_int, NON_NONE_DEFAULT, make_dict_from_map +#else keep it as +#from utils import try_int, NON_NONE_DEFAULT, make_dict_from_map + + +def convert_key_to_str(key): + if not isinstance(key, (paramiko.RSAKey, paramiko.DSSKey)): + return key + k = io.StringIO() + key.write_private_key(k) + return k.getvalue() + + +# class SSHError(Exception): +# pass +# +# +# class SSHTimeout(SSHError): +# pass + + +class SSH(object): + """Represent ssh connection.""" + #pylint: disable=no-member + + SSH_PORT = paramiko.config.SSH_PORT + DEFAULT_WAIT_TIMEOUT = 120 + + @staticmethod + def gen_keys(key_filename, bit_count=2048): + rsa_key = paramiko.RSAKey.generate(bits=bit_count, progress_func=None) + rsa_key.write_private_key_file(key_filename) + print("Writing %s ..." % key_filename) + with open('.'.join([key_filename, "pub"]), "w") as pubkey_file: + pubkey_file.write(rsa_key.get_name()) + pubkey_file.write(' ') + pubkey_file.write(rsa_key.get_base64()) + pubkey_file.write('\n') + + @staticmethod + def get_class(): + # must return static class name, anything else + # refers to the calling class + # i.e. the subclass, not the superclass + return SSH + + @classmethod + def get_arg_key_map(cls): + return { + 'user': ('user', NON_NONE_DEFAULT), + 'host': ('ip', NON_NONE_DEFAULT), + 'port': ('ssh_port', cls.SSH_PORT), + 'pkey': ('pkey', None), + 'key_filename': ('key_filename', None), + 'password': ('password', None), + 'name': ('name', None), + } + + def __init__(self, user, host, port=None, pkey=None, + key_filename=None, password=None, name=None): + """Initialize SSH client. + + :param user: ssh username + :param host: hostname or ip address of remote ssh server + :param port: remote ssh port + :param pkey: RSA or DSS private key string or file object + :param key_filename: private key filename + :param password: password + """ + self.name = name + if name: + self.log = logging.getLogger(__name__ + '.' + self.name) + else: + self.log = logging.getLogger(__name__) + + self.wait_timeout = self.DEFAULT_WAIT_TIMEOUT + self.user = user + self.host = host + # everybody wants to debug this in the caller, do it here instead + self.log.debug("user:%s host:%s", user, host) + + # we may get text port from YAML, convert to int + self.port = try_int(port, self.SSH_PORT) + self.pkey = self._get_pkey(pkey) if pkey else None + self.password = password + self.key_filename = key_filename + self._client = False + # paramiko loglevel debug will output ssh protocl debug + # we don't ever really want that unless we are debugging paramiko + # ssh issues + if os.environ.get("PARAMIKO_DEBUG", "").lower() == "true": + logging.getLogger("paramiko").setLevel(logging.DEBUG) + else: + logging.getLogger("paramiko").setLevel(logging.WARN) + + @classmethod + def args_from_node(cls, node, overrides=None, defaults=None): + if overrides is None: + overrides = {} + if defaults is None: + defaults = {} + + params = ChainMap(overrides, node, defaults) + return make_dict_from_map(params, cls.get_arg_key_map()) + + @classmethod + def from_node(cls, node, overrides=None, defaults=None): + return cls(**cls.args_from_node(node, overrides, defaults)) + + def _get_pkey(self, key): + if isinstance(key, six.string_types): + key = six.moves.StringIO(key) + errors = [] + for key_class in (paramiko.rsakey.RSAKey, paramiko.dsskey.DSSKey): + try: + return key_class.from_private_key(key) + except paramiko.SSHException as e: + errors.append(e) + raise exceptions.SSHError(error_msg='Invalid pkey: %s' % errors) + + @property + def is_connected(self): + return bool(self._client) + + def _get_client(self): + if self.is_connected: + return self._client + try: + self._client = paramiko.SSHClient() + self._client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + self._client.connect(self.host, username=self.user, + port=self.port, pkey=self.pkey, + key_filename=self.key_filename, + password=self.password, + allow_agent=False, look_for_keys=False, + timeout=1) + return self._client + except Exception as e: + message = ("Exception %(exception_type)s was raised " + "during connect. Exception value is: %(exception)r" % + {"exception": e, "exception_type": type(e)}) + self._client = False + raise exceptions.SSHError(error_msg=message) + + def _make_dict(self): + return { + 'user': self.user, + 'host': self.host, + 'port': self.port, + 'pkey': self.pkey, + 'key_filename': self.key_filename, + 'password': self.password, + 'name': self.name, + } + + def copy(self): + return self.get_class()(**self._make_dict()) + + def close(self): + if self._client: + self._client.close() + self._client = False + + def run(self, cmd, stdin=None, stdout=None, stderr=None, + raise_on_error=True, timeout=3600, + keep_stdin_open=False, pty=False): + """Execute specified command on the server. + + :param cmd: Command to be executed. + :type cmd: str + :param stdin: Open file or string to pass to stdin. + :param stdout: Open file to connect to stdout. + :param stderr: Open file to connect to stderr. + :param raise_on_error: If False then exit code will be return. If True + then exception will be raized if non-zero code. + :param timeout: Timeout in seconds for command execution. + Default 1 hour. No timeout if set to 0. + :param keep_stdin_open: don't close stdin on empty reads + :type keep_stdin_open: bool + :param pty: Request a pseudo terminal for this connection. + This allows passing control characters. + Default False. + :type pty: bool + """ + + client = self._get_client() + + if isinstance(stdin, six.string_types): + stdin = six.moves.StringIO(stdin) + + return self._run(client, cmd, stdin=stdin, stdout=stdout, + stderr=stderr, raise_on_error=raise_on_error, + timeout=timeout, + keep_stdin_open=keep_stdin_open, pty=pty) + + def _run(self, client, cmd, stdin=None, stdout=None, stderr=None, + raise_on_error=True, timeout=3600, + keep_stdin_open=False, pty=False): + + transport = client.get_transport() + session = transport.open_session() + if pty: + session.get_pty() + session.exec_command(cmd) + start_time = time.time() + + # encode on transmit, decode on receive + data_to_send = encodeutils.safe_encode("", incoming='utf-8') + stderr_data = None + + # If we have data to be sent to stdin then `select' should also + # check for stdin availability. + if stdin and not stdin.closed: + writes = [session] + else: + writes = [] + + while True: + # Block until data can be read/write. + e = select.select([session], writes, [session], 1)[2] + + if session.recv_ready(): + data = encodeutils.safe_decode(session.recv(4096), 'utf-8') + self.log.debug("stdout: %r", data) + if stdout is not None: + stdout.write(data) + continue + + if session.recv_stderr_ready(): + stderr_data = encodeutils.safe_decode( + session.recv_stderr(4096), 'utf-8') + self.log.debug("stderr: %r", stderr_data) + if stderr is not None: + stderr.write(stderr_data) + continue + + if session.send_ready(): + if stdin is not None and not stdin.closed: + if not data_to_send: + stdin_txt = stdin.read(4096) + if stdin_txt is None: + stdin_txt = '' + data_to_send = encodeutils.safe_encode( + stdin_txt, incoming='utf-8') + if not data_to_send: + # we may need to keep stdin open + if not keep_stdin_open: + stdin.close() + session.shutdown_write() + writes = [] + if data_to_send: + sent_bytes = session.send(data_to_send) + # LOG.debug("sent: %s" % data_to_send[:sent_bytes]) + data_to_send = data_to_send[sent_bytes:] + + if session.exit_status_ready(): + break + + if timeout and (time.time() - timeout) > start_time: + message = ('Timeout executing command %(cmd)s on host %(host)s' + % {"cmd": cmd, "host": self.host}) + raise exceptions.SSHTimeout(error_msg=message) + if e: + raise exceptions.SSHError(error_msg='Socket error') + + exit_status = session.recv_exit_status() + if exit_status != 0 and raise_on_error: + fmt = "Command '%(cmd)s' failed with exit_status %(status)d." + details = fmt % {"cmd": cmd, "status": exit_status} + if stderr_data: + details += " Last stderr data: '%s'." % stderr_data + raise exceptions.SSHError(error_msg=details) + return exit_status + + def execute(self, cmd, stdin=None, timeout=3600, raise_on_error=False): + """Execute the specified command on the server. + + :param cmd: (str) Command to be executed. + :param stdin: (StringIO) Open file to be sent on process stdin. + :param timeout: (int) Timeout for execution of the command. + :param raise_on_error: (bool) If True, then an SSHError will be raised + when non-zero exit code. + + :returns: tuple (exit_status, stdout, stderr) + """ + stdout = six.moves.StringIO() + stderr = six.moves.StringIO() + + exit_status = self.run(cmd, stderr=stderr, + stdout=stdout, stdin=stdin, + timeout=timeout, raise_on_error=raise_on_error) + stdout.seek(0) + stderr.seek(0) + return exit_status, stdout.read(), stderr.read() + + def wait(self, timeout=None, interval=1): + """Wait for the host will be available via ssh.""" + if timeout is None: + timeout = self.wait_timeout + + end_time = time.time() + timeout + while True: + try: + return self.execute("uname") + except (socket.error, exceptions.SSHError) as e: + self.log.debug("Ssh is still unavailable: %r", e) + time.sleep(interval) + if time.time() > end_time: + raise exceptions.SSHTimeout( + error_msg='Timeout waiting for "%s"' % self.host) + + def put(self, files, remote_path=b'.', recursive=False): + client = self._get_client() + + with SCPClient(client.get_transport()) as scp: + scp.put(files, remote_path, recursive) + + def get(self, remote_path, local_path='/tmp/', recursive=True): + client = self._get_client() + + with SCPClient(client.get_transport()) as scp: + scp.get(remote_path, local_path, recursive) + + # keep shell running in the background, e.g. screen + def send_command(self, command): + client = self._get_client() + client.exec_command(command, get_pty=True) + + def _put_file_sftp(self, localpath, remotepath, mode=None): + client = self._get_client() + + with client.open_sftp() as sftp: + sftp.put(localpath, remotepath) + if mode is None: + mode = 0o777 & os.stat(localpath).st_mode + sftp.chmod(remotepath, mode) + + TILDE_EXPANSIONS_RE = re.compile("(^~[^/]*/)?(.*)") + + def _put_file_shell(self, localpath, remotepath, mode=None): + # quote to stop wordpslit + tilde, remotepath = self.TILDE_EXPANSIONS_RE.match(remotepath).groups() + if not tilde: + tilde = '' + cmd = ['cat > %s"%s"' % (tilde, remotepath)] + if mode is not None: + # use -- so no options + cmd.append('chmod -- 0%o %s"%s"' % (mode, tilde, remotepath)) + + with open(localpath, "rb") as localfile: + # only chmod on successful cat + self.run("&& ".join(cmd), stdin=localfile) + + def put_file(self, localpath, remotepath, mode=None): + """Copy specified local file to the server. + + :param localpath: Local filename. + :param remotepath: Remote filename. + :param mode: Permissions to set after upload + """ + try: + self._put_file_sftp(localpath, remotepath, mode=mode) + except (paramiko.SSHException, socket.error): + self._put_file_shell(localpath, remotepath, mode=mode) + + def put_file_obj(self, file_obj, remotepath, mode=None): + client = self._get_client() + + with client.open_sftp() as sftp: + sftp.putfo(file_obj, remotepath) + if mode is not None: + sftp.chmod(remotepath, mode) + + def get_file_obj(self, remotepath, file_obj): + client = self._get_client() + + with client.open_sftp() as sftp: + sftp.getfo(remotepath, file_obj) + + +class AutoConnectSSH(SSH): + + @classmethod + def get_arg_key_map(cls): + arg_key_map = super(AutoConnectSSH, cls).get_arg_key_map() + arg_key_map['wait'] = ('wait', True) + return arg_key_map + + # always wait or we will get OpenStack SSH errors + def __init__(self, user, host, port=None, pkey=None, + key_filename=None, password=None, name=None, wait=True): + super(AutoConnectSSH, self).__init__(user, host, port, pkey, + key_filename, password, name) + if wait and wait is not True: + self.wait_timeout = int(wait) + + def _make_dict(self): + data = super(AutoConnectSSH, self)._make_dict() + data.update({ + 'wait': self.wait_timeout + }) + return data + + def _connect(self): + if not self.is_connected: + interval = 1 + timeout = self.wait_timeout + + end_time = time.time() + timeout + while True: + try: + return self._get_client() + except (socket.error, exceptions.SSHError) as e: + self.log.debug("Ssh is still unavailable: %r", e) + time.sleep(interval) + if time.time() > end_time: + raise exceptions.SSHTimeout( + error_msg='Timeout waiting for "%s"' % self.host) + + def drop_connection(self): + """ Don't close anything, just force creation of a new client """ + self._client = False + + def execute(self, cmd, stdin=None, timeout=3600, raise_on_error=False): + self._connect() + return super(AutoConnectSSH, self).execute(cmd, stdin, timeout, + raise_on_error) + + def run(self, cmd, stdin=None, stdout=None, stderr=None, + raise_on_error=True, timeout=3600, + keep_stdin_open=False, pty=False): + self._connect() + return super(AutoConnectSSH, self).run(cmd, stdin, stdout, + stderr, raise_on_error, + timeout, keep_stdin_open, pty) + + def put(self, files, remote_path=b'.', recursive=False): + self._connect() + return super(AutoConnectSSH, self).put(files, remote_path, recursive) + + def put_file(self, local_path, remote_path, mode=None): + self._connect() + return super(AutoConnectSSH, self).put_file(local_path, + remote_path, mode) + + def put_file_obj(self, file_obj, remote_path, mode=None): + self._connect() + return super(AutoConnectSSH, self).put_file_obj(file_obj, + remote_path, mode) + + def get_file_obj(self, remote_path, file_obj): + self._connect() + return super(AutoConnectSSH, self).get_file_obj(remote_path, file_obj) + + @staticmethod + def get_class(): + # must return static class name, + # anything else refers to the calling class + # i.e. the subclass, not the superclass + return AutoConnectSSH diff --git a/xtesting/baremetal/testcases.yaml b/xtesting/baremetal/testcases.yaml new file mode 100644 index 00000000..91cef451 --- /dev/null +++ b/xtesting/baremetal/testcases.yaml @@ -0,0 +1,16 @@ +--- +tiers: + - + name: vsperfbm + order: 1 + description: '' + testcases: + - + case_name: phy2phy_tput + project_name: vsperf + criteria: 100 + blocking: true + clean_flag: false + description: '' + run: + name: vsperf_controller diff --git a/xtesting/baremetal/utils.py b/xtesting/baremetal/utils.py new file mode 100644 index 00000000..d945381e --- /dev/null +++ b/xtesting/baremetal/utils.py @@ -0,0 +1,41 @@ +""" +# Copyright 2013: Mirantis Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +""" + + +NON_NONE_DEFAULT = object() + + +def get_key_with_default(data, key, default=NON_NONE_DEFAULT): + """get default key""" + value = data.get(key, default) + if value is NON_NONE_DEFAULT: + raise KeyError(key) + return value + + +def make_dict_from_map(data, key_map): + """mapping dict""" + return {dest_key: get_key_with_default(data, src_key, default) + for dest_key, (src_key, default) in key_map.items()} + +def try_int(s, *args): + """Convert to integer if possible.""" + #pylint: disable=invalid-name + try: + return int(s) + except (TypeError, ValueError): + return args[0] if args else s diff --git a/xtesting/baremetal/vsperf.conf b/xtesting/baremetal/vsperf.conf new file mode 100644 index 00000000..8ed7115f --- /dev/null +++ b/xtesting/baremetal/vsperf.conf @@ -0,0 +1,21 @@ +VSWITCH_BRIDGE_NAME = 'vsperf-br0' +WHITELIST_NICS = ['02:00.0', '02:00.1'] +TRAFFICGEN = 'Trex' +TRAFFICGEN_TREX_HOST_IP_ADDR = '10.10.120.25' +TRAFFICGEN_TREX_USER = 'root' +TRAFFICGEN_TREX_BASE_DIR = '/root/trex_2.86/' +TRAFFICGEN_TREX_LINE_SPEED_GBPS = '10' +TRAFFICGEN_TREX_PORT1 = '0000:81:00.0' +TRAFFICGEN_TREX_PORT2 = '0000:81:00.1' +TRAFFICGEN_TREX_PROMISCUOUS = False +TRAFFICGEN_DURATION=1 +TRAFFICGEN_LOSSRATE=0 +TRAFFICGEN_RFC2544_TESTS=10 +#TRAFFICGEN_PKT_SIZES=(64,128,256,512,1024,1280,1518) +TRAFFICGEN_PKT_SIZES=(1024,) +GUEST_TESTPMD_FWD_MODE = ['io'] +GUEST_IMAGE = ['/home/opnfv/vnfs/vloop-vnf-ubuntu-18.04_20180920.qcow2'] +TRAFFICGEN_TREX_LATENCY_PPS = 1000 +TRAFFICGEN_TREX_RFC2544_BINARY_SEARCH_LOSS_VERIFICATION = True +TRAFFICGEN_TREX_RFC2544_MAX_REPEAT = 2 + diff --git a/xtesting/baremetal/vsperf_controller.py b/xtesting/baremetal/vsperf_controller.py new file mode 100644 index 00000000..91bad766 --- /dev/null +++ b/xtesting/baremetal/vsperf_controller.py @@ -0,0 +1,194 @@ +#!/usr/bin/env python + +# Copyright 2018-19 Spirent Communications. +# +# 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. + +""" +VSPERF-controller +""" + +# Fetching Environment Variable for controller, You can configure or +# modifies list.env file for setting your environment variable. + +#pylint: disable=global-statement,no-else-continue +#pylint: disable=too-many-branches + +import os +import sys +from stat import S_ISDIR +import time +import paramiko +from xtesting.core import testcase +import ssh + +TIMER = float() + + + +DUT_IP = os.getenv('DUT_IP_ADDRESS') +DUT_USER = os.getenv('DUT_USERNAME') +DUT_PWD = os.getenv('DUT_PASSWORD') +RES_PATH= os.getenv('RES_PATH') + +VSPERF_TEST = os.getenv('VSPERF_TESTS') +VSPERF_CONF = os.getenv('VSPERF_CONFFILE') +VSPERF_TRAFFICGEN_MODE = str(os.getenv('VSPERF_TRAFFICGEN_MODE')) + +DUT_CLIENT = None +TGEN_CLIENT = None + +RECV_BYTES = 4096 + +def host_connect(): + """ + Handle host connectivity to DUT + """ + global DUT_CLIENT + DUT_CLIENT = ssh.SSH(host=DUT_IP, user=DUT_USER, password=DUT_PWD) + print("DUT Successfully Connected ..............................................[OK] \n ") + +def upload_test_config_file(): + """ + #Upload Test Config File on DUT + """ + #localpath = '/usr/src/app/vsperf/vsperf.conf' + if VSPERF_CONF: + localpath = VSPERF_CONF + else: + localpath = 'vsperf.conf' + if not os.path.exists(localpath): + print("VSPERF Test config File does not exists.......................[Failed]") + return + remotepath = '~/vsperf.conf' + check_test_config_cmd = "find ~/ -maxdepth 1 -name '{}'".format( + remotepath[2:]) + check_test_result = str(DUT_CLIENT.execute(check_test_config_cmd)[1]) + if remotepath[2:] in check_test_result: + DUT_CLIENT.run("rm -f {}".format(remotepath[2:])) + DUT_CLIENT.put_file(localpath, remotepath) + check_test_config_cmd_1= "find ~/ -maxdepth 1 -name '{}'".format( + remotepath[2:]) + print(check_test_config_cmd_1) + check_test_result_1= str(DUT_CLIENT.execute(check_test_config_cmd)[1]) + if remotepath[2:] in check_test_result_1: + print( + "Test Configuration File Uploaded on DUT-Host.............................[OK] \n ") + else: + print("VSPERF Test config file upload failed.....................................[Critical]") + +def run_vsperf_test(): + """ + Here we will perform the actual vsperf test + """ + global TIMER + rmv_cmd = "cd /mnt/huge && echo {} | sudo -S rm -rf *".format(DUT_PWD) + DUT_CLIENT.run(rmv_cmd, pty=True) + cmd = "source ~/vsperfenv/bin/activate ; " + #cmd = "scl enable python33 bash ; " + cmd += "cd vswitchperf && " + cmd += "./vsperf " + cmd += "--conf-file ~/vsperf.conf " + if "yes" in VSPERF_TRAFFICGEN_MODE.lower(): + cmd += "--mode trafficgen" + vsperf_test_list = VSPERF_TEST.split(",") + print(vsperf_test_list) + for test in vsperf_test_list: + atest = cmd + atest += test + DUT_CLIENT.run(atest, pty=True) + print( + "Test Successfully Completed................................................[OK]\n ") + +def get_result(): + """ + Get Latest results from DUT + """ + stdout_data = [] + stderr_data = [] + client = paramiko.Transport((DUT_IP, 22)) + client.connect(username=DUT_USER, password=DUT_PWD) + session = client.open_channel(kind='session') + directory_to_download = '' + session.exec_command('ls /tmp | grep results') + if not directory_to_download: + while True: + if session.recv_ready(): + stdout_data.append(session.recv(RECV_BYTES)) + if session.recv_stderr_ready(): + stderr_data.append(session.recv_stderr(RECV_BYTES)) + if session.exit_status_ready(): + break + if stdout_data: + line = stdout_data[0] + filenames = line.decode("utf-8").rstrip("\n").split("\n") + filenames = sorted(filenames) + latest = filenames[-1] + directory_to_download = os.path.join('/tmp', latest) + stdout_data = [] + stderr_data = [] + if directory_to_download: + destination = os.path.join(RES_PATH, + os.path.basename(os.path.normpath( + directory_to_download))) + os.makedirs(destination) + print(directory_to_download) + # Begin the actual downlaod + sftp = paramiko.SFTPClient.from_transport(client) + def sftp_walk(remotepath): + path=remotepath + files=[] + folders=[] + for fle in sftp.listdir_attr(remotepath): + if S_ISDIR(fle.st_mode): + folders.append(fle.filename) + else: + files.append(fle.filename) + if files: + yield path, files + # Filewise download happens here + for path,files in sftp_walk(directory_to_download): + for fil in files: + remote = os.path.join(path,fil) + local = os.path.join(destination, fil) + print(local) + sftp.get(remote, local) + # Ready to work with downloaded data, close the session and client. + session.close() + client.close() + +class VsperfBm(testcase.TestCase): + """ + VSPERF-Xtesting Baremetal Control Class + """ + def run(self, **kwargs): + global RES_PATH + try: + self.start_time = time.time() + self.result=100 + os.makedirs(self.res_dir, exist_ok=True) + RES_PATH = self.res_dir + if DUT_IP: + host_connect() + if not DUT_CLIENT: + print('Failed to connect to DUT ...............[Critical]') + self.result = 0 + else: + upload_test_config_file() + run_vsperf_test() + get_result() + self.stop_time = time.time() + except Exception: # pylint: disable=broad-except + print("Unexpected error:", sys.exc_info()[0]) + self.result = 0 + self.stop_time = time.time() diff --git a/xtesting/openstack/Dockerfile b/xtesting/openstack/Dockerfile new file mode 100644 index 00000000..2e613872 --- /dev/null +++ b/xtesting/openstack/Dockerfile @@ -0,0 +1,61 @@ +# Copyright 2020 Spirent Communications. +# +# 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. + +FROM opnfv/xtesting +LABEL maintainer="sridhar.rao@spirent.com" + +# Install required packages +RUN apk add --no-cache --update python3 python3-dev \ + py3-wheel py3-pip git openssh-client python3-tkinter \ + tk gcc musl-dev libffi-dev openssl-dev make + +# Clone VSPERF. +RUN git clone https://gerrit.opnfv.org/gerrit/vswitchperf /vswitchperf + +# +# Remove unnecessary python packages. +# +RUN cd /vswitchperf && \ + sed -e '/numpy/ s/^#*/#\ /' -i requirements.txt && \ + sed -e '/matplotlib/ s/^#*/#\ /' -i requirements.txt && \ + sed -e '/pycrypto/ s/^#*/#\ /' -i requirements.txt && \ + sed -e '/pypsi/ s/^#*/#\ /' -i requirements.txt && \ + sed -e '/paramiko/ s/^#*/#\ /' -i requirements.txt && \ + sed -e '/pyzmq/ s/^#*/#\ /' -i requirements.txt && \ + sed -e '/kubernetes/ s/^#*/#\ /' -i requirements.txt + +# +# Build VSPERF +# +RUN cd /vswitchperf && \ + pip3 install --ignore-installed distlib -r requirements.txt && \ + cd /vswitchperf/src/trex && make + +# Include vsperf into Path. +ENV PATH "$PATH:/vswitchperf" + +COPY vsperfostack.conf /vsperfostack.conf + +# Required step for Xtesting +ADD . /src/ +RUN git init /src && pip3 install /src + +# Copy Testcase +COPY testcases.yaml /usr/lib/python3.8/site-packages/xtesting/ci/testcases.yaml + +# Set working directory - This helps to resolve path to templates. +WORKDIR /vswitchperf + +# Command Run +CMD ["run_tests", "-t", "all"] diff --git a/xtesting/openstack/cloud.rc b/xtesting/openstack/cloud.rc new file mode 100644 index 00000000..3f867743 --- /dev/null +++ b/xtesting/openstack/cloud.rc @@ -0,0 +1,10 @@ +export OS_AUTH_URL=http://10.10.180.21/identity +export OS_PROJECT_ID=0440a230a799460facec0d09dde64497 +export OS_PROJECT_NAME="admin" +export OS_USER_DOMAIN_NAME="Default" +export OS_PROJECT_DOMAIN_ID="default" +export OS_USERNAME="admin" +export OS_PASSWORD="admin123" +export OS_REGION_NAME="RegionOne" +export OS_INTERFACE=public +export OS_IDENTITY_API_VERSION=3 diff --git a/xtesting/openstack/setup.cfg b/xtesting/openstack/setup.cfg new file mode 100644 index 00000000..4b98992a --- /dev/null +++ b/xtesting/openstack/setup.cfg @@ -0,0 +1,10 @@ +[metadata] +name = vsperfostack +version = 1 + +[files] +packages = . + +[entry_points] +xtesting.testcase = + vsperfostack = vsperfostack:VsperfOstack diff --git a/xtesting/openstack/setup.py b/xtesting/openstack/setup.py new file mode 100644 index 00000000..1394cdfe --- /dev/null +++ b/xtesting/openstack/setup.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python + +# pylint: disable=missing-docstring + +import setuptools + +setuptools.setup( + setup_requires=['pbr>=2.0.0'], + pbr=True) diff --git a/xtesting/openstack/site.yml b/xtesting/openstack/site.yml new file mode 100644 index 00000000..1ca663f4 --- /dev/null +++ b/xtesting/openstack/site.yml @@ -0,0 +1,13 @@ +--- +- hosts: + - 127.0.0.1 + roles: + - role: collivier.xtesting + project: vsperfostack + repo: 127.0.0.1 + dport: 5000 + gerrit: + suites: + - container: vsperfos + tests: + - phy2phy_tput diff --git a/xtesting/openstack/testcases.yaml b/xtesting/openstack/testcases.yaml new file mode 100644 index 00000000..aab3b16a --- /dev/null +++ b/xtesting/openstack/testcases.yaml @@ -0,0 +1,19 @@ +--- +tiers: + - + name: vsperfostack + order: 1 + description: 'VSPERF Openstack Testing' + testcases: + - + case_name: phy2phy_tput + project_name: vsperfostack + criteria: 100 + blocking: true + clean_flag: false + description: 'VSPERF Openstack RFC2544 Throughput Test' + run: + name: vsperfostack + args: + conf_file: vsperfostack.conf + deploy_tgen: false diff --git a/xtesting/openstack/vsperfostack.conf b/xtesting/openstack/vsperfostack.conf new file mode 100644 index 00000000..489054a7 --- /dev/null +++ b/xtesting/openstack/vsperfostack.conf @@ -0,0 +1,80 @@ +# Copyright 20202 Spirent Communications. +# +# 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. + +# OPenstack Information + +OS_AUTH_URL="http://10.10.180.21/identity" +OS_PROJECT_ID="0440a230a799460facec0d09dde64497" +OS_PROJECT_NAME="admin" +OS_USER_DOMAIN_NAME="Default" +OS_PROJECT_DOMAIN_ID="default" +OS_USERNAME="admin" +OS_PASSWORD="admin123" +OS_REGION_NAME="RegionOne" +OS_INTERFACE="public" +OS_IDENTITY_API_VERSION=3 +OS_INSECURE=False +OS_CA_CERT= 'None' + +# Deployment Information +SCENARIOS = ['templates/l2_2c_2i.yaml'] +FLAVOR_NAME = 'm1.large' +IMAGE_NAME = 'stcv' +EXTERNAL_NET = 'public' + +# Traffic Information +TRAFFICGEN_PKT_SIZES = (1024,) +TRAFFICGEN_DURATION = 10 + +# Traffigen to Use +TRAFFICGEN='TestCenter' + + +# Trafficgen Specific Information +# STC +TRAFFICGEN_STC_LAB_SERVER_ADDR = "10.10.180.245" +TRAFFICGEN_STC_LICENSE_SERVER_ADDR = "10.10.50.226" +TRAFFICGEN_STC_EAST_SLOT_NUM = "1" +TRAFFICGEN_STC_EAST_PORT_NUM = "1" +TRAFFICGEN_STC_WEST_SLOT_NUM = "1" +TRAFFICGEN_STC_WEST_PORT_NUM = "1" +TRAFFICGEN_STC_PYTHON2_PATH = "/usr/bin/python3" +TRAFFICGEN_STC_RFC2544_TPUT_TEST_FILE_NAME = "testcenter-rfc2544-rest.py" +TRAFFICGEN_STC_RFC2544_METRIC="throughput" + + +# Ixia +TRAFFICGEN_EAST_IXIA_CARD = '1' +TRAFFICGEN_WEST_IXIA_CARD = '1' +TRAFFICGEN_EAST_IXIA_PORT = '1' +TRAFFICGEN_WEST_IXIA_PORT = '1' +TRAFFICGEN_IXIA_LIB_PATH = '/opt/ixia/ixos-api/9.00.0.20/lib/ixTcl1.0' +TRAFFICGEN_IXNET_LIB_PATH = '/opt/ixia/ixnetwork/9.00.1915.16/lib/TclApi/IxTclNetwork' +TRAFFICGEN_IXNET_MACHINE = '10.10.180.240' # quad dotted ip address +TRAFFICGEN_IXNET_PORT = '443' +TRAFFICGEN_IXNET_USER = 'admin' +TRAFFICGEN_IXNET_TESTER_RESULT_DIR = 'c:/ixia_results/vsperf_sandbox' +TRAFFICGEN_IXNET_DUT_RESULT_DIR = '/mnt/ixia_results/vsperf_sandbox' + +# Trex +TRAFFICGEN_TREX_HOST_IP_ADDR = '10.10.120.25' +TRAFFICGEN_TREX_USER = 'root' +TRAFFICGEN_TREX_BASE_DIR = '/root/trex_2.86/' +TRAFFICGEN_TREX_LINE_SPEED_GBPS = '10' +TRAFFICGEN_TREX_PORT1 = '0000:81:00.0' +TRAFFICGEN_TREX_PORT2 = '0000:81:00.1' +TRAFFICGEN_TREX_PROMISCUOUS = False +TRAFFICGEN_TREX_LATENCY_PPS = 1000 +TRAFFICGEN_TREX_RFC2544_BINARY_SEARCH_LOSS_VERIFICATION = False +TRAFFICGEN_TREX_RFC2544_MAX_REPEAT = 2 diff --git a/xtesting/openstack/vsperfostack.py b/xtesting/openstack/vsperfostack.py new file mode 100755 index 00000000..d4a14c06 --- /dev/null +++ b/xtesting/openstack/vsperfostack.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 + +# Copyright 2020 Spirent Communications. +# +# 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. + +"""VSPERF-Xtesting-Openstack Control script. +""" + +import os +import subprocess +import sys +import time + +from xtesting.core import testcase + + +class VsperfOstack(testcase.TestCase): + """ + Implement Xtesting's testcase class + """ + def run(self, **kwargs): + """ + Main Run. + """ + custom_conffile = '/vswitchperf/conf/99_xtesting.conf' + try: + test_params = {} + for key in kwargs: + test_params[key] = kwargs[key] + # Make results directory - Xtesting Requirement + os.makedirs(self.res_dir, exist_ok=True) + # Start the timer + self.start_time = time.time() + + # Get the parameter + if 'conf_file' in test_params.keys(): + conffile = os.path.join('/', test_params['conf_file']) + else: + conffile = '/vsperfostack.conf' + + # Remove customfile if it exists. + if os.path.exists(custom_conffile): + os.remove(custom_conffile) + + # Write custom configuration. + with open(custom_conffile, 'a+') as fil: + fil.writelines("LOG_DIR='{}'".format(self.res_dir)) + fil.close() + # Start the vsperf command + if('deploy_tgen' in test_params.keys() and + test_params['deploy_tgen']): + output = subprocess.check_output(['vsperf', + '--conf-file', + conffile, + '--openstack', + '--load-env', + '--tests', + self.case_name]) + else: + output = subprocess.check_output(['vsperf', + '--conf-file', + conffile, + '--load-env', + '--mode', + 'traffigen', + '--tests', + self.case_name]) + print(output) + self.result = 100 + self.stop_time = time.time() + except Exception: # pylint: disable=broad-except + print("Unexpected error:", sys.exc_info()[0]) + self.result = 0 + self.stop_time = time.time() -- 2.16.6