aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--INFO.yaml4
-rw-r--r--README.rst3
-rw-r--r--client/client.py36
-rw-r--r--client/nfvbench_client.py5
-rw-r--r--client/requirements.txt6
-rw-r--r--docker/Dockerfile22
-rwxr-xr-xdocker/nfvbench-entrypoint.sh2
-rw-r--r--docs/testing/user/userguide/advanced.rst16
-rw-r--r--docs/testing/user/userguide/faq.rst2
-rw-r--r--docs/testing/user/userguide/quickstart_docker.rst366
-rw-r--r--docs/testing/user/userguide/server.rst89
-rwxr-xr-xnfvbench/cfg.default.yaml105
-rw-r--r--nfvbench/chain_runner.py7
-rw-r--r--nfvbench/chaining.py191
-rw-r--r--nfvbench/cleanup.py4
-rw-r--r--nfvbench/config.py2
-rw-r--r--nfvbench/credentials.py19
-rw-r--r--nfvbench/nfvbench.py53
-rw-r--r--nfvbench/nfvbenchd.py78
-rw-r--r--nfvbench/stats_collector.py51
-rwxr-xr-xnfvbench/traffic_client.py29
-rw-r--r--nfvbench/traffic_gen/dummy.py2
-rw-r--r--nfvbench/traffic_gen/traffic_base.py2
-rw-r--r--nfvbench/traffic_gen/trex_gen.py (renamed from nfvbench/traffic_gen/trex.py)201
-rw-r--r--nfvbench/traffic_server.py74
-rwxr-xr-xnfvbenchvm/dib/build-image.sh2
-rw-r--r--nfvbenchvm/dib/elements/nfvbenchvm/static/etc/rc.d/rc.local164
-rw-r--r--requirements.txt4
-rw-r--r--test/mock_trex.py41
-rw-r--r--test/test_chains.py80
31 files changed, 1043 insertions, 618 deletions
diff --git a/.gitignore b/.gitignore
index 2b16029..c2bb485 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,7 @@
.tox
.cache
.eggs
+.vscode
.pytest_cache/
venv
nfvbench.egg-info
diff --git a/INFO.yaml b/INFO.yaml
index 2796535..5b59f9e 100644
--- a/INFO.yaml
+++ b/INFO.yaml
@@ -38,10 +38,6 @@ committers:
email: 'yicwang@cisco.com'
company: 'cisco.com'
id: 'yicwang'
- - name: 'Al Morton'
- email: 'acmorton@att.com'
- company: 'att.com'
- id: 'acm'
tsc:
# yamllint disable rule:line-length
approval: ''
diff --git a/README.rst b/README.rst
index a3f67cf..9779392 100644
--- a/README.rst
+++ b/README.rst
@@ -22,8 +22,7 @@ Online Documentation
--------------------
The latest version of the NFVbench documentation is available online at:
-http://docs.opnfv.org/en/latest/submodules/nfvbench/docs/testing/user/userguide/index.html
-
+https://opnfv-nfvbench.readthedocs.io/en/latest/testing/user/userguide/index.html
Contact Information
-------------------
diff --git a/client/client.py b/client/client.py
index 5cbc733..b1be69c 100644
--- a/client/client.py
+++ b/client/client.py
@@ -17,8 +17,6 @@
import requests
import time
-from socketIO_client import SocketIO
-
class TimeOutException(Exception):
pass
@@ -32,42 +30,14 @@ class NfvbenchClient(object):
"""Python client class to control a nfvbench server
The nfvbench server must run in background using the --server option.
- Since HTML pages are not required, the path to pass to --server can be
- any directory on the host.
"""
- def __init__(self, nfvbench_url, use_socketio):
+ def __init__(self, nfvbench_url):
"""Client class to send requests to the nfvbench server
Args:
nfvbench_url: the URL of the nfvbench server (e.g. 'http://127.0.0.1:7555')
"""
self.url = nfvbench_url
- self.use_socketio = use_socketio
-
- def socketio_send(self, send_event, receive_event, config, timeout):
- class Exec(object):
- socketIO = None
- socketio_result = None
-
- def close_socketio(result):
- Exec.socketio_result = result
- Exec.socketIO.disconnect()
-
- def on_response(*args):
- close_socketio(args[0])
-
- def on_error(*args):
- raise NfvbenchException(args[0])
-
- Exec.socketIO = SocketIO(self.url)
- Exec.socketIO.on(receive_event, on_response)
- Exec.socketIO.on('error', on_error)
- Exec.socketIO.emit(send_event, config)
- Exec.socketIO.wait(seconds=timeout)
-
- if timeout and not Exec.socketio_result:
- raise TimeOutException()
- return Exec.socketio_result
def http_get(self, command, config):
url = self.url + '/' + command
@@ -102,8 +72,6 @@ class NfvbenchClient(object):
TimeOutException: the request timed out (and might still being executed
by the server)
"""
- if self.use_socketio:
- return self.socketio_send('echo', 'echo', config, timeout)
return self.http_get('echo', config)
def run_config(self, config, timeout=300, poll_interval=5):
@@ -132,8 +100,6 @@ class NfvbenchClient(object):
the exception contains the description of the failure.
TimeOutException: the request timed out but will still be executed by the server.
"""
- if self.use_socketio:
- return self.socketio_send('start_run', 'run_end', config, timeout)
res = self.http_post('start_run', config)
if res['status'] != 'PENDING':
raise NfvbenchException(res['error_message'])
diff --git a/client/nfvbench_client.py b/client/nfvbench_client.py
index 3973b9c..c528df8 100644
--- a/client/nfvbench_client.py
+++ b/client/nfvbench_client.py
@@ -54,9 +54,6 @@ def main():
action='store',
help='time (seconds) to wait for NFVbench result',
metavar='<config>')
- parser.add_argument('--use-socketio', dest='use_socketio',
- action='store_true',
- help='NFVbench config to echo (json format)')
parser.add_argument('url', help='nfvbench server url (e.g. http://10.0.0.1:5000)')
opts = parser.parse_args()
@@ -64,7 +61,7 @@ def main():
print('at least one of -f or -c or -e required')
sys.exit(-1)
- nfvbench = NfvbenchClient(opts.url, opts.use_socketio)
+ nfvbench = NfvbenchClient(opts.url)
# convert JSON into a dict
try:
timeout = int(opts.timeout)
diff --git a/client/requirements.txt b/client/requirements.txt
index 80fc402..84ced9c 100644
--- a/client/requirements.txt
+++ b/client/requirements.txt
@@ -1,7 +1,5 @@
#
#
backports.ssl-match-hostname==3.5.0.1 # via websocket-client
-requests==2.13.0 # via socketio-client
-six==1.10.0 # via socketio-client, websocket-client
-socketIO-client==0.7.2
-websocket-client==0.40.0 # via socketio-client
+requests==2.13.0
+six==1.10.0 # via websocket-client
diff --git a/docker/Dockerfile b/docker/Dockerfile
index cd59fa0..2c4055e 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -1,9 +1,12 @@
# docker file for creating a container that has nfvbench installed and ready to use
FROM ubuntu:16.04
-ENV TREX_VER "v2.32"
+ENV TREX_VER "v2.56"
ENV VM_IMAGE_VER "0.6"
+# Note: do not clone with --depth 1 as it will cause pbr to fail extracting the nfvbench version
+# from the git tag
+
RUN apt-get update && apt-get install -y \
git \
kmod \
@@ -13,22 +16,24 @@ RUN apt-get update && apt-get install -y \
vim \
wget \
net-tools \
+ iproute2 \
libelf1 \
- && mkdir /tmp/http_root \
&& mkdir -p /opt/trex \
&& mkdir /var/log/nfvbench \
&& wget --no-cache https://trex-tgn.cisco.com/trex/release/$TREX_VER.tar.gz \
&& tar xzf $TREX_VER.tar.gz -C /opt/trex \
&& rm -f /$TREX_VER.tar.gz \
&& rm -f /opt/trex/$TREX_VER/trex_client_$TREX_VER.tar.gz \
- && cp -a /opt/trex/$TREX_VER/automation/trex_control_plane/stl/trex_stl_lib /usr/local/lib/python2.7/dist-packages/ \
- && rm -rf /opt/trex/$TREX_VER/automation/trex_control_plane/stl/trex_stl_lib \
+ && cp -a /opt/trex/$TREX_VER/automation/trex_control_plane/interactive/trex /usr/local/lib/python2.7/dist-packages/ \
+ && rm -rf /opt/trex/$TREX_VER/automation/trex_control_plane/interactive/trex \
&& sed -i -e "s/2048 /512 /" -e "s/2048\"/512\"/" /opt/trex/$TREX_VER/trex-cfg \
- && pip install -U pip pbr \
- && hash -r pip \
+ && apt-get remove -y python-pip \
+ && wget https://bootstrap.pypa.io/get-pip.py \
+ && python get-pip.py \
+ && pip install -U pbr \
&& pip install -U setuptools \
&& cd / \
- && git clone --depth 1 https://gerrit.opnfv.org/gerrit/nfvbench \
+ && git clone https://gerrit.opnfv.org/gerrit/nfvbench \
&& cd /nfvbench && pip install -e . \
&& wget -O nfvbenchvm-$VM_IMAGE_VER.qcow2 http://artifacts.opnfv.org/nfvbench/images/nfvbenchvm_centos-$VM_IMAGE_VER.qcow2 \
&& python ./docker/cleanup_generators.py \
@@ -36,6 +41,7 @@ RUN apt-get update && apt-get install -y \
&& apt-get remove -y wget git \
&& apt-get autoremove -y && apt-get clean && rm -rf /var/lib/apt/lists/*
-ENV TREX_STL_EXT_PATH "/opt/trex/$TREX_VER/external_libs"
+ENV TREX_EXT_LIBS "/opt/trex/$TREX_VER/external_libs"
+
ENTRYPOINT ["/nfvbench/docker/nfvbench-entrypoint.sh"]
diff --git a/docker/nfvbench-entrypoint.sh b/docker/nfvbench-entrypoint.sh
index ed98ced..a7195a3 100755
--- a/docker/nfvbench-entrypoint.sh
+++ b/docker/nfvbench-entrypoint.sh
@@ -17,7 +17,7 @@
if [ -z "$1" ] || [ $1 != 'start_rest_server' ]; then
tail -f /dev/null
else
- PARAMS="--server /tmp/http_root"
+ PARAMS="--server"
if [ -n "$HOST" ]; then
PARAMS+=" --host $HOST"
fi
diff --git a/docs/testing/user/userguide/advanced.rst b/docs/testing/user/userguide/advanced.rst
index 1d2ac36..1a6e999 100644
--- a/docs/testing/user/userguide/advanced.rst
+++ b/docs/testing/user/userguide/advanced.rst
@@ -78,6 +78,22 @@ Used parameters:
* ``--no-traffic`` or ``-0`` : sending traffic from traffic generator is skipped
+TRex force restart
+------------------------------------
+
+NFVbench allows to restart TRex traffic generator between runs.
+It runs the whole test, but restart TRex instance before generating new traffic.
+
+To force restart, use the --restart option:
+
+.. code-block:: bash
+
+ nfvbench --restart
+
+Used parameters:
+
+* ``--restart`` : restart traffic generator (TRex)
+
Fixed Rate Run
--------------
diff --git a/docs/testing/user/userguide/faq.rst b/docs/testing/user/userguide/faq.rst
index 9da2e90..9a1a7da 100644
--- a/docs/testing/user/userguide/faq.rst
+++ b/docs/testing/user/userguide/faq.rst
@@ -24,7 +24,7 @@ Yes.
Can I drive NFVbench using a REST interface?
--------------------------------------------
-NFVbench can run in server mode and accept HTTP or WebSocket/SocketIO events to run any type of measurement (fixed rate run or NDR_PDR run)
+NFVbench can run in server mode and accept HTTP requests to run any type of measurement (fixed rate run or NDR_PDR run)
with any run configuration.
Can I run NFVbench on a Cisco UCS-B series blade?
diff --git a/docs/testing/user/userguide/quickstart_docker.rst b/docs/testing/user/userguide/quickstart_docker.rst
index 6803bc3..ae277de 100644
--- a/docs/testing/user/userguide/quickstart_docker.rst
+++ b/docs/testing/user/userguide/quickstart_docker.rst
@@ -11,6 +11,9 @@ NFVbench Installation and Quick Start Guide
Make sure you satisfy the `hardware and software requirements <requirements>` before you start .
+NFVbench can be used in CLI mode or in REST server mode.
+The CLI mode allows to run NFVbench benchmarks from the CLI. The REST server mode allows to run NFVbench benchmarks through a REST interface.
+
1. Container installation
-------------------------
@@ -20,113 +23,137 @@ To pull the latest NFVbench container image:
docker pull opnfv/nfvbench
-2. Docker Container configuration
----------------------------------
+2. NFVbench configuration file
+------------------------------
-The NFVbench container requires the following Docker options to operate properly.
+Create a directory under $HOME called nfvbench to store the minimal configuration file:
-+-------------------------------------------------------+-------------------------------------------------------+
-| Docker options | Description |
-+=======================================================+=======================================================+
-| -v /lib/modules/$(uname -r):/lib/modules/$(uname -r) | needed by kernel modules in the container |
-+-------------------------------------------------------+-------------------------------------------------------+
-| -v /usr/src/kernels:/usr/src/kernels | needed by TRex to build kernel modules when needed |
-+-------------------------------------------------------+-------------------------------------------------------+
-| -v /dev:/dev | needed by kernel modules in the container |
-+-------------------------------------------------------+-------------------------------------------------------+
-| -v $PWD:/tmp/nfvbench | optional but recommended to pass files between the |
-| | host and the docker space (see examples below) |
-| | Here we map the current directory on the host to the |
-| | /tmp/nfvbench director in the container but any |
-| | other similar mapping can work as well |
-+-------------------------------------------------------+-------------------------------------------------------+
-| --net=host | (optional) needed if you run the NFVbench |
-| | server in the container (or use any appropriate |
-| | docker network mode other than "host") |
-+-------------------------------------------------------+-------------------------------------------------------+
-| --privileged | (optional) required if SELinux is enabled on the host |
-+-------------------------------------------------------+-------------------------------------------------------+
-| -e HOST="127.0.0.1" | (optional) required if REST server is enabled |
-+-------------------------------------------------------+-------------------------------------------------------+
-| -e PORT=7556 | (optional) required if REST server is enabled |
-+-------------------------------------------------------+-------------------------------------------------------+
-| -e CONFIG_FILE="/root/nfvbenchconfig.json | (optional) required if REST server is enabled |
-+-------------------------------------------------------+-------------------------------------------------------+
+.. code-block:: bash
-It can be convenient to write a shell script (or an alias) to automatically insert the necessary options.
+ mkdir $HOME/nfvbench
-The minimal configuration file required must specify the PCI addresses of the 2 NIC ports to use.
-If OpenStack is used, the openrc_file property must be defined to point to a valid OpenStack rc file.
+Create a new file containing the minimal configuration for NFVbench, we can call it any name, for example "nfvbench.cfg" and paste the following yaml template in the file:
+.. code-block:: bash
-Here is an example of mimimal configuration using OpenStack where:
+ openrc_file: /tmp/nfvbench/openrc
+ traffic_generator:
+ generator_profile:
+ - name: trex-local
+ tool: TRex
+ ip: 127.0.0.1
+ cores: 3
+ software_mode: false
+ interfaces:
+ - port: 0
+ pci: "0a:00.0"
+ - port: 1
+ pci: "0a:00.1"
+ intf_speed:
+
+If OpenStack is not used, the openrc_file property can be removed.
-- the openrc file is located on the host current directory which is mapped under /tmp/nfvbench in the container (this is achieved using -v $PWD:/tmp/nfvbench)
-- the 2 NIC ports to use for generating traffic have the PCI addresses "04:00.0" and "04:00.1"
+If OpenStack is used, the openrc_file property must contain a valid container pathname of the OpenStack ``openrc`` file to connect to OpenStack using the OpenStack API.
+This file can be downloaded from the OpenStack Horizon dashboard (refer to the OpenStack documentation on how to
+retrieve the openrc file). This property must point to a valid pathname in the container (/tmp/nfvbench/openrc).
+We will map the host $HOME/nfvbench directory to the container /tmp/nfvbench directory and name the file "openrc".
+The file name viewed from the container will be "/tmp/nfvbench/openrc" (see container file pathname mapping in the next sections).
+
+The PCI address of the 2 physical interfaces that will be used by the traffic generator must be configured.
+The PCI address can be obtained for example by using the "lspci" Linux command. For example:
.. code-block:: bash
- {
- "openrc_file": "/tmp/nfvbench/openrc",
- "traffic_generator": {
- "generator_profile": [
- {
- "interfaces": [
- {
- "pci": "04:00.0",
- "port": 0,
- },
- {
- "pci": "04:00.1",
- "port": 1,
- }
- ],
- "intf_speed": "",
- "ip": "127.0.0.1",
- "name": "trex-local",
- "software_mode": false,
- "tool": "TRex"
- }
- ]
- }
- }
+ [root@sjc04-pod6-build ~]# lspci | grep 710
+ 0a:00.0 Ethernet controller: Intel Corporation Ethernet Controller X710 for 10GbE SFP+ (rev 01)
+ 0a:00.1 Ethernet controller: Intel Corporation Ethernet Controller X710 for 10GbE SFP+ (rev 01)
+ 0a:00.2 Ethernet controller: Intel Corporation Ethernet Controller X710 for 10GbE SFP+ (rev 01)
+ 0a:00.3 Ethernet controller: Intel Corporation Ethernet Controller X710 for 10GbE SFP+ (rev 01)
-The other options in the minimal configuration must be present and must have the same values as above.
+In the above example, the PCI addresses "0a:00.0" and "0a:00.1" (first 2 ports of the quad port NIC) are used.
-3. Start the Docker container
------------------------------
-As for any Docker container, you can execute NFVbench measurement sessions using a temporary container ("docker run" - which exits after each NFVbench run)
-or you can decide to run the NFVbench container in the background then execute one or more NFVbench measurement sessions on that container ("docker exec").
+.. warning::
+
+ You have to put quotes around the pci addresses as shown in the above example, otherwise TRex will read it wrong.
+ The other fields in the minimal configuration must be present and must have the same values as above.
-The former approach is simpler to manage (since each container is started and terminated after each command) but incurs a small delay at start time (several seconds).
-The second approach is more responsive as the delay is only incurred once when starting the container.
-We will take the second approach and start the NFVbench container in detached mode with the name "nfvbench" (this works with bash, prefix with "sudo" if you do not use the root login)
+3. Starting NFVbench in CLI mode
+--------------------------------
-First create a new working directory, and change the current working directory to there. A "nfvbench_ws" directory under your home directory is good place for that, and this is where the OpenStack RC file and NFVbench config file will sit.
+In this mode, the NFVbench code will reside in a container running in the background. This container will not run anything in the background.
+An alias is then used to invoke a new NFVbench benchmark run using docker exec.
+The $HOME/nfvbench directory on the host is mapped on the /tmp/nfvbench in the container to facilitate file sharing between the 2 environments.
-To run NFVBench without server mode
+Start NFVbench container
+~~~~~~~~~~~~~~~~~~~~~~~~
+The NFVbench container can be started using docker run command or using docker compose.
+
+To run NFVBench in CLI mode using docker run:
.. code-block:: bash
- cd ~/nfvbench_ws
- docker run --detach --net=host --privileged -v $PWD:/tmp/nfvbench -v /dev:/dev -v /lib/modules/$(uname -r):/lib/modules/$(uname -r) -v /usr/src/kernels:/usr/src/kernels --name nfvbench opnfv/nfvbench
+ docker run --name nfvbench --detach --privileged -v /lib/modules/$(uname -r):/lib/modules/$(uname -r) -v /usr/src/kernels:/usr/src/kernels -v /dev:/dev -v $HOME/nfvbench:/tmp/nfvbench opnfv/nfvbench
+
++-------------------------------------------------------+-------------------------------------------------------+
+| Docker options | Description |
++=======================================================+=======================================================+
+| --name nfvbench | container name is "nfvbench" |
++-------------------------------------------------------+-------------------------------------------------------+
+| --detach | run container in background |
++-------------------------------------------------------+-------------------------------------------------------+
+| --privileged | (optional) required if SELinux is enabled on the host |
++-------------------------------------------------------+-------------------------------------------------------+
+| -v /lib/modules:/lib/modules | needed by kernel modules in the container |
++-------------------------------------------------------+-------------------------------------------------------+
+| -v /usr/src/kernels:/usr/src/kernels | needed by TRex to build kernel modules when needed |
++-------------------------------------------------------+-------------------------------------------------------+
+| -v /dev:/dev | needed by kernel modules in the container |
++-------------------------------------------------------+-------------------------------------------------------+
+| -v $HOME/nfvbench:/tmp/nfvbench | folder mapping to pass files between the |
+| | host and the docker space (see examples below) |
+| | Here we map the $HOME/nfvbench directory on the host |
+| | to the /tmp/nfvbench director in the container. |
+| | Any other mapping can work as well |
++-------------------------------------------------------+-------------------------------------------------------+
+| opnfv/nfvbench | container image name |
++-------------------------------------------------------+-------------------------------------------------------+
+
+To run NFVbench using docker compose, create the docker-compose.yml file and paste the following content:
+
+.. code-block:: bash
-To run NFVBench enabling REST server (mount the configuration json and the path for openrc)
+ version: '3'
+ services:
+ nfvbench:
+ image: "opnfv/nfvbench"
+ container_name: "nfvbench"
+ volumes:
+ - /dev:/dev
+ - /usr/src/kernels:/usr/src/kernels
+ - /lib/modules:/lib/modules
+ - ${HOME}/nfvbench:/tmp/nfvbench
+ network_mode: "host"
+ privileged: true
+
+Then start the container in detached mode:
.. code-block:: bash
- cd ~/nfvbench_ws
- docker run --detach --net=host --privileged -e HOST="127.0.0.1" -e PORT=7556 -e CONFIG_FILE="/tmp/nfvbench/nfvbenchconfig.json -v $PWD:/tmp/nfvbench -v /dev:/dev -v /lib/modules/$(uname -r):/lib/modules/$(uname -r) -v /usr/src/kernels:/usr/src/kernels --name nfvbench opnfv/nfvbench start_rest_server
+ docker-compose up -d
+Requesting an NFVbench benchmark run
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-The create an alias to make it easy to execute nfvbench commands directly from the host shell prompt:
+Create an alias to make it easy to execute nfvbench commands directly from the host shell prompt:
.. code-block:: bash
alias nfvbench='docker exec -it nfvbench nfvbench'
-The next to last "nfvbench" refers to the name of the container while the last "nfvbench" refers to the NFVbench binary that is available to run in the container.
+The next to last "nfvbench" refers to the name of the container while the last "nfvbench" refers to the NFVbench binary that is available to run inside the container.
+
+Once the alias is set, NFVbench runs can simply be requested from teh command line using "nfvbench <options>".
To verify it is working:
@@ -135,102 +162,165 @@ To verify it is working:
nfvbench --version
nfvbench --help
+Example of run
+~~~~~~~~~~~~~~
-4. NFVbench configuration
--------------------------
+To do a single run at 10,000pps bi-directional (or 5kpps in each direction) using the PVP packet path:
-Create a new file containing the minimal configuration for NFVbench, we can call it any name, for example "my_nfvbench.cfg" and paste the following yaml template in the file:
+.. code-block:: bash
+
+ nfvbench -c /tmp/nfvbench/nfvbench.cfg --rate 10kpps
+
+NFVbench options used:
+
+* ``-c /tmp/nfvbench/nfvbench.cfg`` : specify the config file to use
+* ``--rate 10kpps`` : specify rate of packets for test for both directions using the kpps unit (thousands of packets per second)
+
+
+Retrieve complete configuration file as template
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The full configuration file template with comments (yaml format) can be obtained using the --show-default-config option in order to use more advanced configuration options:
.. code-block:: bash
- openrc_file:
- traffic_generator:
- generator_profile:
- - name: trex-local
- tool: TRex
- ip: 127.0.0.1
- cores: 3
- software_mode: false,
- interfaces:
- - port: 0
- pci:
- - port: 1
- pci:
- intf_speed:
+ nfvbench --show-default-config > $HOME/nfvbench/full_nfvbench.cfg
-If OpenStack is used, NFVbench requires an ``openrc`` file to connect to OpenStack using the OpenStack API. This file can be downloaded from the OpenStack Horizon dashboard (refer to the OpenStack documentation on how to
-retrieve the openrc file). The file pathname in the container must be stored in the "openrc_file" property. If it is stored on the host in the current directory, its full pathname must start with /tmp/nfvbench (since the current directory is mapped to /tmp/nfvbench in the container).
+Edit the full_nfvbench.cfg file to only keep those properties that need to be modified (preserving the nesting).
-If OpenStack is not used, remove the openrc_file property.
-The PCI address of the 2 physical interfaces that will be used by the traffic generator must be configured.
-The PCI address can be obtained for example by using the "lspci" Linux command. For example:
+4. Start NFVbench in REST server mode
+-------------------------------------
+In this mode, the NFVbench REST server will run in the container.
+The $HOME/nfvbench directory on the host is mapped on the /tmp/nfvbench in the container to facilitate file sharing between the 2 environments.
+
+Start NFVbench container
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+To start the NFVbench container with REST server using docker run cli:
.. code-block:: bash
- [root@sjc04-pod6-build ~]# lspci | grep 710
- 0a:00.0 Ethernet controller: Intel Corporation Ethernet Controller X710 for 10GbE SFP+ (rev 01)
- 0a:00.1 Ethernet controller: Intel Corporation Ethernet Controller X710 for 10GbE SFP+ (rev 01)
- 0a:00.2 Ethernet controller: Intel Corporation Ethernet Controller X710 for 10GbE SFP+ (rev 01)
- 0a:00.3 Ethernet controller: Intel Corporation Ethernet Controller X710 for 10GbE SFP+ (rev 01)
+ docker run --name nfvbench --detach --privileged --net=host -e CONFIG_FILE="/tmp/nfvbench/nfvbench.cfg" -v /lib/modules/$(uname -r):/lib/modules/$(uname -r) -v /usr/src/kernels:/usr/src/kernels -v /dev:/dev -v $HOME/nfvbench:/tmp/nfvbench opnfv/nfvbench start_rest_server
+
+REST mode requires the same arguments as CLI mode and adds the following options:
++-------------------------------------------------------+-------------------------------------------------------+
+| Docker options | Description |
++=======================================================+=======================================================+
+| --net=host | use "host" docker networking mode |
+| | Other modes (such as NAT) could be used if required |
+| | with proper adjustment of the port to use for REST |
++-------------------------------------------------------+-------------------------------------------------------+
+| -e CONFIG_FILE="/tmp/nfvbench/nfvbench.cfg" | (optional) |
+| | specify the initial NFVbench config file to use. |
+| | defaults to none |
++-------------------------------------------------------+-------------------------------------------------------+
+| start_rest_server | to request a REST server to run in background in the |
+| | container |
++-------------------------------------------------------+-------------------------------------------------------+
+| -e HOST="127.0.0.1" | (optional) |
+| | specify the IP address to listen to. |
+| | defaults to 127.0.0.1 |
++-------------------------------------------------------+-------------------------------------------------------+
+| -e PORT=7555 | (optional) |
+| | specify the port address to listen to. |
+| | defaults to 7555 |
++-------------------------------------------------------+-------------------------------------------------------+
+
+The initial configuration file is optional but is handy to define mandatory deployment parameters that are common to all subsequent REST requests.
+If this initial configuration file is not passed at container start time, it must be included in every REST request.
-Example of edited configuration with an OpenStack RC file stored in the current directory with the "openrc" name, and
-PCI addresses "0a:00.0" and "0a:00.1" (first 2 ports of the quad port NIC):
+To start the NFVbench container with REST server using docker compose, use the following compose file:
.. code-block:: bash
- openrc_file: /tmp/nfvbench/openrc
- traffic_generator:
- generator_profile:
- - name: trex-local
- tool: TRex
- ip: 127.0.0.1
- cores: 3
- software_mode: false,
- interfaces:
- - port: 0
- switch_port:
- pci: "0a:00.0"
- - port: 1
- switch_port:
- pci: "0a:00.1"
- intf_speed:
+ version: '3'
+ services:
+ nfvbench:
+ image: "opnfv/nfvbench"
+ container_name: "nfvbench_server"
+ command: start_rest_server
+ volumes:
+ - /dev:/dev
+ - /usr/src/kernels:/usr/src/kernels
+ - /lib/modules:/lib/modules
+ - ${HOME}/nfvbench:/tmp/nfvbench
+ network_mode: "host"
+ environment:
+ - HOST="127.0.0.1"
+ - PORT=7555
+ privileged: true
+
+Requesting an NFVbench benchmark run
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+To request a benchmark run, you must create a JSON document that describes the benchmark and send it to the NFVbench server in the body of a POST request.
+
+
+Examples of REST requests
+~~~~~~~~~~~~~~~~~~~~~~~~~
+In this example, we will use curl to interact with the NFVbench REST server.
+
+Query the NFVbench version:
-.. warning::
+.. code-block:: bash
- You have to put quotes around the pci addresses as shown in the above example, otherwise TRex will read it wrong.
+ [root@sjc04-pod3-mgmt ~]# curl -G http://127.0.0.1:7555/version
+ 3.1.1
-Alternatively, the full template with comments can be obtained using the --show-default-config option in yaml format:
+This is the JSON for a fixed rate run at 10,000pps bi-directional (or 5kpps in each direction) using the PVP packet path:
.. code-block:: bash
- nfvbench --show-default-config > my_nfvbench.cfg
+ {"rate": "10kpps"}
-Edit the nfvbench.cfg file to only keep those properties that need to be modified (preserving the nesting).
+This is the curl request to send this benchmark request to the NFVbench server:
-Make sure you have your nfvbench configuration file (my_nfvbench.cfg) and - if OpenStack is used - OpenStack RC file in your pre-created working directory.
+.. code-block:: bash
+ [root@sjc04-pod3-mgmt ~]# curl -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{"rate": "10kpps"}' http://127.0.0.1:7555/start_run
+ {
+ "error_message": "nfvbench run still pending",
+ "status": "PENDING"
+ }
+ [root@sjc04-pod3-mgmt ~]#
-5. Run NFVbench
----------------
+This request will return immediately with status set to "PENDING" if the request was started successfully.
-To do a single run at 10,000pps bi-directional (or 5kpps in each direction) using the PVP packet path:
+The status can be polled until the run completes. Here the poll returns a "PENDING" status, indicating the run is still not completed:
.. code-block:: bash
- nfvbench -c /tmp/nfvbench/my_nfvbench.cfg --rate 10kpps
+ [root@sjc04-pod3-mgmt ~]# curl -G http://127.0.0.1:7555/status
+ {
+ "error_message": "nfvbench run still pending",
+ "status": "PENDING"
+ }
+ [root@sjc04-pod3-mgmt ~]#
-NFVbench options used:
+Finally, the status request returns a "OK" status along with the full results (truncated here):
-* ``-c /tmp/nfvbench/my_nfvbench.cfg`` : specify the config file to use (this must reflect the file path from inside the container)
-* ``--rate 10kpps`` : specify rate of packets for test for both directions using the kpps unit (thousands of packets per second)
+.. code-block:: bash
-This should produce a result similar to this (a simple run with the above options should take less than 5 minutes):
+ [root@sjc04-pod3-mgmt ~]# curl -G http://127.0.0.1:7555/status
+ {
+ "result": {
+ "benchmarks": {
+ "network": {
+ "service_chain": {
+ "PVP": {
+ "result": {
+ "bidirectional": true,
+
+ ...
+
+ "status": "OK"
+ }
+ [root@sjc04-pod3-mgmt ~]#
-.. code-block:: none
- [TBP]
+Retrieve complete configuration file as template
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
7. Terminating the NFVbench container
diff --git a/docs/testing/user/userguide/server.rst b/docs/testing/user/userguide/server.rst
index 921e3bc..52645ad 100644
--- a/docs/testing/user/userguide/server.rst
+++ b/docs/testing/user/userguide/server.rst
@@ -5,36 +5,6 @@
NFVbench Server mode and NFVbench client API
============================================
-NFVbench can run as an HTTP server to:
-
-- optionally provide access to any arbitrary HTLM files (HTTP server function) - this is optional
-- service fully parameterized aynchronous run requests using the HTTP protocol (REST/json with polling)
-- service fully parameterized run requests with interval stats reporting using the WebSocket/SocketIO protocol.
-
-Start the NFVbench server
--------------------------
-To run in server mode, simply use the --server <http_root_path> and optionally the listen address to use (--host <ip>, default is 0.0.0.0) and listening port to use (--port <port>, default is 7555).
-
-
-If HTTP files are to be serviced, they must be stored right under the http root path.
-This root path must contain a static folder to hold static files (css, js) and a templates folder with at least an index.html file to hold the template of the index.html file to be used.
-This mode is convenient when you do not already have a WEB server hosting the UI front end.
-If HTTP files servicing is not needed (REST only or WebSocket/SocketIO mode), the root path can point to any dummy folder.
-
-Once started, the NFVbench server will be ready to service HTTP or WebSocket/SocketIO requests at the advertised URL.
-
-Example of NFVbench server start in a container:
-
-.. code-block:: bash
-
- # get to the container shell (assume the container name is "nfvbench")
- docker exec -it nfvbench bash
- # from the container shell start the NFVbench server in the background
- nfvbench -c /tmp/nfvbench/nfvbench.cfg --server /tmp &
- # exit container
- exit
-
-
HTTP Interface
--------------
@@ -48,7 +18,7 @@ Example request:
.. code-block:: bash
- curl -XGET '127.0.0.1:7556/echo' -H "Content-Type: application/json" -d '{"nfvbench": "test"}'
+ curl -XGET '127.0.0.1:7555/echo' -H "Content-Type: application/json" -d '{"nfvbench": "test"}'
Response:
{
"nfvbench": "test"
@@ -114,44 +84,6 @@ If there is already an NFVBench running then it will return:
"status": "ERROR"
}
-WebSocket/SocketIO events
--------------------------
-
-List of SocketIO events supported:
-
-Client to Server
-^^^^^^^^^^^^^^^^
-
-start_run:
-
- sent by client to start a new run with the configuration passed in argument (JSON).
- The configuration can be any valid NFVbench configuration passed as a JSON document (see "NFVbench configuration JSON parameter" below)
-
-Server to Client
-^^^^^^^^^^^^^^^^
-
-run_interval_stats:
-
- sent by server to report statistics during a run
- the message contains the statistics {'time_ms': time_ms, 'tx_pps': tx_pps, 'rx_pps': rx_pps, 'drop_pct': drop_pct}
-
-ndr_found:
-
- (during NDR-PDR search)
- sent by server when the NDR rate is found
- the message contains the NDR value {'rate_pps': ndr_pps}
-
-ndr_found:
-
- (during NDR-PDR search)
- sent by server when the PDR rate is found
- the message contains the PDR value {'rate_pps': pdr_pps}
-
-
-run_end:
-
- sent by server to report the end of a run
- the message contains the complete results in JSON format
NFVbench configuration JSON parameter
-------------------------------------
@@ -341,6 +273,15 @@ A short run of 5 seconds at a fixed rate of 1Mpps (and everything else same as t
"rate": "1Mpps"
}
+Use the default configuration but force TRex restart:
+
+.. code-block:: bash
+
+ {
+ "restart": true
+ }
+
+
Example of interaction with the NFVbench server using HTTP and curl
-------------------------------------------------------------------
HTTP requests can be sent directly to the NFVbench server from CLI using curl from any host that can connect to the server (here we run it from the local host).
@@ -406,7 +347,7 @@ Finally, the status request returns a "OK" status along with the full results (t
Example of interaction with the NFVbench server using a python CLI app (nfvbench_client)
----------------------------------------------------------------------------------------
-The module client/client.py contains an example of python class that can be used to control the NFVbench server from a python app using HTTP or WebSocket/SocketIO.
+The module client/client.py contains an example of python class that can be used to control the NFVbench server from a python app using HTTP.
The module client/nfvbench_client.py has a simple main application to control the NFVbench server from CLI.
The "nfvbench_client" wrapper script can be used to invoke the client front end (this wrapper is pre-installed in the NFVbench container)
@@ -428,11 +369,3 @@ use the default NFVbench configuration but do not generate traffic (no_traffic p
...
[root@sjc04-pod3-mgmt ~]#
-
-The http interface is used unless --use-socketio is defined.
-
-Example of invocation using Websocket/SocketIO, execute NFVbench using the default configuration but with a duration of 5 seconds and a fixed rate run of 5kpps.
-
-.. code-block:: bash
-
- [root@sjc04-pod3-mgmt ~]# docker exec -it nfvbench nfvbench_client -c '{"duration":5,"rate":"5kpps"}' --use-socketio http://127.0.0.1:7555 >results.json
diff --git a/nfvbench/cfg.default.yaml b/nfvbench/cfg.default.yaml
index fa3d807..eb5fa11 100755
--- a/nfvbench/cfg.default.yaml
+++ b/nfvbench/cfg.default.yaml
@@ -25,6 +25,11 @@
# 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 openrc is not admin some parameters are mandatory and must be filled with valid values in config file such as :
+# - availability_zone
+# - hypervisor_hostname
+# - vlans
openrc_file:
# Forwarder to use in nfvbenchvm image. Available options: ['vpp', 'testpmd']
@@ -66,11 +71,15 @@ flavor:
# Name of the availability zone to use for the test VMs
# Must be one of the zones listed by 'nova availability-zone-list'
# availability_zone: 'nova'
+# If openrc is not admin set a valid value
availability_zone:
# To force placement on a given hypervisor, set the name here
# (if multiple names are provided, the first will be used)
# Leave empty to let openstack pick the hypervisor
compute_nodes:
+# If openrc is not admin set a valid value for hypervisor hostname
+# Example of value: hypervisor_hostname: "server1"
+hypervisor_hostname:
# Type of service chain to run, possible options are PVP, PVVP and EXT
# PVP - port to VM to port
@@ -192,7 +201,7 @@ traffic_generator:
#
# Generator profiles are listed in the following format:
# `name`: Traffic generator profile name (use a unique name, no space or special character)
- # DFo not change this field
+ # Do not change this field
# `tool`: Traffic generator tool to be used (currently supported is `TRex`).
# Do not change this field
# `ip`: IP address of the traffic generator.
@@ -205,6 +214,13 @@ traffic_generator:
# software mode, therefore the performance of TRex will be significantly
# lower. ONLY applies to trex-local.
# Recommended to leave the default value (false)
+ # `limit_memory`: Specify the memory reserved for running the TRex traffic generator (in MB). Limit the amount
+ # of packet memory used. (Passed to dpdk as -m arg)
+ # ONLY applies to trex-local.
+ # `zmq_pub_port`: Specify the ZMQ pub port number for the TRex traffic generator instance (default value is 4500).
+ # ONLY applies to trex-local.
+ # `zmq_rpc_port`: Specify the ZMQ rpc port for the TRex traffic generator instance (default value is 4501).
+ # ONLY applies to trex-local.
# `interfaces`: Configuration of traffic generator interfaces.
# `interfaces.port`: The port of the traffic generator to be used (leave as 0 and 1 resp.)
# `interfaces.switch_port`: Leave empty (deprecated)
@@ -218,12 +234,33 @@ traffic_generator:
# Do not use unless you want to override the speed discovered by the
# traffic generator. Expected format: 10Gbps
#
+ # `platform`: Optional. Used to tune the performance and allocate the cores to the right NUMA.
+ # See https://trex-tgn.cisco.com/trex/doc/trex_manual.html (6.2.3. Platform section configuration)
+ # for more details
+ # `platform.master_thread_id`: Hardware thread_id for control thread. (Valid value is mandatory if platform property is set)
+ # `platform.latency_thread_id`: Hardware thread_id for RX thread. (Valid value is mandatory if platform property is set)
+ # `platform.dual_if`: Section defines info for interface pairs (according to the order in “interfaces” list). (Valid value is mandatory if platform property is set)
+ # Each section, starting with “- socket” defines info for different interface pair. (Valid value is mandatory if platform property is set)
+ # `platform.dual_if.socket`: The NUMA node from which memory will be allocated for use by the interface pair. (Valid value is mandatory if platform property is set)
+ # `platform.dual_if.threads`: Hardware threads to be used for sending packets for the interface pair. (Valid value is mandatory if platform property is set)
+ # Threads are pinned to cores, so specifying threads actually determines the hardware cores.
+ # Example of values:
+ # platform:
+ # master_thread_id: 0
+ # latency_thread_id: 2
+ # dual_if:
+ # - socket: 0
+ # threads: [1]
+ #
generator_profile:
- name: trex-local
tool: TRex
ip: 127.0.0.1
cores: 4
software_mode: false
+ limit_memory: 1024
+ zmq_pub_port: 4500
+ zmq_rpc_port: 4501
interfaces:
- port: 0
pci:
@@ -232,6 +269,16 @@ traffic_generator:
pci:
switch_port:
intf_speed:
+ platform:
+ master_thread_id:
+ latency_thread_id:
+ dual_if:
+ - socket:
+ threads:
+
+# Use 'true' to force restart of local TRex server before next run
+# TRex local server will be restarted even if restart property is false in case of generator config changes between runs
+restart: false
# Simpler override for trex core count and mbuf multilier factor
# if empty defaults to the one specified in generator_profile.cores
@@ -338,23 +385,69 @@ internal_networks:
segmentation_id:
physical_network:
+# IDLE INTERFACES: PVP, PVVP and non shared net only.
+# By default each test VM will have 2 virtual interfaces for looping traffic.
+# If service_chain_shared_net is false, additional virtual interfaces can be
+# added at VM creation time, these interfaces will not carry any traffic and
+# can be used to test the impact of idle interfaces in the overall performance.
+# All these idle interfaces will use normal ports (not direct).
+# Number of idle interfaces per VM (none by default)
+idle_interfaces_per_vm: 0
+
+# A new network is created for each idle interface.
+# If service_chain_shared_net is true, the options below will be ignored
+# and no idle interfaces will be added.
+idle_networks:
+ # Prefix for all idle networks
+ name: 'nfvbench-idle-net'
+ # Prefix for all idle subnetworks
+ subnet: 'nfvbench-idle-subnet'
+ # CIDR to use for all idle networks (value should not matter)
+ cidr: '192.169.1.0/24'
+ # Type of network associated to the idle virtual interfaces (vlan or vxlan)
+ network_type: 'vlan'
+ # segmentation ID to use for the network attached to the idle virtual interfaces
+ # vlan: leave empty to let neutron pick the segmentation ID
+ # vxlan: must specify the VNI value to be used (cannot be empty)
+ # Note that NFVbench will use as many consecutive segmentation IDs as needed.
+ # For example, for 4 PVP chains and 8 idle
+ # interfaces per VM, NFVbench will use 32 consecutive values of segmentation ID
+ # starting from the value provided.
+ segmentation_id:
+ # physnet name to use for all idle interfaces
+ physical_network:
+
# In the scenario of PVVP + SRIOV, there is choice of how the traffic will be
# handled in the middle network. The default (false) will use vswitch, while
# SRIOV can be used by toggling below setting.
use_sriov_middle_net: false
-# EXT chain only. Prefix names of edge networks which will be used to send traffic via traffic generator.
+# EXT chain only. Prefix names of edge networks or list of edge network names
+# used to send traffic via traffic generator.
#
# If service_chain_shared_net is true, the left and right networks must pre-exist and match exactly by name.
#
# If service_chain_shared_net is false, each chain must have its own pre-existing left and right networks.
-# An index will be appended to each network name to form the final name:
+# left and right can take either a string prefix or a list of arbitrary network names
+# If a string prefix is passed, an index will be appended to each network name to form the final name.
+# Example:
+# external_networks:
+# left: 'ext-lnet'
+# right: 'ext-rnet'
# ext-lnet0 ext-rnet0 for chain #0
# ext-lnet1 ext-rnet1 for chain #1
# etc...
+# If a list of strings is passed, each string in the list must be the name of the network used for the
+# chain indexed by the entry position in the list.
+# The list must have at least as many entries as there are chains
+# Example:
+# external_networks:
+# left: ['ext-lnet', 'ext-lnet2']
+# right: ['ext-rnet', 'ext-rnet2']
+#
external_networks:
- left: 'ext-lnet'
- right: 'ext-rnet'
+ left:
+ right:
# Use 'true' to enable VXLAN encapsulation support and sent by the traffic generator
# When this option enabled internal networks 'network type' parameter value should be 'vxlan'
@@ -369,7 +462,7 @@ vxlan: false
# is not supported). Use the vtep_vlan option to enable vlan tagging for the VxLAN overlay network.
vlan_tagging: true
-# Used only in the case of EXT chain and no openstack to specify the VLAN IDs to use.
+# Used only in the case of EXT chain and no openstack or not admin access to specify the VLAN IDs to use.
# This property is ignored when OpenStakc is used or in the case of l2-loopback.
# If OpenStack is used leave the list empty, VLAN IDs are retrieved from OpenStack networks using Neutron API.
# If networks are shared across all chains (service_chain_shared_net=true), the list should have exactly 2 values
diff --git a/nfvbench/chain_runner.py b/nfvbench/chain_runner.py
index a4e461d..627e9ea 100644
--- a/nfvbench/chain_runner.py
+++ b/nfvbench/chain_runner.py
@@ -74,8 +74,11 @@ class ChainRunner(object):
# the only case we do not need to set the dest MAC is in the case of
# l2-loopback (because the traffic gen will default to use the peer MAC)
- # or EXT+ARP (because dest MAC will be discovered by TRex ARP)
- if not config.l2_loopback and (config.service_chain != ChainType.EXT or config.no_arp):
+ # or EXT+ARP+VLAN (because dest MAC will be discovered by TRex ARP)
+ # Note that in the case of EXT+ARP+VxLAN, the dest MACs need to be loaded
+ # because ARP only operates on the dest VTEP IP not on the VM dest MAC
+ if not config.l2_loopback and \
+ (config.service_chain != ChainType.EXT or config.no_arp or config.vxlan):
gen_config.set_dest_macs(0, self.chain_manager.get_dest_macs(0))
gen_config.set_dest_macs(1, self.chain_manager.get_dest_macs(1))
diff --git a/nfvbench/chaining.py b/nfvbench/chaining.py
index a97cd0b..60f3832 100644
--- a/nfvbench/chaining.py
+++ b/nfvbench/chaining.py
@@ -49,7 +49,7 @@ import os
import re
import time
-from glanceclient.v2 import client as glanceclient
+import glanceclient
from neutronclient.neutron import client as neutronclient
from novaclient.client import Client
@@ -193,21 +193,32 @@ class ChainVnfPort(object):
class ChainNetwork(object):
"""Could be a shared network across all chains or a chain private network."""
- def __init__(self, manager, network_config, chain_id=None, lookup_only=False):
+ def __init__(self, manager, network_config, chain_id=None, lookup_only=False,
+ suffix=None):
"""Create a network for given chain.
network_config: a dict containing the network properties
- (segmentation_id and physical_network)
+ (name, segmentation_id and physical_network)
chain_id: to which chain the networks belong.
a None value will mean that these networks are shared by all chains
+ suffix: a suffix to add to the network name (if not None)
"""
self.manager = manager
- self.name = network_config.name
+ if chain_id is None:
+ self.name = network_config.name
+ else:
+ # the name itself can be either a string or a list of names indexed by chain ID
+ if isinstance(network_config.name, tuple):
+ self.name = network_config.name[chain_id]
+ else:
+ # network_config.name is a prefix string
+ self.name = network_config.name + str(chain_id)
+ if suffix:
+ self.name = self.name + suffix
self.segmentation_id = self._get_item(network_config.segmentation_id,
chain_id, auto_index=True)
self.physical_network = self._get_item(network_config.physical_network, chain_id)
- if chain_id is not None:
- self.name += str(chain_id)
+
self.reuse = False
self.network = None
self.vlan = None
@@ -292,6 +303,8 @@ class ChainNetwork(object):
if self.physical_network:
body['network']['provider:physical_network'] = self.physical_network
self.network = self.manager.neutron_client.create_network(body)['network']
+ # create associated subnet, all subnets have the same name (which is ok since
+ # we do not need to address them directly by name)
body = {
'subnet': {'name': network_config.subnet,
'cidr': network_config.cidr,
@@ -369,11 +382,18 @@ class ChainVnf(object):
if len(networks) > 2:
# we will have more than 1 VM in each chain
self.name += '-' + str(vnf_id)
+ # A list of ports for this chain
+ # There are normally 2 ports carrying traffic (index 0, and index 1) and
+ # potentially multiple idle ports not carrying traffic (index 2 and up)
+ # For example if 7 idle interfaces are requested, the corresp. ports will be
+ # at index 2 to 8
self.ports = []
self.status = None
self.instance = None
self.reuse = False
self.host_ip = None
+ self.idle_networks = []
+ self.idle_ports = []
try:
# the vnf_id is conveniently also the starting index in networks
# for the left and right networks associated to this VNF
@@ -408,7 +428,7 @@ class ChainVnf(object):
def _get_vnic_type(self, port_index):
"""Get the right vnic type for given port indexself.
- If SR-IOV is speficied, middle ports in multi-VNF chains
+ If SR-IOV is specified, middle ports in multi-VNF chains
can use vswitch or SR-IOV based on config.use_sriov_middle_net
"""
if self.manager.config.sriov:
@@ -423,6 +443,62 @@ class ChainVnf(object):
return 'direct'
return 'normal'
+ def _get_idle_networks_ports(self):
+ """Get the idle networks for PVP or PVVP chain (non shared net only)
+
+ For EXT packet path or shared net, returns empty list.
+ For PVP, PVVP these networks will be created if they do not exist.
+ chain_id: to which chain the networks belong.
+ a None value will mean that these networks are shared by all chains
+ """
+ networks = []
+ ports = []
+ config = self.manager.config
+ chain_id = self.chain.chain_id
+ idle_interfaces_per_vm = config.idle_interfaces_per_vm
+ if config.service_chain == ChainType.EXT or chain_id is None or \
+ idle_interfaces_per_vm == 0:
+ return
+
+ # Make a copy of the idle networks dict as we may have to modify the
+ # segmentation ID
+ idle_network_cfg = AttrDict(config.idle_networks)
+ if idle_network_cfg.segmentation_id:
+ segmentation_id = idle_network_cfg.segmentation_id + \
+ chain_id * idle_interfaces_per_vm
+ else:
+ segmentation_id = None
+ try:
+ # create as many idle networks and ports as requested
+ for idle_index in range(idle_interfaces_per_vm):
+ if config.service_chain == ChainType.PVP:
+ suffix = '.%d' % (idle_index)
+ else:
+ suffix = '.%d.%d' % (self.vnf_id, idle_index)
+ port_name = self.name + '-idle' + str(idle_index)
+ # update the segmentation id based on chain id and idle index
+ if segmentation_id:
+ idle_network_cfg.segmentation_id = segmentation_id + idle_index
+ port_name = port_name + "." + str(segmentation_id)
+
+ networks.append(ChainNetwork(self.manager,
+ idle_network_cfg,
+ chain_id,
+ suffix=suffix))
+ ports.append(ChainVnfPort(port_name,
+ self,
+ networks[idle_index],
+ 'normal'))
+ except Exception:
+ # need to cleanup all successful networks
+ for net in networks:
+ net.delete()
+ for port in ports:
+ port.delete()
+ raise
+ self.idle_networks = networks
+ self.idle_ports = ports
+
def _setup(self, networks):
flavor_id = self.manager.flavor.flavor.id
# Check if we can reuse an instance with same name
@@ -454,6 +530,12 @@ class ChainVnf(object):
self,
networks[index],
self._get_vnic_type(index)) for index in [0, 1]]
+
+ # create idle networks and ports only if instance is not reused
+ # if reused, we do not care about idle networks/ports
+ if not self.reuse:
+ self._get_idle_networks_ports()
+
# if no reuse, actual vm creation is deferred after all ports in the chain are created
# since we need to know the next mac in a multi-vnf chain
@@ -462,6 +544,9 @@ class ChainVnf(object):
if self.instance is None:
port_ids = [{'port-id': vnf_port.port['id']}
for vnf_port in self.ports]
+ # add idle ports
+ for idle_port in self.idle_ports:
+ port_ids.append({'port-id': idle_port.port['id']})
vm_config = self._get_vm_config(remote_mac_pair)
az = self.manager.placer.get_required_az()
server = self.manager.comp.create_server(self.name,
@@ -522,7 +607,13 @@ class ChainVnf(object):
def get_hostname(self):
"""Get the hypervisor host name running this VNF instance."""
- return getattr(self.instance, 'OS-EXT-SRV-ATTR:hypervisor_hostname')
+ if self.manager.is_admin:
+ hypervisor_hostname = getattr(self.instance, 'OS-EXT-SRV-ATTR:hypervisor_hostname')
+ else:
+ hypervisor_hostname = self.manager.config.hypervisor_hostname
+ if not hypervisor_hostname:
+ raise ChainException('Hypervisor hostname parameter is mandatory')
+ return hypervisor_hostname
def get_host_ip(self):
"""Get the IP address of the host where this instance runs.
@@ -536,7 +627,12 @@ class ChainVnf(object):
def get_hypervisor_name(self):
"""Get hypervisor name (az:hostname) for this VNF instance."""
if self.instance:
- az = getattr(self.instance, 'OS-EXT-AZ:availability_zone')
+ if self.manager.is_admin:
+ az = getattr(self.instance, 'OS-EXT-AZ:availability_zone')
+ else:
+ az = self.manager.config.availability_zone
+ if not az:
+ raise ChainException('Availability zone parameter is mandatory')
hostname = self.get_hostname()
if az:
return az + ':' + hostname
@@ -557,6 +653,10 @@ class ChainVnf(object):
LOG.info("Deleted instance %s", self.name)
for port in self.ports:
port.delete()
+ for port in self.idle_ports:
+ port.delete()
+ for network in self.idle_networks:
+ network.delete()
class Chain(object):
"""A class to manage a single chain.
@@ -851,6 +951,7 @@ class ChainManager(object):
if self.openstack:
# openstack only
session = chain_runner.cred.get_session()
+ self.is_admin = chain_runner.cred.is_admin
self.nova_client = Client(2, session=session)
self.neutron_client = neutronclient.Client('2.0', session=session)
self.glance_client = glanceclient.Client('2', session=session)
@@ -864,6 +965,12 @@ class ChainManager(object):
self.flavor = ChainFlavor(config.flavor_type, config.flavor, self.comp)
# Get list of all existing instances to check if some instances can be reused
self.existing_instances = self.comp.get_server_list()
+ else:
+ # For EXT chains, the external_networks left and right fields in the config
+ # must be either a prefix string or a list of at least chain-count strings
+ self._check_extnet('left', config.external_networks.left)
+ self._check_extnet('right', config.external_networks.right)
+
# If networks are shared across chains, get the list of networks
if config.service_chain_shared_net:
self.networks = self.get_networks()
@@ -871,18 +978,20 @@ class ChainManager(object):
for chain_id in range(self.chain_count):
self.chains.append(Chain(chain_id, self))
if config.service_chain == ChainType.EXT:
- # if EXT and no ARP we need to read dest MACs from config
- if config.no_arp:
+ # if EXT and no ARP or VxLAN we need to read dest MACs from config
+ if config.no_arp or config.vxlan:
self._get_dest_macs_from_config()
else:
# Make sure all instances are active before proceeding
self._ensure_instances_active()
+ # network API call do not show VLANS ID if not admin read from config
+ if not self.is_admin and config.vlan_tagging:
+ self._get_config_vlans()
except Exception:
self.delete()
raise
else:
# no openstack, no need to create chains
-
if not config.l2_loopback and config.no_arp:
self._get_dest_macs_from_config()
if config.vlan_tagging:
@@ -890,12 +999,26 @@ class ChainManager(object):
if len(config.vlans) != 2:
raise ChainException('The config vlans property must be a list '
'with 2 lists of VLAN IDs')
- re_vlan = "[0-9]*$"
- self.vlans = [self._check_list('vlans[0]', config.vlans[0], re_vlan),
- self._check_list('vlans[1]', config.vlans[1], re_vlan)]
+ self._get_config_vlans()
if config.vxlan:
raise ChainException('VxLAN is only supported with OpenStack')
+ def _check_extnet(self, side, name):
+ if not name:
+ raise ChainException('external_networks.%s must contain a valid network'
+ ' name prefix or a list of network names' % side)
+ if isinstance(name, tuple) and len(name) < self.chain_count:
+ raise ChainException('external_networks.%s %s'
+ ' must have at least %d names' % (side, name, self.chain_count))
+
+ def _get_config_vlans(self):
+ re_vlan = "[0-9]*$"
+ try:
+ self.vlans = [self._check_list('vlans[0]', self.config.vlans[0], re_vlan),
+ self._check_list('vlans[1]', self.config.vlans[1], re_vlan)]
+ except IndexError:
+ raise ChainException('vlans parameter is mandatory. Set valid value in config file')
+
def _get_dest_macs_from_config(self):
re_mac = "[0-9a-fA-F]{2}([-:])[0-9a-fA-F]{2}(\\1[0-9a-fA-F]{2}){4}$"
tg_config = self.config.traffic_generator
@@ -1091,11 +1214,11 @@ class ChainManager(object):
"""
return self.get_existing_ports().get(chain_network.get_uuid(), None)
- def get_host_ip_from_mac(self, mac):
- """Get the host IP address matching a MAC.
+ def get_hypervisor_from_mac(self, mac):
+ """Get the hypervisor that hosts a VM MAC.
mac: MAC address to look for
- return: the IP address of the host where the matching port runs or None if not found
+ return: the hypervisor where the matching port runs or None if not found
"""
# _existing_ports is a dict of list of ports indexed by network id
for port_list in self.get_existing_ports().values():
@@ -1103,18 +1226,29 @@ class ChainManager(object):
try:
if port['mac_address'] == mac:
host_id = port['binding:host_id']
- return self.comp.get_hypervisor(host_id).host_ip
+ return self.comp.get_hypervisor(host_id)
except KeyError:
pass
return None
+ def get_host_ip_from_mac(self, mac):
+ """Get the host IP address matching a MAC.
+
+ mac: MAC address to look for
+ return: the IP address of the host where the matching port runs or None if not found
+ """
+ hypervisor = self.get_hypervisor_from_mac(mac)
+ if hypervisor:
+ return hypervisor.host_ip
+ return None
+
def get_chain_vlans(self, port_index):
"""Get the list of per chain VLAN id on a given port.
port_index: left port is 0, right port is 1
return: a VLAN ID list indexed by the chain index or None if no vlan tagging
"""
- if self.chains:
+ if self.chains and self.is_admin:
return [self.chains[chain_index].get_vlan(port_index)
for chain_index in range(self.chain_count)]
# no openstack
@@ -1126,11 +1260,11 @@ class ChainManager(object):
port_index: left port is 0, right port is 1
return: a VNIs ID list indexed by the chain index or None if no vlan tagging
"""
- if self.chains:
+ if self.chains and self.is_admin:
return [self.chains[chain_index].get_vxlan(port_index)
for chain_index in range(self.chain_count)]
# no openstack
- raise ChainException('VxLAN is only supported with OpenStack')
+ raise ChainException('VxLAN is only supported with OpenStack and with admin user')
def get_dest_macs(self, port_index):
"""Get the list of per chain dest MACs on a given port.
@@ -1178,7 +1312,18 @@ class ChainManager(object):
if self.chains:
# in the case of EXT, the compute node must be retrieved from the port
# associated to any of the dest MACs
- return self.chains[0].get_compute_nodes()
+ if self.config.service_chain != ChainType.EXT:
+ return self.chains[0].get_compute_nodes()
+ # in the case of EXT, the compute node must be retrieved from the port
+ # associated to any of the dest MACs
+ dst_macs = self.generator_config.get_dest_macs()
+ # dest MAC on port 0, chain 0
+ dst_mac = dst_macs[0][0]
+ hypervisor = self.get_hypervisor_from_mac(dst_mac)
+ if hypervisor:
+ LOG.info('Found hypervisor for EXT chain: %s', hypervisor.hypervisor_hostname)
+ return[':' + hypervisor.hypervisor_hostname]
+
# no openstack = no chains
return []
diff --git a/nfvbench/cleanup.py b/nfvbench/cleanup.py
index 819514a..6b13f69 100644
--- a/nfvbench/cleanup.py
+++ b/nfvbench/cleanup.py
@@ -107,6 +107,7 @@ class NetworkCleaner(object):
except Exception:
LOG.exception("Port deletion failed")
+ # associated subnets are automatically deleted by neutron
for net in self.networks:
LOG.info("Deleting network %s...", net['name'])
try:
@@ -147,6 +148,9 @@ class Cleaner(object):
self.neutron_client = nclient.Client('2.0', session=session)
self.nova_client = Client(2, session=session)
network_names = [inet['name'] for inet in config.internal_networks.values()]
+ # add idle networks as well
+ if config.idle_networks.name:
+ network_names.append(config.idle_networks.name)
self.cleaners = [ComputeCleaner(self.nova_client, config.loop_vm_name),
FlavorCleaner(self.nova_client, config.flavor_type),
NetworkCleaner(self.neutron_client, network_names)]
diff --git a/nfvbench/config.py b/nfvbench/config.py
index 5feeda5..0f0d64a 100644
--- a/nfvbench/config.py
+++ b/nfvbench/config.py
@@ -43,7 +43,7 @@ def config_loads(cfg_text, from_cfg=None, whitelist_keys=None):
"""Same as config_load but load from a string
"""
try:
- cfg = AttrDict(yaml.load(cfg_text))
+ cfg = AttrDict(yaml.safe_load(cfg_text))
except TypeError:
# empty string
cfg = AttrDict()
diff --git a/nfvbench/credentials.py b/nfvbench/credentials.py
index 530ad69..17811f9 100644
--- a/nfvbench/credentials.py
+++ b/nfvbench/credentials.py
@@ -21,6 +21,8 @@ import getpass
from keystoneauth1.identity import v2
from keystoneauth1.identity import v3
from keystoneauth1 import session
+from keystoneclient import client
+from keystoneclient import utils
from log import LOG
@@ -106,6 +108,7 @@ class Credentials(object):
self.rc_project_domain_name = None
self.rc_project_name = None
self.rc_identity_api_version = 2
+ self.is_admin = False
success = True
if openrc_file:
@@ -164,3 +167,19 @@ class Credentials(object):
'Please enter your OpenStack Password: ')
if not self.rc_password:
self.rc_password = ""
+
+ # check if user has admin role in OpenStack project
+ try:
+ keystone = client.Client(session=self.get_session())
+ user = utils.find_resource(keystone.users, self.rc_username)
+ if self.rc_identity_api_version == 2:
+ tenant = utils.find_resource(keystone.tenants, self.rc_tenant_name)
+ roles = keystone.roles.roles_for_user(user, tenant=tenant.id)
+ elif self.rc_identity_api_version == 3:
+ project = utils.find_resource(keystone.projects, self.rc_project_name)
+ roles = keystone.roles.list(user=user.id, project=project.id)
+ for role in roles:
+ if role.name == 'admin':
+ self.is_admin = True
+ except Exception:
+ LOG.warning("User is not admin, no permission to list user roles")
diff --git a/nfvbench/nfvbench.py b/nfvbench/nfvbench.py
index cdb99c8..e585154 100644
--- a/nfvbench/nfvbench.py
+++ b/nfvbench/nfvbench.py
@@ -36,7 +36,7 @@ import credentials as credentials
from fluentd import FluentLogHandler
import log
from log import LOG
-from nfvbenchd import WebSocketIoServer
+from nfvbenchd import WebServer
from specs import ChainType
from specs import Specs
from summarizer import NFVBenchSummarizer
@@ -71,6 +71,11 @@ class NFVBench(object):
self.notifier = notifier
def run(self, opts, args):
+ """This run() method is called for every NFVbench benchmark request.
+
+ In CLI mode, this method is called only once per invocation.
+ In REST server mode, this is called once per REST POST request
+ """
status = NFVBench.STATUS_OK
result = None
message = ''
@@ -82,6 +87,12 @@ class NFVBench(object):
try:
# recalc the running config based on the base config and options for this run
self._update_config(opts)
+
+ # 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")
+
self.specs.set_run_spec(self.config_plugin.get_run_spec(self.config,
self.specs.openstack))
self.chain_runner = ChainRunner(self.config,
@@ -239,10 +250,8 @@ def _parse_opts_from_cli():
parser.add_argument('--server', dest='server',
default=None,
- action='store',
- metavar='<http_root_pathname>',
- help='Run nfvbench in server mode and pass'
- ' the HTTP root folder full pathname')
+ action='store_true',
+ help='Run nfvbench in server mode')
parser.add_argument('--host', dest='host',
action='store',
@@ -345,6 +354,11 @@ def _parse_opts_from_cli():
action='store_true',
help='Cleanup NFVbench resources (do not prompt)')
+ parser.add_argument('--restart', dest='restart',
+ default=None,
+ action='store_true',
+ help='Restart TRex server')
+
parser.add_argument('--json', dest='json',
action='store',
help='store results in json format file',
@@ -543,7 +557,8 @@ def main():
config.compute_nodes = opts.hypervisor
if opts.vxlan:
config.vxlan = True
-
+ if opts.restart:
+ config.restart = True
# port to port loopback (direct or through switch)
if opts.l2_loopback:
config.l2_loopback = True
@@ -574,14 +589,6 @@ def main():
print json.dumps(config, sort_keys=True, indent=4)
sys.exit(0)
- # check that an empty openrc file (no OpenStack) is only allowed
- # with EXT chain
- if not config.openrc_file:
- if config.service_chain == ChainType.EXT:
- LOG.info('EXT chain with OpenStack mode disabled')
- else:
- raise Exception("openrc_file is empty in the configuration and is required")
-
# update the config in the config plugin as it might have changed
# in a copy of the dict (config plugin still holds the original dict)
config_plugin.set_config(config)
@@ -599,18 +606,14 @@ def main():
nfvbench_instance = NFVBench(config, openstack_spec, config_plugin, factory)
if opts.server:
- if os.path.isdir(opts.server):
- server = WebSocketIoServer(opts.server, nfvbench_instance, fluent_logger)
- nfvbench_instance.set_notifier(server)
- try:
- port = int(opts.port)
- except ValueError:
- server.run(host=opts.host)
- else:
- server.run(host=opts.host, port=port)
+ server = WebServer(nfvbench_instance, fluent_logger)
+ try:
+ port = int(opts.port)
+ except ValueError:
+ server.run(host=opts.host)
else:
- print 'Invalid HTTP root directory: ' + opts.server
- sys.exit(1)
+ server.run(host=opts.host, port=port)
+ # server.run() should never return
else:
with utils.RunLock():
run_summary_required = True
diff --git a/nfvbench/nfvbenchd.py b/nfvbench/nfvbenchd.py
index fa781af..ae89e7a 100644
--- a/nfvbench/nfvbenchd.py
+++ b/nfvbench/nfvbenchd.py
@@ -16,37 +16,31 @@
import json
import Queue
+from threading import Thread
import uuid
from flask import Flask
from flask import jsonify
-from flask import render_template
from flask import request
-from flask_socketio import emit
-from flask_socketio import SocketIO
from summarizer import NFVBenchSummarizer
from log import LOG
from utils import byteify
from utils import RunLock
-# this global cannot reside in Ctx because of the @app and @socketio decorators
-app = None
-socketio = None
+from __init__ import __version__
STATUS_OK = 'OK'
STATUS_ERROR = 'ERROR'
STATUS_PENDING = 'PENDING'
STATUS_NOT_FOUND = 'NOT_FOUND'
-
def result_json(status, message, request_id=None):
body = {
'status': status,
'error_message': message
}
-
if request_id is not None:
body['request_id'] = request_id
@@ -66,15 +60,13 @@ class Ctx(object):
run_queue = Queue.Queue()
busy = False
result = None
- request_from_socketio = False
results = {}
ids = []
current_id = None
@staticmethod
- def enqueue(config, request_id, from_socketio=False):
+ def enqueue(config, request_id):
Ctx.busy = True
- Ctx.request_from_socketio = from_socketio
config['request_id'] = request_id
Ctx.run_queue.put(config)
@@ -129,40 +121,18 @@ class Ctx(object):
return Ctx.current_id
-def setup_flask(root_path):
- global socketio
- global app
+def setup_flask():
app = Flask(__name__)
- app.root_path = root_path
- socketio = SocketIO(app, async_mode='threading')
busy_json = result_json(STATUS_ERROR, 'there is already an NFVbench request running')
not_busy_json = result_json(STATUS_ERROR, 'no pending NFVbench run')
not_found_msg = 'results not found'
pending_msg = 'NFVbench run still pending'
- # --------- socketio requests ------------
-
- @socketio.on('start_run')
- def _socketio_start_run(config):
- if not Ctx.is_busy():
- Ctx.enqueue(config, get_uuid(), from_socketio=True)
- else:
- emit('error', {'reason': 'there is already an NFVbench request running'})
-
- @socketio.on('echo')
- def _socketio_echo(config):
- emit('echo', config)
-
# --------- HTTP requests ------------
- @app.route('/')
- def _index():
- return render_template('index.html')
-
- @app.route('/echo', methods=['GET'])
- def _echo():
- config = request.json
- return jsonify(config)
+ @app.route('/version', methods=['GET'])
+ def _version():
+ return __version__
@app.route('/start_run', methods=['POST'])
def _start_run():
@@ -201,23 +171,24 @@ def setup_flask(root_path):
return jsonify(res)
return jsonify(not_busy_json)
+ return app
+
-class WebSocketIoServer(object):
- """This class takes care of the web socketio server, accepts websocket events, and sends back
- notifications using websocket events (send_ methods). Caller should simply create an instance
+class WebServer(object):
+ """This class takes care of the web server. Caller should simply create an instance
of this class and pass a runner object then invoke the run method
"""
- def __init__(self, http_root, runner, fluent_logger):
+ def __init__(self, runner, fluent_logger):
self.nfvbench_runner = runner
- setup_flask(http_root)
+ self.app = setup_flask()
self.fluent_logger = fluent_logger
- def run(self, host='127.0.0.1', port=7556):
+ def run(self, host, port):
- # socketio.run will not return so we need to run it in a background thread so that
+ # app.run will not return so we need to run it in a background thread so that
# the calling thread (main thread) can keep doing work
- socketio.start_background_task(target=socketio.run, app=app, host=host, port=port)
+ Thread(target=self.app.run, args=(host, port)).start()
# wait for run requests
# the runner must be executed from the main thread (Trex client library requirement)
@@ -238,11 +209,8 @@ class WebSocketIoServer(object):
results = result_json(STATUS_ERROR, str(exc))
LOG.exception('NFVbench runner exception:')
- if Ctx.request_from_socketio:
- socketio.emit('run_end', results)
- else:
- # this might overwrite a previously unfetched result
- Ctx.set_result(results)
+ # this might overwrite a previously unfetched result
+ Ctx.set_result(results)
try:
summary = NFVBenchSummarizer(results['result'], self.fluent_logger)
LOG.info(str(summary))
@@ -255,13 +223,3 @@ class WebSocketIoServer(object):
Ctx.release()
if self.fluent_logger:
self.fluent_logger.send_run_summary(True)
-
- def send_interval_stats(self, time_ms, tx_pps, rx_pps, drop_pct):
- stats = {'time_ms': time_ms, 'tx_pps': tx_pps, 'rx_pps': rx_pps, 'drop_pct': drop_pct}
- socketio.emit('run_interval_stats', stats)
-
- def send_ndr_found(self, ndr_pps):
- socketio.emit('ndr_found', {'rate_pps': ndr_pps})
-
- def send_pdr_found(self, pdr_pps):
- socketio.emit('pdr_found', {'rate_pps': pdr_pps})
diff --git a/nfvbench/stats_collector.py b/nfvbench/stats_collector.py
index 964d704..dc750db 100644
--- a/nfvbench/stats_collector.py
+++ b/nfvbench/stats_collector.py
@@ -56,9 +56,7 @@ class IntervalCollector(StatsCollector):
self.notifier = notifier
def add(self, stats):
- if self.notifier:
- current_stats = self.__compute_tx_rx_diff(stats)
- self.notifier.send_interval_stats(**current_stats)
+ pass
def reset(self):
# don't reset time!
@@ -66,52 +64,7 @@ class IntervalCollector(StatsCollector):
self.last_tx_pkts = 0
def add_ndr_pdr(self, tag, stats):
- if self.notifier:
-
- current_time = self._get_current_time_diff()
- rx_pps = self._get_rx_pps(stats['tx_pps'], stats['drop_percentage'])
-
- self.last_tx_pkts = stats['tx_pps'] / 1000 * (current_time - self.last_time)
- self.last_rx_pkts = rx_pps / 1000 * (current_time - self.last_time)
- self.last_time = current_time
-
- # 'drop_pct' key is an unfortunate name, since in iteration stats it means
- # number of the packets. More suitable would be 'drop_percentage'.
- # FDS frontend depends on this key
- current_stats = {
- '{}_pps'.format(tag): stats['tx_pps'],
- 'tx_pps': stats['tx_pps'],
- 'rx_pps': rx_pps,
- 'drop_pct': stats['drop_percentage'],
- 'time_ms': current_time
- }
-
- self.notifier.send_interval_stats(time_ms=current_stats['time_ms'],
- tx_pps=current_stats['tx_pps'],
- rx_pps=current_stats['rx_pps'],
- drop_pct=current_stats['drop_pct'])
- if tag == 'ndr':
- self.notifier.send_ndr_found(stats['tx_pps'])
- else:
- self.notifier.send_pdr_found(stats['tx_pps'])
-
- def __compute_tx_rx_diff(self, stats):
- current_time = self._get_current_time_diff()
- tx_diff = stats['overall']['tx']['total_pkts'] - self.last_tx_pkts
- tx_pps = (tx_diff * 1000) / (current_time - self.last_time)
- rx_diff = stats['overall']['rx']['total_pkts'] - self.last_rx_pkts
- rx_pps = (rx_diff * 1000) / (current_time - self.last_time)
-
- self.last_rx_pkts = stats['overall']['rx']['total_pkts']
- self.last_tx_pkts = stats['overall']['tx']['total_pkts']
- self.last_time = current_time
-
- return {
- 'tx_pps': tx_pps,
- 'rx_pps': rx_pps,
- 'drop_pct': max(0.0, (1 - (float(rx_pps) / tx_pps)) * 100),
- 'time_ms': current_time
- }
+ pass
class IterationCollector(StatsCollector):
diff --git a/nfvbench/traffic_client.py b/nfvbench/traffic_client.py
index dbb8206..75c40c1 100755
--- a/nfvbench/traffic_client.py
+++ b/nfvbench/traffic_client.py
@@ -23,7 +23,7 @@ from attrdict import AttrDict
import bitmath
from netaddr import IPNetwork
# pylint: disable=import-error
-from trex_stl_lib.api import STLError
+from trex.stl.api import STLError
# pylint: enable=import-error
from log import LOG
@@ -340,11 +340,16 @@ class GeneratorConfig(object):
else:
# interface speed is discovered/provided by the traffic generator
self.intf_speed = 0
+ self.name = gen_config.name
+ self.zmq_pub_port = gen_config.get('zmq_pub_port', 4500)
+ self.zmq_rpc_port = gen_config.get('zmq_rpc_port', 4501)
+ self.limit_memory = gen_config.get('limit_memory', 1024)
self.software_mode = gen_config.get('software_mode', False)
self.interfaces = gen_config.interfaces
if self.interfaces[0].port != 0 or self.interfaces[1].port != 1:
raise TrafficClientException('Invalid port order/id in generator_profile.interfaces')
-
+ if hasattr(gen_config, 'platform'):
+ self.platform = gen_config.platform
self.service_chain = config.service_chain
self.service_chain_count = config.service_chain_count
self.flow_count = config.flow_count
@@ -386,10 +391,11 @@ class GeneratorConfig(object):
port_index: the port for which dest macs must be set
dest_macs: a list of dest MACs indexed by chain id
"""
- if len(dest_macs) != self.config.service_chain_count:
+ if len(dest_macs) < self.config.service_chain_count:
raise TrafficClientException('Dest MAC list %s must have %d entries' %
(dest_macs, self.config.service_chain_count))
- self.devices[port_index].set_dest_macs(dest_macs)
+ # only pass the first scc dest MACs
+ self.devices[port_index].set_dest_macs(dest_macs[:self.config.service_chain_count])
LOG.info('Port %d: dst MAC %s', port_index, [str(mac) for mac in dest_macs])
def set_vtep_dest_macs(self, port_index, dest_macs):
@@ -493,8 +499,8 @@ class TrafficClient(object):
def _get_generator(self):
tool = self.tool.lower()
if tool == 'trex':
- from traffic_gen import trex
- return trex.TRex(self)
+ from traffic_gen import trex_gen
+ return trex_gen.TRex(self)
if tool == 'dummy':
from traffic_gen import dummy
return dummy.DummyTG(self)
@@ -571,8 +577,8 @@ class TrafficClient(object):
LOG.info('Starting traffic generator to ensure end-to-end connectivity')
# send 2pps on each chain and each direction
rate_pps = {'rate_pps': str(self.config.service_chain_count * 2)}
- self.gen.create_traffic('64', [rate_pps, rate_pps], bidirectional=True, latency=False)
-
+ self.gen.create_traffic('64', [rate_pps, rate_pps], bidirectional=True, latency=False,
+ e2e=True)
# ensures enough traffic is coming back
retry_count = (self.config.check_traffic_time_sec +
self.config.generic_poll_sec - 1) / self.config.generic_poll_sec
@@ -652,7 +658,12 @@ class TrafficClient(object):
self.run_config['rates'][idx] = {'rate_pps': self.__convert_rates(rate)['rate_pps']}
self.gen.clear_streamblock()
- self.gen.create_traffic(frame_size, self.run_config['rates'], bidirectional, latency=True)
+ if not self.config.vxlan:
+ self.gen.create_traffic(frame_size, self.run_config['rates'], bidirectional,
+ latency=True)
+ else:
+ self.gen.create_traffic(frame_size, self.run_config['rates'], bidirectional,
+ latency=False)
def _modify_load(self, load):
self.current_total_rate = {'rate_percent': str(load)}
diff --git a/nfvbench/traffic_gen/dummy.py b/nfvbench/traffic_gen/dummy.py
index 9beea28..120a99b 100644
--- a/nfvbench/traffic_gen/dummy.py
+++ b/nfvbench/traffic_gen/dummy.py
@@ -95,7 +95,7 @@ class DummyTG(AbstractTrafficGenerator):
ports = list(self.traffic_client.generator_config.ports)
self.port_handle = ports
- def create_traffic(self, l2frame_size, rates, bidirectional, latency=True):
+ def create_traffic(self, l2frame_size, rates, bidirectional, latency=True, e2e=False):
self.rates = [utils.to_rate_str(rate) for rate in rates]
self.l2_frame_size = l2frame_size
diff --git a/nfvbench/traffic_gen/traffic_base.py b/nfvbench/traffic_gen/traffic_base.py
index 0360591..434fdae 100644
--- a/nfvbench/traffic_gen/traffic_base.py
+++ b/nfvbench/traffic_gen/traffic_base.py
@@ -68,7 +68,7 @@ class AbstractTrafficGenerator(object):
return None
@abc.abstractmethod
- def create_traffic(self, l2frame_size, rates, bidirectional, latency=True):
+ def create_traffic(self, l2frame_size, rates, bidirectional, latency=True, e2e=False):
# Must be implemented by sub classes
return None
diff --git a/nfvbench/traffic_gen/trex.py b/nfvbench/traffic_gen/trex_gen.py
index 1f460f6..daf5fb1 100644
--- a/nfvbench/traffic_gen/trex.py
+++ b/nfvbench/traffic_gen/trex_gen.py
@@ -33,29 +33,29 @@ from traffic_utils import IMIX_L2_SIZES
from traffic_utils import IMIX_RATIOS
# pylint: disable=import-error
-from trex_stl_lib.api import bind_layers
-from trex_stl_lib.api import CTRexVmInsFixHwCs
-from trex_stl_lib.api import Dot1Q
-from trex_stl_lib.api import Ether
-from trex_stl_lib.api import FlagsField
-from trex_stl_lib.api import IP
-from trex_stl_lib.api import Packet
-from trex_stl_lib.api import STLClient
-from trex_stl_lib.api import STLError
-from trex_stl_lib.api import STLFlowLatencyStats
-from trex_stl_lib.api import STLFlowStats
-from trex_stl_lib.api import STLPktBuilder
-from trex_stl_lib.api import STLScVmRaw
-from trex_stl_lib.api import STLStream
-from trex_stl_lib.api import STLTXCont
-from trex_stl_lib.api import STLVmFixChecksumHw
-from trex_stl_lib.api import STLVmFlowVar
-from trex_stl_lib.api import STLVmFlowVarRepetableRandom
-from trex_stl_lib.api import STLVmWrFlowVar
-from trex_stl_lib.api import ThreeBytesField
-from trex_stl_lib.api import UDP
-from trex_stl_lib.api import XByteField
-from trex_stl_lib.services.trex_stl_service_arp import STLServiceARP
+from trex.common.services.trex_service_arp import ServiceARP
+from trex.stl.api import bind_layers
+from trex.stl.api import CTRexVmInsFixHwCs
+from trex.stl.api import Dot1Q
+from trex.stl.api import Ether
+from trex.stl.api import FlagsField
+from trex.stl.api import IP
+from trex.stl.api import Packet
+from trex.stl.api import STLClient
+from trex.stl.api import STLError
+from trex.stl.api import STLFlowLatencyStats
+from trex.stl.api import STLFlowStats
+from trex.stl.api import STLPktBuilder
+from trex.stl.api import STLScVmRaw
+from trex.stl.api import STLStream
+from trex.stl.api import STLTXCont
+from trex.stl.api import STLVmFixChecksumHw
+from trex.stl.api import STLVmFlowVar
+from trex.stl.api import STLVmFlowVarRepeatableRandom
+from trex.stl.api import STLVmWrFlowVar
+from trex.stl.api import ThreeBytesField
+from trex.stl.api import UDP
+from trex.stl.api import XByteField
# pylint: enable=import-error
@@ -306,14 +306,14 @@ class TRex(AbstractTrafficGenerator):
pkt_base /= IP() / UDP(**udp_args)
if stream_cfg['ip_addrs_step'] == 'random':
- src_fv = STLVmFlowVarRepetableRandom(
+ src_fv = STLVmFlowVarRepeatableRandom(
name="ip_src",
min_value=stream_cfg['ip_src_addr'],
max_value=stream_cfg['ip_src_addr_max'],
size=4,
seed=random.randint(0, 32767),
limit=stream_cfg['ip_src_count'])
- dst_fv = STLVmFlowVarRepetableRandom(
+ dst_fv = STLVmFlowVarRepeatableRandom(
name="ip_dst",
min_value=stream_cfg['ip_dst_addr'],
max_value=stream_cfg['ip_dst_addr_max'],
@@ -349,7 +349,8 @@ class TRex(AbstractTrafficGenerator):
return STLPktBuilder(pkt=pkt_base / pad, vm=STLScVmRaw(vm_param))
- def generate_streams(self, port, chain_id, stream_cfg, l2frame, latency=True):
+ def generate_streams(self, port, chain_id, stream_cfg, l2frame, latency=True,
+ e2e=False):
"""Create a list of streams corresponding to a given chain and stream config.
port: port where the streams originate (0 or 1)
@@ -357,15 +358,26 @@ class TRex(AbstractTrafficGenerator):
stream_cfg: stream configuration
l2frame: L2 frame size (including 4-byte FCS) or 'IMIX'
latency: if True also create a latency stream
+ e2e: True if performing "end to end" connectivity check
"""
streams = []
pg_id, lat_pg_id = self.get_pg_id(port, chain_id)
if l2frame == 'IMIX':
for ratio, l2_frame_size in zip(IMIX_RATIOS, IMIX_L2_SIZES):
pkt = self._create_pkt(stream_cfg, l2_frame_size)
- streams.append(STLStream(packet=pkt,
- flow_stats=STLFlowStats(pg_id=pg_id),
- mode=STLTXCont(pps=ratio)))
+ if e2e:
+ streams.append(STLStream(packet=pkt,
+ mode=STLTXCont(pps=ratio)))
+ else:
+ if stream_cfg['vxlan'] is True:
+ streams.append(STLStream(packet=pkt,
+ flow_stats=STLFlowStats(pg_id=pg_id,
+ vxlan=True),
+ mode=STLTXCont(pps=ratio)))
+ else:
+ streams.append(STLStream(packet=pkt,
+ flow_stats=STLFlowStats(pg_id=pg_id),
+ mode=STLTXCont(pps=ratio)))
if latency:
# for IMIX, the latency packets have the average IMIX packet size
@@ -374,9 +386,19 @@ class TRex(AbstractTrafficGenerator):
else:
l2frame_size = int(l2frame)
pkt = self._create_pkt(stream_cfg, l2frame_size)
- streams.append(STLStream(packet=pkt,
- flow_stats=STLFlowStats(pg_id=pg_id),
- mode=STLTXCont()))
+ if e2e:
+ streams.append(STLStream(packet=pkt,
+ mode=STLTXCont()))
+ else:
+ if stream_cfg['vxlan'] is True:
+ streams.append(STLStream(packet=pkt,
+ flow_stats=STLFlowStats(pg_id=pg_id,
+ vxlan=True),
+ mode=STLTXCont()))
+ else:
+ streams.append(STLStream(packet=pkt,
+ flow_stats=STLFlowStats(pg_id=pg_id),
+ mode=STLTXCont()))
# for the latency stream, the minimum payload is 16 bytes even in case of vlan tagging
# without vlan, the min l2 frame size is 64
# with vlan it is 68
@@ -385,6 +407,7 @@ class TRex(AbstractTrafficGenerator):
pkt = self._create_pkt(stream_cfg, 68)
if latency:
+ # TRex limitation: VXLAN skip is not supported for latency stream
streams.append(STLStream(packet=pkt,
flow_stats=STLFlowLatencyStats(pg_id=lat_pg_id),
mode=STLTXCont(pps=self.LATENCY_PPS)))
@@ -413,33 +436,17 @@ class TRex(AbstractTrafficGenerator):
LOG.info("Connecting to TRex (%s)...", server_ip)
# Connect to TRex server
- self.client = STLClient(server=server_ip)
+ self.client = STLClient(server=server_ip, sync_port=self.generator_config.zmq_rpc_port,
+ async_port=self.generator_config.zmq_pub_port)
try:
self.__connect(self.client)
+ if server_ip == '127.0.0.1':
+ config_updated = self.__check_config()
+ if config_updated or self.config.restart:
+ self.__restart()
except (TimeoutError, STLError) as e:
if server_ip == '127.0.0.1':
- try:
- self.__start_server()
- self.__connect_after_start()
- except (TimeoutError, STLError) as e:
- LOG.error('Cannot connect to TRex')
- LOG.error(traceback.format_exc())
- logpath = '/tmp/trex.log'
- if os.path.isfile(logpath):
- # Wait for TRex to finish writing error message
- last_size = 0
- for _ in xrange(self.config.generic_retry_count):
- size = os.path.getsize(logpath)
- if size == last_size:
- # probably not writing anymore
- break
- last_size = size
- time.sleep(1)
- with open(logpath, 'r') as f:
- message = f.read()
- else:
- message = e.message
- raise TrafficGeneratorException(message)
+ self.__start_local_server()
else:
raise TrafficGeneratorException(e.message)
@@ -474,10 +481,63 @@ class TRex(AbstractTrafficGenerator):
(self.port_info[0]['speed'],
self.port_info[1]['speed']))
+ def __start_local_server(self):
+ try:
+ LOG.info("Starting TRex ...")
+ self.__start_server()
+ self.__connect_after_start()
+ except (TimeoutError, STLError) as e:
+ LOG.error('Cannot connect to TRex')
+ LOG.error(traceback.format_exc())
+ logpath = '/tmp/trex.log'
+ if os.path.isfile(logpath):
+ # Wait for TRex to finish writing error message
+ last_size = 0
+ for _ in xrange(self.config.generic_retry_count):
+ size = os.path.getsize(logpath)
+ if size == last_size:
+ # probably not writing anymore
+ break
+ last_size = size
+ time.sleep(1)
+ with open(logpath, 'r') as f:
+ message = f.read()
+ else:
+ message = e.message
+ raise TrafficGeneratorException(message)
+
def __start_server(self):
server = TRexTrafficServer()
server.run_server(self.generator_config)
+ def __check_config(self):
+ server = TRexTrafficServer()
+ return server.check_config_updated(self.generator_config)
+
+ def __restart(self):
+ LOG.info("Restarting TRex ...")
+ self.__stop_server()
+ # Wait for server stopped
+ for _ in xrange(self.config.generic_retry_count):
+ time.sleep(1)
+ if not self.client.is_connected():
+ LOG.info("TRex is stopped...")
+ break
+ self.__start_local_server()
+
+ def __stop_server(self):
+ if self.generator_config.ip == '127.0.0.1':
+ ports = self.client.get_acquired_ports()
+ LOG.info('Release ports %s and stopping TRex...', ports)
+ try:
+ if ports:
+ self.client.release(ports=ports)
+ self.client.server_shutdown()
+ except STLError as e:
+ LOG.warn('Unable to stop TRex. Error: %s', e)
+ else:
+ LOG.info('Using remote TRex. Unable to stop TRex')
+
def resolve_arp(self):
"""Resolve all configured remote IP addresses.
@@ -499,19 +559,19 @@ class TRex(AbstractTrafficGenerator):
# the index in the list is the chain id
if self.config.vxlan:
arps = [
- STLServiceARP(ctx,
- src_ip=device.vtep_src_ip,
- dst_ip=device.vtep_dst_ip,
- vlan=device.vtep_vlan)
+ ServiceARP(ctx,
+ src_ip=device.vtep_src_ip,
+ dst_ip=device.vtep_dst_ip,
+ vlan=device.vtep_vlan)
for cfg in stream_configs
]
else:
arps = [
- STLServiceARP(ctx,
- src_ip=cfg['ip_src_tg_gw'],
- dst_ip=cfg['mac_discovery_gw'],
- # will be None if no vlan tagging
- vlan=cfg['vlan_tag'])
+ ServiceARP(ctx,
+ src_ip=cfg['ip_src_tg_gw'],
+ dst_ip=cfg['mac_discovery_gw'],
+ # will be None if no vlan tagging
+ vlan=cfg['vlan_tag'])
for cfg in stream_configs
]
@@ -580,7 +640,7 @@ class TRex(AbstractTrafficGenerator):
return {'result': True}
- def create_traffic(self, l2frame_size, rates, bidirectional, latency=True):
+ def create_traffic(self, l2frame_size, rates, bidirectional, latency=True, e2e=False):
"""Program all the streams in Trex server.
l2frame_size: L2 frame size or IMIX
@@ -588,6 +648,7 @@ class TRex(AbstractTrafficGenerator):
each rate is a dict like {'rate_pps': '10kpps'}
bidirectional: True if bidirectional
latency: True if latency measurement is needed
+ e2e: True if performing "end to end" connectivity check
"""
r = self.__is_rate_enough(l2frame_size, rates, bidirectional, latency)
if not r['result']:
@@ -611,15 +672,21 @@ class TRex(AbstractTrafficGenerator):
chain_id,
fwd_stream_cfg,
l2frame_size,
- latency=latency))
+ latency=latency,
+ e2e=e2e))
if len(self.rates) > 1:
streamblock[1].extend(self.generate_streams(self.port_handle[1],
chain_id,
rev_stream_cfg,
l2frame_size,
- latency=bidirectional and latency))
+ latency=bidirectional and latency,
+ e2e=e2e))
for port in self.port_handle:
+ if self.config.vxlan:
+ self.client.set_port_attr(ports=port, vxlan_fs=[4789])
+ else:
+ self.client.set_port_attr(ports=port, vxlan_fs=None)
self.client.add_streams(streamblock[port], ports=port)
LOG.info('Created %d traffic streams for port %s.', len(streamblock[port]), port)
diff --git a/nfvbench/traffic_server.py b/nfvbench/traffic_server.py
index c3d4d14..91608dd 100644
--- a/nfvbench/traffic_server.py
+++ b/nfvbench/traffic_server.py
@@ -49,26 +49,82 @@ class TRexTrafficServer(TrafficServer):
mbuf_opt = "--mbuf-factor " + str(generator_config.mbuf_factor)
else:
mbuf_opt = ""
+ # --unbind-unused-ports: for NIC that have more than 2 ports such as Intel X710
+ # this will instruct trex to unbind all ports that are unused instead of
+ # erroring out with an exception (i40e only)
subprocess.Popen(['nohup', '/bin/bash', '-c',
- './t-rex-64 -i -c {} --iom 0 --no-scapy-server --close-at-end {} '
+ './t-rex-64 -i -c {} --iom 0 --no-scapy-server '
+ '--unbind-unused-ports --close-at-end {} '
'{} {} --cfg {} &> /tmp/trex.log & disown'.format(cores, sw_mode,
vlan_opt,
mbuf_opt, cfg)],
cwd=self.trex_dir)
LOG.info('TRex server is running...')
- def __save_config(self, generator_config, filename):
- ifs = ",".join([repr(pci) for pci in generator_config.pcis])
-
- result = """# Config generated by NFVbench
- - port_limit : 2
- version : 2
- interfaces : [{ifs}]""".format(ifs=ifs)
+ def __load_config(self, filename):
+ result = {}
+ if os.path.exists(filename):
+ with open(filename, 'r') as stream:
+ try:
+ result = yaml.load(stream)
+ except yaml.YAMLError as exc:
+ print exc
+ return result
+ def __save_config(self, generator_config, filename):
+ result = self.__prepare_config(generator_config)
yaml.safe_load(result)
if os.path.exists(filename):
os.remove(filename)
with open(filename, 'w') as f:
f.write(result)
-
return filename
+
+ def __prepare_config(self, generator_config):
+ ifs = ",".join([repr(pci) for pci in generator_config.pcis])
+ result = """# Config generated by NFVbench
+ - port_limit : 2
+ version : 2
+ zmq_pub_port : {zmq_pub_port}
+ zmq_rpc_port : {zmq_rpc_port}
+ prefix : {prefix}
+ limit_memory : {limit_memory}
+ interfaces : [{ifs}]""".format(zmq_pub_port=generator_config.zmq_pub_port,
+ zmq_rpc_port=generator_config.zmq_rpc_port,
+ prefix=generator_config.name,
+ limit_memory=generator_config.limit_memory,
+ ifs=ifs)
+ if hasattr(generator_config, 'platform'):
+ if generator_config.platform.master_thread_id \
+ and generator_config.platform.latency_thread_id:
+ platform = """
+ platform :
+ master_thread_id : {master_thread_id}
+ latency_thread_id : {latency_thread_id}
+ dual_if:""".format(master_thread_id=generator_config.platform.master_thread_id,
+ latency_thread_id=generator_config.platform.latency_thread_id)
+ result += platform
+
+ for core in generator_config.platform.dual_if:
+ threads = ""
+ try:
+ threads = ",".join([repr(thread) for thread in core.threads])
+ except TypeError:
+ LOG.warn("No threads defined for socket %s", core.socket)
+ core_result = """
+ - socket : {socket}
+ threads : [{threads}]""".format(socket=core.socket, threads=threads)
+ result += core_result
+ else:
+ LOG.info("Generator profile 'platform' sub-properties are set but not filled in \
+ config file. TRex will use default values.")
+ return result
+
+ def check_config_updated(self, generator_config):
+ existing_config = self.__load_config(filename='/etc/trex_cfg.yaml')
+ new_config = yaml.safe_load(self.__prepare_config(generator_config))
+ LOG.debug("Existing config: %s", existing_config)
+ LOG.debug("New config: %s", new_config)
+ if existing_config == new_config:
+ return False
+ return True
diff --git a/nfvbenchvm/dib/build-image.sh b/nfvbenchvm/dib/build-image.sh
index d9ab20e..2291844 100755
--- a/nfvbenchvm/dib/build-image.sh
+++ b/nfvbenchvm/dib/build-image.sh
@@ -11,7 +11,7 @@ set -e
gs_url=artifacts.opnfv.org/nfvbench/images
# image version number
-__version__=0.6
+__version__=0.7
image_name=nfvbenchvm_centos-$__version__
# if image exists skip building
diff --git a/nfvbenchvm/dib/elements/nfvbenchvm/static/etc/rc.d/rc.local b/nfvbenchvm/dib/elements/nfvbenchvm/static/etc/rc.d/rc.local
index caf3142..94fbd74 100644
--- a/nfvbenchvm/dib/elements/nfvbenchvm/static/etc/rc.d/rc.local
+++ b/nfvbenchvm/dib/elements/nfvbenchvm/static/etc/rc.d/rc.local
@@ -18,9 +18,8 @@ fi
echo "Generating configurations for forwarder..."
eval $(cat $NFVBENCH_CONF)
touch /nfvbench_configured.flag
-NICS=`lspci -D | grep Ethernet | cut -d' ' -f1 | xargs`
-PCI_ADDRESS_1=`echo $NICS | awk '{ print $1 }'`
-PCI_ADDRESS_2=`echo $NICS | awk '{ print $2 }'`
+
+
CPU_CORES=`grep -c ^processor /proc/cpuinfo`
CPU_MASKS=0x`echo "obase=16; 2 ^ $CPU_CORES - 1" | bc`
WORKER_CORES=`expr $CPU_CORES - 1`
@@ -30,81 +29,114 @@ echo 1 > /sys/bus/workqueue/devices/writeback/cpumask
echo 1 > /sys/devices/virtual/workqueue/cpumask
echo 1 > /proc/irq/default_smp_affinity
for irq in `ls /proc/irq/`; do
- echo 1 > /proc/irq/$irq/smp_affinity
+ if [ -f /proc/irq/$irq/smp_affinity ]; then
+ echo 1 > /proc/irq/$irq/smp_affinity
+ fi
done
tuna -c $(seq -s, 1 1 $WORKER_CORES) --isolate
+NET_PATH=/sys/class/net
+
+get_pci_address() {
+ # device mapping for CentOS Linux 7:
+ # lspci:
+ # 00.03.0 Ethernet controller: Red Hat, Inc. Virtio network device
+ # 00.04.0 Ethernet controller: Red Hat, Inc. Virtio network device
+ # /sys/class/net:
+ # /sys/class/net/eth0 -> ../../devices/pci0000:00/0000:00:03.0/virtio0/net/eth0
+ # /sys/class/net/eth1 -> ../../devices/pci0000:00/0000:00:04.0/virtio1/net/eth1
+
+ mac=$1
+ for f in $(ls $NET_PATH/); do
+ if grep -q "$mac" $NET_PATH/$f/address; then
+ pci_addr=$(readlink $NET_PATH/$f | cut -d "/" -f5)
+ # some virtual interfaces match on MAC and do not have a PCI address
+ if [ "$pci_addr" -a "$pci_addr" != "N/A" ]; then
+ break
+ else
+ pci_addr=""
+ fi
+ fi;
+ done
+ if [ -z "$pci_addr" ]; then
+ echo "ERROR: Cannot find pci address for MAC $mac" >&2
+ logger "NFVBENCHVM ERROR: Cannot find pci address for MAC $mac"
+ return 1
+ fi
+ echo $pci_addr
+ return 0
+}
+
# Sometimes the interfaces on the loopback VM will use different drivers, e.g.
# one from vswitch which is virtio based, one is from SRIOV VF. In this case,
# we have to make sure the forwarder uses them in the right order, which is
# especially important if the VM is in a PVVP chain.
-SWAP_FLAG=0
if [ $INTF_MAC1 ] && [ $INTF_MAC2 ]; then
- NET_PATH=/sys/class/net
- EXP_INTF_1=$(for f in $(ls $NET_PATH/); do if grep -q "$INTF_MAC1" $NET_PATH/$f/address; then echo $f; break; fi; done)
- EXP_PCI_ADDRESS_1=$(ethtool -i $EXP_INTF_1 | grep "bus-info" | awk -F' ' '{ print $2 }')
- EXP_INTF_2=$(for f in $(ls $NET_PATH/); do if grep -q "$INTF_MAC2" $NET_PATH/$f/address; then echo $f; break; fi; done)
- EXP_PCI_ADDRESS_2=$(ethtool -i $EXP_INTF_2 | grep "bus-info" | awk -F' ' '{ print $2 }')
- if [ "$PCI_ADDRESS_1" == "$EXP_PCI_ADDRESS_2" ] && [ "$PCI_ADDRESS_2" == "$EXP_PCI_ADDRESS_1" ]; then
- # Interfaces are not coming in the expected order:
- # (1) Swap the traffic generator MAC in the case of testpmd;
- # (2) Swap the interface configs in the case of VPP;
- SWAP_FLAG=1
- fi
+ PCI_ADDRESS_1=$(get_pci_address $INTF_MAC1)
+ PCI_ADDRESS_2=$(get_pci_address $INTF_MAC2)
+else
+ echo "ERROR: VM MAC Addresses missing in $NFVBENCH_CONF"
+ logger "NFVBENCHVM ERROR: VM MAC Addresses missing in $NFVBENCH_CONF"
fi
-# Configure the forwarder
-if [ -z "`lsmod | grep igb_uio`" ]; then
- modprobe uio
- insmod /dpdk/igb_uio.ko
-fi
-if [ "$FORWARDER" == "testpmd" ]; then
- echo "Configuring testpmd..."
- if [ $SWAP_FLAG -eq 1 ]; then
- TEMP=$TG_MAC1; TG_MAC1=$TG_MAC2; TG_MAC2=$TEMP
+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)"
+ # Configure the forwarder
+ if [ -z "`lsmod | grep igb_uio`" ]; then
+ modprobe uio
+ insmod /dpdk/igb_uio.ko
fi
- # Binding ports to DPDK
- /dpdk/dpdk-devbind.py -b igb_uio $PCI_ADDRESS_1
- /dpdk/dpdk-devbind.py -b igb_uio $PCI_ADDRESS_2
- screen -dmSL testpmd /dpdk/testpmd \
- -c $CPU_MASKS \
- -n 4 \
- -- \
- --burst=32 \
- --txd=256 \
- --rxd=1024 \
- --eth-peer=0,$TG_MAC1 \
- --eth-peer=1,$TG_MAC2 \
- --forward-mode=mac \
- --nb-cores=$WORKER_CORES \
- --max-pkt-len=9000 \
- --cmdline-file=/dpdk/testpmd_cmd.txt
-else
- echo "Configuring vpp..."
- cp /vpp/startup.conf /etc/vpp/startup.conf
- cp /vpp/vm.conf /etc/vpp/vm.conf
+ if [ "$FORWARDER" == "testpmd" ]; then
+ echo "Configuring testpmd..."
+ # Binding ports to DPDK
+ /dpdk/dpdk-devbind.py -b igb_uio $PCI_ADDRESS_1
+ /dpdk/dpdk-devbind.py -b igb_uio $PCI_ADDRESS_2
+ screen -dmSL testpmd /dpdk/testpmd \
+ -c $CPU_MASKS \
+ -n 4 \
+ -- \
+ --burst=32 \
+ --txd=256 \
+ --rxd=1024 \
+ --eth-peer=0,$TG_MAC1 \
+ --eth-peer=1,$TG_MAC2 \
+ --forward-mode=mac \
+ --nb-cores=$WORKER_CORES \
+ --max-pkt-len=9000 \
+ --cmdline-file=/dpdk/testpmd_cmd.txt
+ echo "testpmd running in screen 'testpmd'"
+ logger "NFVBENCHVM: testpmd running in screen 'testpmd'"
+ else
+ echo "Configuring vpp..."
+ cp /vpp/startup.conf /etc/vpp/startup.conf
+ cp /vpp/vm.conf /etc/vpp/vm.conf
- sed -i "s/{{PCI_ADDRESS_1}}/$PCI_ADDRESS_1/g" /etc/vpp/startup.conf
- sed -i "s/{{PCI_ADDRESS_2}}/$PCI_ADDRESS_2/g" /etc/vpp/startup.conf
- sed -i "s/{{WORKER_CORES}}/$WORKER_CORES/g" /etc/vpp/startup.conf
- service vpp start
- sleep 10
+ sed -i "s/{{PCI_ADDRESS_1}}/$PCI_ADDRESS_1/g" /etc/vpp/startup.conf
+ sed -i "s/{{PCI_ADDRESS_2}}/$PCI_ADDRESS_2/g" /etc/vpp/startup.conf
+ sed -i "s/{{WORKER_CORES}}/$WORKER_CORES/g" /etc/vpp/startup.conf
+ service vpp start
+ sleep 10
- INTFS=`vppctl show int | grep Ethernet | xargs`
- INTF_1=`echo $INTFS | awk '{ print $1 }'`
- INTF_2=`echo $INTFS | awk '{ print $4 }'`
- if [ $SWAP_FLAG -eq 1 ]; then
- TEMP=$INTF_1; INTF_1=$INTF_2; INTF_2=$TEMP
+ INTFS=`vppctl show int | grep Ethernet | xargs`
+ INTF_1=`echo $INTFS | awk '{ print $1 }'`
+ INTF_2=`echo $INTFS | awk '{ print $4 }'`
+ sed -i "s/{{INTF_1}}/${INTF_1//\//\/}/g" /etc/vpp/vm.conf
+ sed -i "s/{{INTF_2}}/${INTF_2//\//\/}/g" /etc/vpp/vm.conf
+ sed -i "s/{{VNF_GATEWAY1_CIDR}}/${VNF_GATEWAY1_CIDR//\//\/}/g" /etc/vpp/vm.conf
+ sed -i "s/{{VNF_GATEWAY2_CIDR}}/${VNF_GATEWAY2_CIDR//\//\/}/g" /etc/vpp/vm.conf
+ sed -i "s/{{TG_MAC1}}/${TG_MAC1}/g" /etc/vpp/vm.conf
+ sed -i "s/{{TG_MAC2}}/${TG_MAC2}/g" /etc/vpp/vm.conf
+ sed -i "s/{{TG_NET1}}/${TG_NET1//\//\/}/g" /etc/vpp/vm.conf
+ sed -i "s/{{TG_NET2}}/${TG_NET2//\//\/}/g" /etc/vpp/vm.conf
+ sed -i "s/{{TG_GATEWAY1_IP}}/${TG_GATEWAY1_IP}/g" /etc/vpp/vm.conf
+ sed -i "s/{{TG_GATEWAY2_IP}}/${TG_GATEWAY2_IP}/g" /etc/vpp/vm.conf
+ service vpp restart
+ logger "NFVBENCHVM: vpp service restarted"
fi
- sed -i "s/{{INTF_1}}/${INTF_1//\//\/}/g" /etc/vpp/vm.conf
- sed -i "s/{{INTF_2}}/${INTF_2//\//\/}/g" /etc/vpp/vm.conf
- sed -i "s/{{VNF_GATEWAY1_CIDR}}/${VNF_GATEWAY1_CIDR//\//\/}/g" /etc/vpp/vm.conf
- sed -i "s/{{VNF_GATEWAY2_CIDR}}/${VNF_GATEWAY2_CIDR//\//\/}/g" /etc/vpp/vm.conf
- sed -i "s/{{TG_MAC1}}/${TG_MAC1}/g" /etc/vpp/vm.conf
- sed -i "s/{{TG_MAC2}}/${TG_MAC2}/g" /etc/vpp/vm.conf
- sed -i "s/{{TG_NET1}}/${TG_NET1//\//\/}/g" /etc/vpp/vm.conf
- sed -i "s/{{TG_NET2}}/${TG_NET2//\//\/}/g" /etc/vpp/vm.conf
- sed -i "s/{{TG_GATEWAY1_IP}}/${TG_GATEWAY1_IP}/g" /etc/vpp/vm.conf
- sed -i "s/{{TG_GATEWAY2_IP}}/${TG_GATEWAY2_IP}/g" /etc/vpp/vm.conf
- service vpp restart
+else
+ echo "ERROR: Cannot find PCI Address from MAC"
+ echo "$INTF_MAC1: $PCI_ADDRESS_1"
+ echo "$INTF_MAC2: $PCI_ADDRESS_2"
+ logger "NFVBENCHVM ERROR: Cannot find PCI Address from MAC"
fi
diff --git a/requirements.txt b/requirements.txt
index 0a67060..490864c 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -20,8 +20,4 @@ pyzmq>=15.3.0
requests>=2.13.0
tabulate>=0.7.5
flask>=0.12
-flask_socketio>=2.8.3
-backports.ssl-match-hostname==3.5.0.1 # via websocket-client
-socketIO-client==0.7.2
-websocket-client==0.40.0 # via socketio-client
fluent-logger>=0.5.3
diff --git a/test/mock_trex.py b/test/mock_trex.py
index c4ce9d7..ed6b20e 100644
--- a/test/mock_trex.py
+++ b/test/mock_trex.py
@@ -17,26 +17,29 @@ import sys
# Because trex_stl_lib may not be installed when running unit test
# nfvbench.traffic_client will try to import STLError:
-# from trex_stl_lib.api import STLError
-# will raise ImportError: No module named trex_stl_lib.api
-# trex.py will also try to import a number of trex_stl_lib classes
+# from trex.stl.api import STLError
+# will raise ImportError: No module named trex.stl.api
+# trex_gen.py will also try to import a number of trex.stl.api classes
try:
- import trex_stl_lib.api
- assert trex_stl_lib.api
+ import trex.stl.api
+ assert trex.stl.api
except ImportError:
from types import ModuleType
- # Make up a trex_stl_lib.api.STLError class
+ # Make up a trex.stl.api.STLError class
class STLDummy(Exception):
"""Dummy class."""
pass
- stl_lib_mod = ModuleType('trex_stl_lib')
- sys.modules['trex_stl_lib'] = stl_lib_mod
- api_mod = ModuleType('trex_stl_lib.api')
+ trex_lib_mod = ModuleType('trex')
+ sys.modules['trex'] = trex_lib_mod
+ stl_lib_mod = ModuleType('trex.stl')
+ trex_lib_mod.stl = stl_lib_mod
+ sys.modules['trex.stl'] = stl_lib_mod
+ api_mod = ModuleType('trex.stl.api')
stl_lib_mod.api = api_mod
- sys.modules['trex_stl_lib.api'] = api_mod
+ sys.modules['trex.stl.api'] = api_mod
api_mod.STLError = STLDummy
api_mod.STLxyz = STLDummy
api_mod.CTRexVmInsFixHwCs = STLDummy
@@ -52,7 +55,7 @@ except ImportError:
api_mod.STLTXCont = STLDummy
api_mod.STLVmFixChecksumHw = STLDummy
api_mod.STLVmFlowVar = STLDummy
- api_mod.STLVmFlowVarRepetableRandom = STLDummy
+ api_mod.STLVmFlowVarRepeatableRandom = STLDummy
api_mod.STLVmWrFlowVar = STLDummy
api_mod.UDP = STLDummy
api_mod.bind_layers = STLDummy
@@ -61,14 +64,16 @@ except ImportError:
api_mod.ThreeBytesField = STLDummy
api_mod.XByteField = STLDummy
- services_mod = ModuleType('trex_stl_lib.services')
- stl_lib_mod.services = services_mod
- sys.modules['trex_stl_lib.services'] = services_mod
-
- arp_mod = ModuleType('trex_stl_lib.services.trex_stl_service_arp')
+ common_mod = ModuleType('trex.common')
+ trex_lib_mod.common = common_mod
+ sys.modules['trex.common'] = common_mod
+ services_mod = ModuleType('trex.common.services')
+ common_mod.services = services_mod
+ sys.modules['trex.common.services'] = services_mod
+ arp_mod = ModuleType('trex.common.services.trex_service_arp')
services_mod.trex_stl_service_arp = arp_mod
- sys.modules['trex_stl_lib.services.trex_stl_service_arp'] = arp_mod
- arp_mod.STLServiceARP = STLDummy
+ sys.modules['trex.common.services.trex_service_arp'] = arp_mod
+ arp_mod.ServiceARP = STLDummy
def no_op():
"""Empty function."""
diff --git a/test/test_chains.py b/test/test_chains.py
index ebc606e..f7a2ce3 100644
--- a/test/test_chains.py
+++ b/test/test_chains.py
@@ -22,6 +22,7 @@ from mock import patch
import pytest
from nfvbench.chain_runner import ChainRunner
+from nfvbench.chaining import ChainException
from nfvbench.chaining import ChainVnfPort
from nfvbench.chaining import InstancePlacer
from nfvbench.compute import Compute
@@ -37,7 +38,7 @@ from nfvbench.specs import Specs
from nfvbench.summarizer import _annotate_chain_stats
from nfvbench.traffic_client import TrafficClient
from nfvbench.traffic_gen.traffic_base import Latency
-from nfvbench.traffic_gen.trex import TRex
+from nfvbench.traffic_gen.trex_gen import TRex
# just to get rid of the unused function warning
@@ -119,18 +120,87 @@ def _test_pvp_chain(config, cred, mock_glance, mock_neutron, mock_client):
openstack_spec = OpenStackSpec()
specs.set_openstack_spec(openstack_spec)
cred = MagicMock(spec=nfvbench.credentials.Credentials)
+ cred.is_admin = True
runner = ChainRunner(config, cred, specs, BasicFactory())
runner.close()
def test_pvp_chain_runner():
"""Test PVP chain runner."""
cred = MagicMock(spec=nfvbench.credentials.Credentials)
+ cred.is_admin = True
for shared_net in [True, False]:
for sc in [ChainType.PVP]:
for scc in [1, 2]:
config = _get_chain_config(sc, scc, shared_net)
_test_pvp_chain(config, cred)
+
+# Test not admin exception with empty value is raised
+@patch.object(Compute, 'find_image', _mock_find_image)
+@patch('nfvbench.chaining.Client')
+@patch('nfvbench.chaining.neutronclient')
+@patch('nfvbench.chaining.glanceclient')
+def _test_pvp_chain_no_admin_no_config_values(config, cred, mock_glance, mock_neutron, mock_client):
+ # instance = self.novaclient.servers.create(name=vmname,...)
+ # instance.status == 'ACTIVE'
+ mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
+ netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
+ mock_neutron.Client.return_value.create_network.return_value = {'network': netw}
+ mock_neutron.Client.return_value.list_networks.return_value = {'networks': None}
+ specs = Specs()
+ openstack_spec = OpenStackSpec()
+ specs.set_openstack_spec(openstack_spec)
+ runner = ChainRunner(config, cred, specs, BasicFactory())
+ runner.close()
+
+def test_pvp_chain_runner_no_admin_no_config_values():
+ """Test PVP chain runner."""
+ cred = MagicMock(spec=nfvbench.credentials.Credentials)
+ cred.is_admin = False
+ for shared_net in [True, False]:
+ for sc in [ChainType.PVP]:
+ for scc in [1, 2]:
+ config = _get_chain_config(sc, scc, shared_net)
+ with pytest.raises(ChainException):
+ _test_pvp_chain_no_admin_no_config_values(config, cred)
+
+# Test not admin with mandatory parameters values in config file
+@patch.object(Compute, 'find_image', _mock_find_image)
+@patch('nfvbench.chaining.Client')
+@patch('nfvbench.chaining.neutronclient')
+@patch('nfvbench.chaining.glanceclient')
+def _test_pvp_chain_no_admin_config_values(config, cred, mock_glance, mock_neutron, mock_client):
+ # instance = self.novaclient.servers.create(name=vmname,...)
+ # instance.status == 'ACTIVE'
+ mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
+ netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
+ mock_neutron.Client.return_value.create_network.return_value = {'network': netw}
+ mock_neutron.Client.return_value.list_networks.return_value = {'networks': None}
+ specs = Specs()
+ openstack_spec = OpenStackSpec()
+ specs.set_openstack_spec(openstack_spec)
+ runner = ChainRunner(config, cred, specs, BasicFactory())
+ runner.close()
+
+def test_pvp_chain_runner_no_admin_config_values():
+ """Test PVP chain runner."""
+ cred = MagicMock(spec=nfvbench.credentials.Credentials)
+ cred.is_admin = False
+ for shared_net in [True, False]:
+ for sc in [ChainType.PVP]:
+ for scc in [1, 2]:
+ config = _get_chain_config(sc, scc, shared_net)
+ config.availability_zone = "az"
+ config.hypervisor_hostname = "server"
+ # these are the 2 valid forms of vlan ranges
+ if scc == 1:
+ config.vlans = [100, 200]
+ else:
+ config.vlans = [[port * 100 + index for index in range(scc)]
+ for port in range(2)]
+ _test_pvp_chain_no_admin_config_values(config, cred)
+
+
@patch.object(Compute, 'find_image', _mock_find_image)
@patch('nfvbench.chaining.Client')
@patch('nfvbench.chaining.neutronclient')
@@ -145,6 +215,7 @@ def _test_ext_chain(config, cred, mock_glance, mock_neutron, mock_client):
openstack_spec = OpenStackSpec()
specs.set_openstack_spec(openstack_spec)
cred = MagicMock(spec=nfvbench.credentials.Credentials)
+ cred.is_admin = True
runner = ChainRunner(config, cred, specs, BasicFactory())
runner.close()
@@ -155,11 +226,15 @@ def test_ext_chain_runner():
shared/not shared net x arp/no_arp x scc 1 or 2
"""
cred = MagicMock(spec=nfvbench.credentials.Credentials)
+ cred.is_admin = True
for shared_net in [True, False]:
for no_arp in [False, True]:
for scc in [1, 2]:
config = _get_chain_config(ChainType.EXT, scc, shared_net)
config.no_arp = no_arp
+ # this time use a tuple of network names
+ config['external_networks']['left'] = ('ext-lnet00', 'ext-lnet01')
+ config['external_networks']['right'] = ('ext-rnet00', 'ext-rnet01')
if no_arp:
# If EXT and no arp, the config must provide mac addresses (1 pair per chain)
config['traffic_generator']['mac_addrs_left'] = ['00:00:00:00:00:00'] * scc
@@ -172,6 +247,9 @@ def _check_nfvbench_openstack(sc=ChainType.PVP, l2_loopback=False):
if l2_loopback:
config.l2_loopback = True
config.vlans = [[100], [200]]
+ if sc == ChainType.EXT:
+ config['external_networks']['left'] = 'ext-lnet'
+ config['external_networks']['right'] = 'ext-rnet'
factory = BasicFactory()
config_plugin = factory.get_config_plugin_class()(config)
config = config_plugin.get_config()