aboutsummaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
authorahothan <ahothan@cisco.com>2018-10-07 15:55:25 -0700
committerahothan <ahothan@cisco.com>2018-10-08 10:44:31 -0700
commit391dcf76fefb747888a3411ae3b8df7b1ad26685 (patch)
treeb823ae8a5f0e837bb285f53acb1502e0aad1bdf0 /test
parent99260f95219301bb5c0b58921e793bcad6ec4990 (diff)
2.0 beta NFVBENCH-91 Allow multi-chaining with separate edge networks
Includes complete refactoring of code Beta for NFVbench 2.0 Change-Id: I2997f0fb7722d5ac626cd11a68692ae458c7676e Signed-off-by: ahothan <ahothan@cisco.com>
Diffstat (limited to 'test')
-rw-r--r--test/mock_trex.py70
-rw-r--r--test/test_chains.py311
-rw-r--r--test/test_nfvbench.py465
3 files changed, 462 insertions, 384 deletions
diff --git a/test/mock_trex.py b/test/mock_trex.py
new file mode 100644
index 0000000..c128e9a
--- /dev/null
+++ b/test/mock_trex.py
@@ -0,0 +1,70 @@
+# Copyright 2018 Cisco Systems, Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+"""This module creates the missing Trex library classes when they are not installed."""
+
+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
+try:
+ import trex_stl_lib.api
+ assert trex_stl_lib.api
+except ImportError:
+ from types import ModuleType
+
+ # Make up a trex_stl_lib.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')
+ stl_lib_mod.api = api_mod
+ sys.modules['trex_stl_lib.api'] = api_mod
+ api_mod.STLError = STLDummy
+ api_mod.STLxyz = STLDummy
+ api_mod.CTRexVmInsFixHwCs = STLDummy
+ api_mod.Dot1Q = STLDummy
+ api_mod.Ether = STLDummy
+ api_mod.IP = STLDummy
+ api_mod.STLClient = STLDummy
+ api_mod.STLFlowLatencyStats = STLDummy
+ api_mod.STLFlowStats = STLDummy
+ api_mod.STLPktBuilder = STLDummy
+ api_mod.STLScVmRaw = STLDummy
+ api_mod.STLStream = STLDummy
+ api_mod.STLTXCont = STLDummy
+ api_mod.STLVmFixChecksumHw = STLDummy
+ api_mod.STLVmFlowVar = STLDummy
+ api_mod.STLVmFlowVarRepetableRandom = STLDummy
+ api_mod.STLVmWrFlowVar = STLDummy
+ api_mod.UDP = 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')
+ services_mod.trex_stl_service_arp = arp_mod
+ sys.modules['trex_stl_lib.services.trex_stl_service_arp'] = arp_mod
+ arp_mod.STLServiceARP = STLDummy
+
+def no_op():
+ """Empty function."""
+ pass
diff --git a/test/test_chains.py b/test/test_chains.py
new file mode 100644
index 0000000..14ed0b5
--- /dev/null
+++ b/test/test_chains.py
@@ -0,0 +1,311 @@
+#!/usr/bin/env python
+# Copyright 2016 Cisco Systems, Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+"""Test Chaining functions."""
+
+from mock_trex import no_op
+
+from mock import MagicMock
+from mock import patch
+
+from nfvbench.chain_runner import ChainRunner
+from nfvbench.compute import Compute
+import nfvbench.credentials
+from nfvbench.factory import BasicFactory
+import nfvbench.log
+from nfvbench.nfvbench import load_default_config
+from nfvbench.nfvbench import NFVBench
+from nfvbench.packet_stats import InterfaceStats
+from nfvbench.specs import ChainType
+from nfvbench.specs import OpenStackSpec
+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
+
+# just to get rid of the unused function warning
+no_op()
+
+
+def setup_module(module):
+ """Enable log."""
+ nfvbench.log.setup(mute_stdout=False)
+ nfvbench.log.set_level(debug=True)
+
+def _get_chain_config(sc=ChainType.PVP, scc=1, shared_net=True):
+ config, _ = load_default_config()
+ config.vm_image_file = 'nfvbenchvm-0.0.qcow2'
+ config.service_chain_count = scc
+ config.service_chain = sc
+ config.service_chain_shared_net = shared_net
+ config.rate = '1Mpps'
+ config['traffic_generator']['generator_profile'] = [{'name': 'dummy',
+ 'tool': 'dummy',
+ 'ip': '127.0.0.1',
+ 'intf_speed': None,
+ 'interfaces': [{'port': 0, 'pci': '0.0'},
+ {'port': 1, 'pci': '0.0'}]}]
+ config.ndr_run = False
+ config.pdr_run = False
+ config.single_run = True
+ config.generator_profile = 'dummy'
+ config.duration_sec = 2
+ config.interval_sec = 1
+ config.openrc_file = "dummy.rc"
+ return config
+
+def test_chain_runner_ext_no_openstack():
+ """Test ChainRunner EXT no openstack."""
+ config = _get_chain_config(sc=ChainType.EXT)
+ specs = Specs()
+ config.vlans = [100, 200]
+ config['traffic_generator']['mac_addrs_left'] = ['00:00:00:00:00:00']
+ config['traffic_generator']['mac_addrs_right'] = ['00:00:00:00:01:00']
+ runner = ChainRunner(config, None, specs, BasicFactory())
+ runner.close()
+
+def _mock_get_enabled_az_host_list(self, required_count=1):
+ return ['nova:c1', 'nova:c2']
+
+def _mock_find_image(self, image_name):
+ return True
+
+@patch.object(Compute, 'get_enabled_az_host_list', _mock_get_enabled_az_host_list)
+@patch.object(Compute, 'find_image', _mock_find_image)
+@patch('nfvbench.chaining.Client')
+@patch('nfvbench.chaining.neutronclient')
+@patch('nfvbench.chaining.glanceclient')
+def _test_pvp_chain(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)
+ cred = MagicMock(spec=nfvbench.credentials.Credentials)
+ runner = ChainRunner(config, cred, specs, BasicFactory())
+ runner.close()
+
+def test_pvp_chain_runner():
+ """Test PVP chain runner."""
+ cred = MagicMock(spec=nfvbench.credentials.Credentials)
+ 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)
+
+@patch.object(Compute, 'get_enabled_az_host_list', _mock_get_enabled_az_host_list)
+@patch.object(Compute, 'find_image', _mock_find_image)
+@patch('nfvbench.chaining.Client')
+@patch('nfvbench.chaining.neutronclient')
+@patch('nfvbench.chaining.glanceclient')
+def _test_ext_chain(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.list_networks.return_value = {'networks': [netw]}
+ specs = Specs()
+ openstack_spec = OpenStackSpec()
+ specs.set_openstack_spec(openstack_spec)
+ cred = MagicMock(spec=nfvbench.credentials.Credentials)
+ runner = ChainRunner(config, cred, specs, BasicFactory())
+ runner.close()
+
+def test_ext_chain_runner():
+ """Test openstack+EXT chain runner."""
+ cred = MagicMock(spec=nfvbench.credentials.Credentials)
+ 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
+ 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
+ config['traffic_generator']['mac_addrs_right'] = ['00:00:00:00:01:00'] * scc
+ _test_ext_chain(config, cred)
+
+def _check_nfvbench_openstack(sc=ChainType.PVP, l2_loopback=False):
+ for scc in range(1, 3):
+ config = _get_chain_config(sc, scc=scc, shared_net=True)
+ if l2_loopback:
+ config.l2_loopback = True
+ config.vlans = [[100], [200]]
+ factory = BasicFactory()
+ config_plugin = factory.get_config_plugin_class()(config)
+ config = config_plugin.get_config()
+ openstack_spec = config_plugin.get_openstack_spec()
+ nfvb = NFVBench(config, openstack_spec, config_plugin, factory)
+ res = nfvb.run({}, 'pytest')
+ if res['status'] != 'OK':
+ print res
+ assert res['status'] == 'OK'
+
+@patch.object(Compute, 'get_enabled_az_host_list', _mock_get_enabled_az_host_list)
+@patch.object(Compute, 'find_image', _mock_find_image)
+@patch.object(TrafficClient, 'skip_sleep', lambda x: True)
+@patch('nfvbench.chaining.Client')
+@patch('nfvbench.chaining.neutronclient')
+@patch('nfvbench.chaining.glanceclient')
+@patch('nfvbench.nfvbench.credentials')
+def test_nfvbench_run(mock_cred, mock_glance, mock_neutron, mock_client):
+ """Test NFVbench class with openstack+PVP."""
+ # 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}
+ _check_nfvbench_openstack()
+
+@patch.object(Compute, 'get_enabled_az_host_list', _mock_get_enabled_az_host_list)
+@patch.object(Compute, 'find_image', _mock_find_image)
+@patch.object(TrafficClient, 'skip_sleep', lambda x: True)
+@patch('nfvbench.chaining.Client')
+@patch('nfvbench.chaining.neutronclient')
+@patch('nfvbench.chaining.glanceclient')
+@patch('nfvbench.nfvbench.credentials')
+def test_nfvbench_ext_arp(mock_cred, mock_glance, mock_neutron, mock_client):
+ """Test NFVbench class with openstack+EXT+ARP."""
+ # 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.list_networks.return_value = {'networks': [netw]}
+ _check_nfvbench_openstack(sc=ChainType.EXT)
+
+@patch.object(Compute, 'get_enabled_az_host_list', _mock_get_enabled_az_host_list)
+@patch.object(Compute, 'find_image', _mock_find_image)
+@patch.object(TrafficClient, 'skip_sleep', lambda x: True)
+@patch('nfvbench.chaining.Client')
+@patch('nfvbench.chaining.neutronclient')
+@patch('nfvbench.chaining.glanceclient')
+@patch('nfvbench.nfvbench.credentials')
+def test_nfvbench_l2_loopback(mock_cred, mock_glance, mock_neutron, mock_client):
+ """Test NFVbench class with l2-loopback."""
+ # instance = self.novaclient.servers.create(name=vmname,...)
+ # instance.status == 'ACTIVE'
+ mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
+ _check_nfvbench_openstack(l2_loopback=True)
+
+
+# This is a reduced version of flow stats coming from Trex
+# with 2 chains and latency for a total of 8 packet groups
+# Random numbers with random losses
+CH0_P0_TX = 1234
+CH0_P1_RX = 1200
+CH0_P1_TX = 28900
+CH0_P0_RX = 28000
+LCH0_P0_TX = 167
+LCH0_P1_RX = 130
+LCH0_P1_TX = 523
+LCH0_P0_RX = 490
+CH1_P0_TX = 132344
+CH1_P1_RX = 132004
+CH1_P1_TX = 1289300
+CH1_P0_RX = 1280400
+LCH1_P0_TX = 51367
+LCH1_P1_RX = 5730
+LCH1_P1_TX = 35623
+LCH1_P0_RX = 67
+
+TREX_STATS = {
+ 'flow_stats': {
+ # chain 0 port 0 normal stream
+ 0: {'rx_pkts': {0: 0, 1: CH0_P1_RX, 'total': CH0_P1_RX},
+ 'tx_pkts': {0: CH0_P0_TX, 1: 0, 'total': CH0_P0_TX}},
+ # chain 1 port 0 normal stream
+ 1: {'rx_pkts': {0: 0, 1: CH1_P1_RX, 'total': CH1_P1_RX},
+ 'tx_pkts': {0: CH1_P0_TX, 1: 0, 'total': CH1_P0_TX}},
+ # chain 0 port 1 normal stream
+ 128: {'rx_pkts': {0: CH0_P0_RX, 1: 0, 'total': CH0_P0_RX},
+ 'tx_pkts': {0: 0, 1: CH0_P1_TX, 'total': CH0_P1_TX}},
+ # chain 1 port 1 normal stream
+ 129: {'rx_pkts': {0: CH1_P0_RX, 1: 0, 'total': CH1_P0_RX},
+ 'tx_pkts': {0: 0, 1: CH1_P1_TX, 'total': CH1_P1_TX}},
+ # chain 0 port 0 latency stream
+ 256: {'rx_pkts': {0: 0, 1: LCH0_P1_RX, 'total': LCH0_P1_RX},
+ 'tx_pkts': {0: LCH0_P0_TX, 1: 0, 'total': LCH0_P0_TX}},
+ # chain 1 port 0 latency stream
+ 257: {'rx_pkts': {0: 0, 1: LCH1_P1_RX, 'total': LCH1_P1_RX},
+ 'tx_pkts': {0: LCH1_P0_TX, 1: 0, 'total': LCH1_P0_TX}},
+ # chain 0 port 1 latency stream
+ 384: {'rx_pkts': {0: LCH0_P0_RX, 1: 0, 'total': LCH0_P0_RX},
+ 'tx_pkts': {0: 0, 1: LCH0_P1_TX, 'total': LCH0_P1_TX}},
+ # chain 1 port 1 latency stream
+ 385: {'rx_pkts': {0: LCH1_P0_RX, 1: 0, 'total': LCH1_P0_RX},
+ 'tx_pkts': {0: 0, 1: LCH1_P1_TX, 'total': LCH1_P1_TX}}}}
+
+def test_trex_streams_stats():
+ """Test TRex stats for chains 0 and 1."""
+ traffic_client = MagicMock()
+ trex = TRex(traffic_client)
+ if_stats = [InterfaceStats("p0", "dev0"), InterfaceStats("p1", "dev1")]
+ latencies = [Latency()] * 2
+ trex.get_stream_stats(TREX_STATS, if_stats, latencies, 0)
+ assert if_stats[0].tx == CH0_P0_TX + LCH0_P0_TX
+ assert if_stats[0].rx == CH0_P0_RX + LCH0_P0_RX
+ assert if_stats[1].tx == CH0_P1_TX + LCH0_P1_TX
+ assert if_stats[1].rx == CH0_P1_RX + LCH0_P1_RX
+
+ trex.get_stream_stats(TREX_STATS, if_stats, latencies, 1)
+ assert if_stats[0].tx == CH1_P0_TX + LCH1_P0_TX
+ assert if_stats[0].rx == CH1_P0_RX + LCH1_P0_RX
+ assert if_stats[1].tx == CH1_P1_TX + LCH1_P1_TX
+ assert if_stats[1].rx == CH1_P1_RX + LCH1_P1_RX
+
+
+# without total, with total and only 2 col
+CHAIN_STATS = [{0: {'packets': [2000054, 1999996, 1999996]}},
+ {0: {'packets': [2000054, 1999996, 1999996]},
+ 1: {'packets': [2000054, 2000054, 2000054]},
+ 'total': {'packets': [4000108, 4000050, 4000050]}},
+ {0: {'packets': [2000054, 2000054]}},
+ {0: {'packets': [2000054, 1999996]}},
+ # shared networks no drops, shared nets will have empty strings
+ {0: {'packets': [15000002, '', 15000002, 15000002, '', 15000002]},
+ 1: {'packets': [15000002, '', 15000002, 15000002, '', 15000002]},
+ 'total': {'packets': [30000004, 30000004, 30000004, 30000004, 30000004, 30000004]}},
+ {0: {'packets': [15000002, '', 14000002, 14000002, '', 13000002]},
+ 1: {'packets': [15000002, '', 15000002, 15000002, '', 15000002]},
+ 'total': {'packets': [30000004, 29000004, 29000004, 29000004, 29000004, 28000004]}}]
+XP_CHAIN_STATS = [{0: {'packets': [2000054, '-58 (-0.0029%)', 1999996]}},
+ {0: {'packets': [2000054, '-58 (-0.0029%)', 1999996]},
+ 1: {'packets': [2000054, '=>', 2000054]},
+ 'total': {'packets': [4000108, '-58 (-0.0014%)', 4000050]}},
+ {0: {'packets': [2000054, 2000054]}},
+ {0: {'packets': [2000054, '-58 (-0.0029%)']}},
+ # shared net, leave spaces alone
+ {0: {'packets': [15000002, '', '=>', '=>', '', 15000002]},
+ 1: {'packets': [15000002, '', '=>', '=>', '', 15000002]},
+ 'total': {'packets': [30000004, '=>', '=>', '=>', '=>', 30000004]}},
+ {0: {'packets': [15000002, '', '-1,000,000 (-6.6667%)', '=>', '',
+ '-1,000,000 (-7.1429%)']},
+ 1: {'packets': [15000002, '', '=>', '=>', '', 15000002]},
+ 'total': {'packets': [30000004, '-1,000,000 (-3.3333%)', '=>', '=>', '=>',
+ '-1,000,000 (-3.4483%)']}}]
+
+
+def test_summarizer():
+ """Test Summarizer class."""
+ for stats, exp_stats in zip(CHAIN_STATS, XP_CHAIN_STATS):
+ _annotate_chain_stats(stats)
+ assert stats == exp_stats
diff --git a/test/test_nfvbench.py b/test/test_nfvbench.py
index 4603ae1..b430436 100644
--- a/test/test_nfvbench.py
+++ b/test/test_nfvbench.py
@@ -13,259 +13,34 @@
# License for the specific language governing permissions and limitations
# under the License.
#
+from mock_trex import no_op
+
import json
import logging
-import os
import sys
+from attrdict import AttrDict
+from mock import patch
import pytest
-from attrdict import AttrDict
from nfvbench.config import config_loads
from nfvbench.credentials import Credentials
from nfvbench.fluentd import FluentLogHandler
import nfvbench.log
-from nfvbench.network import Interface
-from nfvbench.network import Network
-from nfvbench.specs import ChainType
-from nfvbench.specs import Encaps
+import nfvbench.nfvbench
+from nfvbench.traffic_client import Device
+from nfvbench.traffic_client import GeneratorConfig
+from nfvbench.traffic_client import IpBlock
+from nfvbench.traffic_client import TrafficClient
import nfvbench.traffic_gen.traffic_utils as traffic_utils
-__location__ = os.path.realpath(os.path.join(os.getcwd(),
- os.path.dirname(__file__)))
-
-
-@pytest.fixture
-def openstack_vxlan_spec():
- return AttrDict(
- {
- 'openstack': AttrDict({
- 'vswitch': "VTS",
- 'encaps': Encaps.VxLAN}),
- 'run_spec': AttrDict({
- 'use_vpp': True
- })
- }
- )
-
-
-# =========================================================================
-# PVP Chain tests
-# =========================================================================
-
-def test_chain_interface():
- iface = Interface('testname', 'vpp', tx_packets=1234, rx_packets=4321)
- assert iface.name == 'testname'
- assert iface.device == 'vpp'
- assert iface.get_packet_count('tx') == 1234
- assert iface.get_packet_count('rx') == 4321
- assert iface.get_packet_count('wrong_key') == 0
-
-
-# pylint: disable=redefined-outer-name
-@pytest.fixture(scope='session')
-def iface1():
- return Interface('iface1', 'trex', tx_packets=10000, rx_packets=1234)
-
-
-@pytest.fixture(scope='session')
-def iface2():
- return Interface('iface2', 'n9k', tx_packets=1234, rx_packets=9901)
-
-
-@pytest.fixture(scope='session')
-def iface3():
- return Interface('iface3', 'n9k', tx_packets=9900, rx_packets=1234)
+# just to get rid of the unused function warning
+no_op()
-@pytest.fixture(scope='session')
-def iface4():
- return Interface('iface4', 'vpp', tx_packets=1234, rx_packets=9801)
-
-
-@pytest.fixture(scope='session')
-def net1(iface1, iface2, iface3, iface4):
- return Network([iface1, iface2, iface3, iface4], reverse=False)
-
-
-@pytest.fixture(scope='session')
-def net2(iface1, iface2, iface3):
- return Network([iface1, iface2, iface3], reverse=True)
-
-
-def test_chain_network(net1, net2, iface1, iface2, iface3, iface4):
- assert [iface1, iface2, iface3, iface4] == net1.get_interfaces()
- assert [iface3, iface2, iface1] == net2.get_interfaces()
- net2.add_interface(iface4)
- assert [iface4, iface3, iface2, iface1] == net2.get_interfaces()
-
-
-# pylint: enable=redefined-outer-name
-
-# pylint: disable=pointless-string-statement
-"""
-def test_chain_analysis(net1, monkeypatch, openstack_vxlan_spec):
- def mock_empty(self, *args, **kwargs):
- pass
-
- monkeypatch.setattr(ServiceChain, '_setup', mock_empty)
-
- f = ServiceChain(AttrDict({'service_chain': 'DUMMY'}), [], {'tor': {}}, openstack_vxlan_spec,
- lambda x, y, z: None)
- result = f.get_analysis([net1])
- assert result[1]['packet_drop_count'] == 99
- assert result[1]['packet_drop_percentage'] == 0.99
- assert result[2]['packet_drop_count'] == 1
- assert result[2]['packet_drop_percentage'] == 0.01
- assert result[3]['packet_drop_count'] == 99
- assert result[3]['packet_drop_percentage'] == 0.99
-
- net1.reverse = True
- result = f.get_analysis([net1])
- assert result[1]['packet_drop_count'] == 0
- assert result[1]['packet_drop_percentage'] == 0.0
- assert result[2]['packet_drop_count'] == 0
- assert result[2]['packet_drop_percentage'] == 0.0
- assert result[3]['packet_drop_count'] == 0
- assert result[3]['packet_drop_percentage'] == 0.0
-
-
-@pytest.fixture
-def pvp_chain(monkeypatch, openstack_vxlan_spec):
- tor_vni1 = Interface('vni-4097', 'n9k', 50, 77)
- vsw_vni1 = Interface('vxlan_tunnel0', 'vpp', 77, 48)
- vsw_vif1 = Interface('VirtualEthernet0/0/2', 'vpp', 48, 77)
- vsw_vif2 = Interface('VirtualEthernet0/0/3', 'vpp', 77, 47)
- vsw_vni2 = Interface('vxlan_tunnel1', 'vpp', 43, 77)
- tor_vni2 = Interface('vni-4098', 'n9k', 77, 40)
-
- def mock_init(self, *args, **kwargs):
- self.vni_ports = [4097, 4098]
- self.specs = openstack_vxlan_spec
- self.clients = {
- 'vpp': AttrDict({
- 'set_interface_counters': lambda: None,
- })
- }
- self.worker = AttrDict({
- 'run': lambda: None,
- })
-
- def mock_empty(self, *args, **kwargs):
- pass
-
- def mock_get_network(self, traffic_port, vni_id, reverse=False):
- if vni_id == 0:
- return Network([tor_vni1, vsw_vni1, vsw_vif1], reverse)
- else:
- return Network([tor_vni2, vsw_vni2, vsw_vif2], reverse)
-
- def mock_get_data(self):
- return {}
-
- monkeypatch.setattr(PVPChain, '_get_network', mock_get_network)
- monkeypatch.setattr(PVPChain, '_get_data', mock_get_data)
- monkeypatch.setattr(PVPChain, '_setup', mock_empty)
- monkeypatch.setattr(VxLANWorker, '_clear_interfaces', mock_empty)
- monkeypatch.setattr(PVPChain, '_generate_traffic', mock_empty)
- monkeypatch.setattr(PVPChain, '__init__', mock_init)
- return PVPChain(None, None, {'vm': None, 'vpp': None, 'tor': None, 'traffic': None}, None)
-
-
-def test_pvp_chain_run(pvp_chain):
- result = pvp_chain.run()
- expected_result = {
- 'raw_data': {},
- 'stats': None,
- 'packet_analysis': {
- 'direction-forward': [
- OrderedDict([
- ('interface', 'vni-4097'),
- ('device', 'n9k'),
- ('packet_count', 50)
- ]),
- OrderedDict([
- ('interface', 'vxlan_tunnel0'),
- ('device', 'vpp'),
- ('packet_count', 48),
- ('packet_drop_count', 2),
- ('packet_drop_percentage', 4.0)
- ]),
- OrderedDict([
- ('interface', 'VirtualEthernet0/0/2'),
- ('device', 'vpp'),
- ('packet_count', 48),
- ('packet_drop_count', 0),
- ('packet_drop_percentage', 0.0)
- ]),
- OrderedDict([
- ('interface', 'VirtualEthernet0/0/3'),
- ('device', 'vpp'),
- ('packet_count', 47),
- ('packet_drop_count', 1),
- ('packet_drop_percentage', 2.0)
- ]),
- OrderedDict([
- ('interface', 'vxlan_tunnel1'),
- ('device', 'vpp'),
- ('packet_count', 43),
- ('packet_drop_count', 4),
- ('packet_drop_percentage', 8.0)
- ]),
- OrderedDict([
- ('interface', 'vni-4098'),
- ('device', 'n9k'),
- ('packet_count', 40),
- ('packet_drop_count', 3),
- ('packet_drop_percentage', 6.0)
- ])
- ],
- 'direction-reverse': [
- OrderedDict([
- ('interface', 'vni-4098'),
- ('device', 'n9k'),
- ('packet_count', 77)
- ]),
- OrderedDict([
- ('interface', 'vxlan_tunnel1'),
- ('device', 'vpp'),
- ('packet_count', 77),
- ('packet_drop_count', 0),
- ('packet_drop_percentage', 0.0)
- ]),
- OrderedDict([
- ('interface', 'VirtualEthernet0/0/3'),
- ('device', 'vpp'),
- ('packet_count', 77),
- ('packet_drop_count', 0),
- ('packet_drop_percentage', 0.0)
- ]),
- OrderedDict([
- ('interface', 'VirtualEthernet0/0/2'),
- ('device', 'vpp'),
- ('packet_count', 77),
- ('packet_drop_count', 0),
- ('packet_drop_percentage', 0.0)
- ]),
- OrderedDict([
- ('interface', 'vxlan_tunnel0'),
- ('device', 'vpp'),
- ('packet_count', 77),
- ('packet_drop_count', 0),
- ('packet_drop_percentage', 0.0)
- ]),
- OrderedDict([
- ('interface', 'vni-4097'),
- ('device', 'n9k'),
- ('packet_count', 77),
- ('packet_drop_count', 0),
- ('packet_drop_percentage', 0.0)
- ])
- ]
- }
- }
- assert result == expected_result
-"""
+def setup_module(module):
+ """Enable log."""
+ nfvbench.log.setup(mute_stdout=True)
# =========================================================================
# Traffic client tests
@@ -298,7 +73,6 @@ def test_parse_rate_str():
return True
else:
return False
-
return False
assert should_raise_error('101')
@@ -326,12 +100,13 @@ def test_rate_conversion():
assert traffic_utils.pps_to_bps(31.6066319896, 1518) == pytest.approx(388888)
assert traffic_utils.pps_to_bps(3225895.85831, 340.3) == pytest.approx(9298322222)
+
# pps at 10Gbps line rate for 64 byte frames
LR_64B_PPS = 14880952
LR_1518B_PPS = 812743
def assert_equivalence(reference, value, allowance_pct=1):
- '''Asserts if a value is equivalent to a reference value with given margin
+ """Assert if a value is equivalent to a reference value with given margin.
:param float reference: reference value to compare to
:param float value: value to compare to reference
@@ -340,7 +115,7 @@ def assert_equivalence(reference, value, allowance_pct=1):
1 : must be equal within 1% of the reference value
...
100: always true
- '''
+ """
if reference == 0:
assert value == 0
else:
@@ -359,38 +134,10 @@ def test_load_from_rate():
avg_frame_size=1518,
line_rate='20Gbps'))
-"""
-@pytest.fixture
-def traffic_client(monkeypatch):
-
- def mock_init(self, *args, **kwargs):
- self.run_config = {
- 'bidirectional': False,
- 'l2frame_size': '64',
- 'duration_sec': 30,
- 'rates': [{'rate_percent': '10'}, {'rate_pps': '1'}]
- }
-
- def mock_modify_load(self, load):
- self.run_config['rates'][0] = {'rate_percent': str(load)}
- self.current_load = load
-
- monkeypatch.setattr(TrafficClient, '__init__', mock_init)
- monkeypatch.setattr(TrafficClient, 'modify_load', mock_modify_load)
-
- return TrafficClient()
-"""
-
-
-# pylint: enable=pointless-string-statement
-
# =========================================================================
# Other tests
# =========================================================================
-def setup_module(module):
- nfvbench.log.setup(mute_stdout=True)
-
def test_no_credentials():
cred = Credentials('/completely/wrong/path/openrc', None, False)
if cred.rc_auth_url:
@@ -399,36 +146,6 @@ def test_no_credentials():
else:
assert True
-
-# 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
-try:
- import trex_stl_lib.api
-
- assert trex_stl_lib.api
-except ImportError:
- # Make up a trex_stl_lib.api.STLError class
- class STLError(Exception):
- pass
-
-
- from types import ModuleType
-
- stl_lib_mod = ModuleType('trex_stl_lib')
- sys.modules['trex_stl_lib'] = stl_lib_mod
- api_mod = ModuleType('trex_stl_lib.api')
- stl_lib_mod.api = api_mod
- sys.modules['trex_stl_lib.api'] = api_mod
- api_mod.STLError = STLError
-
-# pylint: disable=wrong-import-position,ungrouped-imports
-from nfvbench.traffic_client import Device
-from nfvbench.traffic_client import IpBlock
-from nfvbench.traffic_client import TrafficClient
-from nfvbench.traffic_client import TrafficGeneratorFactory
-
def test_ip_block():
ipb = IpBlock('10.0.0.0', '0.0.0.1', 256)
assert ipb.get_ip() == '10.0.0.0'
@@ -444,70 +161,34 @@ def test_ip_block():
with pytest.raises(IndexError):
ipb.get_ip(256)
-
-def check_config(configs, cc, fc, src_ip, dst_ip, step_ip):
- '''Verify that the range configs for each chain have adjacent IP ranges
- of the right size and without holes between chains
- '''
- step = Device.ip_to_int(step_ip)
+def check_stream_configs(gen_config):
+ """Verify that the range for each chain have adjacent IP ranges without holes between chains."""
+ config = gen_config.config
+ tgc = config['traffic_generator']
+ step = Device.ip_to_int(tgc['ip_addrs_step'])
cfc = 0
- sip = Device.ip_to_int(src_ip)
- dip = Device.ip_to_int(dst_ip)
- for index in range(cc):
- config = configs[index]
- assert config['ip_src_count'] == config['ip_dst_count']
- assert Device.ip_to_int(config['ip_src_addr']) == sip
- assert Device.ip_to_int(config['ip_dst_addr']) == dip
- count = config['ip_src_count']
+ sip = Device.ip_to_int(tgc['ip_addrs'][0].split('/')[0])
+ dip = Device.ip_to_int(tgc['ip_addrs'][1].split('/')[0])
+ stream_configs = gen_config.devices[0].get_stream_configs()
+ for index in range(config['service_chain_count']):
+ stream_cfg = stream_configs[index]
+ assert stream_cfg['ip_src_count'] == stream_cfg['ip_dst_count']
+ assert Device.ip_to_int(stream_cfg['ip_src_addr']) == sip
+ assert Device.ip_to_int(stream_cfg['ip_dst_addr']) == dip
+ count = stream_cfg['ip_src_count']
cfc += count
sip += count * step
dip += count * step
- assert cfc == fc
-
-
-def create_device(fc, cc, ip, gip, tggip, step_ip, mac):
- return Device(0, 0, flow_count=fc, chain_count=cc, ip=ip, gateway_ip=gip, tg_gateway_ip=tggip,
- ip_addrs_step=step_ip,
- tg_gateway_ip_addrs_step=step_ip,
- gateway_ip_addrs_step=step_ip,
- dst_mac=mac)
-
-
-def check_device_flow_config(step_ip):
- fc = 99999
- cc = 10
- ip0 = '10.0.0.0'
- ip1 = '20.0.0.0'
- tggip = '50.0.0.0'
- gip = '60.0.0.0'
- mac = ['00:11:22:33:44:55'] * cc
- dev0 = create_device(fc, cc, ip0, gip, tggip, step_ip, mac)
- dev1 = create_device(fc, cc, ip1, gip, tggip, step_ip, mac)
- dev0.set_destination(dev1)
- configs = dev0.get_stream_configs(ChainType.EXT)
- check_config(configs, cc, fc, ip0, ip1, step_ip)
+ assert cfc == int(config['flow_count'] / 2)
+def _check_device_flow_config(step_ip):
+ config = _get_dummy_tg_config('PVP', '1Mpps', scc=10, fc=99999, step_ip=step_ip)
+ gen_config = GeneratorConfig(config)
+ check_stream_configs(gen_config)
def test_device_flow_config():
- check_device_flow_config('0.0.0.1')
- check_device_flow_config('0.0.0.2')
-
-
-def test_device_ip_range():
- def ip_range_overlaps(ip0, ip1, flows):
- tggip = '50.0.0.0'
- gip = '60.0.0.0'
- mac = ['00:11:22:33:44:55'] * 10
- dev0 = create_device(flows, 10, ip0, gip, tggip, '0.0.0.1', mac)
- dev1 = create_device(flows, 10, ip1, gip, tggip, '0.0.0.1', mac)
- dev0.set_destination(dev1)
- return dev0.ip_range_overlaps()
-
- assert not ip_range_overlaps('10.0.0.0', '20.0.0.0', 10000)
- assert ip_range_overlaps('10.0.0.0', '10.0.1.0', 10000)
- assert ip_range_overlaps('10.0.0.0', '10.0.1.0', 257)
- assert ip_range_overlaps('10.0.1.0', '10.0.0.0', 257)
-
+ _check_device_flow_config('0.0.0.1')
+ _check_device_flow_config('0.0.0.2')
def test_config():
refcfg = {1: 100, 2: {21: 100, 22: 200}, 3: None}
@@ -596,7 +277,8 @@ def assert_ndr_pdr(stats, ndr, ndr_dr, pdr, pdr_dr):
assert_equivalence(pdr, stats['pdr']['rate_percent'])
assert_equivalence(pdr_dr, stats['pdr']['stats']['overall']['drop_percentage'])
-def get_dummy_tg_config(chain_type, rate):
+def _get_dummy_tg_config(chain_type, rate, scc=1, fc=10, step_ip='0.0.0.1',
+ ip0='10.0.0.0/8', ip1='20.0.0.0/8'):
return AttrDict({
'traffic_generator': {'host_name': 'nfvbench_tg',
'default_profile': 'dummy',
@@ -606,19 +288,23 @@ def get_dummy_tg_config(chain_type, rate):
'intf_speed': '10Gbps',
'interfaces': [{'port': 0, 'pci': '0.0'},
{'port': 1, 'pci': '0.0'}]}],
- 'ip_addrs_step': '0.0.0.1',
- 'ip_addrs': ['10.0.0.0/8', '20.0.0.0/8'],
+ 'ip_addrs_step': step_ip,
+ 'ip_addrs': [ip0, ip1],
'tg_gateway_ip_addrs': ['1.1.0.100', '2.2.0.100'],
- 'tg_gateway_ip_addrs_step': '0.0.0.1',
+ 'tg_gateway_ip_addrs_step': step_ip,
'gateway_ip_addrs': ['1.1.0.2', '2.2.0.2'],
- 'gateway_ip_addrs_step': '0.0.0.1',
+ 'gateway_ip_addrs_step': step_ip,
'mac_addrs_left': None,
'mac_addrs_right': None,
'udp_src_port': None,
'udp_dst_port': None},
+ 'traffic': {'profile': 'profile_64',
+ 'bidirectional': True},
+ 'traffic_profile': [{'name': 'profile_64', 'l2frame_size': ['64']}],
+ 'generator_profile': None,
'service_chain': chain_type,
- 'service_chain_count': 1,
- 'flow_count': 10,
+ 'service_chain_count': scc,
+ 'flow_count': fc,
'vlan_tagging': True,
'no_arp': False,
'duration_sec': 1,
@@ -631,23 +317,22 @@ def get_dummy_tg_config(chain_type, rate):
'l2_loopback': False
})
-def get_traffic_client():
- config = get_dummy_tg_config('PVP', 'ndr_pdr')
+def _get_traffic_client():
+ config = _get_dummy_tg_config('PVP', 'ndr_pdr')
config['ndr_run'] = True
config['pdr_run'] = True
config['generator_profile'] = 'dummy'
config['single_run'] = False
- generator_factory = TrafficGeneratorFactory(config)
- config.generator_config = generator_factory.get_generator_config(config.generator_profile)
- traffic_client = TrafficClient(config, skip_sleep=True)
+ traffic_client = TrafficClient(config)
traffic_client.start_traffic_generator()
traffic_client.set_traffic('64', True)
return traffic_client
+@patch.object(TrafficClient, 'skip_sleep', lambda x: True)
def test_ndr_at_lr():
- traffic_client = get_traffic_client()
+ """Test NDR at line rate."""
+ traffic_client = _get_traffic_client()
tg = traffic_client.gen
-
# this is a perfect sut with no loss at LR
tg.set_response_curve(lr_dr=0, ndr=100, max_actual_tx=100, max_11_tx=100)
# tx packets should be line rate for 64B and no drops...
@@ -655,16 +340,20 @@ def test_ndr_at_lr():
# NDR and PDR should be at 100%
traffic_client.ensure_end_to_end()
results = traffic_client.get_ndr_and_pdr()
-
assert_ndr_pdr(results, 200.0, 0.0, 200.0, 0.0)
+@patch.object(TrafficClient, 'skip_sleep', lambda x: True)
def test_ndr_at_50():
- traffic_client = get_traffic_client()
+ """Test NDR at 50% line rate.
+
+ This is a sut with an NDR of 50% and linear drop rate after NDR up to 20% drops at LR
+ (meaning that if you send 100% TX, you will only receive 80% RX)
+ the tg requested TX/actual TX ratio is up to 50%, after 50%
+ is linear up 80% actuak TX when requesting 100%
+ """
+ traffic_client = _get_traffic_client()
tg = traffic_client.gen
- # this is a sut with an NDR of 50% and linear drop rate after NDR up to 20% drops at LR
- # (meaning that if you send 100% TX, you will only receive 80% RX)
- # the tg requested TX/actual TX ratio is 1up to 50%, after 50%
- # is linear up 80% actuak TX when requesting 100%
+
tg.set_response_curve(lr_dr=20, ndr=50, max_actual_tx=80, max_11_tx=50)
# tx packets should be half line rate for 64B and no drops...
assert tg.get_tx_pps_dropped_pps(50) == (LR_64B_PPS / 2, 0)
@@ -674,12 +363,16 @@ def test_ndr_at_50():
results = traffic_client.get_ndr_and_pdr()
assert_ndr_pdr(results, 100.0, 0.0, 100.781, 0.09374)
+@patch.object(TrafficClient, 'skip_sleep', lambda x: True)
def test_ndr_pdr_low_cpu():
- traffic_client = get_traffic_client()
+ """Test NDR and PDR with too low cpu.
+
+ This test is for the case where the TG is underpowered and cannot send fast enough for the NDR
+ true NDR=40%, actual TX at 50% = 30%, actual measured DR is 0%
+ The ndr/pdr should bail out with a warning and a best effort measured NDR of 30%
+ """
+ traffic_client = _get_traffic_client()
tg = traffic_client.gen
- # This test is for the case where the TG is underpowered and cannot send fast enough for the NDR
- # true NDR=40%, actual TX at 50% = 30%, actual measured DR is 0%
- # The ndr/pdr should bail out with a warning and a best effort measured NDR of 30%
tg.set_response_curve(lr_dr=50, ndr=40, max_actual_tx=60, max_11_tx=0)
# tx packets should be 30% at requested half line rate for 64B and no drops...
assert tg.get_tx_pps_dropped_pps(50) == (int(LR_64B_PPS * 0.3), 0)
@@ -689,11 +382,15 @@ def test_ndr_pdr_low_cpu():
# pp = pprint.PrettyPrinter(indent=4)
# pp.pprint(results)
-import nfvbench.nfvbench
-
+@patch.object(TrafficClient, 'skip_sleep', lambda x: True)
def test_no_openstack():
- config = get_dummy_tg_config('EXT', '1000pps')
+ """Test nfvbench using main."""
+ config = _get_dummy_tg_config('EXT', '1000pps')
config.openrc_file = None
+ config.vlans = [[100], [200]]
+ config['traffic_generator']['mac_addrs_left'] = ['00:00:00:00:00:00']
+ config['traffic_generator']['mac_addrs_right'] = ['00:00:00:00:01:00']
+ del config['generator_profile']
old_argv = sys.argv
sys.argv = [old_argv[0], '-c', json.dumps(config)]
nfvbench.nfvbench.main()