Adding trex trafficgen example. 29/26629/4
authorDeepak S <deepak.s@linux.intel.com>
Fri, 30 Dec 2016 17:24:52 +0000 (09:24 -0800)
committerDeepak S <deepak.s@linux.intel.com>
Thu, 19 Jan 2017 02:59:35 +0000 (08:29 +0530)
This patch uses trex trafficgen example to define dynamic traffic profiles
and how it can be mapped to real world traffic.

JIRA: YARDSTICK-492
Change-Id: Ica24957ebf43315a8d81adabd4745c27d3c7c36a
Signed-off-by: Deepak S <deepak.s@linux.intel.com>
samples/vnf_samples/nsut/2trex/tc_trex_baremetal_context.yaml [new file with mode: 0644]
samples/vnf_samples/nsut/2trex/trex_tg_topology.yaml [new file with mode: 0644]
samples/vnf_samples/traffic_profiles/example.yaml [new file with mode: 0644]
samples/vnf_samples/traffic_profiles/fixed.yaml [new file with mode: 0644]
samples/vnf_samples/vnf_descriptors/tg_trex_tpl.yaml [new file with mode: 0644]
tests/unit/benchmark/scenarios/networking/test_vnf_generic.py
tests/unit/network_services/vnf_generic/vnf/test_tg_trex.py [new file with mode: 0644]
yardstick/network_services/vnf_generic/vnf/tg_trex.py [new file with mode: 0644]

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 (file)
index 0000000..e0ba6d9
--- /dev/null
@@ -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 (file)
index 0000000..1a9b50c
--- /dev/null
@@ -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 (file)
index 0000000..7137199
--- /dev/null
@@ -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 (file)
index 0000000..afccd6f
--- /dev/null
@@ -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 (file)
index 0000000..b164183
--- /dev/null
@@ -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
index 540d7f2..40b07b1 100644 (file)
@@ -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 (file)
index 0000000..a7f4d88
--- /dev/null
@@ -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 (file)
index 0000000..2731476
--- /dev/null
@@ -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()