diff options
8 files changed, 973 insertions, 3 deletions
diff --git a/samples/vnf_samples/nsut/2trex/tc_trex_baremetal_context.yaml b/samples/vnf_samples/nsut/2trex/tc_trex_baremetal_context.yaml new file mode 100644 index 000000000..e0ba6d96f --- /dev/null +++ b/samples/vnf_samples/nsut/2trex/tc_trex_baremetal_context.yaml @@ -0,0 +1,43 @@ +# Copyright (c) 2016-2017 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- +schema: "yardstick:task:0.1" + +scenarios: +- + type: NSPerf + traffic_profile: ../../traffic_profiles/fixed.yaml + topology: trex_tg_topology.yaml # TODO: look in relative path where the tc.yaml is found + + nodes: # This section is copied from pod.xml or resolved via Heat + trexgen__1: trafficgen_1.yardstick + trexvnf__1: vnf.yardstick + + vnf_options: + trexgen__1: + target_ip: trexvnf__1.xe0.local_ip # TODO: resolve to config vars + trexvnf__1: + target_ip: trexgen__1.xe1.local_ip # TODO: resolve to config vars + runner: + type: Duration + duration: 10 + +context: + type: Node # Heat, Node(baremetal), Node-ovs, Node-sriov + name: yardstick + #file: pod.yaml + # image: yardstick-baremetal # only important for heat context + nfvi_type: baremetal # options: baremetal, sriov, ovs-virtio TODO: what about mixed scenarios? + file: /etc/yardstick/nodes/pod.yaml diff --git a/samples/vnf_samples/nsut/2trex/trex_tg_topology.yaml b/samples/vnf_samples/nsut/2trex/trex_tg_topology.yaml new file mode 100644 index 000000000..1a9b50c84 --- /dev/null +++ b/samples/vnf_samples/nsut/2trex/trex_tg_topology.yaml @@ -0,0 +1,50 @@ +# Copyright (c) 2016-2017 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +nsd:nsd-catalog: + nsd: + - id: trex-tg-topology + name: trex-tg-topology + short-name: trex-tg-topology + description: trex-tg-topology + constituent-vnfd: + - member-vnf-index: '1' + vnfd-id-ref: trexgen__1 + VNF model: ../../vnf_descriptors/tg_trex_tpl.yaml #VNF type + - member-vnf-index: '2' + vnfd-id-ref: trexvnf__1 + VNF model: ../../vnf_descriptors/tg_trex_tpl.yaml #VNF type + + vld: + - id: private + name: trexgen__1 to trexvnf__1 link 1 + type: ELAN + vnfd-connection-point-ref: + - member-vnf-index-ref: '1' + vnfd-connection-point-ref: xe0 + vnfd-id-ref: trexgen + - member-vnf-index-ref: '2' + vnfd-connection-point-ref: xe0 + vnfd-id-ref: trexgen + + - id: public + name: trexvnf__1 to trexgen__1 link 2 + type: ELAN + vnfd-connection-point-ref: + - member-vnf-index-ref: '1' + vnfd-connection-point-ref: xe1 + vnfd-id-ref: trexgen + - member-vnf-index-ref: '2' + vnfd-connection-point-ref: xe1 + vnfd-id-ref: trexgen diff --git a/samples/vnf_samples/traffic_profiles/example.yaml b/samples/vnf_samples/traffic_profiles/example.yaml new file mode 100644 index 000000000..71371995e --- /dev/null +++ b/samples/vnf_samples/traffic_profiles/example.yaml @@ -0,0 +1,198 @@ +# Copyright (c) 2016-2017 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +schema: "nsb:traffic_profile:0.1" + +# This file is a template, it will be filled with values from tc.yaml before passing to the traffic generator + +name: rfc2544 +description: Traffic profile to run RFC2544 latency +traffic_profile: + traffic_type : rfc2544 # defines traffic behavior - constant or look for highest possible throughput + frame_rate : 100 # pc of linerate + flow_number: {{ flow.number }} #This decides how many frames should be generated. Each frame should have a random of the field + # that specifies a range (e.g. ipv4 address, port) + +private: + - ipv4_1: + outer_l2: + framesize: + 64B: {{ get(imix_small, 12) }} + 128B: {{get(imix_128B, 12) }} + 256B: {{get(imix_256B, 12) }} + 373b: {{ get(imix_373B, 12) }} + 570B: {{get(imix_570B, 12) }} + 1400B: {{get(imix_1400B, 12) }} + 1518b: {{get(imix_1500B, 12) }} + srcmac: {{src_mac}} # xe0.local_mac + dstmac: {{dst_mac}} # xe0.dst_mac + QinQ: + S-VLAN: + id: {{ get(flow.s_vlan_range, "0") }} + priority: 0 + cfi: 0 + C-VLAN: + id: {{ get(flow.c_vlan_range, "0") }} + priority: 0 + cfi: 0 + mpls: + label: {{ get(flow.mpls_label, "0") }} + + outer_l3v4: + proto: {{ get(flow.outer_proto, "udp") }}, + srcip4: {{ get(flow.outer_srcip4, "1.1.1.1") }} + dstip4: {{ get(flow.outer_dstip4, "90.90.90.90") }} + ttl: 32 + dscp: {{ get(flow.tos, 0) }} + + outer_l4: + srcport: {{ get(flow.outer_srcport, "3002") }} + dstport: {{ get(flow.outer_dstport, "3001") }} + vni: {{ get(flow.vxlan_vni, "2345") }} #optional + inner_l2: #optional + srcmac: {{ get(flow.inner_srcmac, "00:00:00:00:00:00") }} + dstmac: {{ get(flow.inner_dstmac, "00:00:00:00:00:00") }} + + inner_l3v4: #optional + proto: {{ get(flow.inner_proto, "udp") }}, + srcip4: {{ get(flow.inner_srcip4, "1.1.1.1") }} + dstip4: {{ get(flow.inner_dstip4, "90.90.90.90") }} + ttl: 32 + dscp: {{ get(flow.inner_tos, 0) }} + + inner_l3v6: + proto: {{ get(flow.inner_proto, "udp") }}, + srcip6: {{ get(flow.inner_srcip6, "2001::1") }} + dstip6: {{ get(flow.outer_dstip6, "2001::11") }} + ttl: 32 + tc: {{ get(flow.inner_tos, 0) }} + + inner_l4: + srcport: {{ get(flow.inner_srcport, "3002") }} + dstport: {{ get(flow.inner_dstport, "3001") }} + + - ipv6_2: + outer_l2: + framesize: + 64B: {{ get(imix_small, 12) }} + 128B: {{get(imix_128B, 12) }} + 256B: {{get(imix_256B, 12) }} + 373b: {{ get(imix_373B, 12) }} + 570B: {{get(imix_570B, 12) }} + 1400B: {{get(imix_1400B, 12) }} + 1518b: {{get(imix_1500B, 12) }} + srcmac: {{src_mac}} # xe0.local_mac + dstmac: {{dst_mac}} # xe0.dst_mac + QinQ: + S-VLAN: + id: {{ get(flow.s_vlan_range, "0") }} + priority: 0 + cfi: 0 + C-VLAN: + id: {{ get(flow.c_vlan_range, "0") }} + priority: 0 + cfi: 0 + mpls: + label: {{ get(flow.mpls_label, "0") }} + + outer_l3v6: + proto: {{ get(flow.outer_proto, "udp") }}, + srcip6: {{ get(flow.outer_srcip6, "2001::1") }} + dstip6: {{ get(flow.outer_dstip6, "2001::11") }} + ttl: 32 + tc: {{ get(flow.outer_tos, 0) }} + + outer_l4: + srcport: {{ get(flow.outer_srcport, "3002") }} + dstport: {{ get(flow.outer_dstport, "3001") }} + vni: {{ get(flow.vxlan_vni, "2345") }} #optional + inner_l2: #optional + srcmac: {{ get(flow.inner_srcmac, "00:00:00:00:00:00") }} + dstmac: {{ get(flow.inner_dstmac, "00:00:00:00:00:00") }} + + inner_l3v4: #optional + proto: {{ get(flow.inner_proto, "udp") }}, + srcip4: {{ get(flow.inner_srcip4, "1.1.1.1") }} + dstip4: {{ get(flow.inner_dstip4, "90.90.90.90") }} + ttl: 32 + dscp: {{ get(flow.inner_tos, 0) }} + + inner_l3v6: + proto: {{ get(flow.inner_proto, "udp") }}, + srcip6: {{ get(flow.inner_srcip6, "2001::1") }} + dstip6: {{ get(flow.outer_dstip6, "2001::11") }} + ttl: 32 + tc: {{ get(flow.inner_tos, 0) }} + + inner_l4: + srcport: {{ get(flow.inner_srcport, "3002") }} + dstport: {{ get(flow.inner_dstport, "3001") }} + +public: + - ipv4_2: + outer_l2: + framesize: + 64B: {{ get(imix_small, 12) }} + 128B: {{get(imix_128B, 12) }} + 256B: {{get(imix_256B, 12) }} + 373b: {{ get(imix_373B, 12) }} + 570B: {{get(imix_570B, 12) }} + 1400B: {{get(imix_1400B, 12) }} + 1518b: {{get(imix_1500B, 12) }} + srcmac: {{get(private.ipv4_1.dst_mac}} # xe0.local_mac + dstmac: {{get)private.ipv4_1.src_mac}} # xe0.dst_mac + QinQ: + S-VLAN: + id: {{ get(flow.s_vlan_range, "0") }} + priority: 0 + cfi: 0 + C-VLAN: + id: {{ get(flow.c_vlan_range, "0") }} + priority: 0 + cfi: 0 + mpls: + label: {{ get(flow.mpls_label, "0") }} + + outer_l3v4: + proto: {{ get(flow.outer_proto, "udp") }}, + srcip4: {{ get(private.ipv4_1.outer_l3v4.dstip4) }} + dstip4: {{ get(private.ipv4_1.outer_l3v4.srcip4) }} + ttl: 32 + dscp: {{ get(flow.tos, 0) }} + + outer_l4: + srcport: {{ get(private.ipv4_1.outer_l4.dstport) }} + dstport: {{ get(private.ipv4_1.outer_l4.srcport) }} + vni: {{ get(flow.vxlan_vni, "2345") }} #optional + inner_l2: #optional + srcmac: {{ get(private.ipv4_1.outer_l4.inner_l2.dstmac) }} + dstmac: {{ get(private.ipv4_1.outer_l4.inner_l2.srcmac) }} + + inner_l3v4: #optional + proto: {{ get(flow.inner_proto, "udp") }}, + srcip4: {{ get(private.ipv4_1.inner_l3v4.dstip4) }} + dstip4: {{ get(private.ipv4_1.inner_l3v4.srcip4) }} + ttl: 32 + dscp: {{ get(flow.inner_tos, 0) }} + + inner_l3v6: + proto: {{ get(flow.inner_proto, "udp") }}, + srcip6: {{ get(private.ipv4_1.inner_l3v6.dstip6) }} + dstip6: {{ get(private.ipv4_1.inner_l3v6.srcip6) }} + ttl: 32 + tc: {{ get(flow.inner_tos, 0) }} + + inner_l4: + srcport: {{ get(private.ipv4_1.inner_l4.dstport) }} + dstport: {{ get(private.ipv4_1.inner_l4.srcport) }} diff --git a/samples/vnf_samples/traffic_profiles/fixed.yaml b/samples/vnf_samples/traffic_profiles/fixed.yaml new file mode 100644 index 000000000..afccd6fd4 --- /dev/null +++ b/samples/vnf_samples/traffic_profiles/fixed.yaml @@ -0,0 +1,24 @@ +# Copyright (c) 2016-2017 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +schema: "nsb:traffic_profile:0.1" + +name: fixed +description: Fixed traffic profile to run UDP traffic + +traffic_profile: + traffic_type: FixedProfile + frame_rate: 100 # pps + flow_number: 10 + frame_size: 64 diff --git a/samples/vnf_samples/vnf_descriptors/tg_trex_tpl.yaml b/samples/vnf_samples/vnf_descriptors/tg_trex_tpl.yaml new file mode 100644 index 000000000..b1641836b --- /dev/null +++ b/samples/vnf_samples/vnf_descriptors/tg_trex_tpl.yaml @@ -0,0 +1,75 @@ +# Copyright (c) 2016-2017 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +vnfd:vnfd-catalog: + vnfd: + - id: TrexTrafficGen # NSPerf class mapping + name: trexgen + short-name: trexgen + description: TRex stateless traffic generator for tests + vm-flavor: + vcpu-count: '4' + memory-mb: '4096' + mgmt-interface: + vdu-id: trexgen-baremetal + user: '{{user}}' # Value filled by vnfdgen + password: '{{password}}' # Value filled by vnfdgen + ip: '{{ip}}' # Value filled by vnfdgen + connection-point: + - name: xe0 + type: VPORT + - name: xe1 + type: VPORT + vdu: + - id: trexgen-baremetal + name: trexgen-baremetal + description: TRex stateless traffic generator for tests + external-interface: + - name: xe0 + virtual-interface: + type: PCI-PASSTHROUGH + # Substitution variables MUST be quoted. Otherwise Python can misinterpet them. + vpci: '{{ interfaces.xe0.vpci }}' # Value filled by vnfdgen + local_ip: '{{ interfaces.xe0.local_ip }}' # Value filled by vnfdgen + driver: '{{ interfaces.xe0.driver}}' # Value filled by vnfdgen + dst_ip: '{{ interfaces.xe0.dst_ip }}' # Value filled by vnfdgen + local_mac: '{{ interfaces.xe0.local_mac }}' # Value filled by vnfdgen + dst_mac: '{{ interfaces.xe0.dst_mac }}' # Value filled by vnfdgen + vld_id: '{{ interfaces.xe0.vld_id }}' # Value filled by vnfdgen + bandwidth: 10 Gbps + vnfd-connection-point-ref: xe0 + - name: xe1 + virtual-interface: + type: PCI-PASSTHROUGH + vpci: '{{ interfaces.xe1.vpci }}' # Value filled by vnfdgen + local_ip: '{{ interfaces.xe1.local_ip }}' # Value filled by vnfdgen + driver: '{{ interfaces.xe1.driver}}' # Value filled by vnfdgen + dst_ip: '{{ interfaces.xe1.dst_ip }}' # Value filled by vnfdgen + local_mac: '{{ interfaces.xe1.local_mac }}' # Value filled by vnfdgen + dst_mac: '{{ interfaces.xe1.dst_mac }}' # Value filled by vnfdgen + vld_id: '{{ interfaces.xe1.vld_id }}' # Value filled by vnfdgen + bandwidth: 10 Gbps + vnfd-connection-point-ref: xe1 + + benchmark: + kpi: + - rx_throughput_fps + - tx_throughput_fps + - tx_throughput_mbps + - rx_throughput_mbps + - tx_throughput_pc_linerate + - rx_throughput_pc_linerate + - min_latency + - max_latency + - avg_latency diff --git a/tests/unit/benchmark/scenarios/networking/test_vnf_generic.py b/tests/unit/benchmark/scenarios/networking/test_vnf_generic.py index 540d7f214..40b07b1ca 100644 --- a/tests/unit/benchmark/scenarios/networking/test_vnf_generic.py +++ b/tests/unit/benchmark/scenarios/networking/test_vnf_generic.py @@ -262,9 +262,9 @@ class TestNetworkServiceTestCase(unittest.TestCase): "ipv4_1flow_Packets_vpe.yaml1" self.assertEqual({}, self.s._get_traffic_flow(self.scenario_cfg)) - def test_get_vnf_impl_invalid(self): - self.assertRaises(IncorrectConfig, self.s.get_vnf_impl, - COMPLETE_TREX_VNFD['vnfd:vnfd-catalog']['vnfd'][0]) + def test_get_vnf_imp(self): + vnfd = COMPLETE_TREX_VNFD['vnfd:vnfd-catalog']['vnfd'][0] + self.assertIsNotNone(self.s.get_vnf_impl(vnfd)) def test_load_vnf_models_invalid(self): self.context_cfg["nodes"]['trexgen__1']['VNF model'] = \ diff --git a/tests/unit/network_services/vnf_generic/vnf/test_tg_trex.py b/tests/unit/network_services/vnf_generic/vnf/test_tg_trex.py new file mode 100644 index 000000000..a7f4d8870 --- /dev/null +++ b/tests/unit/network_services/vnf_generic/vnf/test_tg_trex.py @@ -0,0 +1,302 @@ +#!/usr/bin/env python + +# Copyright (c) 2016-2017 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from __future__ import absolute_import +import unittest +import mock +import multiprocessing +from multiprocessing import Queue + +from stl.trex_stl_lib.trex_stl_client import STLClient +from yardstick.network_services.vnf_generic.vnf.tg_trex import TrexTrafficGen +from yardstick.network_services.traffic_profile.base import TrafficProfile + + +class TestTrexTrafficGen(unittest.TestCase): + VNFD = {'vnfd:vnfd-catalog': + {'vnfd': + [{'short-name': 'VpeVnf', + 'vdu': + [{'routing_table': + [{'network': '152.16.100.20', + 'netmask': '255.255.255.0', + 'gateway': '152.16.100.20', + 'if': 'xe0'}, + {'network': '152.16.40.20', + 'netmask': '255.255.255.0', + 'gateway': '152.16.40.20', + 'if': 'xe1'}], + 'description': 'VPE approximation using DPDK', + 'name': 'vpevnf-baremetal', + 'nd_route_tbl': + [{'network': '0064:ff9b:0:0:0:0:9810:6414', + 'netmask': '112', + 'gateway': '0064:ff9b:0:0:0:0:9810:6414', + 'if': 'xe0'}, + {'network': '0064:ff9b:0:0:0:0:9810:2814', + 'netmask': '112', + 'gateway': '0064:ff9b:0:0:0:0:9810:2814', + 'if': 'xe1'}], + 'id': 'vpevnf-baremetal', + 'external-interface': + [{'virtual-interface': + {'dst_mac': '00:00:00:00:00:04', + 'vpci': '0000:05:00.0', + 'local_ip': '152.16.100.19', + 'type': 'PCI-PASSTHROUGH', + 'netmask': '255.255.255.0', + 'dpdk_port_num': '0', + 'bandwidth': '10 Gbps', + 'driver': "i40e", + 'dst_ip': '152.16.100.20', + 'local_iface_name': 'xe0', + 'local_mac': '00:00:00:00:00:02'}, + 'vnfd-connection-point-ref': 'xe0', + 'name': 'xe0'}, + {'virtual-interface': + {'dst_mac': '00:00:00:00:00:03', + 'vpci': '0000:05:00.1', + 'local_ip': '152.16.40.19', + 'type': 'PCI-PASSTHROUGH', + 'driver': "i40e", + 'netmask': '255.255.255.0', + 'dpdk_port_num': '1', + 'bandwidth': '10 Gbps', + 'dst_ip': '152.16.40.20', + 'local_iface_name': 'xe1', + 'local_mac': '00:00:00:00:00:01'}, + 'vnfd-connection-point-ref': 'xe1', + 'name': 'xe1'}]}], + 'description': 'Vpe approximation using DPDK', + 'mgmt-interface': + {'vdu-id': 'vpevnf-baremetal', + 'host': '1.1.1.1', + 'password': 'r00t', + 'user': 'root', + 'ip': '1.1.1.1'}, + 'benchmark': + {'kpi': ['packets_in', 'packets_fwd', 'packets_dropped']}, + 'connection-point': [{'type': 'VPORT', 'name': 'xe0'}, + {'type': 'VPORT', 'name': 'xe1'}], + 'id': 'VpeApproxVnf', 'name': 'VPEVnfSsh'}]}} + + TRAFFIC_PROFILE = { + "schema": "isb:traffic_profile:0.1", + "name": "fixed", + "description": "Fixed traffic profile to run UDP traffic", + "traffic_profile": { + "traffic_type": "FixedTraffic", + "frame_rate": 100, # pps + "flow_number": 10, + "frame_size": 64}} + + def test___init__(self): + with mock.patch("yardstick.ssh.SSH") as ssh: + vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(0, "", "")) + ssh.return_value = ssh_mock + vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] + trex_traffic_gen = TrexTrafficGen(vnfd) + self.assertIsNotNone(trex_traffic_gen._terminated) + + def test_collect_kpi(self): + with mock.patch("yardstick.ssh.SSH") as ssh: + vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(0, "", "")) + ssh.return_value = ssh_mock + vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] + trex_traffic_gen = TrexTrafficGen(vnfd) + trex_traffic_gen._queue.put({}) + restult = trex_traffic_gen.collect_kpi() + self.assertEqual({}, restult) + + def test_listen_traffic(self): + with mock.patch("yardstick.ssh.SSH") as ssh: + vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(0, "", "")) + ssh.return_value = ssh_mock + vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] + trex_traffic_gen = TrexTrafficGen(vnfd) + self.assertEqual(None, trex_traffic_gen.listen_traffic({})) + + def test_instantiate(self): + mock_traffic_profile = mock.Mock(autospec=TrafficProfile) + mock_traffic_profile.get_traffic_definition.return_value = "64" + mock_traffic_profile.params = self.TRAFFIC_PROFILE + with mock.patch("yardstick.ssh.SSH") as ssh: + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(0, "", "")) + ssh_mock.run = \ + mock.Mock(return_value=(0, "", "")) + ssh.return_value = ssh_mock + vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] + trex_traffic_gen = TrexTrafficGen(vnfd) + self.assertEqual(0, trex_traffic_gen.instantiate({}, {})) + + def test_instantiate_error(self): + mock_traffic_profile = mock.Mock(autospec=TrafficProfile) + mock_traffic_profile.get_traffic_definition.return_value = "64" + mock_traffic_profile.params = self.TRAFFIC_PROFILE + with mock.patch("yardstick.ssh.SSH") as ssh: + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(1, "", "")) + ssh_mock.run = \ + mock.Mock(return_value=(0, "", "")) + ssh.return_value = ssh_mock + vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] + trex_traffic_gen = TrexTrafficGen(vnfd) + self.assertRaises(RuntimeError, + trex_traffic_gen.instantiate, {}, {}) + + def test__start_server(self): + with mock.patch("yardstick.ssh.SSH") as ssh: + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(0, "", "")) + ssh_mock.run = \ + mock.Mock(return_value=(0, "", "")) + ssh.return_value = ssh_mock + vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] + trex_traffic_gen = TrexTrafficGen(vnfd) + self.assertEqual(None, trex_traffic_gen._start_server()) + + def test__traffic_runner(self): + mock_traffic_profile = mock.Mock(autospec=TrafficProfile) + mock_traffic_profile.get_traffic_definition.return_value = "64" + mock_traffic_profile.execute.return_value = "64" + mock_traffic_profile.params = self.TRAFFIC_PROFILE + with mock.patch("yardstick.ssh.SSH") as ssh: + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(0, "", "")) + ssh_mock.run = \ + mock.Mock(return_value=(0, "", "")) + ssh.return_value = ssh_mock + vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] + self.sut = TrexTrafficGen(vnfd) + self.sut.connection = mock.Mock() + self.sut.connection.run = mock.Mock() + q = Queue() + client_started = multiprocessing.Value('i', 1) + self.sut._vpci_ascending = ["0000:05:00.0", "0000:05:00.1"] + self.sut._connect_client = mock.Mock(autospec=STLClient) + self.sut._connect_client.get_stats = mock.Mock(return_value="0") + self.sut._traffic_runner(mock_traffic_profile, q, client_started, + self.sut._terminated) + + def test__generate_trex_cfg(self): + with mock.patch("yardstick.ssh.SSH") as ssh: + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(0, "", "")) + ssh_mock.run = \ + mock.Mock(return_value=(0, "", "")) + ssh.return_value = ssh_mock + vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] + trex_traffic_gen = TrexTrafficGen(vnfd) + self.assertEqual(None, trex_traffic_gen._generate_trex_cfg(vnfd)) + + def test__split_mac_address_into_list(self): + with mock.patch("yardstick.ssh.SSH") as ssh: + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(0, "", "")) + ssh_mock.run = \ + mock.Mock(return_value=(0, "", "")) + ssh.return_value = ssh_mock + vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] + trex_traffic_gen = TrexTrafficGen(vnfd) + result = ['0x00', '0x00', '0x00', '0x00', '0x00', '0x01'] + self.assertEqual( + result, trex_traffic_gen._split_mac_address_into_list( + "00:00:00:00:00:01")) + + def test_run_traffic(self): + mock_traffic_profile = mock.Mock(autospec=TrafficProfile) + mock_traffic_profile.get_traffic_definition.return_value = "64" + mock_traffic_profile.params = self.TRAFFIC_PROFILE + with mock.patch("yardstick.ssh.SSH") as ssh: + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(0, "", "")) + ssh_mock.run = \ + mock.Mock(return_value=(0, "", "")) + ssh.return_value = ssh_mock + vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] + self.sut = TrexTrafficGen(vnfd) + self.sut.connection = mock.Mock() + self.sut.connection.run = mock.Mock() + self.sut._traffic_runner = mock.Mock(return_value=0) + self.sut.client_started.value = 1 + result = self.sut.run_traffic(mock_traffic_profile) + self.sut._traffic_process.terminate() + self.assertEqual(True, result) + + def test_scale(self): + with mock.patch("yardstick.ssh.SSH") as ssh: + vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(1, "", "")) + ssh.return_value = ssh_mock + vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] + flavor = "" + trex_traffic_gen = TrexTrafficGen(vnfd) + self.assertRaises(NotImplementedError, + trex_traffic_gen.scale, flavor) + + def test_setup_vnf_environment(self): + with mock.patch("yardstick.ssh.SSH") as ssh: + vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(1, "", "")) + ssh.return_value = ssh_mock + trex_traffic_gen = TrexTrafficGen(vnfd) + self.assertEqual( + None, trex_traffic_gen.setup_vnf_environment(ssh_mock)) + + def test_terminate(self): + with mock.patch("yardstick.ssh.SSH") as ssh: + vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(0, "", "")) + ssh.return_value = ssh_mock + trex_traffic_gen = TrexTrafficGen(vnfd) + self.assertEqual(None, trex_traffic_gen.terminate()) + + def test__connect_client(self): + with mock.patch("yardstick.ssh.SSH") as ssh: + vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(0, "", "")) + ssh.return_value = ssh_mock + vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] + trex_traffic_gen = TrexTrafficGen(vnfd) + client = mock.Mock(autospec=STLClient) + client.connect = mock.Mock(return_value=0) + self.assertIsNotNone(trex_traffic_gen._connect_client(client)) diff --git a/yardstick/network_services/vnf_generic/vnf/tg_trex.py b/yardstick/network_services/vnf_generic/vnf/tg_trex.py new file mode 100644 index 000000000..2731476e0 --- /dev/null +++ b/yardstick/network_services/vnf_generic/vnf/tg_trex.py @@ -0,0 +1,278 @@ +# Copyright (c) 2016-2017 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" Trex acts as traffic generation and vnf definitions based on IETS Spec """ + +from __future__ import absolute_import +from __future__ import print_function +import multiprocessing +import time +import logging +import os +import yaml + +from yardstick import ssh +from yardstick.network_services.vnf_generic.vnf.base import GenericTrafficGen +from yardstick.network_services.utils import get_nsb_option +from yardstick.network_services.utils import provision_tool +from stl.trex_stl_lib.trex_stl_client import STLClient +from stl.trex_stl_lib.trex_stl_client import LoggerApi +from stl.trex_stl_lib.trex_stl_exceptions import STLError + +LOG = logging.getLogger(__name__) +DURATION = 30 +TREX_SYNC_PORT = 4500 +TREX_ASYNC_PORT = 4501 + + +class TrexTrafficGen(GenericTrafficGen): + """ + This class handles mapping traffic profile and generating + traffic for given testcase + """ + + def __init__(self, vnfd): + super(TrexTrafficGen, self).__init__(vnfd) + self._result = {} + self._queue = multiprocessing.Queue() + self._terminated = multiprocessing.Value('i', 0) + self._traffic_process = None + self._vpci_ascending = None + self.client = None + self.my_ports = None + self.client_started = multiprocessing.Value('i', 0) + + mgmt_interface = self.vnfd["mgmt-interface"] + ssh_port = mgmt_interface.get("ssh_port", ssh.DEFAULT_PORT) + self.connection = ssh.SSH(mgmt_interface["user"], + mgmt_interface["ip"], + password=mgmt_interface["password"], + port=ssh_port) + self.connection.wait() + + @classmethod + def _split_mac_address_into_list(cls, mac): + octets = mac.split(':') + for i, elem in enumerate(octets): + octets[i] = "0x" + str(elem) + return octets + + def _generate_trex_cfg(self, vnfd): + """ + + :param vnfd: vnfd.yaml + :return: trex_cfg.yaml file + """ + trex_cfg = dict( + port_limit=0, + version='2', + interfaces=[], + port_info=list(dict( + )) + ) + trex_cfg["port_limit"] = len(vnfd["vdu"][0]["external-interface"]) + trex_cfg["version"] = '2' + + cfg_file = [] + vpci = [] + port = {} + + for interface in range(len(vnfd["vdu"][0]["external-interface"])): + ext_intrf = vnfd["vdu"][0]["external-interface"] + virtual_interface = ext_intrf[interface]["virtual-interface"] + vpci.append(virtual_interface["vpci"]) + + port["src_mac"] = self._split_mac_address_into_list( + virtual_interface["local_mac"]) + port["dest_mac"] = self._split_mac_address_into_list( + virtual_interface["dst_mac"]) + + trex_cfg["port_info"].append(port.copy()) + + trex_cfg["interfaces"] = vpci + cfg_file.append(trex_cfg) + + with open('/tmp/trex_cfg.yaml', 'w') as outfile: + outfile.write(yaml.safe_dump(cfg_file, default_flow_style=False)) + self.connection.put('/tmp/trex_cfg.yaml', '/etc') + + self._vpci_ascending = sorted(vpci) + + @classmethod + def __setup_hugepages(cls, connection): + hugepages = \ + connection.execute( + "awk '/Hugepagesize/ { print $2$3 }' < /proc/meminfo")[1] + hugepages = hugepages.rstrip() + + memory_path = \ + '/sys/kernel/mm/hugepages/hugepages-%s/nr_hugepages' % hugepages + connection.execute("awk -F: '{ print $1 }' < %s" % memory_path) + + pages = 16384 if hugepages.rstrip() == "2048kB" else 16 + connection.execute("echo %s > %s" % (pages, memory_path)) + + def setup_vnf_environment(self, connection): + ''' setup dpdk environment needed for vnf to run ''' + + self.__setup_hugepages(connection) + connection.execute("modprobe uio && modprobe igb_uio") + + exit_status = connection.execute("lsmod | grep -i igb_uio")[0] + if exit_status == 0: + return + + dpdk = os.path.join(self.bin_path, "dpdk-16.07") + dpdk_setup = \ + provision_tool(self.connection, + os.path.join(self.bin_path, "nsb_setup.sh")) + status = connection.execute("ls {} >/dev/null 2>&1".format(dpdk))[0] + if status: + connection.execute("bash %s dpdk >/dev/null 2>&1" % dpdk_setup) + + def scale(self, flavor=""): + ''' scale vnfbased on flavor input ''' + super(TrexTrafficGen, self).scale(flavor) + + def instantiate(self, scenario_cfg, context_cfg): + self._generate_trex_cfg(self.vnfd) + self.setup_vnf_environment(self.connection) + + trex = os.path.join(self.bin_path, "trex") + err = \ + self.connection.execute("ls {} >/dev/null 2>&1".format(trex))[0] + if err != 0: + LOG.info("Copying trex to destination...") + self.connection.put("/root/.bash_profile", "/root/.bash_profile") + self.connection.put(trex, trex, True) + ko_src = os.path.join(trex, "scripts/ko/src/") + self.connection.execute("cd %s && make && make install" % ko_src) + + LOG.info("Starting TRex server...") + _tg_process = \ + multiprocessing.Process(target=self._start_server) + _tg_process.start() + while True: + if not _tg_process.is_alive(): + raise RuntimeError("Traffic Generator process died.") + LOG.info("Waiting for TG Server to start.. ") + time.sleep(1) + status = \ + self.connection.execute("lsof -i:%s" % TREX_SYNC_PORT)[0] + if status == 0: + LOG.info("TG server is up and running.") + return _tg_process.exitcode + + def listen_traffic(self, traffic_profile): + pass + + def _get_logical_if_name(self, vpci): + ext_intf = self.vnfd["vdu"][0]["external-interface"] + for interface in range(len(self.vnfd["vdu"][0]["external-interface"])): + virtual_intf = ext_intf[interface]["virtual-interface"] + if virtual_intf["vpci"] == vpci: + return ext_intf[interface]["name"] + + def run_traffic(self, traffic_profile): + self._traffic_process = \ + multiprocessing.Process(target=self._traffic_runner, + args=(traffic_profile, self._queue, + self.client_started, + self._terminated)) + self._traffic_process.start() + # Wait for traffic process to start + while self.client_started.value == 0: + time.sleep(1) + + return self._traffic_process.is_alive() + + def _start_server(self): + mgmt_interface = self.vnfd["mgmt-interface"] + ssh_port = mgmt_interface.get("ssh_port", ssh.DEFAULT_PORT) + _server = ssh.SSH(mgmt_interface["user"], mgmt_interface["ip"], + password=mgmt_interface["password"], + port=ssh_port) + _server.wait() + + _server.execute("fuser -n tcp %s %s -k > /dev/null 2>&1" % + (TREX_SYNC_PORT, TREX_ASYNC_PORT)) + + trex_path = os.path.join(self.bin_path, "trex/scripts") + path = get_nsb_option("trex_path", trex_path) + trex_cmd = "cd " + path + "; sudo ./t-rex-64 -i > /dev/null 2>&1" + + _server.execute(trex_cmd) + + def _connect_client(self, client=None): + if client is None: + client = STLClient(username=self.vnfd["mgmt-interface"]["user"], + server=self.vnfd["mgmt-interface"]["ip"], + verbose_level=LoggerApi.VERBOSE_QUIET) + # try to connect with 5s intervals, 30s max + for idx in range(6): + try: + client.connect() + break + except STLError: + LOG.info("Unable to connect to Trex Server.. Attempt %s", idx) + time.sleep(5) + return client + + def _traffic_runner(self, traffic_profile, queue, + client_started, terminated): + LOG.info("Starting TRex client...") + + self.my_ports = [0, 1] + self.client = self._connect_client() + self.client.reset(ports=self.my_ports) + + self.client.remove_all_streams(self.my_ports) # remove all streams + + while not terminated.value: + traffic_profile.execute(self) + client_started.value = 1 + last_res = self.client.get_stats(self.my_ports) + if not isinstance(last_res, dict): # added for mock unit test + terminated.value = 1 + last_res = {} + + samples = {} + for vpci_idx in range(len(self._vpci_ascending)): + name = \ + self._get_logical_if_name(self._vpci_ascending[vpci_idx]) + # fixme: VNFDs KPIs values needs to be mapped to TRex structure + xe_value = last_res.get(vpci_idx, {}) + samples[name] = \ + {"rx_throughput_fps": float(xe_value.get("rx_pps", 0.0)), + "tx_throughput_fps": float(xe_value.get("tx_pps", 0.0)), + "rx_throughput_mbps": float(xe_value.get("rx_bps", 0.0)), + "tx_throughput_mbps": float(xe_value.get("tx_bps", 0.0)), + "in_packets": xe_value.get("ipackets", 0), + "out_packets": xe_value.get("opackets", 0)} + queue.put(samples) + + self.client.disconnect() + terminated.value = 0 + + def collect_kpi(self): + if not self._queue.empty(): + self._result.update(self._queue.get()) + LOG.debug("trex collect Kpis %s", self._result) + return self._result + + def terminate(self): + self.connection.execute("fuser -n tcp %s %s -k > /dev/null 2>&1" % + (TREX_SYNC_PORT, TREX_ASYNC_PORT)) + self.traffic_finished = True + if self._traffic_process: + self._traffic_process.terminate() |