Merge "Support bind driver for QAT HW cards"
authorVolodymyr Mytnyk <volodymyrx.mytnyk@intel.com>
Mon, 1 Apr 2019 09:22:50 +0000 (09:22 +0000)
committerGerrit Code Review <gerrit@opnfv.org>
Mon, 1 Apr 2019 09:22:50 +0000 (09:22 +0000)
36 files changed:
dashboard/RFC2544_2Port.json
dashboard/RFC2544_2Port_Multiframesize.json
docs/testing/user/userguide/14-nsb-operation.rst
docs/testing/user/userguide/nsb/nsb-list-of-tcs.rst
docs/testing/user/userguide/nsb/tc_vpp_baremetal_crypto_ipsec.rst [new file with mode: 0644]
etc/yardstick/nodes/standalone/sipp_baremetal_pod.yaml [new file with mode: 0644]
etc/yardstick/nodes/standalone/sipp_pod.yaml [new file with mode: 0644]
samples/vnf_samples/nsut/cmts/k8s_vcmts_topology.yaml [new file with mode: 0755]
samples/vnf_samples/nsut/cmts/tc_vcmts_k8s_pktgen.yaml [new file with mode: 0755]
samples/vnf_samples/nsut/vims/tc_vims_baremetal_sipp.yaml [new file with mode: 0644]
samples/vnf_samples/nsut/vims/tc_vims_heat_sipp.yaml [new file with mode: 0644]
samples/vnf_samples/nsut/vims/vims-topology.yaml [new file with mode: 0644]
samples/vnf_samples/traffic_profiles/ipv4_throughput_latency_vpp.yaml
samples/vnf_samples/vnf_descriptors/tg_sipp_vnfd.yaml [new file with mode: 0644]
samples/vnf_samples/vnf_descriptors/tg_vcmts_tpl.yaml [new file with mode: 0755]
samples/vnf_samples/vnf_descriptors/vims_hss_vnfd.yaml [new file with mode: 0644]
samples/vnf_samples/vnf_descriptors/vims_pcscf_vnfd.yaml [new file with mode: 0644]
samples/vnf_samples/vnf_descriptors/vnf_vcmts_tpl.yaml [new file with mode: 0755]
tools/run_tests.sh
yardstick/network_services/helpers/vpp_helpers/__init__.py [new file with mode: 0644]
yardstick/network_services/helpers/vpp_helpers/abstract_search_algorithm.py [new file with mode: 0644]
yardstick/network_services/helpers/vpp_helpers/multiple_loss_ratio_search.py [new file with mode: 0644]
yardstick/network_services/helpers/vpp_helpers/ndr_pdr_result.py [new file with mode: 0644]
yardstick/network_services/helpers/vpp_helpers/receive_rate_interval.py [new file with mode: 0644]
yardstick/network_services/helpers/vpp_helpers/receive_rate_measurement.py [new file with mode: 0644]
yardstick/network_services/traffic_profile/vpp_rfc2544.py
yardstick/network_services/vnf_generic/vnf/tg_vcmts_pktgen.py [new file with mode: 0755]
yardstick/network_services/vnf_generic/vnf/vcmts_vnf.py [new file with mode: 0755]
yardstick/tests/unit/network_services/helpers/vpp_helpers/__init__.py [new file with mode: 0644]
yardstick/tests/unit/network_services/helpers/vpp_helpers/test_multiple_loss_ratio_search.py [new file with mode: 0644]
yardstick/tests/unit/network_services/helpers/vpp_helpers/test_ndr_pdr_result.py [new file with mode: 0644]
yardstick/tests/unit/network_services/helpers/vpp_helpers/test_receive_rate_interval.py [new file with mode: 0644]
yardstick/tests/unit/network_services/helpers/vpp_helpers/test_receive_rate_measurement.py [new file with mode: 0644]
yardstick/tests/unit/network_services/traffic_profile/test_vpp_rfc2544.py
yardstick/tests/unit/network_services/vnf_generic/vnf/test_tg_vcmts_pktgen.py [new file with mode: 0755]
yardstick/tests/unit/network_services/vnf_generic/vnf/test_vcmts_vnf.py [new file with mode: 0755]

index e6f3265..de9448c 100644 (file)
@@ -1,14 +1,50 @@
 {
+  "__inputs": [
+    {
+      "name": "DS_YARDSTICK",
+      "label": "yardstick",
+      "description": "",
+      "type": "datasource",
+      "pluginId": "influxdb",
+      "pluginName": "InfluxDB"
+    }
+  ],
+  "__requires": [
+    {
+      "type": "grafana",
+      "id": "grafana",
+      "name": "Grafana",
+      "version": "4.4.3"
+    },
+    {
+      "type": "panel",
+      "id": "graph",
+      "name": "Graph",
+      "version": ""
+    },
+    {
+      "type": "datasource",
+      "id": "influxdb",
+      "name": "InfluxDB",
+      "version": "1.0.0"
+    },
+    {
+      "type": "panel",
+      "id": "table",
+      "name": "Table",
+      "version": ""
+    }
+  ],
   "annotations": {
     "list": [
       {
-        "datasource": "yardstick",
+        "datasource": "${DS_YARDSTICK}",
         "enable": true,
         "hide": false,
         "iconColor": "rgb(248, 255, 0)",
         "limit": 100,
         "name": "status",
-        "query": "SELECT tg__0.collect_stats.Status FROM \"tc_heat_rfc2544_ipv4_1rule_1flow_trex\" WHERE \"tg__0.collect_stats.Status\"='Success' AND task_id='$task_id'",
+        "query": "SELECT tg__0.collect_stats.Status FROM $test_name WHERE \"tg__0.collect_stats.Status\"='Success' AND task_id='$task_id'",
         "showIn": 0,
         "titleColumn": "Status",
         "type": "alert"
@@ -19,7 +55,7 @@
   "gnetId": null,
   "graphTooltip": 0,
   "hideControls": false,
-  "id": 6,
+  "id": null,
   "links": [],
   "refresh": false,
   "rows": [
@@ -32,7 +68,7 @@
           "bars": false,
           "dashLength": 10,
           "dashes": false,
-          "datasource": "yardstick",
+          "datasource": "${DS_YARDSTICK}",
           "fill": 0,
           "hideTimeOverride": true,
           "id": 3,
               "measurement": "/^$test_name$/",
               "orderByTime": "ASC",
               "policy": "default",
-              "query": "SELECT \"tg__0.collect_stats.xe0.InBytes\", \"tg__0.collect_stats.xe0.OutBytes\", \"tg__0.collect_stats.xe1.InBytes\", \"tg__0.collect_stats.xe1.OutBytes\" FROM /^$test_name$/ WHERE \"task_id\" =~ /^$task_id$/ AND \"tg__0.collect_stats.PktSize\" =~ /^$framesize$/ AND $timeFilter",
-              "rawQuery": false,
+              "query": "SELECT \"tg__0.collect_stats.xe0.InBytes\", \"tg__0.collect_stats.xe0.OutBytes\", \"tg__0.collect_stats.xe1.InBytes\", \"tg__0.collect_stats.xe1.OutBytes\" FROM /^$test_name$/ WHERE \"task_id\" =~ /^$task_id$/ AND $timeFilter",
+              "rawQuery": true,
               "refId": "A",
               "resultFormat": "time_series",
               "select": [
           "bars": false,
           "dashLength": 10,
           "dashes": false,
-          "datasource": "yardstick",
+          "datasource": "${DS_YARDSTICK}",
           "fill": 1,
           "id": 1,
           "legend": {
               "alias": "$col",
               "dsType": "influxdb",
               "groupBy": [],
-              "measurement": "tc_heat_rfc2544_ipv4_1rule_1flow_trex",
+              "measurement": "/^$test_name$/",
               "orderByTime": "ASC",
               "policy": "default",
               "refId": "A",
           "bars": false,
           "dashLength": 10,
           "dashes": false,
-          "datasource": null,
+          "datasource": "${DS_YARDSTICK}",
           "fill": 1,
           "id": 5,
           "legend": {
     "list": [
       {
         "allValue": null,
-        "current": {
-          "text": "tc_heat_rfc2544_ipv4_1rule_1flow_trex",
-          "value": "tc_heat_rfc2544_ipv4_1rule_1flow_trex"
-        },
-        "datasource": "yardstick",
+        "current": {},
+        "datasource": "${DS_YARDSTICK}",
         "hide": 0,
         "includeAll": false,
         "label": "test_name",
       },
       {
         "allValue": null,
-        "current": {
-          "text": "004b5387-74b3-4cf0-9597-5198db9e2731",
-          "value": "004b5387-74b3-4cf0-9597-5198db9e2731"
-        },
-        "datasource": "yardstick",
+        "current": {},
+        "datasource": "${DS_YARDSTICK}",
         "hide": 0,
         "includeAll": false,
         "label": "task_id",
         "multi": false,
         "name": "task_id",
         "options": [],
-        "query": "SHOW TAG VALUES FROM $test_name WITH KEY = \"task_id\" ",
+        "query": "SHOW TAG VALUES FROM $test_name WITH KEY = \"task_id\"  ",
         "refresh": 2,
         "regex": "",
         "sort": 0,
     ]
   },
   "time": {
-    "from": "2019-03-05T12:44:02.829Z",
-    "to": "2019-03-05T13:32:27.585Z"
+    "from": "2019-03-06T13:54:13.610Z",
+    "to": "2019-03-06T13:56:59.693Z"
   },
   "timepicker": {
     "refresh_intervals": [
   },
   "timezone": "",
   "title": "RFC2544",
-  "version": 2
+  "version": 4
 }
index f08cf3d..2d8e952 100644 (file)
@@ -1,14 +1,44 @@
 {
+  "__inputs": [
+    {
+      "name": "DS_YARDSTICK",
+      "label": "yardstick",
+      "description": "",
+      "type": "datasource",
+      "pluginId": "influxdb",
+      "pluginName": "InfluxDB"
+    }
+  ],
+  "__requires": [
+    {
+      "type": "grafana",
+      "id": "grafana",
+      "name": "Grafana",
+      "version": "4.4.3"
+    },
+    {
+      "type": "panel",
+      "id": "graph",
+      "name": "Graph",
+      "version": ""
+    },
+    {
+      "type": "datasource",
+      "id": "influxdb",
+      "name": "InfluxDB",
+      "version": "1.0.0"
+    }
+  ],
   "annotations": {
     "list": [
       {
-        "datasource": "yardstick",
+        "datasource": "${DS_YARDSTICK}",
         "enable": true,
         "hide": false,
         "iconColor": "rgb(248, 255, 0)",
         "limit": 100,
         "name": "status",
-        "query": "SELECT tg__0.collect_stats.Status FROM \"tc_heat_rfc2544_ipv4_1rule_1flow_trex\" WHERE \"tg__0.collect_stats.Status\"='Success' AND task_id='$task_id'",
+        "query": "SELECT tg__0.collect_stats.Status FROM $test_name WHERE \"tg__0.collect_stats.Status\"='Success' AND task_id='$task_id'",
         "showIn": 0,
         "titleColumn": "Status",
         "type": "alert"
@@ -19,7 +49,7 @@
   "gnetId": null,
   "graphTooltip": 0,
   "hideControls": false,
-  "id": 2,
+  "id": null,
   "links": [],
   "refresh": false,
   "rows": [
@@ -32,7 +62,7 @@
           "bars": false,
           "dashLength": 10,
           "dashes": false,
-          "datasource": "yardstick",
+          "datasource": "${DS_YARDSTICK}",
           "fill": 0,
           "hideTimeOverride": true,
           "id": 3,
           "bars": false,
           "dashLength": 10,
           "dashes": false,
-          "datasource": "yardstick",
+          "datasource": "${DS_YARDSTICK}",
           "fill": 1,
           "id": 1,
           "legend": {
               "alias": "$col",
               "dsType": "influxdb",
               "groupBy": [],
-              "measurement": "tc_heat_rfc2544_ipv4_1rule_1flow_trex",
+              "measurement": "/^$test_name$/",
               "orderByTime": "ASC",
               "policy": "default",
               "refId": "A",
           "bars": false,
           "dashLength": 10,
           "dashes": false,
-          "datasource": null,
+          "datasource": "${DS_YARDSTICK}",
           "fill": 1,
           "id": 5,
           "legend": {
           "bars": true,
           "dashLength": 10,
           "dashes": false,
-          "datasource": null,
+          "datasource": "${DS_YARDSTICK}",
           "fill": 1,
           "id": 4,
           "legend": {
     "list": [
       {
         "allValue": null,
-        "current": {
-          "text": "tc_heat_rfc2544_ipv4_1rule_1flow_trex",
-          "value": "tc_heat_rfc2544_ipv4_1rule_1flow_trex"
-        },
-        "datasource": "yardstick",
+        "current": {},
+        "datasource": "${DS_YARDSTICK}",
         "hide": 0,
         "includeAll": false,
         "label": "test_name",
       },
       {
         "allValue": null,
-        "current": {
-          "text": "fdb337ec-11ea-410f-b45e-83f30edb7590",
-          "value": "fdb337ec-11ea-410f-b45e-83f30edb7590"
-        },
-        "datasource": "yardstick",
+        "current": {},
+        "datasource": "${DS_YARDSTICK}",
         "hide": 0,
         "includeAll": false,
         "label": "task_id",
       {
         "allValue": null,
         "current": {
-          "tags": [],
           "text": "64B + 128B + 512B",
           "value": [
             "64B",
   },
   "timezone": "",
   "title": "RFC2544 Multi framesize",
-  "version": 14
+  "version": 15
 }
index 941a0bb..69ffb8a 100644 (file)
@@ -640,3 +640,37 @@ A testcase can be started with the following command as an example:
 .. code-block:: bash
 
     yardstick task start /yardstick/samples/vnf_samples/nsut/vpe/tc_baremetal_rfc2544_ipv4_1flow_64B_ixia.yaml
+
+Preparing test run of vIPSEC test case
+------------------------------------
+
+Location of vIPSEC test cases: ``samples/vnf_samples/nsut/ipsec/``.
+
+Before running a specific vIPSEC test case using NSB, some dependencies have to be
+preinstalled and properly configured.
+- VPP
+
+.. code-block:: console
+
+    export UBUNTU="xenial"
+    export RELEASE=".stable.1810"
+    sudo rm /etc/apt/sources.list.d/99fd.io.list
+    echo "deb [trusted=yes] https://nexus.fd.io/content/repositories/fd.io$RELEASE.ubuntu.$UBUNTU.main/ ./" | sudo tee -a /etc/apt/sources.list.d/99fd.io.list
+    sudo apt-get update
+    sudo apt-get install vpp vpp-lib vpp-plugin vpp-dbg vpp-dev vpp-api-java vpp-api-python vpp-api-lua
+
+- VAT templates
+
+    VAT templates is required for the VPP API.
+
+.. code-block:: console
+
+    mkdir -p /opt/nsb_bin/vpp/templates/
+    echo 'exec trace add dpdk-input 50' > /opt/nsb_bin/vpp/templates/enable_dpdk_traces.vat
+    echo 'exec trace add vhost-user-input 50' > /opt/nsb_bin/vpp/templates/enable_vhost_user_traces.vat
+    echo 'exec trace add memif-input 50' > /opt/nsb_bin/vpp/templates/enable_memif_traces.vat
+    cat > /opt/nsb_bin/vpp/templates/dump_interfaces.vat << EOL
+    sw_interface_dump
+    dump_interface_table
+    quit
+    EOL
index 6c18c7d..a578216 100644 (file)
@@ -36,3 +36,4 @@ NSB PROX Test Case Descriptions
    tc_vfw_rfc2544
    tc_vfw_rfc2544_correlated
    tc_vfw_rfc3511
+   tc_vpp_baremetal_crypto_ipsec
diff --git a/docs/testing/user/userguide/nsb/tc_vpp_baremetal_crypto_ipsec.rst b/docs/testing/user/userguide/nsb/tc_vpp_baremetal_crypto_ipsec.rst
new file mode 100644 (file)
index 0000000..6a4a376
--- /dev/null
@@ -0,0 +1,113 @@
+.. This work is licensed under a Creative Commons Attribution 4.0 International
+.. License.
+.. http://creativecommons.org/licenses/by/4.0
+.. (c) OPNFV, 2019 Viosoft Corporation.
+
+***********************************************
+Yardstick Test Case Description: NSB VPP IPSEC
+***********************************************
+
++------------------------------------------------------------------------------+
+|NSB VPP test for vIPSEC characterization                                      |
+|                                                                              |
++--------------+---------------------------------------------------------------+
+|test case id  | tc_baremetal_rfc2544_ipv4_{crypto_dev}_{crypto_alg}           |
+|              |                                                               |
+|              | * crypto_dev = HW_cryptodev or SW_cryptodev;                  |
+|              | * crypto_alg = aes-gcm or cbc-sha1;                           |
+|              |                                                               |
++--------------+---------------------------------------------------------------+
+|metric        | * Network Throughput NDR or PDR;                              |
+|              | * Connections Per Second (CPS);                               |
+|              | * Latency;                                                    |
+|              | * Number of tunnels;                                          |
+|              | * TG Packets Out;                                             |
+|              | * TG Packets In;                                              |
+|              | * VNF Packets Out;                                            |
+|              | * VNF Packets In;                                             |
+|              | * Dropped packets;                                            |
+|              |                                                               |
++--------------+---------------------------------------------------------------+
+|test purpose  | IPv4 IPsec tunnel mode performance test:                      |
+|              |                                                               |
+|              | * Finds and reports throughput NDR (Non Drop Rate) with zero  |
+|              |   packet loss tolerance or throughput PDR (Partial Drop Rate) |
+|              |   with non-zero packet loss tolerance (LT) expressed in       |
+|              |   number of packets transmitted.                              |
+|              |                                                               |
+|              | * The IPSEC test cases are implemented to run in baremetal    |
+|              |                                                               |
++--------------+---------------------------------------------------------------+
+|configuration | The IPSEC test cases are listed below:                        |
+|              |                                                               |
+|              | * tc_baremetal_rfc2544_ipv4_hw_aesgcm_IMIX_trex.yaml          |
+|              | * tc_baremetal_rfc2544_ipv4_hw_aesgcm_trex.yaml               |
+|              | * tc_baremetal_rfc2544_ipv4_hw_cbcsha1_IMIX_trex.yaml         |
+|              | * tc_baremetal_rfc2544_ipv4_hw_cbcsha1_trex.yaml              |
+|              | * tc_baremetal_rfc2544_ipv4_sw_aesgcm_IMIX_trex.yaml          |
+|              | * tc_baremetal_rfc2544_ipv4_sw_aesgcm_trex.yaml               |
+|              | * tc_baremetal_rfc2544_ipv4_sw_cbcsha1_IMIX_trex.yaml         |
+|              | * tc_baremetal_rfc2544_ipv4_sw_cbcsha1_trex.yaml              |
+|              |                                                               |
+|              | Test duration is set as 500sec for each test.                 |
+|              | Packet size set as 64 bytes or higher.                        |
+|              | Number of tunnels set as 1 or higher.                         |
+|              | Number of connections set as 1 or higher                      |
+|              | These can be configured                                       |
+|              |                                                               |
++--------------+---------------------------------------------------------------+
+|test tool     | Vector Packet Processing (VPP)                                |
+|              | The VPP platform is an extensible framework that provides     |
+|              | out-of-the-box production quality switch/router functionality.|
+|              | Its high performance, proven technology, its modularity and,  |
+|              | flexibility and rich feature set                              |
+|              |                                                               |
++--------------+---------------------------------------------------------------+
+|applicability | This VPP IPSEC test cases can be configured with different:   |
+|              |                                                               |
+|              | * packet sizes;                                               |
+|              | * test durations;                                             |
+|              | * tolerated loss;                                             |
+|              | * crypto device type;                                         |
+|              | * number of physical cores;                                   |
+|              | * number of tunnels;                                          |
+|              | * number of connections;                                      |
+|              | * encryption algorithms - integrity algorithm;                |
+|              |                                                               |
+|              | Default values exist.                                         |
+|              |                                                               |
++--------------+---------------------------------------------------------------+
+|pre-test      | For Baremetal tests cases VPP and DPDK must be installed in   |
+|conditions    | the hosts where the test is executed. The pod.yaml file must  |
+|              | have the necessary system and NIC information                 |
+|              |                                                               |
++--------------+---------------------------------------------------------------+
+|test sequence | description and expected result                               |
+|              |                                                               |
++--------------+---------------------------------------------------------------+
+|step 1        | For Baremetal test: The TG and VNF are started on the hosts   |
+|              | based on the pod file.                                        |
+|              |                                                               |
++--------------+---------------------------------------------------------------+
+|step 2        | Yardstick is connected with the TG and VNF by using ssh.      |
+|              | The test will resolve the topology and instantiate the VNF    |
+|              | and TG and collect the KPI's/metrics.                         |
+|              |                                                               |
++--------------+---------------------------------------------------------------+
+|step 3        | Test packets are generated by TG on links to DUTs. If the     |
+|              | number of dropped packets is more than the tolerated loss     |
+|              | the line rate or throughput is halved. This is done until     |
+|              | the dropped packets are within an acceptable tolerated loss.  |
+|              |                                                               |
+|              | The KPI is the number of packets per second for a packet size |
+|              | specified in the test case with an accepted minimal packet    |
+|              | loss for the default configuration.                           |
+|              |                                                               |
++--------------+---------------------------------------------------------------+
+|step 4        | In Baremetal test: The test quits the application and unbind  |
+|              | the DPDK ports.                                               |
+|              |                                                               |
++--------------+---------------------------------------------------------------+
+|test verdict  | The test case will achieve a Throughput with an accepted      |
+|              | minimal tolerated packet loss.                                |
++--------------+---------------------------------------------------------------+
\ No newline at end of file
diff --git a/etc/yardstick/nodes/standalone/sipp_baremetal_pod.yaml b/etc/yardstick/nodes/standalone/sipp_baremetal_pod.yaml
new file mode 100644 (file)
index 0000000..c7a0af1
--- /dev/null
@@ -0,0 +1,52 @@
+# Copyright (c) 2019 Viosoft 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.
+
+nodes:
+-
+    name: sipp
+    role: TrafficGen
+    ip: 10.80.3.7
+    user: USER_ROOT
+    password: PASSWORD_ROOT
+    interfaces:
+        xe0:  # logical name from topology.yaml and vnfd.yaml
+            local_ip: "10.80.3.7"
+            netmask:   "255.255.255.0"
+            local_mac:   "90:e2:ba:7c:30:e8"
+        xe1:
+            local_ip: "10.80.3.7"
+            netmask:   "255.255.255.0"
+            local_mac:   "90:e2:ba:7c:30:e8"
+-
+    name: pcscf
+    role: VirtualNetworkFunction
+    ip: 10.80.3.11
+    user: USER_ROOT
+    password: PASSWORD_ROOT
+    interfaces:
+        xe0:
+            local_ip: "10.80.3.11"
+            netmask:   "255.255.255.0"
+            local_mac:   "90:e2:ba:7c:41:a8"
+-
+    name: hss
+    role: VirtualNetworkFunction
+    ip: 10.80.3.11
+    user: USER_ROOT
+    password: PASSWORD_ROOT
+    interfaces:
+        xe0:
+            local_ip: "10.80.3.11"
+            netmask:   "255.255.255.0"
+            local_mac: "90:e2:ba:7c:41:a8"
diff --git a/etc/yardstick/nodes/standalone/sipp_pod.yaml b/etc/yardstick/nodes/standalone/sipp_pod.yaml
new file mode 100644 (file)
index 0000000..ae473c5
--- /dev/null
@@ -0,0 +1,30 @@
+# Copyright (c) 2019 Viosoft 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.
+
+nodes:
+-
+    name: sipp
+    role: TrafficGen
+    ip: 10.80.3.7
+    user: USER_ROOT
+    password: PASSWORD_ROOT
+    interfaces:
+        xe0:  # logical name from topology.yaml and vnfd.yaml
+            local_ip: "10.80.3.7"
+            netmask:   "255.255.255.0"
+            local_mac:  "0c:c4:7a:de:8f:da"
+        xe1:  # logical name from topology.yaml and vnfd.yaml
+            local_ip: "10.80.3.7"
+            netmask:   "255.255.255.0"
+            local_mac:  "0c:c4:7a:de:8f:da"
diff --git a/samples/vnf_samples/nsut/cmts/k8s_vcmts_topology.yaml b/samples/vnf_samples/nsut/cmts/k8s_vcmts_topology.yaml
new file mode 100755 (executable)
index 0000000..95ac769
--- /dev/null
@@ -0,0 +1,36 @@
+# Copyright (c) 2019 Viosoft 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.
+
+{% set num_tg = get(extra_args, 'num_tg', 2) %}
+{% set num_sg = get(extra_args, 'num_sg', 2) %}
+
+nsd:nsd-catalog:
+    nsd:
+    -   id: vcmts-topology
+        name: vcmts-topology
+        short-name: vcmts-topology
+        description: vcmts-topology
+        constituent-vnfd:
+        {% for tg_num in range(0, num_tg) %}
+        -   member-vnf-index: '{{ tg_num + 1 }}'
+            vnfd-id-ref: tg__{{ tg_num }}
+            VNF model: ../../vnf_descriptors/tg_vcmts_tpl.yaml      #VNF type
+        {% endfor %}
+        {% for vnf_num in range(0, num_sg * 2) %}
+        -   member-vnf-index: '{{ vnf_num + num_tg + 1 }}'
+            vnfd-id-ref: vnf__{{ vnf_num }}
+            VNF model: ../../vnf_descriptors/vnf_vcmts_tpl.yaml      #VNF type
+        {% endfor %}
+
+        vld: []
diff --git a/samples/vnf_samples/nsut/cmts/tc_vcmts_k8s_pktgen.yaml b/samples/vnf_samples/nsut/cmts/tc_vcmts_k8s_pktgen.yaml
new file mode 100755 (executable)
index 0000000..6c85a08
--- /dev/null
@@ -0,0 +1,360 @@
+# Copyright (c) 2019 Viosoft 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.
+
+
+{% set num_tg = 2 %}
+{% set num_sg = 4 %}
+
+{% set vcmtsd_image = "vcmts-d:perf" %}
+{% set qat_on = false %}
+
+schema: "yardstick:task:0.1"
+
+scenarios:
+- type: NSPerf
+  traffic_profile: ../../traffic_profiles/fixed.yaml
+  extra_args:
+    num_sg: {{ num_sg }}
+    num_tg: {{ num_tg }}
+  topology: k8s_vcmts_topology.yaml
+  nodes:
+{% for tg_num in range(0, num_tg) %}
+    tg__{{ tg_num }}: pktgen{{ tg_num }}-k8syardstick
+{% endfor %}
+{% for vnf_index in range(0, num_sg) %}
+    vnf__{{ vnf_index * 2 }}: vnf{{ vnf_index }}us-k8syardstick
+    vnf__{{ (vnf_index * 2) + 1}}: vnf{{ vnf_index }}ds-k8syardstick
+{% endfor %}
+
+  runner:
+    type: Duration
+    duration: 120
+
+  options:
+    vcmts_influxdb_ip: "10.80.5.150"
+    vcmts_influxdb_port: 8086
+    vcmtsd_values: /etc/yardstick/vcmtsd_values.yaml
+    pktgen_values: /etc/yardstick/pktgen_values.yaml
+    pktgen_rate: 6.5
+{% for vnf_index in range(0, num_sg) %}
+    vnf__{{ vnf_index * 2 }}:
+      sg_id: {{ vnf_index }}
+      stream_dir: "us"
+    vnf__{{ (vnf_index * 2) + 1}}:
+      sg_id: {{ vnf_index }}
+      stream_dir: "ds"
+{% endfor %}
+{% for tg_num in range(0, num_tg) %}
+    tg__{{ tg_num }}:
+      pktgen_id: {{ tg_num }}
+{% endfor %}
+
+context:
+  name: k8syardstick
+  type: Kubernetes
+
+  servers:
+{% for vnf_index in range(0, num_sg) %}
+    vnf{{ vnf_index }}us:
+      nodeSelector:
+        vcmts: "true"
+      containers:
+        - image: {{ vcmtsd_image }}
+          imagePullPolicy: IfNotPresent
+          env:
+          - name: CMK_PROC_FS
+            value: "/host/proc"
+          command: /bin/bash
+          args: ['-c', 'mkdir /root/.ssh; cp /tmp/.ssh/authorized_keys ~/.ssh/.;
+                    chmod 700 ~/.ssh; chmod 600 ~/.ssh/*; service ssh restart;
+                    while true ; do sleep 10000; done']
+          resources:
+            requests:
+              memory: 10Ki
+{% if qat_on %}
+              hugepages-1Gi: 2Gi
+              qat.intel.com/generic: '1'
+{% else %}
+              hugepages-1Gi: 1Gi
+{% endif %}
+            limits:
+              memory: 1Gi
+{% if qat_on %}
+              hugepages-1Gi: 2Gi
+              qat.intel.com/generic: '1'
+{% else %}
+              hugepages-1Gi: 1Gi
+{% endif %}
+          lifecycle:
+            postStart:
+              exec:
+                command: [ "/bin/bash", "-c", "env > /tmp/qat" ]
+          volumeMounts:
+            - name: vcmts-configmap-vcmtspod
+              mountPath: /vcmts-config
+            - name: hugepages
+              mountPath: /hugepages
+              readOnly: false
+            - name: collectd
+              mountPath: /opt/collectd/var
+              readOnly: false
+            - name: sysfs
+              mountPath: /sys
+              readOnly: false
+            - name: sriov
+              mountPath: /sriov-cni
+              readOnly: false
+            - name: host-proc
+              mountPath: /host/proc
+              readOnly: true
+            - name: cmk-install-dir
+              mountPath: /opt/bin
+            - name: cmk-conf-dir
+              mountPath: /etc/cmk
+            - name: power-mgmt
+              mountPath: /opt/power_mgmt
+          ports:
+            - containerPort: 22022
+          securityContext:
+            allowPrivilegeEscalation: true
+            privileged: true
+      node_ports:
+        - name: lua  # Lower case alphanumeric characters or '-'
+          port: 22022
+      networks:
+        - flannel
+        - xe0
+        - xe1
+      volumes:
+      - name: vcmts-configmap-vcmtspod
+        configMap:
+          name: vcmts-configmap-vcmtspod
+          defaultMode: 0744
+      - name: hugepages
+        emptyDir:
+          medium: HugePages
+      - name: collectd
+        hostPath:
+          path: /opt/collectd/var
+      - name: sysfs
+        hostPath:
+          path: /sys
+      - name: sriov
+        hostPath:
+          path: /var/lib/cni/sriov
+      - name: cmk-install-dir
+        hostPath:
+          path: /opt/bin
+      - name: host-proc
+        hostPath:
+          path: /proc
+      - name: cmk-conf-dir
+        hostPath:
+          path: /etc/cmk
+      - name: power-mgmt
+        hostPath:
+          path: /opt/power_mgmt
+
+    vnf{{ vnf_index }}ds:
+      nodeSelector:
+        vcmts: "true"
+      containers:
+        - image: {{ vcmtsd_image }}
+          imagePullPolicy: IfNotPresent
+          env:
+          - name: CMK_PROC_FS
+            value: "/host/proc"
+          command: /bin/bash
+          args: ['-c', 'mkdir /root/.ssh; cp /tmp/.ssh/authorized_keys ~/.ssh/.;
+                    chmod 700 ~/.ssh; chmod 600 ~/.ssh/*; service ssh restart;
+                    while true ; do sleep 10000; done']
+          resources:
+            requests:
+              memory: 10Ki
+{% if qat_on %}
+              hugepages-1Gi: 2Gi
+              qat.intel.com/generic: '1'
+{% else %}
+              hugepages-1Gi: 1Gi
+{% endif %}
+            limits:
+              memory: 1Gi
+{% if qat_on %}
+              hugepages-1Gi: 2Gi
+              qat.intel.com/generic: '1'
+{% else %}
+              hugepages-1Gi: 1Gi
+{% endif %}
+          lifecycle:
+            postStart:
+              exec:
+                command: [ "/bin/bash", "-c", "env > /tmp/qat" ]
+          volumeMounts:
+            - name: vcmts-configmap-vcmtspod
+              mountPath: /vcmts-config
+            - name: hugepages
+              mountPath: /hugepages
+              readOnly: false
+            - name: collectd
+              mountPath: /opt/collectd/var
+              readOnly: false
+            - name: sysfs
+              mountPath: /sys
+              readOnly: false
+            - name: sriov
+              mountPath: /sriov-cni
+              readOnly: false
+            - name: host-proc
+              mountPath: /host/proc
+              readOnly: true
+            - name: cmk-install-dir
+              mountPath: /opt/bin
+            - name: cmk-conf-dir
+              mountPath: /etc/cmk
+            - name: power-mgmt
+              mountPath: /opt/power_mgmt
+          ports:
+            - containerPort: 22022
+          securityContext:
+            allowPrivilegeEscalation: true
+            privileged: true
+      node_ports:
+        - name: lua  # Lower case alphanumeric characters or '-'
+          port: 22022
+      networks:
+        - flannel
+        - xe0
+        - xe1
+      volumes:
+      - name: vcmts-configmap-vcmtspod
+        configMap:
+          name: vcmts-configmap-vcmtspod
+          defaultMode: 0744
+      - name: hugepages
+        emptyDir:
+          medium: HugePages
+      - name: collectd
+        hostPath:
+          path: /opt/collectd/var
+      - name: sysfs
+        hostPath:
+          path: /sys
+      - name: sriov
+        hostPath:
+          path: /var/lib/cni/sriov
+      - name: cmk-install-dir
+        hostPath:
+          path: /opt/bin
+      - name: host-proc
+        hostPath:
+          path: /proc
+      - name: cmk-conf-dir
+        hostPath:
+          path: /etc/cmk
+      - name: power-mgmt
+        hostPath:
+          path: /opt/power_mgmt
+{% endfor %}
+
+{% for index in range(0, num_tg) %}
+    pktgen{{index}}:
+      nodeSelector:
+        vcmtspktgen: "true"
+      containers:
+        - image: vcmts-pktgen:v18.10
+          imagePullPolicy: IfNotPresent
+          tty: true
+          stdin: true
+          env:
+          - name: LUA_PATH
+            value: "/vcmts/Pktgen.lua"
+          - name: CMK_PROC_FS
+            value: "/host/proc"
+          command: /bin/bash
+          args: ['-c', 'mkdir /root/.ssh; cp /tmp/.ssh/authorized_keys ~/.ssh/.;
+                    chmod 700 ~/.ssh; chmod 600 ~/.ssh/*; service ssh restart;
+                    while true ; do sleep 10000; done']
+          resources:
+            requests:
+              hugepages-1Gi: 9Gi
+              memory: 200Mi
+            limits:
+              hugepages-1Gi: 9Gi
+              memory: 200Mi
+          volumeMounts:
+            - name: sysfs
+              mountPath: /sys
+              readOnly: false
+            - name: hugepages
+              mountPath: /hugepages
+              readOnly: false
+            - name: sriov
+              mountPath: /sriov-cni
+              readOnly: false
+            - name: host-proc
+              mountPath: /host/proc
+              readOnly: true
+            - name: cmk-install-dir
+              mountPath: /opt/bin
+            - name: cmk-conf-dir
+              mountPath: /etc/cmk
+            - name: pktgen-config
+              mountPath: /pktgen-config
+          ports:
+            - containerPort: 22022
+          securityContext:
+            allowPrivilegeEscalation: true
+            privileged: true
+      volumes:
+        - name: sysfs
+          hostPath:
+            path: /sys
+        - name: hugepages
+          emptyDir:
+            medium: HugePages
+        - name: sriov
+          hostPath:
+            path: /var/lib/cni/sriov
+        - name: cmk-install-dir
+          hostPath:
+            path: /opt/bin
+        - name: host-proc
+          hostPath:
+            path: /proc
+        - name: cmk-conf-dir
+          hostPath:
+            path: /etc/cmk
+        - name: pktgen-config
+          configMap:
+            name: vcmts-configmap-pktgen
+            defaultMode: 0744
+      node_ports:
+        - name: lua  # Lower case alphanumeric characters or '-'
+          port: 22022
+      networks:
+        - flannel
+        - xe0
+        - xe1
+{% endfor %}
+
+  networks:
+    flannel:
+      args: '[{ "delegate": { "isDefaultGateway": true }}]'
+      plugin: flannel
+    xe0:
+      args: '[{ "delegate": { "isDefaultGateway": true }}]'
+      plugin: flannel
+    xe1:
+      args: '[{ "delegate": { "isDefaultGateway": true }}]'
+      plugin: flannel
diff --git a/samples/vnf_samples/nsut/vims/tc_vims_baremetal_sipp.yaml b/samples/vnf_samples/nsut/vims/tc_vims_baremetal_sipp.yaml
new file mode 100644 (file)
index 0000000..bf9f5a4
--- /dev/null
@@ -0,0 +1,57 @@
+# Copyright (c) 2019 Viosoft 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
+run_in_parallel: false
+scenarios:
+-
+  type: NSPerf
+  traffic_profile: "../../traffic_profiles/sip.yaml"
+  topology: vims-topology.yaml
+
+  nodes:
+    vnf__0: pcscf.yardstick
+    vnf__1: hss.yardstick
+    tg__0: sipp.yardstick
+
+  runner:
+    type: Duration
+    duration: 10000
+    interval: 1
+  options:
+    port: 5060
+    start_user: 1
+    end_user: 10000
+    init_reg_cps: 50
+    init_reg_max: 5000
+    reg_cps: 20
+    reg_step: 10
+    rereg_cps: 20
+    rereg_step: 10
+    dereg_cps: 20
+    dereg_step: 10
+    msgc_cps: 10
+    msgc_step: 5
+    run_mode: nortp
+    call_cps: 10
+    hold_time: 15
+    call_step: 5
+
+    wait_time: 5
+
+context:
+  type: Node
+  name: yardstick
+  nfvi_type: baremetal
+  file: /etc/yardstick/nodes/sipp_baremetal_pod.yaml
diff --git a/samples/vnf_samples/nsut/vims/tc_vims_heat_sipp.yaml b/samples/vnf_samples/nsut/vims/tc_vims_heat_sipp.yaml
new file mode 100644 (file)
index 0000000..8bf59c9
--- /dev/null
@@ -0,0 +1,97 @@
+# Copyright (c) 2019 Viosoft 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"
+run_in_parallel: false
+scenarios:
+-
+  type: NSPerf
+  traffic_profile: "../../traffic_profiles/sip.yaml"
+  topology: vims-topology.yaml
+  nodes:
+    tg__0: sipp.trafficgen
+    vnf__0:
+      name: bono-0.yardstick
+      public_ip_attr: "bono_public_ip"
+      private_ip_attr: "bono_private_ip"
+      interfaces:
+        ims_network:
+          local_ip: bono_private_ip
+          local_mac: bono_local_mac
+          netmask: "255.255.255.0"
+          network: private_net_name
+          gateway_ip: private_net_gateway
+
+    vnf__1:
+      name: homestead-0.yardstick
+      public_ip_attr: "homestead_public_ip"
+      private_ip_attr: "homestead_private_ip"
+      interfaces:
+        ims_network:
+          local_ip: homestead_private_ip
+          local_mac: homestead_local_mac
+          netmask: "255.255.255.0"
+          network: private_net_name
+          gateway_ip: private_net_gateway
+  options:
+      # Public key to create keypair on openstack
+      key_name: yardstick
+      public_key: ""
+      port: 5060
+      start_user: 1
+      end_user: 10000
+      init_reg_cps: 50
+      init_reg_max: 5000
+      reg_cps: 50
+      reg_step: 10
+      rereg_cps: 10
+      rereg_step: 5
+      dereg_cps: 10
+      dereg_step: 5
+      msgc_cps: 10
+      msgc_step: 2
+      run_mode: rtp
+      call_cps: 10
+      hold_time: 15
+      call_step: 5
+      wait_time: 600
+  runner:
+    type: Duration
+    duration: 10000
+    interval: 1
+contexts:
+# put node context first, so we don't HEAT deploy if node has errors
+-
+  name: trafficgen
+  type: Node
+  nfvi_type: baremetal
+  file: /etc/yardstick/nodes/sipp_pod.yaml
+
+-
+  name: yardstick
+  user: ubuntu
+  # Private key to access VM
+  key_filename: /etc/yardstick/yardstick.pem
+  heat_template: /tmp/clearwater.yaml
+  heat_parameters:
+    image: "Ubuntu_14_trusty"
+    flavor: m1.small
+    key_name: yardstick
+    public_net_id: "public1"
+    repo_url: "http://repo.cw-ngv.com/archive/repo107/"
+    dnssec_key: "HOz2ZYgvbyCFRUymRLBXyLD4+tESevesP07hhzs/uKUD+Oy3aWvReEWMKgBwy75FAl9XQqw6wIGb"
+    private_net_name: "ims_network"
+    private_net_cidr: "10.0.1.0/24"
+    private_net_gateway: "10.0.1.1"
+    private_net_pool_start: "10.0.1.2"
diff --git a/samples/vnf_samples/nsut/vims/vims-topology.yaml b/samples/vnf_samples/nsut/vims/vims-topology.yaml
new file mode 100644 (file)
index 0000000..a7525ab
--- /dev/null
@@ -0,0 +1,52 @@
+# Copyright (c) 2019 Viosoft 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: vims-tg-topology
+        name: vims-tg-topology
+        short-name: vims-tg-topology
+        description: vims-tg-topology
+        constituent-vnfd:
+        -   member-vnf-index: '1'
+            vnfd-id-ref: tg__0
+            VNF model: ../../vnf_descriptors/tg_sipp_vnfd.yaml        # Traffic Generator
+        -   member-vnf-index: '2'
+            vnfd-id-ref: vnf__0
+            VNF model: ../../vnf_descriptors/vims_pcscf_vnfd.yaml      # vims
+        -   member-vnf-index: '3'
+            vnfd-id-ref: vnf__1
+            VNF model: ../../vnf_descriptors/vims_hss_vnfd.yaml      # vims
+
+        vld:
+        -   id: ims_network
+            name: tg__0 to vnf__0 link 1
+            type: ELAN
+            vnfd-connection-point-ref:
+            -   member-vnf-index-ref: '1'
+                vnfd-connection-point-ref: xe0
+                vnfd-id-ref: tg__0
+            -   member-vnf-index-ref: '2'
+                vnfd-connection-point-ref: xe0
+                vnfd-id-ref: vnf__0
+        -   id: data_network
+            name: tg__0 to vnf__0 link 2
+            type: ELAN
+            vnfd-connection-point-ref:
+            -   member-vnf-index-ref: '1'
+                vnfd-connection-point-ref: xe1
+                vnfd-id-ref: tg__0
+            -   member-vnf-index-ref: '2'
+                vnfd-connection-point-ref: xe0
+                vnfd-id-ref: vnf__0
index 1add9bf..abbad67 100644 (file)
@@ -20,6 +20,7 @@ description:     Traffic profile to run RFC2544 latency
 traffic_profile:
   traffic_type: VppRFC2544Profile # defines traffic behavior - constant or look for highest possible throughput
   enable_latency: true
+  intermediate_phases: 2
   test_precision: 0.1
   duration: 30
   lower_bound: 1.0
diff --git a/samples/vnf_samples/vnf_descriptors/tg_sipp_vnfd.yaml b/samples/vnf_samples/vnf_descriptors/tg_sipp_vnfd.yaml
new file mode 100644 (file)
index 0000000..4a9d705
--- /dev/null
@@ -0,0 +1,54 @@
+# Copyright (c) 2019 Viosoft 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: SippVnf
+        name: SippVnf
+        short-name: SippVnf
+        description: ImsbenchSipp
+        mgmt-interface:
+            vdu-id: sippvnf-baremetal
+            {% if user is defined %}
+            user: '{{user}}'  # Value filled by vnfdgen
+            {% endif %}
+            {% if password is defined %}
+            password: '{{password}}'  # Value filled by vnfdgen
+            {% endif %}
+            {% if ip is defined %}
+            ip: '{{ip}}'  # Value filled by vnfdgen
+            {% endif %}
+            {% if key_filename is defined %}
+            key_filename: '{{key_filename}}'  # Value filled by vnfdgen
+            {% endif %}
+        vdu:
+        -   id: sippvnf-baremetal
+            name: sippvnf-baremetal
+            description: Sipp
+            routing_table: {{ routing_table }}
+            external-interface:
+            -   name: xe0
+                virtual-interface:
+                    local_ip: '{{ interfaces.xe0.local_ip }}'
+                    dst_ip: '{{ interfaces.xe0.dst_ip }}'
+                vnfd-connection-point-ref: xe0
+            -   name: xe1
+                virtual-interface:
+                    local_ip: '{{ interfaces.xe1.local_ip }}'
+                vnfd-connection-point-ref: xe1
+        benchmark:
+            kpi:
+                - packets_in
+                - packets_fwd
+                - packets_dropped
diff --git a/samples/vnf_samples/vnf_descriptors/tg_vcmts_tpl.yaml b/samples/vnf_samples/vnf_descriptors/tg_vcmts_tpl.yaml
new file mode 100755 (executable)
index 0000000..bb56fcb
--- /dev/null
@@ -0,0 +1,77 @@
+# Copyright (c) 2019 Viosoft 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: VcmtsPktgen
+        name: vcmtspktgen
+        short-name: vcmtspktgen
+        description: vCMTS Pktgen Kubernetes
+        vm-flavor:
+            vcpu-count: '4'
+            memory-mb: '4096'
+        mgmt-interface:
+            vdu-id: vcmtspktgen-kubernetes
+            {% if user is defined %}
+            user: '{{user}}'  # Value filled by vnfdgen
+            {% endif %}
+            {% if password is defined %}
+            password: '{{password}}'  # Value filled by vnfdgen
+            {% endif %}
+            {% if ip is defined %}
+            ip: '{{ip}}'  # Value filled by vnfdgen
+            {% endif %}
+            {% if key_filename is defined %}
+            key_filename: '{{key_filename}}'  # Value filled by vnfdgen
+            {% endif %}
+        connection-point:
+        -   name: xe0
+            type: VPORT
+        -   name: xe1
+            type: VPORT
+        vdu:
+        -   id: vcmtspktgen-kubernetes
+            name: vcmtspktgen-kubernetes
+            description: vCMTS Pktgen Kubernetes
+            external-interface:
+            -   name: xe0
+                virtual-interface:
+                    type: virtio
+                    # Substitution variables MUST be quoted. Otherwise Python can misinterpet them.
+                    vpci: '{{ interfaces.xe0.vpci }}' # Value filled by vnfdgen
+                    local_iface_name: eth0 # '{{ interfaces.xe0.local_iface_name }}'
+                    driver: '{{ interfaces.xe0.driver}}' # Value filled by vnfdgen
+                    local_ip: '{{ interfaces.xe0.local_ip }}' # 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
+                    bandwidth: 10 Gbps
+                vnfd-connection-point-ref: xe0
+            -   name: xe1
+                virtual-interface:
+                    type: virtio
+                    # Substitution variables MUST be quoted. Otherwise Python can misinterpet them.
+                    vpci: '{{ interfaces.xe1.vpci }}' # Value filled by vnfdgen
+                    local_iface_name: eth0 # '{{ interfaces.xe1.local_iface_name }}'
+                    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
+                    bandwidth: 10 Gbps
+                vnfd-connection-point-ref: xe0
+        benchmark:
+            kpi:
+                - upstream/bits_per_second
+
diff --git a/samples/vnf_samples/vnf_descriptors/vims_hss_vnfd.yaml b/samples/vnf_samples/vnf_descriptors/vims_hss_vnfd.yaml
new file mode 100644 (file)
index 0000000..835ca49
--- /dev/null
@@ -0,0 +1,54 @@
+# Copyright (c) 2019 Viosoft 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: VimsHssVnf
+        name: HssVnf
+        short-name: HssVnf
+        description: IMS Database
+        mgmt-interface:
+            vdu-id: HssVnf
+            {% if user is defined %}
+            user: '{{user}}'  # Value filled by vnfdgen
+            {% endif %}
+            {% if password is defined %}
+            password: '{{password}}'  # Value filled by vnfdgen
+            {% endif %}
+            {% if ip is defined %}
+            ip: '{{ip}}'  # Value filled by vnfdgen
+            {% endif %}
+            {% if key_filename is defined %}
+            key_filename: '{{key_filename}}'  # Value filled by vnfdgen
+            {% endif %}
+        vdu:
+        -   id: HssVnf
+            name: HssVnf
+            description: IMS Database
+            external-interface:
+            -   name: ims_network
+                virtual-interface:
+                    local_ip: '{{ip}}'
+                    local_mac: '{{local_mac}}'
+                    netmask: '{{netmask}}'
+            vm-flavor:
+                vcpu-count: '4'
+                memory-mb: '4096'
+            routing_table: {{ routing_table }}
+            nd_route_tbl: {{ nd_route_tbl }}
+        benchmark:
+            kpi:
+                - packets_in
+                - packets_fwd
+                - packets_dropped
diff --git a/samples/vnf_samples/vnf_descriptors/vims_pcscf_vnfd.yaml b/samples/vnf_samples/vnf_descriptors/vims_pcscf_vnfd.yaml
new file mode 100644 (file)
index 0000000..02a96d0
--- /dev/null
@@ -0,0 +1,54 @@
+# Copyright (c) 2019 Viosoft 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: VimsPcscfVnf
+        name: PcscfVnf
+        short-name: PcscfVnf
+        description: IMS Gateway
+        mgmt-interface:
+            vdu-id: PcscfVnf
+            {% if user is defined %}
+            user: '{{user}}'  # Value filled by vnfdgen
+            {% endif %}
+            {% if password is defined %}
+            password: '{{password}}'  # Value filled by vnfdgen
+            {% endif %}
+            {% if ip is defined %}
+            ip: '{{ip}}'  # Value filled by vnfdgen
+            {% endif %}
+            {% if key_filename is defined %}
+            key_filename: '{{key_filename}}'  # Value filled by vnfdgen
+            {% endif %}
+        vdu:
+        -   id: PcscfVnf
+            name: PcscfVnf
+            description: VIMS Gateway
+            external-interface:
+            -   name: ims_network
+                virtual-interface:
+                    local_ip: '{{ip}}'
+                    local_mac: '{{local_mac}}'
+                    netmask: '{{netmask}}'
+            vm-flavor:
+                vcpu-count: '4'
+                memory-mb: '4096'
+            routing_table: {{ routing_table }}
+            nd_route_tbl: {{ nd_route_tbl }}
+        benchmark:
+            kpi:
+                - packets_in
+                - packets_fwd
+                - packets_dropped
diff --git a/samples/vnf_samples/vnf_descriptors/vnf_vcmts_tpl.yaml b/samples/vnf_samples/vnf_descriptors/vnf_vcmts_tpl.yaml
new file mode 100755 (executable)
index 0000000..d1eb6a8
--- /dev/null
@@ -0,0 +1,77 @@
+# Copyright (c) 2019 Viosoft 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: VcmtsVNF
+        name: vcmtsvnf
+        short-name: vcmtsvnf
+        description: vCMTS Upstream-Downstream Kubernetes
+        vm-flavor:
+            vcpu-count: '4'
+            memory-mb: '4096'
+        mgmt-interface:
+            vdu-id: vcmtsvnf-kubernetes
+            {% if user is defined %}
+            user: '{{user}}'  # Value filled by vnfdgen
+            {% endif %}
+            {% if password is defined %}
+            password: '{{password}}'  # Value filled by vnfdgen
+            {% endif %}
+            {% if ip is defined %}
+            ip: '{{ip}}'  # Value filled by vnfdgen
+            {% endif %}
+            {% if key_filename is defined %}
+            key_filename: '{{key_filename}}'  # Value filled by vnfdgen
+            {% endif %}
+        connection-point:
+        -   name: xe0
+            type: VPORT
+        -   name: xe1
+            type: VPORT
+        vdu:
+        -   id: vcmtsvnf-kubernetes
+            name: vcmtsvnf-kubernetes
+            description: vCMTS Upstream-Downstream Kubernetes
+            external-interface:
+            -   name: xe0
+                virtual-interface:
+                    type: virtio
+                    # Substitution variables MUST be quoted. Otherwise Python can misinterpet them.
+                    vpci: '{{ interfaces.xe0.vpci }}' # Value filled by vnfdgen
+                    local_iface_name: eth0 # NOT TESTED YET '{{ interfaces.xe0.local_iface_name }}' # Value filled by vnfdgen
+                    driver: '{{ interfaces.xe0.driver}}' # Value filled by vnfdgen
+                    local_ip: '{{ interfaces.xe0.local_ip }}' # 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
+                    bandwidth: 10 Gbps
+                vnfd-connection-point-ref: xe0
+            -   name: xe1
+                virtual-interface:
+                    type: virtio
+                    # Substitution variables MUST be quoted. Otherwise Python can misinterpet them.
+                    vpci: '{{ interfaces.xe1.vpci }}' # Value filled by vnfdgen
+                    local_iface_name: eth0 # NOT TESTED YET '{{ interfaces.xe1.local_iface_name }}' # 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
+                    bandwidth: 10 Gbps
+                vnfd-connection-point-ref: xe0
+        benchmark:
+            kpi:
+                - upstream/bits_per_second
+
index 49f628e..40b21ca 100755 (executable)
@@ -66,16 +66,21 @@ run_functional_test() {
     fi
 }
 
-if [[ $opts =~ "--unit" ]]; then
-    run_tests
-fi
+# get file types of the last change on git
+file_types=$(git diff-tree -r --name-only HEAD~1 HEAD | awk -F[/] '{print $NF}' | awk -F[.] 'NF>1 {print $NF}' | uniq)
 
-if [[ $opts =~ "--coverage" ]]; then
-    run_coverage
-fi
+if [[ $file_types =~ "py" ]]; then
+    if [[ $opts =~ "--unit" ]]; then
+        run_tests
+    fi
 
-if [[ $opts =~ "--functional" ]]; then
-    run_functional_test
+    if [[ $opts =~ "--coverage" ]]; then
+        run_coverage
+    fi
+
+    if [[ $opts =~ "--functional" ]]; then
+        run_functional_test
+    fi
 fi
 
 if [[ -z $opts ]]; then
diff --git a/yardstick/network_services/helpers/vpp_helpers/__init__.py b/yardstick/network_services/helpers/vpp_helpers/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/yardstick/network_services/helpers/vpp_helpers/abstract_search_algorithm.py b/yardstick/network_services/helpers/vpp_helpers/abstract_search_algorithm.py
new file mode 100644 (file)
index 0000000..fced058
--- /dev/null
@@ -0,0 +1,53 @@
+# Copyright (c) 2019 Viosoft 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.
+#
+# This is a modified copy of
+# https://gerrit.fd.io/r/gitweb?p=csit.git;a=blob_plain;f=resources/libraries/python/MLRsearch/AbstractSearchAlgorithm.py;hb=HEAD
+
+
+from abc import ABCMeta, abstractmethod
+
+
+class AbstractSearchAlgorithm(object):
+    """Abstract class defining common API for search algorithms."""
+
+    __metaclass__ = ABCMeta
+
+    def __init__(self, measurer):
+        """Store the rate provider.
+
+        :param measurer: Object able to perform trial or composite measurements.
+        :type measurer: AbstractMeasurer.AbstractMeasurer
+        """
+        # TODO: Type check for AbstractMeasurer?
+        self.measurer = measurer
+
+    @abstractmethod
+    def narrow_down_ndr_and_pdr(
+            self, fail_rate, line_rate, packet_loss_ratio):
+        """Perform measurements to narrow down intervals, return them.
+
+        This will be renamed when custom loss ratio lists are supported.
+
+        :param fail_rate: Minimal target transmit rate [pps].
+        :param line_rate: Maximal target transmit rate [pps].
+        :param packet_loss_ratio: Fraction of packets lost, for PDR [1].
+        :type fail_rate: float
+        :type line_rate: float
+        :type packet_loss_ratio: float
+        :returns: Structure containing narrowed down intervals
+            and their measurements.
+        :rtype: NdrPdrResult.NdrPdrResult
+        """
+        # TODO: Do we agree on arguments related to precision or trial duration?
diff --git a/yardstick/network_services/helpers/vpp_helpers/multiple_loss_ratio_search.py b/yardstick/network_services/helpers/vpp_helpers/multiple_loss_ratio_search.py
new file mode 100644 (file)
index 0000000..582e3dc
--- /dev/null
@@ -0,0 +1,688 @@
+# Copyright (c) 2019 Viosoft 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.
+#
+# This is a modified copy of
+# https://gerrit.fd.io/r/gitweb?p=csit.git;a=blob_plain;f=resources/libraries/python/MLRsearch/MultipleLossRatioSearch.py;hb=HEAD
+
+import datetime
+import logging
+import math
+import time
+
+from yardstick.network_services.helpers.vpp_helpers.abstract_search_algorithm import \
+    AbstractSearchAlgorithm
+from yardstick.network_services.helpers.vpp_helpers.ndr_pdr_result import \
+    NdrPdrResult
+from yardstick.network_services.helpers.vpp_helpers.receive_rate_interval import \
+    ReceiveRateInterval
+from yardstick.network_services.helpers.vpp_helpers.receive_rate_measurement import \
+    ReceiveRateMeasurement
+
+LOGGING = logging.getLogger(__name__)
+
+
+class MultipleLossRatioSearch(AbstractSearchAlgorithm):
+    """Optimized binary search algorithm for finding NDR and PDR bounds.
+
+    Traditional binary search algorithm needs initial interval
+    (lower and upper bound), and returns final interval after bisecting
+    (until some exit condition is met).
+    The exit condition is usually related to the interval width,
+    (upper bound value minus lower bound value).
+
+    The optimized algorithm contains several improvements
+    aimed to reduce overall search time.
+
+    One improvement is searching for two intervals at once.
+    The intervals are for NDR (No Drop Rate) and PDR (Partial Drop Rate).
+
+    Next improvement is that the initial interval does not need to be valid.
+    Imagine initial interval (10, 11) where 11 is smaller
+    than the searched value.
+    The algorithm will try (11, 13) interval next, and if 13 is still smaller,
+    (13, 17) and so on, doubling width until the upper bound is valid.
+    The part when interval expands is called external search,
+    the part when interval is bisected is called internal search.
+
+    Next improvement is that trial measurements at small trial duration
+    can be used to find a reasonable interval for full trial duration search.
+    This results in more trials performed, but smaller overall duration
+    in general.
+
+    Next improvement is bisecting in logarithmic quantities,
+    so that exit criteria can be independent of measurement units.
+
+    Next improvement is basing the initial interval on receive rates.
+
+    Final improvement is exiting early if the minimal value
+    is not a valid lower bound.
+
+    The complete search consist of several phases,
+    each phase performing several trial measurements.
+    Initial phase creates initial interval based on receive rates
+    at maximum rate and at maximum receive rate (MRR).
+    Final phase and preceding intermediate phases are performing
+    external and internal search steps,
+    each resulting interval is the starting point for the next phase.
+    The resulting interval of final phase is the result of the whole algorithm.
+
+    Each non-initial phase uses its own trial duration and width goal.
+    Any non-initial phase stops searching (for NDR or PDR independently)
+    when minimum is not a valid lower bound (at current duration),
+    or all of the following is true:
+    Both bounds are valid, bound bounds are measured at the current phase
+    trial duration, interval width is less than the width goal
+    for current phase.
+
+    TODO: Review and update this docstring according to rst docs.
+    TODO: Support configurable number of Packet Loss Ratios.
+    """
+
+    class ProgressState(object):
+        """Structure containing data to be passed around in recursion."""
+
+        def __init__(
+                self, result, phases, duration, width_goal, packet_loss_ratio,
+                minimum_transmit_rate, maximum_transmit_rate):
+            """Convert and store the argument values.
+
+            :param result: Current measured NDR and PDR intervals.
+            :param phases: How many intermediate phases to perform
+                before the current one.
+            :param duration: Trial duration to use in the current phase [s].
+            :param width_goal: The goal relative width for the curreent phase.
+            :param packet_loss_ratio: PDR fraction for the current search.
+            :param minimum_transmit_rate: Minimum target transmit rate
+                for the current search [pps].
+            :param maximum_transmit_rate: Maximum target transmit rate
+                for the current search [pps].
+            :type result: NdrPdrResult.NdrPdrResult
+            :type phases: int
+            :type duration: float
+            :type width_goal: float
+            :type packet_loss_ratio: float
+            :type minimum_transmit_rate: float
+            :type maximum_transmit_rate: float
+            """
+            self.result = result
+            self.phases = int(phases)
+            self.duration = float(duration)
+            self.width_goal = float(width_goal)
+            self.packet_loss_ratio = float(packet_loss_ratio)
+            self.minimum_transmit_rate = float(minimum_transmit_rate)
+            self.maximum_transmit_rate = float(maximum_transmit_rate)
+
+    def __init__(self, measurer, latency=False, pkt_size=64,
+                 final_relative_width=0.005,
+                 final_trial_duration=30.0, initial_trial_duration=1.0,
+                 number_of_intermediate_phases=2, timeout=600.0, doublings=1):
+        """Store the measurer object and additional arguments.
+
+        :param measurer: Rate provider to use by this search object.
+        :param final_relative_width: Final lower bound transmit rate
+            cannot be more distant that this multiple of upper bound [1].
+        :param final_trial_duration: Trial duration for the final phase [s].
+        :param initial_trial_duration: Trial duration for the initial phase
+            and also for the first intermediate phase [s].
+        :param number_of_intermediate_phases: Number of intermediate phases
+            to perform before the final phase [1].
+        :param timeout: The search will fail itself when not finished
+            before this overall time [s].
+        :param doublings: How many doublings to do in external search step.
+            Default 1 is suitable for fairly stable tests,
+            less stable tests might get better overal duration with 2 or more.
+        :type measurer: AbstractMeasurer.AbstractMeasurer
+        :type final_relative_width: float
+        :type final_trial_duration: float
+        :type initial_trial_duration: int
+        :type number_of_intermediate_phases: int
+        :type timeout: float
+        :type doublings: int
+        """
+        super(MultipleLossRatioSearch, self).__init__(measurer)
+        self.latency = latency
+        self.pkt_size = int(pkt_size)
+        self.final_trial_duration = float(final_trial_duration)
+        self.final_relative_width = float(final_relative_width)
+        self.number_of_intermediate_phases = int(number_of_intermediate_phases)
+        self.initial_trial_duration = float(initial_trial_duration)
+        self.timeout = float(timeout)
+        self.doublings = int(doublings)
+
+        self.queue = None
+        self.port_pg_id = None
+        self.ports = []
+        self.test_data = {}
+        self.profiles = {}
+
+    @staticmethod
+    def double_relative_width(relative_width):
+        """Return relative width corresponding to double logarithmic width.
+
+        :param relative_width: The base relative width to double.
+        :type relative_width: float
+        :returns: The relative width of double logarithmic size.
+        :rtype: float
+        """
+        return 1.999 * relative_width - relative_width * relative_width
+        # The number should be 2.0, but we want to avoid rounding errors,
+        # and ensure half of double is not larger than the original value.
+
+    @staticmethod
+    def double_step_down(relative_width, current_bound):
+        """Return rate of double logarithmic width below.
+
+        :param relative_width: The base relative width to double.
+        :param current_bound: The current target transmit rate to move [pps].
+        :type relative_width: float
+        :type current_bound: float
+        :returns: Transmit rate smaller by logarithmically double width [pps].
+        :rtype: float
+        """
+        return current_bound * (
+                1.0 - MultipleLossRatioSearch.double_relative_width(
+            relative_width))
+
+    @staticmethod
+    def expand_down(relative_width, doublings, current_bound):
+        """Return rate of expanded logarithmic width below.
+
+        :param relative_width: The base relative width to double.
+        :param doublings: How many doublings to do for expansion.
+        :param current_bound: The current target transmit rate to move [pps].
+        :type relative_width: float
+        :type doublings: int
+        :type current_bound: float
+        :returns: Transmit rate smaller by logarithmically double width [pps].
+        :rtype: float
+        """
+        for _ in range(doublings):
+            relative_width = MultipleLossRatioSearch.double_relative_width(
+                relative_width)
+        return current_bound * (1.0 - relative_width)
+
+    @staticmethod
+    def double_step_up(relative_width, current_bound):
+        """Return rate of double logarithmic width above.
+
+        :param relative_width: The base relative width to double.
+        :param current_bound: The current target transmit rate to move [pps].
+        :type relative_width: float
+        :type current_bound: float
+        :returns: Transmit rate larger by logarithmically double width [pps].
+        :rtype: float
+        """
+        return current_bound / (
+                1.0 - MultipleLossRatioSearch.double_relative_width(
+            relative_width))
+
+    @staticmethod
+    def expand_up(relative_width, doublings, current_bound):
+        """Return rate of expanded logarithmic width above.
+
+        :param relative_width: The base relative width to double.
+        :param doublings: How many doublings to do for expansion.
+        :param current_bound: The current target transmit rate to move [pps].
+        :type relative_width: float
+        :type doublings: int
+        :type current_bound: float
+        :returns: Transmit rate smaller by logarithmically double width [pps].
+        :rtype: float
+        """
+        for _ in range(doublings):
+            relative_width = MultipleLossRatioSearch.double_relative_width(
+                relative_width)
+        return current_bound / (1.0 - relative_width)
+
+    @staticmethod
+    def half_relative_width(relative_width):
+        """Return relative width corresponding to half logarithmic width.
+
+        :param relative_width: The base relative width to halve.
+        :type relative_width: float
+        :returns: The relative width of half logarithmic size.
+        :rtype: float
+        """
+        return 1.0 - math.sqrt(1.0 - relative_width)
+
+    @staticmethod
+    def half_step_up(relative_width, current_bound):
+        """Return rate of half logarithmic width above.
+
+        :param relative_width: The base relative width to halve.
+        :param current_bound: The current target transmit rate to move [pps].
+        :type relative_width: float
+        :type current_bound: float
+        :returns: Transmit rate larger by logarithmically half width [pps].
+        :rtype: float
+        """
+        return current_bound / (
+                1.0 - MultipleLossRatioSearch.half_relative_width(
+            relative_width))
+
+    def init_generator(self, ports, port_pg_id, profiles, test_data, queue):
+        self.ports = ports
+        self.port_pg_id = port_pg_id
+        self.profiles = profiles
+        self.test_data = test_data
+        self.queue = queue
+        self.queue.cancel_join_thread()
+
+    def collect_kpi(self, stats, test_value):
+        samples = self.measurer.generate_samples(stats, self.ports,
+                                                 self.port_pg_id, self.latency)
+        samples.update(self.test_data)
+        LOGGING.info("Collect TG KPIs %s %s %s", datetime.datetime.now(),
+                     test_value, samples)
+        self.queue.put(samples)
+
+    def narrow_down_ndr_and_pdr(
+            self, minimum_transmit_rate, maximum_transmit_rate,
+            packet_loss_ratio):
+        """Perform initial phase, create state object, proceed with next phases.
+
+        :param minimum_transmit_rate: Minimal target transmit rate [pps].
+        :param maximum_transmit_rate: Maximal target transmit rate [pps].
+        :param packet_loss_ratio: Fraction of packets lost, for PDR [1].
+        :type minimum_transmit_rate: float
+        :type maximum_transmit_rate: float
+        :type packet_loss_ratio: float
+        :returns: Structure containing narrowed down intervals
+            and their measurements.
+        :rtype: NdrPdrResult.NdrPdrResult
+        :raises RuntimeError: If total duration is larger than timeout.
+        """
+        minimum_transmit_rate = float(minimum_transmit_rate)
+        maximum_transmit_rate = float(maximum_transmit_rate)
+        packet_loss_ratio = float(packet_loss_ratio)
+        line_measurement = self.measure(
+            self.initial_trial_duration, maximum_transmit_rate, self.latency)
+        initial_width_goal = self.final_relative_width
+        for _ in range(self.number_of_intermediate_phases):
+            initial_width_goal = self.double_relative_width(initial_width_goal)
+        max_lo = maximum_transmit_rate * (1.0 - initial_width_goal)
+        mrr = max(
+            minimum_transmit_rate,
+            min(max_lo, line_measurement.receive_rate))
+        mrr_measurement = self.measure(
+            self.initial_trial_duration, mrr, self.latency)
+        # Attempt to get narrower width.
+        if mrr_measurement.loss_fraction > 0.0:
+            max2_lo = mrr * (1.0 - initial_width_goal)
+            mrr2 = min(max2_lo, mrr_measurement.receive_rate)
+        else:
+            mrr2 = mrr / (1.0 - initial_width_goal)
+        if mrr2 > minimum_transmit_rate and mrr2 < maximum_transmit_rate:
+            line_measurement = mrr_measurement
+            mrr_measurement = self.measure(
+                self.initial_trial_duration, mrr2, self.latency)
+            if mrr2 > mrr:
+                buf = line_measurement
+                line_measurement = mrr_measurement
+                mrr_measurement = buf
+        starting_interval = ReceiveRateInterval(
+            mrr_measurement, line_measurement)
+        starting_result = NdrPdrResult(starting_interval, starting_interval)
+        state = self.ProgressState(
+            starting_result, self.number_of_intermediate_phases,
+            self.final_trial_duration, self.final_relative_width,
+            packet_loss_ratio, minimum_transmit_rate, maximum_transmit_rate)
+        state = self.ndrpdr(state)
+        result = state.result
+        # theor_max_thruput = 0
+        result_samples = {}
+
+        MultipleLossRatioSearch.display_single_bound(result_samples,
+                'NDR_LOWER', result.ndr_interval.measured_low.transmit_rate,
+                self.pkt_size, result.ndr_interval.measured_low.latency)
+        MultipleLossRatioSearch.display_single_bound(result_samples,
+                'NDR_UPPER', result.ndr_interval.measured_high.transmit_rate,
+                self.pkt_size)
+        MultipleLossRatioSearch.display_single_bound(result_samples,
+                'PDR_LOWER', result.pdr_interval.measured_low.transmit_rate,
+                self.pkt_size, result.pdr_interval.measured_low.latency)
+        MultipleLossRatioSearch.display_single_bound(result_samples,
+                'PDR_UPPER', result.pdr_interval.measured_high.transmit_rate,
+                self.pkt_size)
+        pdr_msg = self.check_ndrpdr_interval_validity(result_samples, "PDR",
+                                                      result.pdr_interval,
+                                                      packet_loss_ratio)
+        ndr_msg = self.check_ndrpdr_interval_validity(result_samples, "NDR",
+                                                      result.ndr_interval)
+        self.queue.put(result_samples)
+
+        LOGGING.debug("result_samples: %s", result_samples)
+        LOGGING.info(pdr_msg)
+        LOGGING.info(ndr_msg)
+
+        self.perform_additional_measurements_based_on_ndrpdr_result(result)
+
+        return result_samples
+
+    def _measure_and_update_state(self, state, transmit_rate):
+        """Perform trial measurement, update bounds, return new state.
+
+        :param state: State before this measurement.
+        :param transmit_rate: Target transmit rate for this measurement [pps].
+        :type state: ProgressState
+        :type transmit_rate: float
+        :returns: State after the measurement.
+        :rtype: ProgressState
+        """
+        # TODO: Implement https://stackoverflow.com/a/24683360
+        # to avoid the string manipulation if log verbosity is too low.
+        LOGGING.info("result before update: %s", state.result)
+        LOGGING.debug(
+            "relative widths in goals: %s", state.result.width_in_goals(
+                self.final_relative_width))
+        measurement = self.measure(state.duration, transmit_rate, self.latency)
+        ndr_interval = self._new_interval(
+            state.result.ndr_interval, measurement, 0.0)
+        pdr_interval = self._new_interval(
+            state.result.pdr_interval, measurement, state.packet_loss_ratio)
+        state.result = NdrPdrResult(ndr_interval, pdr_interval)
+        return state
+
+    @staticmethod
+    def _new_interval(old_interval, measurement, packet_loss_ratio):
+        """Return new interval with bounds updated according to the measurement.
+
+        :param old_interval: The current interval before the measurement.
+        :param measurement: The new meaqsurement to take into account.
+        :param packet_loss_ratio: Fraction for PDR (or zero for NDR).
+        :type old_interval: ReceiveRateInterval.ReceiveRateInterval
+        :type measurement: ReceiveRateMeasurement.ReceiveRateMeasurement
+        :type packet_loss_ratio: float
+        :returns: The updated interval.
+        :rtype: ReceiveRateInterval.ReceiveRateInterval
+        """
+        old_lo, old_hi = old_interval.measured_low, old_interval.measured_high
+        # Priority zero: direct replace if the target Tr is the same.
+        if measurement.target_tr in (old_lo.target_tr, old_hi.target_tr):
+            if measurement.target_tr == old_lo.target_tr:
+                return ReceiveRateInterval(measurement, old_hi)
+            else:
+                return ReceiveRateInterval(old_lo, measurement)
+        # Priority one: invalid lower bound allows only one type of update.
+        if old_lo.loss_fraction > packet_loss_ratio:
+            # We can only expand down, old bound becomes valid upper one.
+            if measurement.target_tr < old_lo.target_tr:
+                return ReceiveRateInterval(measurement, old_lo)
+            else:
+                return old_interval
+        # Lower bound is now valid.
+        # Next priorities depend on target Tr.
+        if measurement.target_tr < old_lo.target_tr:
+            # Lower external measurement, relevant only
+            # if the new measurement has high loss rate.
+            if measurement.loss_fraction > packet_loss_ratio:
+                # Returning the broader interval as old_lo
+                # would be invalid upper bound.
+                return ReceiveRateInterval(measurement, old_hi)
+        elif measurement.target_tr > old_hi.target_tr:
+            # Upper external measurement, only relevant for invalid upper bound.
+            if old_hi.loss_fraction <= packet_loss_ratio:
+                # Old upper bound becomes valid new lower bound.
+                return ReceiveRateInterval(old_hi, measurement)
+        else:
+            # Internal measurement, replaced boundary
+            # depends on measured loss fraction.
+            if measurement.loss_fraction > packet_loss_ratio:
+                # We have found a narrow valid interval,
+                # regardless of whether old upper bound was valid.
+                return ReceiveRateInterval(old_lo, measurement)
+            else:
+                # In ideal world, we would not want to shrink interval
+                # if upper bound is not valid.
+                # In the real world, we want to shrink it for
+                # "invalid upper bound at maximal rate" case.
+                return ReceiveRateInterval(measurement, old_hi)
+        # Fallback, the interval is unchanged by the measurement.
+        return old_interval
+
+    def ndrpdr(self, state):
+        """Pefrom trials for this phase. Return the new state when done.
+
+        :param state: State before this phase.
+        :type state: ProgressState
+        :returns: The updated state.
+        :rtype: ProgressState
+        :raises RuntimeError: If total duration is larger than timeout.
+        """
+        start_time = time.time()
+        if state.phases > 0:
+            # We need to finish preceding intermediate phases first.
+            saved_phases = state.phases
+            state.phases -= 1
+            # Preceding phases have shorter duration.
+            saved_duration = state.duration
+            duration_multiplier = state.duration / self.initial_trial_duration
+            phase_exponent = float(state.phases) / saved_phases
+            state.duration = self.initial_trial_duration * math.pow(
+                duration_multiplier, phase_exponent)
+            # Shorter durations do not need that narrow widths.
+            saved_width = state.width_goal
+            state.width_goal = self.double_relative_width(state.width_goal)
+            # Recurse.
+            state = self.ndrpdr(state)
+            # Restore the state for current phase.
+            state.duration = saved_duration
+            state.width_goal = saved_width
+            state.phases = saved_phases  # Not needed, but just in case.
+        LOGGING.info(
+            "starting iterations with duration %s and relative width goal %s",
+            state.duration, state.width_goal)
+        while 1:
+            if time.time() > start_time + self.timeout:
+                raise RuntimeError("Optimized search takes too long.")
+            # Order of priorities: invalid bounds (nl, pl, nh, ph),
+            # then narrowing relative Tr widths.
+            # Durations are not priorities yet,
+            # they will settle on their own hopefully.
+            ndr_lo = state.result.ndr_interval.measured_low
+            ndr_hi = state.result.ndr_interval.measured_high
+            pdr_lo = state.result.pdr_interval.measured_low
+            pdr_hi = state.result.pdr_interval.measured_high
+            ndr_rel_width = max(
+                state.width_goal, state.result.ndr_interval.rel_tr_width)
+            pdr_rel_width = max(
+                state.width_goal, state.result.pdr_interval.rel_tr_width)
+            # If we are hitting maximal or minimal rate, we cannot shift,
+            # but we can re-measure.
+            if ndr_lo.loss_fraction > 0.0:
+                if ndr_lo.target_tr > state.minimum_transmit_rate:
+                    new_tr = max(
+                        state.minimum_transmit_rate,
+                        self.expand_down(
+                            ndr_rel_width, self.doublings, ndr_lo.target_tr))
+                    LOGGING.info("ndr lo external %s", new_tr)
+                    state = self._measure_and_update_state(state, new_tr)
+                    continue
+                elif ndr_lo.duration < state.duration:
+                    LOGGING.info("ndr lo minimal re-measure")
+                    state = self._measure_and_update_state(
+                        state, state.minimum_transmit_rate)
+                    continue
+            if pdr_lo.loss_fraction > state.packet_loss_ratio:
+                if pdr_lo.target_tr > state.minimum_transmit_rate:
+                    new_tr = max(
+                        state.minimum_transmit_rate,
+                        self.expand_down(
+                            pdr_rel_width, self.doublings, pdr_lo.target_tr))
+                    LOGGING.info("pdr lo external %s", new_tr)
+                    state = self._measure_and_update_state(state, new_tr)
+                    continue
+                elif pdr_lo.duration < state.duration:
+                    LOGGING.info("pdr lo minimal re-measure")
+                    state = self._measure_and_update_state(
+                        state, state.minimum_transmit_rate)
+                    continue
+            if ndr_hi.loss_fraction <= 0.0:
+                if ndr_hi.target_tr < state.maximum_transmit_rate:
+                    new_tr = min(
+                        state.maximum_transmit_rate,
+                        self.expand_up(
+                            ndr_rel_width, self.doublings, ndr_hi.target_tr))
+                    LOGGING.info("ndr hi external %s", new_tr)
+                    state = self._measure_and_update_state(state, new_tr)
+                    continue
+                elif ndr_hi.duration < state.duration:
+                    LOGGING.info("ndr hi maximal re-measure")
+                    state = self._measure_and_update_state(
+                        state, state.maximum_transmit_rate)
+                    continue
+            if pdr_hi.loss_fraction <= state.packet_loss_ratio:
+                if pdr_hi.target_tr < state.maximum_transmit_rate:
+                    new_tr = min(
+                        state.maximum_transmit_rate,
+                        self.expand_up(
+                            pdr_rel_width, self.doublings, pdr_hi.target_tr))
+                    LOGGING.info("pdr hi external %s", new_tr)
+                    state = self._measure_and_update_state(state, new_tr)
+                    continue
+                elif pdr_hi.duration < state.duration:
+                    LOGGING.info("ndr hi maximal re-measure")
+                    state = self._measure_and_update_state(
+                        state, state.maximum_transmit_rate)
+                    continue
+            # If we are hitting maximum_transmit_rate,
+            # it is still worth narrowing width,
+            # hoping large enough loss fraction will happen.
+            # But if we are hitting the minimal rate (at current duration),
+            # no additional measurement will help with that,
+            # so we can stop narrowing in this phase.
+            if (ndr_lo.target_tr <= state.minimum_transmit_rate
+                    and ndr_lo.loss_fraction > 0.0):
+                ndr_rel_width = 0.0
+            if (pdr_lo.target_tr <= state.minimum_transmit_rate
+                    and pdr_lo.loss_fraction > state.packet_loss_ratio):
+                pdr_rel_width = 0.0
+            if ndr_rel_width > state.width_goal:
+                # We have to narrow NDR width first, as NDR internal search
+                # can invalidate PDR (but not vice versa).
+                new_tr = self.half_step_up(ndr_rel_width, ndr_lo.target_tr)
+                LOGGING.info("Bisecting for NDR at %s", new_tr)
+                state = self._measure_and_update_state(state, new_tr)
+                continue
+            if pdr_rel_width > state.width_goal:
+                # PDR iternal search.
+                new_tr = self.half_step_up(pdr_rel_width, pdr_lo.target_tr)
+                LOGGING.info("Bisecting for PDR at %s", new_tr)
+                state = self._measure_and_update_state(state, new_tr)
+                continue
+            # We do not need to improve width, but there still might be
+            # some measurements with smaller duration.
+            # We need to re-measure with full duration, possibly
+            # creating invalid bounds to resolve (thus broadening width).
+            if ndr_lo.duration < state.duration:
+                LOGGING.info("re-measuring NDR lower bound")
+                state = self._measure_and_update_state(state, ndr_lo.target_tr)
+                continue
+            if pdr_lo.duration < state.duration:
+                LOGGING.info("re-measuring PDR lower bound")
+                state = self._measure_and_update_state(state, pdr_lo.target_tr)
+                continue
+            # Except when lower bounds have high loss fraction, in that case
+            # we do not need to re-measure _upper_ bounds.
+            if ndr_hi.duration < state.duration and ndr_rel_width > 0.0:
+                LOGGING.info("re-measuring NDR upper bound")
+                state = self._measure_and_update_state(state, ndr_hi.target_tr)
+                continue
+            if pdr_hi.duration < state.duration and pdr_rel_width > 0.0:
+                LOGGING.info("re-measuring PDR upper bound")
+                state = self._measure_and_update_state(state, pdr_hi.target_tr)
+                continue
+            # Widths are narrow (or lower bound minimal), bound measurements
+            # are long enough, we can return.
+            LOGGING.info("phase done")
+            break
+        return state
+
+    def measure(self, duration, transmit_rate, latency):
+        duration = float(duration)
+        transmit_rate = float(transmit_rate)
+        # Trex needs target Tr per stream, but reports aggregate Tx and Dx.
+        unit_rate = str(transmit_rate / 2.0) + "pps"
+        stats = self.measurer.send_traffic_on_tg(self.ports, self.port_pg_id,
+                                                 duration, unit_rate,
+                                                 latency=latency)
+        self.measurer.client.reset(ports=self.ports)
+        self.measurer.client.clear_stats(ports=self.ports)
+        self.measurer.client.remove_all_streams(ports=self.ports)
+        for port, profile in self.profiles.items():
+            self.measurer.client.add_streams(profile, ports=[port])
+        self.collect_kpi(stats, unit_rate)
+        transmit_count = int(self.measurer.sent)
+        loss_count = int(self.measurer.loss)
+        measurement = ReceiveRateMeasurement(
+            duration, transmit_rate, transmit_count, loss_count)
+        measurement.latency = self.measurer.latency
+        return measurement
+
+    def perform_additional_measurements_based_on_ndrpdr_result(self, result):
+        duration = 5.0
+        rate = "{}{}".format(result.ndr_interval.measured_low.target_tr / 2.0,
+                             'pps')
+        for _ in range(0, 1):
+            stats = self.measurer.send_traffic_on_tg(self.ports,
+                                                     self.port_pg_id, duration,
+                                                     rate)
+            self.collect_kpi(stats, rate)
+            LOGGING.info('Traffic loss occurred: %s', self.measurer.loss)
+
+    @staticmethod
+    def display_single_bound(result_samples, result_type, rate_total, pkt_size,
+                             latency=None):
+        bandwidth_total = float(rate_total) * (pkt_size + 20) * 8 / (10 ** 9)
+
+        result_samples["Result_{}".format(result_type)] = {
+            "rate_total_pps": float(rate_total),
+            "bandwidth_total_Gbps": float(bandwidth_total),
+        }
+
+        if latency:
+            for item in latency:
+                if latency.index(item) == 0:
+                    name = "Result_{}_{}".format("stream0", result_type)
+                else:
+                    name = "Result_{}_{}".format("stream1", result_type)
+                lat_min, lat_avg, lat_max = item.split('/')
+                result_samples[name] = {
+                    "min_latency": float(lat_min),
+                    "avg_latency": float(lat_avg),
+                    "max_latency": float(lat_max),
+                }
+
+    @staticmethod
+    def check_ndrpdr_interval_validity(result_samples, result_type, interval,
+                                       packet_loss_ratio=0.0):
+        lower_bound = interval.measured_low
+        lower_bound_lf = lower_bound.loss_fraction
+
+        result_samples["Result_{}_packets_lost".format(result_type)] = {
+            "packet_loss_ratio": float(lower_bound_lf),
+            "packets_lost": float(lower_bound.loss_count),
+        }
+
+        if lower_bound_lf <= packet_loss_ratio:
+            return "Minimal rate loss fraction {} reach target {}".format(
+                lower_bound_lf, packet_loss_ratio)
+        else:
+            message = "Minimal rate loss fraction {} does not reach target {}".format(
+                lower_bound_lf, packet_loss_ratio)
+            if lower_bound_lf >= 1.0:
+                return '{}\nZero packets forwarded!'.format(message)
+            else:
+                return '{}\n{} packets lost.'.format(message,
+                                                     lower_bound.loss_count)
diff --git a/yardstick/network_services/helpers/vpp_helpers/ndr_pdr_result.py b/yardstick/network_services/helpers/vpp_helpers/ndr_pdr_result.py
new file mode 100644 (file)
index 0000000..34a97f9
--- /dev/null
@@ -0,0 +1,68 @@
+# Copyright (c) 2019 Viosoft 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.
+#
+# This is a modified copy of
+# https://gerrit.fd.io/r/gitweb?p=csit.git;a=blob_plain;f=resources/libraries/python/MLRsearch/NdrPdrResult.py;hb=HEAD
+
+from yardstick.network_services.helpers.vpp_helpers.receive_rate_interval import \
+    ReceiveRateInterval
+
+
+class NdrPdrResult(object):
+    """Two measurement intervals, return value of search algorithms.
+
+    Partial fraction is NOT part of the result. Pdr interval should be valid
+    for all partial fractions implied by the interval."""
+
+    def __init__(self, ndr_interval, pdr_interval):
+        """Store the measured intervals after checking argument types.
+
+        :param ndr_interval: Object containing data for NDR part of the result.
+        :param pdr_interval: Object containing data for PDR part of the result.
+        :type ndr_interval: ReceiveRateInterval.ReceiveRateInterval
+        :type pdr_interval: ReceiveRateInterval.ReceiveRateInterval
+        """
+        # TODO: Type checking is not very pythonic,
+        # perhaps users can fix wrong usage without it?
+        if not isinstance(ndr_interval, ReceiveRateInterval):
+            raise TypeError("ndr_interval, is not a ReceiveRateInterval: "
+                            "{ndr!r}".format(ndr=ndr_interval))
+        if not isinstance(pdr_interval, ReceiveRateInterval):
+            raise TypeError("pdr_interval, is not a ReceiveRateInterval: "
+                            "{pdr!r}".format(pdr=pdr_interval))
+        self.ndr_interval = ndr_interval
+        self.pdr_interval = pdr_interval
+
+    def width_in_goals(self, relative_width_goal):
+        """Return a debug string related to current widths in logarithmic scale.
+
+        :param relative_width_goal: Upper bound times this is the goal
+            difference between upper bound and lower bound.
+        :type relative_width_goal: float
+        :returns: Message containing NDR and PDR widths in goals.
+        :rtype: str
+        """
+        return "ndr {ndr_in_goals}; pdr {pdr_in_goals}".format(
+            ndr_in_goals=self.ndr_interval.width_in_goals(relative_width_goal),
+            pdr_in_goals=self.pdr_interval.width_in_goals(relative_width_goal))
+
+    def __str__(self):
+        """Return string as tuple of named values."""
+        return "NDR={ndr!s};PDR={pdr!s}".format(
+            ndr=self.ndr_interval, pdr=self.pdr_interval)
+
+    def __repr__(self):
+        """Return string evaluable as a constructor call."""
+        return "NdrPdrResult(ndr_interval={ndr!r},pdr_interval={pdr!r})".format(
+            ndr=self.ndr_interval, pdr=self.pdr_interval)
diff --git a/yardstick/network_services/helpers/vpp_helpers/receive_rate_interval.py b/yardstick/network_services/helpers/vpp_helpers/receive_rate_interval.py
new file mode 100644 (file)
index 0000000..517a99c
--- /dev/null
@@ -0,0 +1,88 @@
+# Copyright (c) 2019 Viosoft 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.
+#
+# This is a modified copy of
+# https://gerrit.fd.io/r/gitweb?p=csit.git;a=blob_plain;f=resources/libraries/python/MLRsearch/ReceiveRateInterval.py;hb=HEAD
+
+import math
+
+from yardstick.network_services.helpers.vpp_helpers.receive_rate_measurement import \
+    ReceiveRateMeasurement
+
+
+class ReceiveRateInterval(object):
+    """Structure defining two Rr measurements, and their relation."""
+
+    def __init__(self, measured_low, measured_high):
+        """Store the bound measurements after checking argument types.
+
+        :param measured_low: Measurement for the lower bound.
+        :param measured_high: Measurement for the upper bound.
+        :type measured_low: ReceiveRateMeasurement.ReceiveRateMeasurement
+        :type measured_high: ReceiveRateMeasurement.ReceiveRateMeasurement
+        """
+        # TODO: Type checking is not very pythonic,
+        # perhaps users can fix wrong usage without it?
+        if not isinstance(measured_low, ReceiveRateMeasurement):
+            raise TypeError("measured_low is not a ReceiveRateMeasurement: "
+                            "{low!r}".format(low=measured_low))
+        if not isinstance(measured_high, ReceiveRateMeasurement):
+            raise TypeError("measured_high is not a ReceiveRateMeasurement: "
+                            "{high!r}".format(high=measured_high))
+        self.measured_low = measured_low
+        self.measured_high = measured_high
+        # Declare secondary quantities to appease pylint.
+        self.abs_tr_width = None
+        """Absolute width of target transmit rate. Upper minus lower."""
+        self.rel_tr_width = None
+        """Relative width of target transmit rate. Absolute divided by upper."""
+        self.sort()
+
+    def sort(self):
+        """Sort bounds by target Tr, compute secondary quantities."""
+        if self.measured_low.target_tr > self.measured_high.target_tr:
+            self.measured_low, self.measured_high = (
+                self.measured_high, self.measured_low)
+        self.abs_tr_width = (
+                self.measured_high.target_tr - self.measured_low.target_tr)
+        self.rel_tr_width = round(
+            self.abs_tr_width / self.measured_high.target_tr, 5)
+
+    def width_in_goals(self, relative_width_goal):
+        """Return float value.
+
+        Relative width goal is some (negative) value on logarithmic scale.
+        Current relative width is another logarithmic value.
+        Return the latter divided by the former.
+        This is useful when investigating how did surprising widths come to be.
+
+        :param relative_width_goal: Upper bound times this is the goal
+            difference between upper bound and lower bound.
+        :type relative_width_goal: float
+        :returns: Current width as logarithmic multiple of goal width [1].
+        :rtype: float
+        """
+        return round(math.log(1.0 - self.rel_tr_width) / math.log(
+            1.0 - relative_width_goal), 5)
+
+    def __str__(self):
+        """Return string as half-open interval."""
+        return "[{low!s};{high!s})".format(
+            low=self.measured_low, high=self.measured_high)
+
+    def __repr__(self):
+        """Return string evaluable as a constructor call."""
+        return ("ReceiveRateInterval(measured_low={low!r}"
+                ",measured_high={high!r})".format(low=self.measured_low,
+                                                  high=self.measured_high))
diff --git a/yardstick/network_services/helpers/vpp_helpers/receive_rate_measurement.py b/yardstick/network_services/helpers/vpp_helpers/receive_rate_measurement.py
new file mode 100644 (file)
index 0000000..2c59ea1
--- /dev/null
@@ -0,0 +1,58 @@
+# Copyright (c) 2019 Viosoft 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.
+#
+# This is a modified copy of
+# https://gerrit.fd.io/r/gitweb?p=csit.git;a=blob_plain;f=resources/libraries/python/MLRsearch/ReceiveRateMeasurement.py;hb=HEAD
+
+
+class ReceiveRateMeasurement(object):
+    """Structure defining the result of single Rr measurement."""
+
+    def __init__(self, duration, target_tr, transmit_count, loss_count):
+        """Constructor, normalize primary and compute secondary quantities.
+
+        :param duration: Measurement duration [s].
+        :param target_tr: Target transmit rate [pps].
+            If bidirectional traffic is measured, this is bidirectional rate.
+        :param transmit_count: Number of packets transmitted [1].
+        :param loss_count: Number of packets transmitted but not received [1].
+        :type duration: float
+        :type target_tr: float
+        :type transmit_count: int
+        :type loss_count: int
+        """
+        self.duration = float(duration)
+        self.target_tr = float(target_tr)
+        self.transmit_count = int(transmit_count)
+        self.loss_count = int(loss_count)
+        self.receive_count = round(transmit_count - loss_count, 5)
+        self.transmit_rate = round(transmit_count / self.duration, 5)
+        self.loss_rate = round(loss_count / self.duration, 5)
+        self.receive_rate = round(self.receive_count / self.duration, 5)
+        self.loss_fraction = round(
+            float(self.loss_count) / self.transmit_count, 5)
+        # TODO: Do we want to store also the real time (duration + overhead)?
+
+    def __str__(self):
+        """Return string reporting input and loss fraction."""
+        return "d={dur!s},Tr={rate!s},Df={frac!s}".format(
+            dur=self.duration, rate=self.target_tr, frac=self.loss_fraction)
+
+    def __repr__(self):
+        """Return string evaluable as a constructor call."""
+        return ("ReceiveRateMeasurement(duration={dur!r},target_tr={rate!r}"
+                ",transmit_count={trans!r},loss_count={loss!r})".format(
+            dur=self.duration, rate=self.target_tr,
+            trans=self.transmit_count,
+            loss=self.loss_count))
index 0f8185f..412e4e6 100644 (file)
 # limitations under the License.
 
 import datetime
+import ipaddress
 import logging
-from random import choice
-from string import ascii_letters
+import random
+import string
 
-from ipaddress import ip_address
 from trex_stl_lib import api as Pkt
 from trex_stl_lib import trex_stl_client
 from trex_stl_lib import trex_stl_packet_builder_scapy
 from trex_stl_lib import trex_stl_streams
 
 from yardstick.common import constants
+from yardstick.network_services.helpers.vpp_helpers.multiple_loss_ratio_search import \
+    MultipleLossRatioSearch
 from yardstick.network_services.traffic_profile.rfc2544 import RFC2544Profile, \
     PortPgIDMap
 from yardstick.network_services.traffic_profile.trex_traffic_profile import IP, \
@@ -37,6 +39,10 @@ class VppRFC2544Profile(RFC2544Profile):
     def __init__(self, traffic_generator):
         super(VppRFC2544Profile, self).__init__(traffic_generator)
 
+        tp_cfg = traffic_generator["traffic_profile"]
+        self.number_of_intermediate_phases = tp_cfg.get("intermediate_phases",
+                                                        2)
+
         self.duration = self.config.duration
         self.precision = self.config.test_precision
         self.lower_bound = self.config.lower_bound
@@ -85,7 +91,7 @@ class VppRFC2544Profile(RFC2544Profile):
     def _gen_payload(length):
         payload = ""
         for _ in range(length):
-            payload += choice(ascii_letters)
+            payload += random.choice(string.ascii_letters)
 
         return payload
 
@@ -153,8 +159,9 @@ class VppRFC2544Profile(RFC2544Profile):
                                 dst=dst_start_ip,
                                 proto=outer_l3v4['proto'])
         if self.flow > 1:
-            dst_start_int = int(ip_address(str(dst_start_ip)))
-            dst_end_ip_new = ip_address(dst_start_int + self.flow - 1)
+            dst_start_int = int(ipaddress.ip_address(str(dst_start_ip)))
+            dst_end_ip_new = ipaddress.ip_address(
+                dst_start_int + self.flow - 1)
             # self._set_proto_addr(IP, SRC, outer_l3v4['srcip4'], outer_l3v4['count'])
             self._set_proto_addr(IP, DST,
                                  "{start_ip}-{end_ip}".format(
@@ -248,8 +255,19 @@ class VppRFC2544Profile(RFC2544Profile):
 
     def binary_search_with_optimized(self, traffic_generator, duration,
                                      timeout, test_data):
-        # TODO Support FD.io Multiple Loss Ratio search (MLRsearch)
-        pass
+        self.queue.cancel_join_thread()
+        algorithm = MultipleLossRatioSearch(
+            measurer=traffic_generator, latency=self.enable_latency,
+            pkt_size=self.pkt_size,
+            final_trial_duration=duration,
+            final_relative_width=self.step_interval / 100,
+            number_of_intermediate_phases=self.number_of_intermediate_phases,
+            initial_trial_duration=1,
+            timeout=timeout)
+        algorithm.init_generator(self.ports, self.port_pg_id, self.profiles,
+                                 test_data, self.queue)
+        return algorithm.narrow_down_ndr_and_pdr(10000, self.max_rate,
+                                                 self.tolerance_high)
 
     def binary_search(self, traffic_generator, duration, tolerance_value,
                       test_data):
diff --git a/yardstick/network_services/vnf_generic/vnf/tg_vcmts_pktgen.py b/yardstick/network_services/vnf_generic/vnf/tg_vcmts_pktgen.py
new file mode 100755 (executable)
index 0000000..c6df9d0
--- /dev/null
@@ -0,0 +1,215 @@
+# Copyright (c) 2019 Viosoft 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.
+
+import logging
+import time
+import socket
+import yaml
+import os
+
+from yardstick.network_services.vnf_generic.vnf import sample_vnf
+from yardstick.common import exceptions
+
+
+LOG = logging.getLogger(__name__)
+
+
+class PktgenHelper(object):
+
+    RETRY_SECONDS = 0.5
+    RETRY_COUNT = 20
+    CONNECT_TIMEOUT = 5
+
+    def __init__(self, host, port=23000):
+        self.host = host
+        self.port = port
+        self.connected = False
+
+    def _connect(self):
+        self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        ret = True
+        try:
+            self._sock.settimeout(self.CONNECT_TIMEOUT)
+            self._sock.connect((self.host, self.port))
+        except (socket.gaierror, socket.error, socket.timeout):
+            self._sock.close()
+            ret = False
+
+        return ret
+
+    def connect(self):
+        if self.connected:
+            return True
+        LOG.info("Connecting to pktgen instance at %s...", self.host)
+        for idx in range(self.RETRY_COUNT):
+            self.connected = self._connect()
+            if self.connected:
+                return True
+            LOG.debug("Connection attempt %d: Unable to connect to %s, " \
+                      "retrying in %d seconds",
+                      idx, self.host, self.RETRY_SECONDS)
+            time.sleep(self.RETRY_SECONDS)
+
+        LOG.error("Unable to connect to pktgen instance on %s !",
+                  self.host)
+        return False
+
+
+    def send_command(self, command):
+        if not self.connected:
+            LOG.error("Pktgen socket is not connected")
+            return False
+
+        try:
+            self._sock.sendall((command + "\n").encode())
+            time.sleep(1)
+        except (socket.timeout, socket.error):
+            LOG.error("Error sending command '%s'", command)
+            return False
+
+        return True
+
+
+class VcmtsPktgenSetupEnvHelper(sample_vnf.SetupEnvHelper):
+
+    BASE_PARAMETERS = "export LUA_PATH=/vcmts/Pktgen.lua;"\
+                    + "export CMK_PROC_FS=/host/proc;"
+
+    PORTS_COUNT = 8
+
+    def generate_pcap_filename(self, port_cfg):
+        return port_cfg['traffic_type'] + "_" + port_cfg['num_subs'] \
+             + "cms_" + port_cfg['num_ofdm'] + "ofdm.pcap"
+
+    def find_port_cfg(self, ports_cfg, port_name):
+        for port_cfg in ports_cfg:
+            if port_name in port_cfg:
+                return port_cfg
+        return None
+
+    def build_pktgen_parameters(self, pod_cfg):
+        ports_cfg = pod_cfg['ports']
+        port_cfg = list()
+
+        for i in range(self.PORTS_COUNT):
+            port_cfg.append(self.find_port_cfg(ports_cfg, 'port_' + str(i)))
+
+        pktgen_parameters = self.BASE_PARAMETERS + " " \
+             + " /pktgen-config/setup.sh " + pod_cfg['pktgen_id'] \
+             + " " + pod_cfg['num_ports']
+
+        for i in range(self.PORTS_COUNT):
+            pktgen_parameters += " " + port_cfg[i]['net_pktgen']
+
+        for i in range(self.PORTS_COUNT):
+            pktgen_parameters += " " + self.generate_pcap_filename(port_cfg[i])
+
+        return pktgen_parameters
+
+    def start_pktgen(self, pod_cfg):
+        self.ssh_helper.drop_connection()
+        cmd = self.build_pktgen_parameters(pod_cfg)
+        LOG.debug("Executing: '%s'", cmd)
+        self.ssh_helper.send_command(cmd)
+        LOG.info("Pktgen executed")
+
+    def setup_vnf_environment(self):
+        pass
+
+
+class VcmtsPktgen(sample_vnf.SampleVNFTrafficGen):
+
+    TG_NAME = 'VcmtsPktgen'
+    APP_NAME = 'VcmtsPktgen'
+    RUN_WAIT = 4
+    DEFAULT_RATE = 8.0
+
+    PKTGEN_BASE_PORT = 23000
+
+    def __init__(self, name, vnfd, setup_env_helper_type=None,
+                 resource_helper_type=None):
+        if setup_env_helper_type is None:
+            setup_env_helper_type = VcmtsPktgenSetupEnvHelper
+        super(VcmtsPktgen, self).__init__(
+            name, vnfd, setup_env_helper_type, resource_helper_type)
+
+        self.pktgen_address = vnfd['mgmt-interface']['ip']
+        LOG.info("Pktgen container '%s', IP: %s", name, self.pktgen_address)
+
+    def extract_pod_cfg(self, pktgen_pods_cfg, pktgen_id):
+        for pod_cfg in pktgen_pods_cfg:
+            if pod_cfg['pktgen_id'] == pktgen_id:
+                return pod_cfg
+        return None
+
+    def instantiate(self, scenario_cfg, context_cfg):
+        super(VcmtsPktgen, self).instantiate(scenario_cfg, context_cfg)
+        self._start_server()
+        options = scenario_cfg.get('options', {})
+        self.pktgen_rate = options.get('pktgen_rate', self.DEFAULT_RATE)
+
+        try:
+            pktgen_values_filepath = options['pktgen_values']
+        except KeyError:
+            raise KeyError("Missing pktgen_values key in scenario options" \
+                           "section of the task definition file")
+
+        if not os.path.isfile(pktgen_values_filepath):
+            raise RuntimeError("The pktgen_values file path provided " \
+                               "does not exists")
+
+        # The yaml_loader.py (SafeLoader) underlying regex has an issue
+        # with reading PCI addresses (processed as double). so the
+        # BaseLoader is used here.
+        with open(pktgen_values_filepath) as stream:
+            pktgen_values = yaml.load(stream, Loader=yaml.BaseLoader)
+
+        if pktgen_values == None:
+            raise RuntimeError("Error reading pktgen_values file provided (" +
+                               pktgen_values_filepath + ")")
+
+        self.pktgen_id = int(options[self.name]['pktgen_id'])
+        self.resource_helper.pktgen_id = self.pktgen_id
+
+        self.pktgen_helper = PktgenHelper(self.pktgen_address,
+                                          self.PKTGEN_BASE_PORT + self.pktgen_id)
+
+        pktgen_pods_cfg = pktgen_values['topology']['pktgen_pods']
+
+        self.pod_cfg = self.extract_pod_cfg(pktgen_pods_cfg,
+                                            str(self.pktgen_id))
+
+        if self.pod_cfg == None:
+            raise KeyError("Pktgen with id " + str(self.pktgen_id) + \
+                           " was not found")
+
+        self.setup_helper.start_pktgen(self.pod_cfg)
+
+    def run_traffic(self, traffic_profile):
+        if not self.pktgen_helper.connect():
+            raise exceptions.PktgenActionError(command="connect")
+        LOG.info("Connected to pktgen instance at %s", self.pktgen_address)
+
+        commands = []
+        for i in range(self.setup_helper.PORTS_COUNT):
+            commands.append('pktgen.set("' + str(i) + '", "rate", ' +
+                            "%0.1f" % self.pktgen_rate + ');')
+
+        commands.append('pktgen.start("all");')
+
+        for command in commands:
+            if self.pktgen_helper.send_command(command):
+                LOG.debug("Command '%s' sent to pktgen", command)
+        LOG.info("Traffic started on %s...", self.name)
+        return True
diff --git a/yardstick/network_services/vnf_generic/vnf/vcmts_vnf.py b/yardstick/network_services/vnf_generic/vnf/vcmts_vnf.py
new file mode 100755 (executable)
index 0000000..0b48ef4
--- /dev/null
@@ -0,0 +1,273 @@
+# Copyright (c) 2019 Viosoft 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.
+
+import logging
+import os
+import yaml
+
+from influxdb import InfluxDBClient
+
+from yardstick.network_services.vnf_generic.vnf.sample_vnf import SetupEnvHelper
+from yardstick.common import constants
+from yardstick.common import exceptions
+from yardstick.network_services.vnf_generic.vnf.base import GenericVNF
+from yardstick.network_services.vnf_generic.vnf.sample_vnf import ScenarioHelper
+from yardstick.network_services.vnf_generic.vnf.vnf_ssh_helper import VnfSshHelper
+from yardstick.network_services.utils import get_nsb_option
+
+
+LOG = logging.getLogger(__name__)
+
+
+class InfluxDBHelper(object):
+
+    INITIAL_VALUE = 'now() - 1m'
+
+    def __init__(self, vcmts_influxdb_ip, vcmts_influxdb_port):
+        self._vcmts_influxdb_ip = vcmts_influxdb_ip
+        self._vcmts_influxdb_port = vcmts_influxdb_port
+        self._last_upstream_rx = self.INITIAL_VALUE
+        self._last_values_time = dict()
+
+    def start(self):
+        self._read_client = InfluxDBClient(host=self._vcmts_influxdb_ip,
+                                           port=self._vcmts_influxdb_port,
+                                           database='collectd')
+        self._write_client = InfluxDBClient(host=constants.INFLUXDB_IP,
+                                            port=constants.INFLUXDB_PORT,
+                                            database='collectd')
+
+    def _get_last_value_time(self, measurement):
+        if measurement in self._last_values_time:
+            return self._last_values_time[measurement]
+        return self.INITIAL_VALUE
+
+    def _set_last_value_time(self, measurement, time):
+        self._last_values_time[measurement] = "'" + time + "'"
+
+    def _query_measurement(self, measurement):
+        # There is a delay before influxdb flushes the data
+        query = "SELECT * FROM " + measurement + " WHERE time > " \
+                + self._get_last_value_time(measurement) \
+                + " ORDER BY time ASC;"
+        query_result = self._read_client.query(query)
+        if len(query_result.keys()) == 0:
+            return None
+        return query_result.get_points(measurement)
+
+    def _rw_measurment(self, measurement, columns):
+        query_result = self._query_measurement(measurement)
+        if query_result == None:
+            return
+
+        points_to_write = list()
+        for entry in query_result:
+            point = {
+                "measurement": measurement,
+                "tags": {
+                    "type": entry['type'],
+                    "host": entry['host']
+                },
+                "time": entry['time'],
+                "fields": {}
+            }
+
+            for column in columns:
+                if column == 'value':
+                    point["fields"][column] = float(entry[column])
+                else:
+                    point["fields"][column] = entry[column]
+
+            points_to_write.append(point)
+            self._set_last_value_time(measurement, entry['time'])
+
+        # Write the points to yardstick database
+        if self._write_client.write_points(points_to_write):
+            LOG.debug("%d new points written to '%s' measurement",
+                      len(points_to_write), measurement)
+
+    def copy_kpi(self):
+        self._rw_measurment("cpu_value", ["instance", "type_instance", "value"])
+        self._rw_measurment("cpufreq_value", ["type_instance", "value"])
+        self._rw_measurment("downstream_rx", ["value"])
+        self._rw_measurment("downstream_tx", ["value"])
+        self._rw_measurment("downstream_value", ["value"])
+        self._rw_measurment("ds_per_cm_value", ["instance", "value"])
+        self._rw_measurment("intel_rdt_value", ["instance", "type_instance", "value"])
+        self._rw_measurment("turbostat_value", ["instance", "type_instance", "value"])
+        self._rw_measurment("upstream_rx", ["value"])
+        self._rw_measurment("upstream_tx", ["value"])
+        self._rw_measurment("upstream_value", ["value"])
+
+
+class VcmtsdSetupEnvHelper(SetupEnvHelper):
+
+    BASE_PARAMETERS = "export LD_LIBRARY_PATH=/opt/collectd/lib:;"\
+                    + "export CMK_PROC_FS=/host/proc;"
+
+    def build_us_parameters(self, pod_cfg):
+        return self.BASE_PARAMETERS + " " \
+             + " /opt/bin/cmk isolate --conf-dir=/etc/cmk" \
+             + " --socket-id=" + pod_cfg['cpu_socket_id'] \
+             + " --pool=shared" \
+             + " /vcmts-config/run_upstream.sh " + pod_cfg['sg_id'] \
+             + " " + pod_cfg['ds_core_type'] \
+             + " " + pod_cfg['num_ofdm'] + "ofdm" \
+             + " " + pod_cfg['num_subs'] + "cm" \
+             + " " + pod_cfg['cm_crypto'] \
+             + " " + pod_cfg['qat'] \
+             + " " + pod_cfg['net_us'] \
+             + " " + pod_cfg['power_mgmt']
+
+    def build_ds_parameters(self, pod_cfg):
+        return self.BASE_PARAMETERS + " " \
+             + " /opt/bin/cmk isolate --conf-dir=/etc/cmk" \
+             + " --socket-id=" + pod_cfg['cpu_socket_id'] \
+             + " --pool=" + pod_cfg['ds_core_type'] \
+             + " /vcmts-config/run_downstream.sh " + pod_cfg['sg_id'] \
+             + " " + pod_cfg['ds_core_type'] \
+             + " " + pod_cfg['ds_core_pool_index'] \
+             + " " + pod_cfg['num_ofdm'] + "ofdm" \
+             + " " + pod_cfg['num_subs'] + "cm" \
+             + " " + pod_cfg['cm_crypto'] \
+             + " " + pod_cfg['qat'] \
+             + " " + pod_cfg['net_ds'] \
+             + " " + pod_cfg['power_mgmt']
+
+    def build_cmd(self, stream_dir, pod_cfg):
+        if stream_dir == 'ds':
+            return self.build_ds_parameters(pod_cfg)
+        else:
+            return self.build_us_parameters(pod_cfg)
+
+    def run_vcmtsd(self, stream_dir, pod_cfg):
+        cmd = self.build_cmd(stream_dir, pod_cfg)
+        LOG.debug("Executing %s", cmd)
+        self.ssh_helper.send_command(cmd)
+
+    def setup_vnf_environment(self):
+        pass
+
+
+class VcmtsVNF(GenericVNF):
+
+    RUN_WAIT = 4
+
+    def __init__(self, name, vnfd):
+        super(VcmtsVNF, self).__init__(name, vnfd)
+        self.name = name
+        self.bin_path = get_nsb_option('bin_path', '')
+        self.scenario_helper = ScenarioHelper(self.name)
+        self.ssh_helper = VnfSshHelper(self.vnfd_helper.mgmt_interface, self.bin_path)
+
+        self.setup_helper = VcmtsdSetupEnvHelper(self.vnfd_helper,
+                                                 self.ssh_helper,
+                                                 self.scenario_helper)
+
+    def extract_pod_cfg(self, vcmts_pods_cfg, sg_id):
+        for pod_cfg in vcmts_pods_cfg:
+            if pod_cfg['sg_id'] == sg_id:
+                return pod_cfg
+
+    def instantiate(self, scenario_cfg, context_cfg):
+        self._update_collectd_options(scenario_cfg, context_cfg)
+        self.scenario_helper.scenario_cfg = scenario_cfg
+        self.context_cfg = context_cfg
+
+        options = scenario_cfg.get('options', {})
+
+        try:
+            self.vcmts_influxdb_ip = options['vcmts_influxdb_ip']
+            self.vcmts_influxdb_port = options['vcmts_influxdb_port']
+        except KeyError:
+            raise KeyError("Missing destination InfluxDB details in scenario" \
+                           " section of the task definition file")
+
+        try:
+            vcmtsd_values_filepath = options['vcmtsd_values']
+        except KeyError:
+            raise KeyError("Missing vcmtsd_values key in scenario options" \
+                           "section of the task definition file")
+
+        if not os.path.isfile(vcmtsd_values_filepath):
+            raise RuntimeError("The vcmtsd_values file path provided " \
+                               "does not exists")
+
+        # The yaml_loader.py (SafeLoader) underlying regex has an issue
+        # with reading PCI addresses (processed as double). so the
+        # BaseLoader is used here.
+        with open(vcmtsd_values_filepath) as stream:
+            vcmtsd_values = yaml.load(stream, Loader=yaml.BaseLoader)
+
+        if vcmtsd_values == None:
+            raise RuntimeError("Error reading vcmtsd_values file provided (" +
+                               vcmtsd_values_filepath + ")")
+
+        vnf_options = options.get(self.name, {})
+        sg_id = str(vnf_options['sg_id'])
+        stream_dir = vnf_options['stream_dir']
+
+        try:
+            vcmts_pods_cfg = vcmtsd_values['topology']['vcmts_pods']
+        except KeyError:
+            raise KeyError("Missing vcmts_pods key in the " \
+                           "vcmtsd_values file provided")
+
+        pod_cfg = self.extract_pod_cfg(vcmts_pods_cfg, sg_id)
+        if pod_cfg == None:
+            raise exceptions.IncorrectConfig(error_msg="Service group " + sg_id + " not found")
+
+        self.setup_helper.run_vcmtsd(stream_dir, pod_cfg)
+
+    def _update_collectd_options(self, scenario_cfg, context_cfg):
+        scenario_options = scenario_cfg.get('options', {})
+        generic_options = scenario_options.get('collectd', {})
+        scenario_node_options = scenario_options.get(self.name, {})\
+            .get('collectd', {})
+        context_node_options = context_cfg.get('nodes', {})\
+            .get(self.name, {}).get('collectd', {})
+
+        options = generic_options
+        self._update_options(options, scenario_node_options)
+        self._update_options(options, context_node_options)
+
+        self.setup_helper.collectd_options = options
+
+    def _update_options(self, options, additional_options):
+        for k, v in additional_options.items():
+            if isinstance(v, dict) and k in options:
+                options[k].update(v)
+            else:
+                options[k] = v
+
+    def wait_for_instantiate(self):
+        pass
+
+    def terminate(self):
+        pass
+
+    def scale(self, flavor=""):
+        pass
+
+    def collect_kpi(self):
+        self.influxdb_helper.copy_kpi()
+        return {"n/a": "n/a"}
+
+    def start_collect(self):
+        self.influxdb_helper = InfluxDBHelper(self.vcmts_influxdb_ip,
+                                              self.vcmts_influxdb_port)
+        self.influxdb_helper.start()
+
+    def stop_collect(self):
+        pass
diff --git a/yardstick/tests/unit/network_services/helpers/vpp_helpers/__init__.py b/yardstick/tests/unit/network_services/helpers/vpp_helpers/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/yardstick/tests/unit/network_services/helpers/vpp_helpers/test_multiple_loss_ratio_search.py b/yardstick/tests/unit/network_services/helpers/vpp_helpers/test_multiple_loss_ratio_search.py
new file mode 100644 (file)
index 0000000..d314554
--- /dev/null
@@ -0,0 +1,2164 @@
+# Copyright (c) 2019 Viosoft 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.
+
+import unittest
+
+import mock
+
+from yardstick.network_services.helpers.vpp_helpers.multiple_loss_ratio_search import \
+    MultipleLossRatioSearch
+from yardstick.network_services.helpers.vpp_helpers.ndr_pdr_result import \
+    NdrPdrResult
+from yardstick.network_services.helpers.vpp_helpers.receive_rate_interval import \
+    ReceiveRateInterval
+from yardstick.network_services.helpers.vpp_helpers.receive_rate_measurement import \
+    ReceiveRateMeasurement
+from yardstick.network_services.traffic_profile.rfc2544 import PortPgIDMap
+
+
+class TestMultipleLossRatioSearch(unittest.TestCase):
+
+    def test___init__(self):
+        algorithm = MultipleLossRatioSearch(measurer=mock.Mock(), latency=True,
+                                            pkt_size=64,
+                                            final_trial_duration=30,
+                                            final_relative_width=0.005,
+                                            number_of_intermediate_phases=2,
+                                            initial_trial_duration=1,
+                                            timeout=720)
+        self.assertEqual(True, algorithm.latency)
+        self.assertEqual(64, algorithm.pkt_size)
+        self.assertEqual(30, algorithm.final_trial_duration)
+        self.assertEqual(0.005, algorithm.final_relative_width)
+        self.assertEqual(2, algorithm.number_of_intermediate_phases)
+        self.assertEqual(1, algorithm.initial_trial_duration)
+        self.assertEqual(720, algorithm.timeout)
+        self.assertEqual(1, algorithm.doublings)
+
+    def test_double_relative_width(self):
+        algorithm = MultipleLossRatioSearch(measurer=mock.Mock(), latency=True,
+                                            pkt_size=64,
+                                            final_trial_duration=30,
+                                            final_relative_width=0.005,
+                                            number_of_intermediate_phases=2,
+                                            initial_trial_duration=1,
+                                            timeout=720)
+        self.assertEqual(0.00997, algorithm.double_relative_width(0.005))
+
+    def test_double_step_down(self):
+        algorithm = MultipleLossRatioSearch(measurer=mock.Mock(), latency=True,
+                                            pkt_size=64,
+                                            final_trial_duration=30,
+                                            final_relative_width=0.005,
+                                            number_of_intermediate_phases=2,
+                                            initial_trial_duration=1,
+                                            timeout=720)
+        self.assertEqual(99003.0, algorithm.double_step_down(0.005, 100000))
+
+    def test_expand_down(self):
+        algorithm = MultipleLossRatioSearch(measurer=mock.Mock(), latency=True,
+                                            pkt_size=64,
+                                            final_trial_duration=30,
+                                            final_relative_width=0.005,
+                                            number_of_intermediate_phases=2,
+                                            initial_trial_duration=1,
+                                            timeout=720)
+        self.assertEqual(99003.0, algorithm.expand_down(0.005, 1, 100000))
+
+    def test_double_step_up(self):
+        algorithm = MultipleLossRatioSearch(measurer=mock.Mock(), latency=True,
+                                            pkt_size=64,
+                                            final_trial_duration=30,
+                                            final_relative_width=0.005,
+                                            number_of_intermediate_phases=2,
+                                            initial_trial_duration=1,
+                                            timeout=720)
+        self.assertEqual(101007.0401907013,
+                         algorithm.double_step_up(0.005, 100000))
+
+    def test_expand_up(self):
+        algorithm = MultipleLossRatioSearch(measurer=mock.Mock(), latency=True,
+                                            pkt_size=64,
+                                            final_trial_duration=30,
+                                            final_relative_width=0.005,
+                                            number_of_intermediate_phases=2,
+                                            initial_trial_duration=1,
+                                            timeout=720)
+        self.assertEqual(101007.0401907013,
+                         algorithm.expand_up(0.005, 1, 100000))
+
+    def test_half_relative_width(self):
+        algorithm = MultipleLossRatioSearch(measurer=mock.Mock(), latency=True,
+                                            pkt_size=64,
+                                            final_trial_duration=30,
+                                            final_relative_width=0.005,
+                                            number_of_intermediate_phases=2,
+                                            initial_trial_duration=1,
+                                            timeout=720)
+        self.assertEqual(0.0025031328369998773,
+                         algorithm.half_relative_width(0.005))
+
+    def test_half_step_up(self):
+        algorithm = MultipleLossRatioSearch(measurer=mock.Mock(), latency=True,
+                                            pkt_size=64,
+                                            final_trial_duration=30,
+                                            final_relative_width=0.005,
+                                            number_of_intermediate_phases=2,
+                                            initial_trial_duration=1,
+                                            timeout=720)
+        self.assertEqual(100250.94142341711,
+                         algorithm.half_step_up(0.005, 100000))
+
+    def test_init_generator(self):
+        algorithm = MultipleLossRatioSearch(measurer=mock.Mock(), latency=True,
+                                            pkt_size=64,
+                                            final_trial_duration=30,
+                                            final_relative_width=0.005,
+                                            number_of_intermediate_phases=2,
+                                            initial_trial_duration=1,
+                                            timeout=720)
+        ports = [0, 1]
+        port_pg_id = PortPgIDMap()
+        port_pg_id.add_port(0)
+        port_pg_id.add_port(1)
+        self.assertIsNone(
+            algorithm.init_generator(ports, port_pg_id, mock.Mock(),
+                                     mock.Mock(), mock.Mock()))
+        self.assertEqual(ports, algorithm.ports)
+        self.assertEqual(port_pg_id, algorithm.port_pg_id)
+
+    def test_collect_kpi(self):
+        algorithm = MultipleLossRatioSearch(measurer=mock.Mock(), latency=True,
+                                            pkt_size=64,
+                                            final_trial_duration=30,
+                                            final_relative_width=0.005,
+                                            number_of_intermediate_phases=2,
+                                            initial_trial_duration=1,
+                                            timeout=720)
+        ports = [0, 1]
+        port_pg_id = PortPgIDMap()
+        port_pg_id.add_port(0)
+        port_pg_id.add_port(1)
+        algorithm.init_generator(ports, port_pg_id, mock.Mock, mock.Mock,
+                                 mock.Mock())
+        self.assertIsNone(algorithm.collect_kpi({}, 100000))
+
+    def test_narrow_down_ndr_and_pdr(self):
+        algorithm = MultipleLossRatioSearch(measurer=mock.Mock(), latency=True,
+                                            pkt_size=64,
+                                            final_trial_duration=30,
+                                            final_relative_width=0.005,
+                                            number_of_intermediate_phases=2,
+                                            initial_trial_duration=1,
+                                            timeout=720)
+        ports = [0, 1]
+        port_pg_id = PortPgIDMap()
+        port_pg_id.add_port(0)
+        port_pg_id.add_port(1)
+        self.assertIsNone(
+            algorithm.init_generator(ports, port_pg_id, mock.Mock(), mock.Mock,
+                                     mock.Mock()))
+        with mock.patch.object(algorithm, 'measure') as \
+                mock_measure, \
+                mock.patch.object(algorithm, 'ndrpdr') as \
+                        mock_ndrpdr:
+            ndr_measured_low = ReceiveRateMeasurement(10, 13880000, 13879927,
+                                                      0)
+            ndr_measured_high = ReceiveRateMeasurement(10, 14880000, 14879927,
+                                                       0)
+            ndr_measured_low.latency = ['1000/3081/3962', '500/3149/3730']
+            ndr_measured_high.latency = ['1000/3081/3962', '500/3149/3730']
+            pdr_measured_low = ReceiveRateMeasurement(10, 11880000, 11879927,
+                                                      0)
+            pdr_measured_high = ReceiveRateMeasurement(10, 12880000, 12879927,
+                                                       0)
+            pdr_measured_low.latency = ['1000/3081/3962', '500/3149/3730']
+            pdr_measured_high.latency = ['1000/3081/3962', '500/3149/3730']
+            ndr_interval = ReceiveRateInterval(ndr_measured_low,
+                                               ndr_measured_high)
+            pdr_interval = ReceiveRateInterval(pdr_measured_low,
+                                               pdr_measured_high)
+            starting_result = NdrPdrResult(ndr_interval, pdr_interval)
+            mock_measure.return_value = ReceiveRateMeasurement(1, 14880000,
+                                                               14879927, 0)
+            mock_ndrpdr.return_value = MultipleLossRatioSearch.ProgressState(
+                starting_result, 2, 30, 0.005, 0.0,
+                4857361, 4977343)
+            self.assertEqual(
+                {'Result_NDR_LOWER': {'bandwidth_total_Gbps': 0.9327310944,
+                                      'rate_total_pps': 1387992.7},
+                 'Result_NDR_UPPER': {
+                     'bandwidth_total_Gbps': 0.9999310943999999,
+                     'rate_total_pps': 1487992.7},
+                 'Result_NDR_packets_lost': {'packet_loss_ratio': 0.0,
+                                             'packets_lost': 0.0},
+                 'Result_PDR_LOWER': {
+                     'bandwidth_total_Gbps': 0.7983310943999999,
+                     'rate_total_pps': 1187992.7},
+                 'Result_PDR_UPPER': {'bandwidth_total_Gbps': 0.8655310944,
+                                      'rate_total_pps': 1287992.7},
+                 'Result_PDR_packets_lost': {'packet_loss_ratio': 0.0,
+                                             'packets_lost': 0.0},
+                 'Result_stream0_NDR_LOWER': {'avg_latency': 3081.0,
+                                              'max_latency': 3962.0,
+                                              'min_latency': 1000.0},
+                 'Result_stream0_PDR_LOWER': {'avg_latency': 3081.0,
+                                              'max_latency': 3962.0,
+                                              'min_latency': 1000.0},
+                 'Result_stream1_NDR_LOWER': {'avg_latency': 3149.0,
+                                              'max_latency': 3730.0,
+                                              'min_latency': 500.0},
+                 'Result_stream1_PDR_LOWER': {'avg_latency': 3149.0,
+                                              'max_latency': 3730.0,
+                                              'min_latency': 500.0}},
+                algorithm.narrow_down_ndr_and_pdr(12880000, 15880000, 0.0))
+
+    def test__measure_and_update_state(self):
+        algorithm = MultipleLossRatioSearch(measurer=mock.Mock(), latency=True,
+                                            pkt_size=64,
+                                            final_trial_duration=30,
+                                            final_relative_width=0.005,
+                                            number_of_intermediate_phases=2,
+                                            initial_trial_duration=1,
+                                            timeout=720)
+        ports = [0, 1]
+        port_pg_id = PortPgIDMap()
+        port_pg_id.add_port(0)
+        port_pg_id.add_port(1)
+        measured_low = ReceiveRateMeasurement(1, 4857361, 4857339, 84965)
+        measured_high = ReceiveRateMeasurement(1, 4977343, 4977320, 119959)
+        starting_interval = ReceiveRateInterval(measured_low, measured_high)
+        starting_result = NdrPdrResult(starting_interval, starting_interval)
+        previous_state = MultipleLossRatioSearch.ProgressState(starting_result,
+                                                               2, 30, 0.005,
+                                                               0.0, 4857361,
+                                                               4977343)
+        self.assertIsNone(
+            algorithm.init_generator(ports, port_pg_id, mock.Mock(), mock.Mock,
+                                     mock.Mock()))
+        with mock.patch.object(algorithm, 'measure') as \
+                mock_measure:
+            mock_measure.return_value = ReceiveRateMeasurement(1,
+                                                               4626121.09635,
+                                                               4626100, 13074)
+            state = algorithm._measure_and_update_state(previous_state,
+                                                        4626121.09635)
+        self.assertIsInstance(state, MultipleLossRatioSearch.ProgressState)
+        self.assertEqual(1, state.result.ndr_interval.measured_low.duration)
+        self.assertEqual(4626121.09635,
+                         state.result.ndr_interval.measured_low.target_tr)
+        self.assertEqual(4626100,
+                         state.result.ndr_interval.measured_low.transmit_count)
+        self.assertEqual(13074,
+                         state.result.ndr_interval.measured_low.loss_count)
+        self.assertEqual(4613026,
+                         state.result.ndr_interval.measured_low.receive_count)
+        self.assertEqual(4626100,
+                         state.result.ndr_interval.measured_low.transmit_rate)
+        self.assertEqual(13074.0,
+                         state.result.ndr_interval.measured_low.loss_rate)
+        self.assertEqual(4613026.0,
+                         state.result.ndr_interval.measured_low.receive_rate)
+        self.assertEqual(0.00283,
+                         state.result.ndr_interval.measured_low.loss_fraction)
+        self.assertEqual(1, state.result.ndr_interval.measured_high.duration)
+        self.assertEqual(4857361,
+                         state.result.ndr_interval.measured_high.target_tr)
+        self.assertEqual(4857339,
+                         state.result.ndr_interval.measured_high.transmit_count)
+        self.assertEqual(84965,
+                         state.result.ndr_interval.measured_high.loss_count)
+        self.assertEqual(4772374,
+                         state.result.ndr_interval.measured_high.receive_count)
+        self.assertEqual(4857339,
+                         state.result.ndr_interval.measured_high.transmit_rate)
+        self.assertEqual(84965.0,
+                         state.result.ndr_interval.measured_high.loss_rate)
+        self.assertEqual(4772374.0,
+                         state.result.ndr_interval.measured_high.receive_rate)
+        self.assertEqual(0.01749,
+                         state.result.ndr_interval.measured_high.loss_fraction)
+        self.assertEqual(1, state.result.pdr_interval.measured_low.duration)
+        self.assertEqual(4626121.09635,
+                         state.result.pdr_interval.measured_low.target_tr)
+        self.assertEqual(4626100,
+                         state.result.pdr_interval.measured_low.transmit_count)
+        self.assertEqual(13074,
+                         state.result.pdr_interval.measured_low.loss_count)
+        self.assertEqual(4613026,
+                         state.result.pdr_interval.measured_low.receive_count)
+        self.assertEqual(4626100,
+                         state.result.pdr_interval.measured_low.transmit_rate)
+        self.assertEqual(13074.0,
+                         state.result.pdr_interval.measured_low.loss_rate)
+        self.assertEqual(4613026.0,
+                         state.result.pdr_interval.measured_low.receive_rate)
+        self.assertEqual(0.00283,
+                         state.result.pdr_interval.measured_low.loss_fraction)
+        self.assertEqual(1, state.result.pdr_interval.measured_high.duration)
+        self.assertEqual(4857361,
+                         state.result.pdr_interval.measured_high.target_tr)
+        self.assertEqual(4857339,
+                         state.result.pdr_interval.measured_high.transmit_count)
+        self.assertEqual(84965,
+                         state.result.pdr_interval.measured_high.loss_count)
+        self.assertEqual(4772374,
+                         state.result.pdr_interval.measured_high.receive_count)
+        self.assertEqual(4857339,
+                         state.result.pdr_interval.measured_high.transmit_rate)
+        self.assertEqual(84965.0,
+                         state.result.pdr_interval.measured_high.loss_rate)
+        self.assertEqual(4772374.0,
+                         state.result.pdr_interval.measured_high.receive_rate)
+        self.assertEqual(0.01749,
+                         state.result.pdr_interval.measured_high.loss_fraction)
+        self.assertEqual(2, state.phases)
+        self.assertEqual(30, state.duration)
+        self.assertEqual(0.005, state.width_goal)
+        self.assertEqual(0.0, state.packet_loss_ratio)
+        self.assertEqual(4857361, state.minimum_transmit_rate)
+        self.assertEqual(4977343, state.maximum_transmit_rate)
+
+    def test_new_interval(self):
+        algorithm = MultipleLossRatioSearch(measurer=mock.Mock(), latency=True,
+                                            pkt_size=64,
+                                            final_trial_duration=30,
+                                            final_relative_width=0.005,
+                                            number_of_intermediate_phases=2,
+                                            initial_trial_duration=1,
+                                            timeout=720)
+        measured = ReceiveRateMeasurement(1, 3972540.4108, 21758482, 0)
+        measured_low = ReceiveRateMeasurement(1, 4857361, 4857339, 84965)
+        measured_high = ReceiveRateMeasurement(1, 4977343, 4977320, 119959)
+        receive_rate_interval = ReceiveRateInterval(measured_low,
+                                                    measured_high)
+        result = algorithm._new_interval(receive_rate_interval, measured, 0.0)
+        self.assertIsInstance(result, ReceiveRateInterval)
+        self.assertEqual(1, result.measured_low.duration)
+        self.assertEqual(3972540.4108, result.measured_low.target_tr)
+        self.assertEqual(21758482, result.measured_low.transmit_count)
+        self.assertEqual(0, result.measured_low.loss_count)
+        self.assertEqual(21758482, result.measured_low.receive_count)
+        self.assertEqual(21758482, result.measured_low.transmit_rate)
+        self.assertEqual(0.0, result.measured_low.loss_rate)
+        self.assertEqual(21758482.0, result.measured_low.receive_rate)
+        self.assertEqual(0.0, result.measured_low.loss_fraction)
+        self.assertEqual(1, result.measured_high.duration)
+        self.assertEqual(4857361, result.measured_high.target_tr)
+        self.assertEqual(4857339, result.measured_high.transmit_count)
+        self.assertEqual(84965, result.measured_high.loss_count)
+        self.assertEqual(4772374, result.measured_high.receive_count)
+        self.assertEqual(4857339, result.measured_high.transmit_rate)
+        self.assertEqual(84965.0, result.measured_high.loss_rate)
+        self.assertEqual(4772374.0, result.measured_high.receive_rate)
+        self.assertEqual(0.01749, result.measured_high.loss_fraction)
+
+    def test_new_interval_zero(self):
+        algorithm = MultipleLossRatioSearch(measurer=mock.Mock(), latency=True,
+                                            pkt_size=64,
+                                            final_trial_duration=30,
+                                            final_relative_width=0.005,
+                                            number_of_intermediate_phases=2,
+                                            initial_trial_duration=1,
+                                            timeout=720)
+        measured = ReceiveRateMeasurement(1, 4977343, 21758482, 0)
+        measured_low = ReceiveRateMeasurement(1, 4857361, 4857339, 84965)
+        measured_high = ReceiveRateMeasurement(1, 4977343, 4977320, 119959)
+        receive_rate_interval = ReceiveRateInterval(measured_low,
+                                                    measured_high)
+        result = algorithm._new_interval(receive_rate_interval, measured, 0.0)
+        self.assertIsInstance(result, ReceiveRateInterval)
+        self.assertEqual(1, result.measured_low.duration)
+        self.assertEqual(4857361.0, result.measured_low.target_tr)
+        self.assertEqual(4857339, result.measured_low.transmit_count)
+        self.assertEqual(84965, result.measured_low.loss_count)
+        self.assertEqual(4772374, result.measured_low.receive_count)
+        self.assertEqual(4857339.0, result.measured_low.transmit_rate)
+        self.assertEqual(84965.0, result.measured_low.loss_rate)
+        self.assertEqual(4772374.0, result.measured_low.receive_rate)
+        self.assertEqual(0.01749, result.measured_low.loss_fraction)
+        self.assertEqual(1, result.measured_high.duration)
+        self.assertEqual(4977343.0, result.measured_high.target_tr)
+        self.assertEqual(21758482, result.measured_high.transmit_count)
+        self.assertEqual(0, result.measured_high.loss_count)
+        self.assertEqual(21758482, result.measured_high.receive_count)
+        self.assertEqual(21758482.0, result.measured_high.transmit_rate)
+        self.assertEqual(0.0, result.measured_high.loss_rate)
+        self.assertEqual(21758482.0, result.measured_high.receive_rate)
+        self.assertEqual(0.0, result.measured_high.loss_fraction)
+
+    def test_new_interval_one(self):
+        algorithm = MultipleLossRatioSearch(measurer=mock.Mock(), latency=True,
+                                            pkt_size=64,
+                                            final_trial_duration=30,
+                                            final_relative_width=0.005,
+                                            number_of_intermediate_phases=2,
+                                            initial_trial_duration=1,
+                                            timeout=720)
+        measured = ReceiveRateMeasurement(1, 5000000, 2175848, 0)
+        measured_low = ReceiveRateMeasurement(1, 4857361, 4857339, 84965)
+        measured_high = ReceiveRateMeasurement(1, 4977343, 4977320, 119959)
+        receive_rate_interval = ReceiveRateInterval(measured_low,
+                                                    measured_high)
+        result = algorithm._new_interval(receive_rate_interval, measured, 0.0)
+        self.assertIsInstance(result, ReceiveRateInterval)
+        self.assertEqual(1, result.measured_low.duration)
+        self.assertEqual(4857361.0, result.measured_low.target_tr)
+        self.assertEqual(4857339, result.measured_low.transmit_count)
+        self.assertEqual(84965, result.measured_low.loss_count)
+        self.assertEqual(4772374, result.measured_low.receive_count)
+        self.assertEqual(4857339.0, result.measured_low.transmit_rate)
+        self.assertEqual(84965.0, result.measured_low.loss_rate)
+        self.assertEqual(4772374.0, result.measured_low.receive_rate)
+        self.assertEqual(0.01749, result.measured_low.loss_fraction)
+        self.assertEqual(1, result.measured_high.duration)
+        self.assertEqual(4977343.0, result.measured_high.target_tr)
+        self.assertEqual(4977320, result.measured_high.transmit_count)
+        self.assertEqual(119959, result.measured_high.loss_count)
+        self.assertEqual(4857361, result.measured_high.receive_count)
+        self.assertEqual(4977320.0, result.measured_high.transmit_rate)
+        self.assertEqual(119959.0, result.measured_high.loss_rate)
+        self.assertEqual(4857361.0, result.measured_high.receive_rate)
+        self.assertEqual(0.0241, result.measured_high.loss_fraction)
+
+    def test_new_interval_valid_1st(self):
+        algorithm = MultipleLossRatioSearch(measurer=mock.Mock(), latency=True,
+                                            pkt_size=64,
+                                            final_trial_duration=30,
+                                            final_relative_width=0.005,
+                                            number_of_intermediate_phases=2,
+                                            initial_trial_duration=1,
+                                            timeout=720)
+        measured = ReceiveRateMeasurement(1, 4000000, 2175848, 0)
+        measured_low = ReceiveRateMeasurement(1, 4857361, 4857339, 84965)
+        measured_high = ReceiveRateMeasurement(1, 4977343, 4977320, 119959)
+        receive_rate_interval = ReceiveRateInterval(measured_low,
+                                                    measured_high)
+        result = algorithm._new_interval(receive_rate_interval, measured, 0.5)
+        self.assertIsInstance(result, ReceiveRateInterval)
+        self.assertEqual(1, result.measured_low.duration)
+        self.assertEqual(4857361.0, result.measured_low.target_tr)
+        self.assertEqual(4857339, result.measured_low.transmit_count)
+        self.assertEqual(84965, result.measured_low.loss_count)
+        self.assertEqual(4772374, result.measured_low.receive_count)
+        self.assertEqual(4857339.0, result.measured_low.transmit_rate)
+        self.assertEqual(84965.0, result.measured_low.loss_rate)
+        self.assertEqual(4772374.0, result.measured_low.receive_rate)
+        self.assertEqual(0.01749, result.measured_low.loss_fraction)
+        self.assertEqual(1, result.measured_high.duration)
+        self.assertEqual(4977343.0, result.measured_high.target_tr)
+        self.assertEqual(4977320, result.measured_high.transmit_count)
+        self.assertEqual(119959, result.measured_high.loss_count)
+        self.assertEqual(4857361, result.measured_high.receive_count)
+        self.assertEqual(4977320.0, result.measured_high.transmit_rate)
+        self.assertEqual(119959.0, result.measured_high.loss_rate)
+        self.assertEqual(4857361.0, result.measured_high.receive_rate)
+        self.assertEqual(0.0241, result.measured_high.loss_fraction)
+
+    def test_new_interval_valid_1st_loss(self):
+        algorithm = MultipleLossRatioSearch(measurer=mock.Mock(), latency=True,
+                                            pkt_size=64,
+                                            final_trial_duration=30,
+                                            final_relative_width=0.005,
+                                            number_of_intermediate_phases=2,
+                                            initial_trial_duration=1,
+                                            timeout=720)
+        measured = ReceiveRateMeasurement(1, 4000000, 2175848, 1000000)
+        measured_low = ReceiveRateMeasurement(1, 4857361, 4857339, 84965)
+        measured_high = ReceiveRateMeasurement(1, 4977343, 4977320, 119959)
+        receive_rate_interval = ReceiveRateInterval(measured_low,
+                                                    measured_high)
+        result = algorithm._new_interval(receive_rate_interval, measured, 0.02)
+        self.assertIsInstance(result, ReceiveRateInterval)
+        self.assertEqual(1, result.measured_low.duration)
+        self.assertEqual(4000000.0, result.measured_low.target_tr)
+        self.assertEqual(2175848, result.measured_low.transmit_count)
+        self.assertEqual(1000000, result.measured_low.loss_count)
+        self.assertEqual(1175848, result.measured_low.receive_count)
+        self.assertEqual(2175848.0, result.measured_low.transmit_rate)
+        self.assertEqual(1000000.0, result.measured_low.loss_rate)
+        self.assertEqual(1175848.0, result.measured_low.receive_rate)
+        self.assertEqual(0.45959, result.measured_low.loss_fraction)
+        self.assertEqual(1, result.measured_high.duration)
+        self.assertEqual(4977343.0, result.measured_high.target_tr)
+        self.assertEqual(4977320, result.measured_high.transmit_count)
+        self.assertEqual(119959, result.measured_high.loss_count)
+        self.assertEqual(4857361, result.measured_high.receive_count)
+        self.assertEqual(4977320.0, result.measured_high.transmit_rate)
+        self.assertEqual(119959.0, result.measured_high.loss_rate)
+        self.assertEqual(4857361.0, result.measured_high.receive_rate)
+        self.assertEqual(0.0241, result.measured_high.loss_fraction)
+
+    def test_new_interval_valid_2nd(self):
+        algorithm = MultipleLossRatioSearch(measurer=mock.Mock(), latency=True,
+                                            pkt_size=64,
+                                            final_trial_duration=30,
+                                            final_relative_width=0.005,
+                                            number_of_intermediate_phases=2,
+                                            initial_trial_duration=1,
+                                            timeout=720)
+        measured = ReceiveRateMeasurement(1, 5000000, 2175848, 0)
+        measured_low = ReceiveRateMeasurement(1, 4857361, 4857339, 84965)
+        measured_high = ReceiveRateMeasurement(1, 4977343, 4977320, 119959)
+        receive_rate_interval = ReceiveRateInterval(measured_low,
+                                                    measured_high)
+        result = algorithm._new_interval(receive_rate_interval, measured, 0.5)
+        self.assertIsInstance(result, ReceiveRateInterval)
+        self.assertEqual(1, result.measured_low.duration)
+        self.assertEqual(4977343.0, result.measured_low.target_tr)
+        self.assertEqual(4977320, result.measured_low.transmit_count)
+        self.assertEqual(119959, result.measured_low.loss_count)
+        self.assertEqual(4857361, result.measured_low.receive_count)
+        self.assertEqual(4977320.0, result.measured_low.transmit_rate)
+        self.assertEqual(119959.0, result.measured_low.loss_rate)
+        self.assertEqual(4857361.0, result.measured_low.receive_rate)
+        self.assertEqual(0.0241, result.measured_low.loss_fraction)
+        self.assertEqual(1, result.measured_high.duration)
+        self.assertEqual(5000000.0, result.measured_high.target_tr)
+        self.assertEqual(2175848, result.measured_high.transmit_count)
+        self.assertEqual(0, result.measured_high.loss_count)
+        self.assertEqual(2175848, result.measured_high.receive_count)
+        self.assertEqual(2175848.0, result.measured_high.transmit_rate)
+        self.assertEqual(0.0, result.measured_high.loss_rate)
+        self.assertEqual(2175848.0, result.measured_high.receive_rate)
+        self.assertEqual(0.0, result.measured_high.loss_fraction)
+
+    def test_new_interval_valid_3rd(self):
+        algorithm = MultipleLossRatioSearch(measurer=mock.Mock(), latency=True,
+                                            pkt_size=64,
+                                            final_trial_duration=30,
+                                            final_relative_width=0.005,
+                                            number_of_intermediate_phases=2,
+                                            initial_trial_duration=1,
+                                            timeout=720)
+        measured = ReceiveRateMeasurement(1, 4867361, 2175848, 0)
+        measured_low = ReceiveRateMeasurement(1, 4857361, 4857339, 84965)
+        measured_high = ReceiveRateMeasurement(1, 4977343, 4977320, 119959)
+        receive_rate_interval = ReceiveRateInterval(measured_low,
+                                                    measured_high)
+        result = algorithm._new_interval(receive_rate_interval, measured, 0.5)
+        self.assertIsInstance(result, ReceiveRateInterval)
+        self.assertEqual(1, result.measured_low.duration)
+        self.assertEqual(4867361.0, result.measured_low.target_tr)
+        self.assertEqual(2175848, result.measured_low.transmit_count)
+        self.assertEqual(0, result.measured_low.loss_count)
+        self.assertEqual(2175848, result.measured_low.receive_count)
+        self.assertEqual(2175848.0, result.measured_low.transmit_rate)
+        self.assertEqual(0.0, result.measured_low.loss_rate)
+        self.assertEqual(2175848.0, result.measured_low.receive_rate)
+        self.assertEqual(0.0, result.measured_low.loss_fraction)
+        self.assertEqual(1, result.measured_high.duration)
+        self.assertEqual(4977343.0, result.measured_high.target_tr)
+        self.assertEqual(4977320, result.measured_high.transmit_count)
+        self.assertEqual(119959, result.measured_high.loss_count)
+        self.assertEqual(4857361, result.measured_high.receive_count)
+        self.assertEqual(4977320.0, result.measured_high.transmit_rate)
+        self.assertEqual(119959.0, result.measured_high.loss_rate)
+        self.assertEqual(4857361.0, result.measured_high.receive_rate)
+        self.assertEqual(0.0241, result.measured_high.loss_fraction)
+
+    def test_new_interval_valid_3rd_loss(self):
+        algorithm = MultipleLossRatioSearch(measurer=mock.Mock(), latency=True,
+                                            pkt_size=64,
+                                            final_trial_duration=30,
+                                            final_relative_width=0.005,
+                                            number_of_intermediate_phases=2,
+                                            initial_trial_duration=1,
+                                            timeout=720)
+        measured = ReceiveRateMeasurement(1, 4867361, 2175848, 1000000)
+        measured_low = ReceiveRateMeasurement(1, 4857361, 4857339, 84965)
+        measured_high = ReceiveRateMeasurement(1, 4977343, 4977320, 119959)
+        receive_rate_interval = ReceiveRateInterval(measured_low,
+                                                    measured_high)
+        result = algorithm._new_interval(receive_rate_interval, measured, 0.2)
+        self.assertIsInstance(result, ReceiveRateInterval)
+        self.assertEqual(1, result.measured_low.duration)
+        self.assertEqual(4857361.0, result.measured_low.target_tr)
+        self.assertEqual(4857339, result.measured_low.transmit_count)
+        self.assertEqual(84965, result.measured_low.loss_count)
+        self.assertEqual(4772374, result.measured_low.receive_count)
+        self.assertEqual(4857339.0, result.measured_low.transmit_rate)
+        self.assertEqual(84965.0, result.measured_low.loss_rate)
+        self.assertEqual(4772374.0, result.measured_low.receive_rate)
+        self.assertEqual(0.01749, result.measured_low.loss_fraction)
+        self.assertEqual(1, result.measured_high.duration)
+        self.assertEqual(4867361.0, result.measured_high.target_tr)
+        self.assertEqual(2175848, result.measured_high.transmit_count)
+        self.assertEqual(1000000, result.measured_high.loss_count)
+        self.assertEqual(1175848, result.measured_high.receive_count)
+        self.assertEqual(2175848.0, result.measured_high.transmit_rate)
+        self.assertEqual(1000000.0, result.measured_high.loss_rate)
+        self.assertEqual(1175848.0, result.measured_high.receive_rate)
+        self.assertEqual(0.45959, result.measured_high.loss_fraction)
+
+    def test_ndrpdr(self):
+        algorithm = MultipleLossRatioSearch(measurer=mock.Mock(), latency=True,
+                                            pkt_size=64,
+                                            final_trial_duration=30,
+                                            final_relative_width=0.005,
+                                            number_of_intermediate_phases=2,
+                                            initial_trial_duration=1,
+                                            timeout=720)
+        ports = [0, 1]
+        port_pg_id = PortPgIDMap()
+        port_pg_id.add_port(0)
+        port_pg_id.add_port(1)
+        self.assertIsNone(
+            algorithm.init_generator(ports, port_pg_id, mock.Mock(), mock.Mock,
+                                     mock.Mock()))
+        with mock.patch.object(algorithm, 'measure') as \
+                mock_measure:
+            measured_low = ReceiveRateMeasurement(30, 14880000, 14879927, 0)
+            measured_high = ReceiveRateMeasurement(30, 14880000, 14879927, 0)
+            measured_low.latency = ['1000/3081/3962', '500/3149/3730']
+            measured_high.latency = ['1000/3081/3962', '500/3149/3730']
+            starting_interval = ReceiveRateInterval(measured_low,
+                                                    measured_high)
+            starting_result = NdrPdrResult(starting_interval,
+                                           starting_interval)
+            mock_measure.return_value = ReceiveRateMeasurement(1, 14880000,
+                                                               14879927, 0)
+            previous_state = MultipleLossRatioSearch.ProgressState(
+                starting_result, -1, 30, 0.005, 0.0, 14880000,
+                14880000)
+            state = algorithm.ndrpdr(previous_state)
+            self.assertIsInstance(state, MultipleLossRatioSearch.ProgressState)
+        self.assertIsInstance(state, MultipleLossRatioSearch.ProgressState)
+        self.assertEqual(30, state.result.ndr_interval.measured_low.duration)
+        self.assertEqual(14880000,
+                         state.result.ndr_interval.measured_low.target_tr)
+        self.assertEqual(14879927,
+                         state.result.ndr_interval.measured_low.transmit_count)
+        self.assertEqual(0, state.result.ndr_interval.measured_low.loss_count)
+        self.assertEqual(14879927,
+                         state.result.ndr_interval.measured_low.receive_count)
+        self.assertEqual(495997.56667,
+                         state.result.ndr_interval.measured_low.transmit_rate)
+        self.assertEqual(0.0, state.result.ndr_interval.measured_low.loss_rate)
+        self.assertEqual(495997.56667,
+                         state.result.ndr_interval.measured_low.receive_rate)
+        self.assertEqual(0.0,
+                         state.result.ndr_interval.measured_low.loss_fraction)
+        self.assertEqual(30, state.result.ndr_interval.measured_high.duration)
+        self.assertEqual(14880000,
+                         state.result.ndr_interval.measured_high.target_tr)
+        self.assertEqual(14879927,
+                         state.result.ndr_interval.measured_high.transmit_count)
+        self.assertEqual(0, state.result.ndr_interval.measured_high.loss_count)
+        self.assertEqual(14879927,
+                         state.result.ndr_interval.measured_high.receive_count)
+        self.assertEqual(495997.56667,
+                         state.result.ndr_interval.measured_high.transmit_rate)
+        self.assertEqual(0.0,
+                         state.result.ndr_interval.measured_high.loss_rate)
+        self.assertEqual(495997.56667,
+                         state.result.ndr_interval.measured_high.receive_rate)
+        self.assertEqual(0.0,
+                         state.result.ndr_interval.measured_high.loss_fraction)
+        self.assertEqual(30, state.result.pdr_interval.measured_low.duration)
+        self.assertEqual(14880000,
+                         state.result.pdr_interval.measured_low.target_tr)
+        self.assertEqual(14879927,
+                         state.result.pdr_interval.measured_low.transmit_count)
+        self.assertEqual(0, state.result.pdr_interval.measured_low.loss_count)
+        self.assertEqual(14879927,
+                         state.result.pdr_interval.measured_low.receive_count)
+        self.assertEqual(495997.56667,
+                         state.result.pdr_interval.measured_low.transmit_rate)
+        self.assertEqual(0.0, state.result.pdr_interval.measured_low.loss_rate)
+        self.assertEqual(495997.56667,
+                         state.result.pdr_interval.measured_low.receive_rate)
+        self.assertEqual(0.0,
+                         state.result.pdr_interval.measured_low.loss_fraction)
+        self.assertEqual(30, state.result.pdr_interval.measured_high.duration)
+        self.assertEqual(14880000,
+                         state.result.pdr_interval.measured_high.target_tr)
+        self.assertEqual(14879927,
+                         state.result.pdr_interval.measured_high.transmit_count)
+        self.assertEqual(0, state.result.pdr_interval.measured_high.loss_count)
+        self.assertEqual(14879927,
+                         state.result.pdr_interval.measured_high.receive_count)
+        self.assertEqual(495997.56667,
+                         state.result.pdr_interval.measured_high.transmit_rate)
+        self.assertEqual(0.0,
+                         state.result.pdr_interval.measured_high.loss_rate)
+        self.assertEqual(495997.56667,
+                         state.result.pdr_interval.measured_high.receive_rate)
+        self.assertEqual(0.0,
+                         state.result.pdr_interval.measured_high.loss_fraction)
+        self.assertEqual(-1, state.phases)
+        self.assertEqual(30, state.duration)
+        self.assertEqual(0.005, state.width_goal)
+        self.assertEqual(0.0, state.packet_loss_ratio)
+        self.assertEqual(14880000, state.minimum_transmit_rate)
+        self.assertEqual(14880000, state.maximum_transmit_rate)
+
+    def test_ndrpdr_ndr_rel_width(self):
+        algorithm = MultipleLossRatioSearch(measurer=mock.Mock(), latency=True,
+                                            pkt_size=64,
+                                            final_trial_duration=30,
+                                            final_relative_width=0.005,
+                                            number_of_intermediate_phases=2,
+                                            initial_trial_duration=1,
+                                            timeout=720)
+        ports = [0, 1]
+        port_pg_id = PortPgIDMap()
+        port_pg_id.add_port(0)
+        port_pg_id.add_port(1)
+        self.assertIsNone(
+            algorithm.init_generator(ports, port_pg_id, mock.Mock(), mock.Mock,
+                                     mock.Mock()))
+        with mock.patch.object(algorithm, 'measure') as \
+                mock_measure, \
+                mock.patch.object(algorithm, '_measure_and_update_state') as \
+                        mock__measure_and_update_state:
+            measured_low = ReceiveRateMeasurement(30, 880000, 879927, 0)
+            measured_high = ReceiveRateMeasurement(30, 14880000, 14879927, 0)
+            measured_low.latency = ['1000/3081/3962', '500/3149/3730']
+            measured_high.latency = ['1000/3081/3962', '500/3149/3730']
+            starting_interval = ReceiveRateInterval(measured_low,
+                                                    measured_high)
+            ending_interval = ReceiveRateInterval(measured_high, measured_high)
+            starting_result = NdrPdrResult(starting_interval,
+                                           starting_interval)
+            ending_result = NdrPdrResult(ending_interval, ending_interval)
+            mock_measure.return_value = ReceiveRateMeasurement(1, 14880000,
+                                                               14879927, 0)
+            mock__measure_and_update_state.return_value = \
+                MultipleLossRatioSearch.ProgressState(ending_result, -1, 30,
+                                                      0.005, 0.0, 14880000,
+                                                      14880000)
+            previous_state = MultipleLossRatioSearch.ProgressState(
+                starting_result, -1, 30, 0.005, 0.0, 14880000,
+                14880000)
+            state = algorithm.ndrpdr(previous_state)
+            self.assertIsInstance(state, MultipleLossRatioSearch.ProgressState)
+        self.assertIsInstance(state, MultipleLossRatioSearch.ProgressState)
+        self.assertEqual(30, state.result.ndr_interval.measured_low.duration)
+        self.assertEqual(14880000,
+                         state.result.ndr_interval.measured_low.target_tr)
+        self.assertEqual(14879927,
+                         state.result.ndr_interval.measured_low.transmit_count)
+        self.assertEqual(0, state.result.ndr_interval.measured_low.loss_count)
+        self.assertEqual(14879927,
+                         state.result.ndr_interval.measured_low.receive_count)
+        self.assertEqual(495997.56667,
+                         state.result.ndr_interval.measured_low.transmit_rate)
+        self.assertEqual(0.0, state.result.ndr_interval.measured_low.loss_rate)
+        self.assertEqual(495997.56667,
+                         state.result.ndr_interval.measured_low.receive_rate)
+        self.assertEqual(0.0,
+                         state.result.ndr_interval.measured_low.loss_fraction)
+        self.assertEqual(30, state.result.ndr_interval.measured_high.duration)
+        self.assertEqual(14880000,
+                         state.result.ndr_interval.measured_high.target_tr)
+        self.assertEqual(14879927,
+                         state.result.ndr_interval.measured_high.transmit_count)
+        self.assertEqual(0, state.result.ndr_interval.measured_high.loss_count)
+        self.assertEqual(14879927,
+                         state.result.ndr_interval.measured_high.receive_count)
+        self.assertEqual(495997.56667,
+                         state.result.ndr_interval.measured_high.transmit_rate)
+        self.assertEqual(0.0,
+                         state.result.ndr_interval.measured_high.loss_rate)
+        self.assertEqual(495997.56667,
+                         state.result.ndr_interval.measured_high.receive_rate)
+        self.assertEqual(0.0,
+                         state.result.ndr_interval.measured_high.loss_fraction)
+        self.assertEqual(30, state.result.pdr_interval.measured_low.duration)
+        self.assertEqual(14880000,
+                         state.result.pdr_interval.measured_low.target_tr)
+        self.assertEqual(14879927,
+                         state.result.pdr_interval.measured_low.transmit_count)
+        self.assertEqual(0, state.result.pdr_interval.measured_low.loss_count)
+        self.assertEqual(14879927,
+                         state.result.pdr_interval.measured_low.receive_count)
+        self.assertEqual(495997.56667,
+                         state.result.pdr_interval.measured_low.transmit_rate)
+        self.assertEqual(0.0, state.result.pdr_interval.measured_low.loss_rate)
+        self.assertEqual(495997.56667,
+                         state.result.pdr_interval.measured_low.receive_rate)
+        self.assertEqual(0.0,
+                         state.result.pdr_interval.measured_low.loss_fraction)
+        self.assertEqual(30, state.result.pdr_interval.measured_high.duration)
+        self.assertEqual(14880000,
+                         state.result.pdr_interval.measured_high.target_tr)
+        self.assertEqual(14879927,
+                         state.result.pdr_interval.measured_high.transmit_count)
+        self.assertEqual(0, state.result.pdr_interval.measured_high.loss_count)
+        self.assertEqual(14879927,
+                         state.result.pdr_interval.measured_high.receive_count)
+        self.assertEqual(495997.56667,
+                         state.result.pdr_interval.measured_high.transmit_rate)
+        self.assertEqual(0.0,
+                         state.result.pdr_interval.measured_high.loss_rate)
+        self.assertEqual(495997.56667,
+                         state.result.pdr_interval.measured_high.receive_rate)
+        self.assertEqual(0.0,
+                         state.result.pdr_interval.measured_high.loss_fraction)
+        self.assertEqual(-1, state.phases)
+        self.assertEqual(30, state.duration)
+        self.assertEqual(0.005, state.width_goal)
+        self.assertEqual(0.0, state.packet_loss_ratio)
+        self.assertEqual(14880000, state.minimum_transmit_rate)
+        self.assertEqual(14880000, state.maximum_transmit_rate)
+
+    def test_ndrpdr_pdr_rel_width(self):
+        algorithm = MultipleLossRatioSearch(measurer=mock.Mock(), latency=True,
+                                            pkt_size=64,
+                                            final_trial_duration=30,
+                                            final_relative_width=0.005,
+                                            number_of_intermediate_phases=2,
+                                            initial_trial_duration=1,
+                                            timeout=720)
+        ports = [0, 1]
+        port_pg_id = PortPgIDMap()
+        port_pg_id.add_port(0)
+        port_pg_id.add_port(1)
+        self.assertIsNone(
+            algorithm.init_generator(ports, port_pg_id, mock.Mock(), mock.Mock,
+                                     mock.Mock()))
+        with mock.patch.object(algorithm, 'measure') as \
+                mock_measure, \
+                mock.patch.object(algorithm, '_measure_and_update_state') as \
+                        mock__measure_and_update_state:
+            ndr_measured_low = ReceiveRateMeasurement(30, 14880000, 14879927,
+                                                      0)
+            ndr_measured_high = ReceiveRateMeasurement(30, 14880000, 14879927,
+                                                       0)
+            ndr_measured_low.latency = ['1000/3081/3962', '500/3149/3730']
+            ndr_measured_high.latency = ['1000/3081/3962', '500/3149/3730']
+            pdr_measured_low = ReceiveRateMeasurement(30, 880000, 879927, 0)
+            pdr_measured_high = ReceiveRateMeasurement(30, 14880000, 14879927,
+                                                       0)
+            pdr_measured_low.latency = ['1000/3081/3962', '500/3149/3730']
+            pdr_measured_high.latency = ['1000/3081/3962', '500/3149/3730']
+            ndr_interval = ReceiveRateInterval(ndr_measured_low,
+                                               ndr_measured_high)
+            pdr_interval = ReceiveRateInterval(pdr_measured_low,
+                                               pdr_measured_high)
+            starting_result = NdrPdrResult(ndr_interval, pdr_interval)
+            ending_result = NdrPdrResult(ndr_interval, ndr_interval)
+            mock_measure.return_value = ReceiveRateMeasurement(1, 14880000,
+                                                               14879927, 0)
+            mock__measure_and_update_state.return_value = \
+                MultipleLossRatioSearch.ProgressState(ending_result, -1, 30,
+                                                      0.005, 0.0, 14880000,
+                                                      14880000)
+            previous_state = MultipleLossRatioSearch.ProgressState(
+                starting_result, -1, 30, 0.005, 0.0, 14880000,
+                14880000)
+            state = algorithm.ndrpdr(previous_state)
+            self.assertIsInstance(state, MultipleLossRatioSearch.ProgressState)
+        self.assertIsInstance(state, MultipleLossRatioSearch.ProgressState)
+        self.assertEqual(30, state.result.ndr_interval.measured_low.duration)
+        self.assertEqual(14880000,
+                         state.result.ndr_interval.measured_low.target_tr)
+        self.assertEqual(14879927,
+                         state.result.ndr_interval.measured_low.transmit_count)
+        self.assertEqual(0, state.result.ndr_interval.measured_low.loss_count)
+        self.assertEqual(14879927,
+                         state.result.ndr_interval.measured_low.receive_count)
+        self.assertEqual(495997.56667,
+                         state.result.ndr_interval.measured_low.transmit_rate)
+        self.assertEqual(0.0, state.result.ndr_interval.measured_low.loss_rate)
+        self.assertEqual(495997.56667,
+                         state.result.ndr_interval.measured_low.receive_rate)
+        self.assertEqual(0.0,
+                         state.result.ndr_interval.measured_low.loss_fraction)
+        self.assertEqual(30, state.result.ndr_interval.measured_high.duration)
+        self.assertEqual(14880000,
+                         state.result.ndr_interval.measured_high.target_tr)
+        self.assertEqual(14879927,
+                         state.result.ndr_interval.measured_high.transmit_count)
+        self.assertEqual(0, state.result.ndr_interval.measured_high.loss_count)
+        self.assertEqual(14879927,
+                         state.result.ndr_interval.measured_high.receive_count)
+        self.assertEqual(495997.56667,
+                         state.result.ndr_interval.measured_high.transmit_rate)
+        self.assertEqual(0.0,
+                         state.result.ndr_interval.measured_high.loss_rate)
+        self.assertEqual(495997.56667,
+                         state.result.ndr_interval.measured_high.receive_rate)
+        self.assertEqual(0.0,
+                         state.result.ndr_interval.measured_high.loss_fraction)
+        self.assertEqual(30, state.result.pdr_interval.measured_low.duration)
+        self.assertEqual(14880000,
+                         state.result.pdr_interval.measured_low.target_tr)
+        self.assertEqual(14879927,
+                         state.result.pdr_interval.measured_low.transmit_count)
+        self.assertEqual(0, state.result.pdr_interval.measured_low.loss_count)
+        self.assertEqual(14879927,
+                         state.result.pdr_interval.measured_low.receive_count)
+        self.assertEqual(495997.56667,
+                         state.result.pdr_interval.measured_low.transmit_rate)
+        self.assertEqual(0.0, state.result.pdr_interval.measured_low.loss_rate)
+        self.assertEqual(495997.56667,
+                         state.result.pdr_interval.measured_low.receive_rate)
+        self.assertEqual(0.0,
+                         state.result.pdr_interval.measured_low.loss_fraction)
+        self.assertEqual(30, state.result.pdr_interval.measured_high.duration)
+        self.assertEqual(14880000,
+                         state.result.pdr_interval.measured_high.target_tr)
+        self.assertEqual(14879927,
+                         state.result.pdr_interval.measured_high.transmit_count)
+        self.assertEqual(0, state.result.pdr_interval.measured_high.loss_count)
+        self.assertEqual(14879927,
+                         state.result.pdr_interval.measured_high.receive_count)
+        self.assertEqual(495997.56667,
+                         state.result.pdr_interval.measured_high.transmit_rate)
+        self.assertEqual(0.0,
+                         state.result.pdr_interval.measured_high.loss_rate)
+        self.assertEqual(495997.56667,
+                         state.result.pdr_interval.measured_high.receive_rate)
+        self.assertEqual(0.0,
+                         state.result.pdr_interval.measured_high.loss_fraction)
+        self.assertEqual(-1, state.phases)
+        self.assertEqual(30, state.duration)
+        self.assertEqual(0.005, state.width_goal)
+        self.assertEqual(0.0, state.packet_loss_ratio)
+        self.assertEqual(14880000, state.minimum_transmit_rate)
+        self.assertEqual(14880000, state.maximum_transmit_rate)
+
+    def test_ndrpdr_ndr_lo_duration(self):
+        algorithm = MultipleLossRatioSearch(measurer=mock.Mock(), latency=True,
+                                            pkt_size=64,
+                                            final_trial_duration=30,
+                                            final_relative_width=0.005,
+                                            number_of_intermediate_phases=2,
+                                            initial_trial_duration=1,
+                                            timeout=720)
+        ports = [0, 1]
+        port_pg_id = PortPgIDMap()
+        port_pg_id.add_port(0)
+        port_pg_id.add_port(1)
+        self.assertIsNone(
+            algorithm.init_generator(ports, port_pg_id, mock.Mock(), mock.Mock,
+                                     mock.Mock()))
+        with mock.patch.object(algorithm, 'measure') as \
+                mock_measure, \
+                mock.patch.object(algorithm, '_measure_and_update_state') as \
+                        mock__measure_and_update_state:
+            measured_low = ReceiveRateMeasurement(30, 14880000, 14879927, 0)
+            measured_high = ReceiveRateMeasurement(30, 14880000, 14879927, 100)
+            measured_low.latency = ['1000/3081/3962', '500/3149/3730']
+            measured_high.latency = ['1000/3081/3962', '500/3149/3730']
+            starting_interval = ReceiveRateInterval(measured_low,
+                                                    measured_high)
+            starting_result = NdrPdrResult(starting_interval,
+                                           starting_interval)
+            mock_measure.return_value = ReceiveRateMeasurement(1, 14880000,
+                                                               14879927, 0)
+            mock__measure_and_update_state.return_value = \
+                MultipleLossRatioSearch.ProgressState(starting_result, -1, 30,
+                                                      0.005, 0.0, 14880000,
+                                                      14880000)
+            previous_state = MultipleLossRatioSearch.ProgressState(
+                starting_result, -1, 50, 0.005, 0.0, 14880000,
+                14880000)
+            state = algorithm.ndrpdr(previous_state)
+            self.assertIsInstance(state, MultipleLossRatioSearch.ProgressState)
+        self.assertIsInstance(state, MultipleLossRatioSearch.ProgressState)
+        self.assertEqual(30, state.result.ndr_interval.measured_low.duration)
+        self.assertEqual(14880000,
+                         state.result.ndr_interval.measured_low.target_tr)
+        self.assertEqual(14879927,
+                         state.result.ndr_interval.measured_low.transmit_count)
+        self.assertEqual(0, state.result.ndr_interval.measured_low.loss_count)
+        self.assertEqual(14879927,
+                         state.result.ndr_interval.measured_low.receive_count)
+        self.assertEqual(495997.56667,
+                         state.result.ndr_interval.measured_low.transmit_rate)
+        self.assertEqual(0.0, state.result.ndr_interval.measured_low.loss_rate)
+        self.assertEqual(495997.56667,
+                         state.result.ndr_interval.measured_low.receive_rate)
+        self.assertEqual(0.0,
+                         state.result.ndr_interval.measured_low.loss_fraction)
+        self.assertEqual(30, state.result.ndr_interval.measured_high.duration)
+        self.assertEqual(14880000,
+                         state.result.ndr_interval.measured_high.target_tr)
+        self.assertEqual(14879927,
+                         state.result.ndr_interval.measured_high.transmit_count)
+        self.assertEqual(100,
+                         state.result.ndr_interval.measured_high.loss_count)
+        self.assertEqual(14879827,
+                         state.result.ndr_interval.measured_high.receive_count)
+        self.assertEqual(495997.56667,
+                         state.result.ndr_interval.measured_high.transmit_rate)
+        self.assertEqual(3.33333,
+                         state.result.ndr_interval.measured_high.loss_rate)
+        self.assertEqual(495994.23333,
+                         state.result.ndr_interval.measured_high.receive_rate)
+        self.assertEqual(1e-05,
+                         state.result.ndr_interval.measured_high.loss_fraction)
+        self.assertEqual(30, state.result.pdr_interval.measured_low.duration)
+        self.assertEqual(14880000,
+                         state.result.pdr_interval.measured_low.target_tr)
+        self.assertEqual(14879927,
+                         state.result.pdr_interval.measured_low.transmit_count)
+        self.assertEqual(0, state.result.pdr_interval.measured_low.loss_count)
+        self.assertEqual(14879927,
+                         state.result.pdr_interval.measured_low.receive_count)
+        self.assertEqual(495997.56667,
+                         state.result.pdr_interval.measured_low.transmit_rate)
+        self.assertEqual(0.0, state.result.pdr_interval.measured_low.loss_rate)
+        self.assertEqual(495997.56667,
+                         state.result.pdr_interval.measured_low.receive_rate)
+        self.assertEqual(0.0,
+                         state.result.pdr_interval.measured_low.loss_fraction)
+        self.assertEqual(30, state.result.pdr_interval.measured_high.duration)
+        self.assertEqual(14880000,
+                         state.result.pdr_interval.measured_high.target_tr)
+        self.assertEqual(14879927,
+                         state.result.pdr_interval.measured_high.transmit_count)
+        self.assertEqual(100,
+                         state.result.pdr_interval.measured_high.loss_count)
+        self.assertEqual(14879827,
+                         state.result.pdr_interval.measured_high.receive_count)
+        self.assertEqual(495997.56667,
+                         state.result.pdr_interval.measured_high.transmit_rate)
+        self.assertEqual(3.33333,
+                         state.result.pdr_interval.measured_high.loss_rate)
+        self.assertEqual(495994.23333,
+                         state.result.pdr_interval.measured_high.receive_rate)
+        self.assertEqual(1e-05,
+                         state.result.pdr_interval.measured_high.loss_fraction)
+        self.assertEqual(-1, state.phases)
+        self.assertEqual(30, state.duration)
+        self.assertEqual(0.005, state.width_goal)
+        self.assertEqual(0.0, state.packet_loss_ratio)
+        self.assertEqual(14880000, state.minimum_transmit_rate)
+        self.assertEqual(14880000, state.maximum_transmit_rate)
+
+    def test_ndrpdr_ndr_hi_duration(self):
+        algorithm = MultipleLossRatioSearch(measurer=mock.Mock(), latency=True,
+                                            pkt_size=64,
+                                            final_trial_duration=30,
+                                            final_relative_width=0.005,
+                                            number_of_intermediate_phases=2,
+                                            initial_trial_duration=1,
+                                            timeout=720)
+        ports = [0, 1]
+        port_pg_id = PortPgIDMap()
+        port_pg_id.add_port(0)
+        port_pg_id.add_port(1)
+        self.assertIsNone(
+            algorithm.init_generator(ports, port_pg_id, mock.Mock(), mock.Mock,
+                                     mock.Mock()))
+        with mock.patch.object(algorithm, 'measure') as \
+                mock_measure, \
+                mock.patch.object(algorithm, '_measure_and_update_state') as \
+                        mock__measure_and_update_state:
+            measured_low = ReceiveRateMeasurement(60, 14880000, 14879927, 0)
+            measured_high = ReceiveRateMeasurement(30, 14880000, 14879927, 100)
+            measured_low.latency = ['1000/3081/3962', '500/3149/3730']
+            measured_high.latency = ['1000/3081/3962', '500/3149/3730']
+            starting_interval = ReceiveRateInterval(measured_low,
+                                                    measured_high)
+            starting_result = NdrPdrResult(starting_interval,
+                                           starting_interval)
+            mock_measure.return_value = ReceiveRateMeasurement(1, 14880000,
+                                                               14879927, 0)
+            mock__measure_and_update_state.return_value = \
+                MultipleLossRatioSearch.ProgressState(starting_result, -1, 30,
+                                                      0.005, 0.0, 14880000,
+                                                      14880000)
+            previous_state = MultipleLossRatioSearch.ProgressState(
+                starting_result, -1, 50, 0.005, 0.0, 14880000,
+                14880000)
+            state = algorithm.ndrpdr(previous_state)
+            self.assertIsInstance(state, MultipleLossRatioSearch.ProgressState)
+        self.assertIsInstance(state, MultipleLossRatioSearch.ProgressState)
+        self.assertEqual(60.0, state.result.ndr_interval.measured_low.duration)
+        self.assertEqual(14880000,
+                         state.result.ndr_interval.measured_low.target_tr)
+        self.assertEqual(14879927,
+                         state.result.ndr_interval.measured_low.transmit_count)
+        self.assertEqual(0, state.result.ndr_interval.measured_low.loss_count)
+        self.assertEqual(14879927,
+                         state.result.ndr_interval.measured_low.receive_count)
+        self.assertEqual(247998.78333,
+                         state.result.ndr_interval.measured_low.transmit_rate)
+        self.assertEqual(0.0, state.result.ndr_interval.measured_low.loss_rate)
+        self.assertEqual(247998.78333,
+                         state.result.ndr_interval.measured_low.receive_rate)
+        self.assertEqual(0.0,
+                         state.result.ndr_interval.measured_low.loss_fraction)
+        self.assertEqual(30, state.result.ndr_interval.measured_high.duration)
+        self.assertEqual(14880000,
+                         state.result.ndr_interval.measured_high.target_tr)
+        self.assertEqual(14879927,
+                         state.result.ndr_interval.measured_high.transmit_count)
+        self.assertEqual(100,
+                         state.result.ndr_interval.measured_high.loss_count)
+        self.assertEqual(14879827,
+                         state.result.ndr_interval.measured_high.receive_count)
+        self.assertEqual(495997.56667,
+                         state.result.ndr_interval.measured_high.transmit_rate)
+        self.assertEqual(3.33333,
+                         state.result.ndr_interval.measured_high.loss_rate)
+        self.assertEqual(495994.23333,
+                         state.result.ndr_interval.measured_high.receive_rate)
+        self.assertEqual(1e-05,
+                         state.result.ndr_interval.measured_high.loss_fraction)
+        self.assertEqual(60.0, state.result.pdr_interval.measured_low.duration)
+        self.assertEqual(14880000,
+                         state.result.pdr_interval.measured_low.target_tr)
+        self.assertEqual(14879927,
+                         state.result.pdr_interval.measured_low.transmit_count)
+        self.assertEqual(0, state.result.pdr_interval.measured_low.loss_count)
+        self.assertEqual(14879927,
+                         state.result.pdr_interval.measured_low.receive_count)
+        self.assertEqual(247998.78333,
+                         state.result.pdr_interval.measured_low.transmit_rate)
+        self.assertEqual(0.0, state.result.pdr_interval.measured_low.loss_rate)
+        self.assertEqual(247998.78333,
+                         state.result.pdr_interval.measured_low.receive_rate)
+        self.assertEqual(0.0,
+                         state.result.pdr_interval.measured_low.loss_fraction)
+        self.assertEqual(30, state.result.pdr_interval.measured_high.duration)
+        self.assertEqual(14880000,
+                         state.result.pdr_interval.measured_high.target_tr)
+        self.assertEqual(14879927,
+                         state.result.pdr_interval.measured_high.transmit_count)
+        self.assertEqual(100,
+                         state.result.pdr_interval.measured_high.loss_count)
+        self.assertEqual(14879827,
+                         state.result.pdr_interval.measured_high.receive_count)
+        self.assertEqual(495997.56667,
+                         state.result.pdr_interval.measured_high.transmit_rate)
+        self.assertEqual(3.33333,
+                         state.result.pdr_interval.measured_high.loss_rate)
+        self.assertEqual(495994.23333,
+                         state.result.pdr_interval.measured_high.receive_rate)
+        self.assertEqual(1e-05,
+                         state.result.pdr_interval.measured_high.loss_fraction)
+        self.assertEqual(-1, state.phases)
+        self.assertEqual(30, state.duration)
+        self.assertEqual(0.005, state.width_goal)
+        self.assertEqual(0.0, state.packet_loss_ratio)
+        self.assertEqual(14880000, state.minimum_transmit_rate)
+        self.assertEqual(14880000, state.maximum_transmit_rate)
+
+    def test_ndrpdr_error(self):
+        algorithm = MultipleLossRatioSearch(measurer=mock.Mock(), latency=True,
+                                            pkt_size=64,
+                                            final_trial_duration=30,
+                                            final_relative_width=0.005,
+                                            number_of_intermediate_phases=2,
+                                            initial_trial_duration=1,
+                                            timeout=0)
+        ports = [0, 1]
+        port_pg_id = PortPgIDMap()
+        port_pg_id.add_port(0)
+        port_pg_id.add_port(1)
+        self.assertIsNone(
+            algorithm.init_generator(ports, port_pg_id, mock.Mock(), mock.Mock,
+                                     mock.Mock()))
+        with mock.patch.object(algorithm, 'measure') as \
+                mock_measure:
+            measured_low = ReceiveRateMeasurement(30, 14880000, 14879927, 0)
+            measured_high = ReceiveRateMeasurement(30, 14880000, 14879927, 0)
+            measured_low.latency = ['1000/3081/3962', '500/3149/3730']
+            measured_high.latency = ['1000/3081/3962', '500/3149/3730']
+            starting_interval = ReceiveRateInterval(measured_low,
+                                                    measured_high)
+            starting_result = NdrPdrResult(starting_interval,
+                                           starting_interval)
+            mock_measure.return_value = ReceiveRateMeasurement(1, 14880000,
+                                                               14879927, 0)
+            previous_state = MultipleLossRatioSearch.ProgressState(
+                starting_result, -1, 30, 0.005, 0.0, 14880000,
+                14880000)
+        with self.assertRaises(RuntimeError) as raised:
+            algorithm.ndrpdr(previous_state)
+
+        self.assertIn('Optimized search takes too long.',
+                      str(raised.exception))
+
+    def test_ndrpdr_update_state_ndr_hi(self):
+        algorithm = MultipleLossRatioSearch(measurer=mock.Mock(), latency=True,
+                                            pkt_size=64,
+                                            final_trial_duration=30,
+                                            final_relative_width=0.005,
+                                            number_of_intermediate_phases=2,
+                                            initial_trial_duration=1,
+                                            timeout=720)
+        ports = [0, 1]
+        port_pg_id = PortPgIDMap()
+        port_pg_id.add_port(0)
+        port_pg_id.add_port(1)
+        self.assertIsNone(
+            algorithm.init_generator(ports, port_pg_id, mock.Mock(), mock.Mock,
+                                     mock.Mock()))
+        with mock.patch.object(algorithm, 'measure') as \
+                mock_measure, \
+                mock.patch.object(algorithm, '_measure_and_update_state') as \
+                        mock__measure_and_update_state:
+            ndr_measured_low = ReceiveRateMeasurement(30, 10880000, 10879927,
+                                                      0)
+            ndr_measured_high = ReceiveRateMeasurement(30, 12880000, 12879927,
+                                                       0)
+            ndr_measured_low.latency = ['1000/3081/3962', '500/3149/3730']
+            ndr_measured_high.latency = ['1000/3081/3962', '500/3149/3730']
+            pdr_measured_low = ReceiveRateMeasurement(30, 12880000, 12879927,
+                                                      0)
+            pdr_measured_high = ReceiveRateMeasurement(30, 14880000, 14879927,
+                                                       0)
+            pdr_measured_low.latency = ['1000/3081/3962', '500/3149/3730']
+            pdr_measured_high.latency = ['1000/3081/3962', '500/3149/3730']
+            ndr_interval = ReceiveRateInterval(ndr_measured_low,
+                                               ndr_measured_high)
+            pdr_interval = ReceiveRateInterval(pdr_measured_low,
+                                               pdr_measured_high)
+            starting_result = NdrPdrResult(ndr_interval, pdr_interval)
+            ending_result = NdrPdrResult(pdr_interval, pdr_interval)
+            mock_measure.return_value = ReceiveRateMeasurement(1, 14880000,
+                                                               14879927, 0)
+            mock__measure_and_update_state.return_value = \
+                MultipleLossRatioSearch.ProgressState(ending_result, -1, 30,
+                                                      0.2, 0.0, 14880000,
+                                                      14880000)
+            previous_state = MultipleLossRatioSearch.ProgressState(
+                starting_result, -1, 30, 0.005, 0.0, 14880000,
+                14880000)
+            state = algorithm.ndrpdr(previous_state)
+            self.assertIsInstance(state, MultipleLossRatioSearch.ProgressState)
+        self.assertIsInstance(state, MultipleLossRatioSearch.ProgressState)
+        self.assertEqual(30, state.result.ndr_interval.measured_low.duration)
+        self.assertEqual(12880000.0,
+                         state.result.ndr_interval.measured_low.target_tr)
+        self.assertEqual(12879927,
+                         state.result.ndr_interval.measured_low.transmit_count)
+        self.assertEqual(0, state.result.ndr_interval.measured_low.loss_count)
+        self.assertEqual(12879927,
+                         state.result.ndr_interval.measured_low.receive_count)
+        self.assertEqual(429330.9,
+                         state.result.ndr_interval.measured_low.transmit_rate)
+        self.assertEqual(0.0, state.result.ndr_interval.measured_low.loss_rate)
+        self.assertEqual(429330.9,
+                         state.result.ndr_interval.measured_low.receive_rate)
+        self.assertEqual(0.0,
+                         state.result.ndr_interval.measured_low.loss_fraction)
+        self.assertEqual(30, state.result.ndr_interval.measured_high.duration)
+        self.assertEqual(14880000.0,
+                         state.result.ndr_interval.measured_high.target_tr)
+        self.assertEqual(14879927,
+                         state.result.ndr_interval.measured_high.transmit_count)
+        self.assertEqual(0, state.result.ndr_interval.measured_high.loss_count)
+        self.assertEqual(14879927,
+                         state.result.ndr_interval.measured_high.receive_count)
+        self.assertEqual(495997.56667,
+                         state.result.ndr_interval.measured_high.transmit_rate)
+        self.assertEqual(0.0,
+                         state.result.ndr_interval.measured_high.loss_rate)
+        self.assertEqual(495997.56667,
+                         state.result.ndr_interval.measured_high.receive_rate)
+        self.assertEqual(0.0,
+                         state.result.ndr_interval.measured_high.loss_fraction)
+        self.assertEqual(30, state.result.pdr_interval.measured_low.duration)
+        self.assertEqual(12880000.0,
+                         state.result.pdr_interval.measured_low.target_tr)
+        self.assertEqual(12879927,
+                         state.result.pdr_interval.measured_low.transmit_count)
+        self.assertEqual(0, state.result.pdr_interval.measured_low.loss_count)
+        self.assertEqual(12879927,
+                         state.result.pdr_interval.measured_low.receive_count)
+        self.assertEqual(429330.9,
+                         state.result.pdr_interval.measured_low.transmit_rate)
+        self.assertEqual(0.0, state.result.pdr_interval.measured_low.loss_rate)
+        self.assertEqual(429330.9,
+                         state.result.pdr_interval.measured_low.receive_rate)
+        self.assertEqual(0.0,
+                         state.result.pdr_interval.measured_low.loss_fraction)
+        self.assertEqual(30, state.result.pdr_interval.measured_high.duration)
+        self.assertEqual(14880000,
+                         state.result.pdr_interval.measured_high.target_tr)
+        self.assertEqual(14879927,
+                         state.result.pdr_interval.measured_high.transmit_count)
+        self.assertEqual(0, state.result.pdr_interval.measured_high.loss_count)
+        self.assertEqual(14879927,
+                         state.result.pdr_interval.measured_high.receive_count)
+        self.assertEqual(495997.56667,
+                         state.result.pdr_interval.measured_high.transmit_rate)
+        self.assertEqual(0.0,
+                         state.result.pdr_interval.measured_high.loss_rate)
+        self.assertEqual(495997.56667,
+                         state.result.pdr_interval.measured_high.receive_rate)
+        self.assertEqual(0.0,
+                         state.result.pdr_interval.measured_high.loss_fraction)
+        self.assertEqual(-1, state.phases)
+        self.assertEqual(30, state.duration)
+        self.assertEqual(0.2, state.width_goal)
+        self.assertEqual(0.0, state.packet_loss_ratio)
+        self.assertEqual(14880000, state.minimum_transmit_rate)
+        self.assertEqual(14880000, state.maximum_transmit_rate)
+
+    def test_ndrpdr_update_state_ndr_hi_duration(self):
+        algorithm = MultipleLossRatioSearch(measurer=mock.Mock(), latency=True,
+                                            pkt_size=64,
+                                            final_trial_duration=30,
+                                            final_relative_width=0.005,
+                                            number_of_intermediate_phases=2,
+                                            initial_trial_duration=1,
+                                            timeout=720)
+        ports = [0, 1]
+        port_pg_id = PortPgIDMap()
+        port_pg_id.add_port(0)
+        port_pg_id.add_port(1)
+        self.assertIsNone(
+            algorithm.init_generator(ports, port_pg_id, mock.Mock(), mock.Mock,
+                                     mock.Mock()))
+        with mock.patch.object(algorithm, 'measure') as \
+                mock_measure, \
+                mock.patch.object(algorithm, '_measure_and_update_state') as \
+                        mock__measure_and_update_state:
+            ndr_measured_low = ReceiveRateMeasurement(30, 10880000, 10879927,
+                                                      0)
+            ndr_measured_high = ReceiveRateMeasurement(30, 12880000, 12879927,
+                                                       0)
+            ndr_measured_low.latency = ['1000/3081/3962', '500/3149/3730']
+            ndr_measured_high.latency = ['1000/3081/3962', '500/3149/3730']
+            pdr_measured_low = ReceiveRateMeasurement(30, 12880000, 12879927,
+                                                      0)
+            pdr_measured_high = ReceiveRateMeasurement(30, 14880000, 14879927,
+                                                       0)
+            pdr_measured_low.latency = ['1000/3081/3962', '500/3149/3730']
+            pdr_measured_high.latency = ['1000/3081/3962', '500/3149/3730']
+            ndr_interval = ReceiveRateInterval(ndr_measured_low,
+                                               ndr_measured_high)
+            pdr_interval = ReceiveRateInterval(pdr_measured_low,
+                                               pdr_measured_high)
+            starting_result = NdrPdrResult(ndr_interval, pdr_interval)
+            ending_result = NdrPdrResult(pdr_interval, pdr_interval)
+            mock_measure.return_value = ReceiveRateMeasurement(1, 14880000,
+                                                               14879927, 0)
+            mock__measure_and_update_state.return_value = \
+                MultipleLossRatioSearch.ProgressState(ending_result, -1, 30,
+                                                      0.2, 0.0, 14880000,
+                                                      14880000)
+            previous_state = MultipleLossRatioSearch.ProgressState(
+                starting_result, -1, 50, 0.005, 0.0, 4880000,
+                10880000)
+            state = algorithm.ndrpdr(previous_state)
+            self.assertIsInstance(state, MultipleLossRatioSearch.ProgressState)
+        self.assertIsInstance(state, MultipleLossRatioSearch.ProgressState)
+        self.assertEqual(30, state.result.ndr_interval.measured_low.duration)
+        self.assertEqual(12880000.0,
+                         state.result.ndr_interval.measured_low.target_tr)
+        self.assertEqual(12879927,
+                         state.result.ndr_interval.measured_low.transmit_count)
+        self.assertEqual(0, state.result.ndr_interval.measured_low.loss_count)
+        self.assertEqual(12879927,
+                         state.result.ndr_interval.measured_low.receive_count)
+        self.assertEqual(429330.9,
+                         state.result.ndr_interval.measured_low.transmit_rate)
+        self.assertEqual(0.0, state.result.ndr_interval.measured_low.loss_rate)
+        self.assertEqual(429330.9,
+                         state.result.ndr_interval.measured_low.receive_rate)
+        self.assertEqual(0.0,
+                         state.result.ndr_interval.measured_low.loss_fraction)
+        self.assertEqual(30, state.result.ndr_interval.measured_high.duration)
+        self.assertEqual(14880000.0,
+                         state.result.ndr_interval.measured_high.target_tr)
+        self.assertEqual(14879927,
+                         state.result.ndr_interval.measured_high.transmit_count)
+        self.assertEqual(0, state.result.ndr_interval.measured_high.loss_count)
+        self.assertEqual(14879927,
+                         state.result.ndr_interval.measured_high.receive_count)
+        self.assertEqual(495997.56667,
+                         state.result.ndr_interval.measured_high.transmit_rate)
+        self.assertEqual(0.0,
+                         state.result.ndr_interval.measured_high.loss_rate)
+        self.assertEqual(495997.56667,
+                         state.result.ndr_interval.measured_high.receive_rate)
+        self.assertEqual(0.0,
+                         state.result.ndr_interval.measured_high.loss_fraction)
+        self.assertEqual(30, state.result.pdr_interval.measured_low.duration)
+        self.assertEqual(12880000.0,
+                         state.result.pdr_interval.measured_low.target_tr)
+        self.assertEqual(12879927,
+                         state.result.pdr_interval.measured_low.transmit_count)
+        self.assertEqual(0, state.result.pdr_interval.measured_low.loss_count)
+        self.assertEqual(12879927,
+                         state.result.pdr_interval.measured_low.receive_count)
+        self.assertEqual(429330.9,
+                         state.result.pdr_interval.measured_low.transmit_rate)
+        self.assertEqual(0.0, state.result.pdr_interval.measured_low.loss_rate)
+        self.assertEqual(429330.9,
+                         state.result.pdr_interval.measured_low.receive_rate)
+        self.assertEqual(0.0,
+                         state.result.pdr_interval.measured_low.loss_fraction)
+        self.assertEqual(30, state.result.pdr_interval.measured_high.duration)
+        self.assertEqual(14880000,
+                         state.result.pdr_interval.measured_high.target_tr)
+        self.assertEqual(14879927,
+                         state.result.pdr_interval.measured_high.transmit_count)
+        self.assertEqual(0, state.result.pdr_interval.measured_high.loss_count)
+        self.assertEqual(14879927,
+                         state.result.pdr_interval.measured_high.receive_count)
+        self.assertEqual(495997.56667,
+                         state.result.pdr_interval.measured_high.transmit_rate)
+        self.assertEqual(0.0,
+                         state.result.pdr_interval.measured_high.loss_rate)
+        self.assertEqual(495997.56667,
+                         state.result.pdr_interval.measured_high.receive_rate)
+        self.assertEqual(0.0,
+                         state.result.pdr_interval.measured_high.loss_fraction)
+        self.assertEqual(-1, state.phases)
+        self.assertEqual(30, state.duration)
+        self.assertEqual(0.2, state.width_goal)
+        self.assertEqual(0.0, state.packet_loss_ratio)
+        self.assertEqual(14880000, state.minimum_transmit_rate)
+        self.assertEqual(14880000, state.maximum_transmit_rate)
+
+    def test_ndrpdr_update_state_ndr_lo(self):
+        algorithm = MultipleLossRatioSearch(measurer=mock.Mock(), latency=True,
+                                            pkt_size=64,
+                                            final_trial_duration=30,
+                                            final_relative_width=0.005,
+                                            number_of_intermediate_phases=2,
+                                            initial_trial_duration=1,
+                                            timeout=720)
+        ports = [0, 1]
+        port_pg_id = PortPgIDMap()
+        port_pg_id.add_port(0)
+        port_pg_id.add_port(1)
+        self.assertIsNone(
+            algorithm.init_generator(ports, port_pg_id, mock.Mock(), mock.Mock,
+                                     mock.Mock()))
+        with mock.patch.object(algorithm, 'measure') as \
+                mock_measure, \
+                mock.patch.object(algorithm, '_measure_and_update_state') as \
+                        mock__measure_and_update_state:
+            ndr_measured_low = ReceiveRateMeasurement(30, 10880000, 10879927,
+                                                      100000)
+            ndr_measured_high = ReceiveRateMeasurement(30, 12880000, 12879927,
+                                                       100000)
+            ndr_measured_low.latency = ['1000/3081/3962', '500/3149/3730']
+            ndr_measured_high.latency = ['1000/3081/3962', '500/3149/3730']
+            pdr_measured_low = ReceiveRateMeasurement(30, 12880000, 12879927,
+                                                      100000)
+            pdr_measured_high = ReceiveRateMeasurement(30, 14880000, 14879927,
+                                                       100000)
+            pdr_measured_low.latency = ['1000/3081/3962', '500/3149/3730']
+            pdr_measured_high.latency = ['1000/3081/3962', '500/3149/3730']
+            ndr_interval = ReceiveRateInterval(ndr_measured_low,
+                                               ndr_measured_high)
+            pdr_interval = ReceiveRateInterval(pdr_measured_low,
+                                               pdr_measured_high)
+            starting_result = NdrPdrResult(ndr_interval, pdr_interval)
+            ending_result = NdrPdrResult(pdr_interval, pdr_interval)
+            mock_measure.return_value = ReceiveRateMeasurement(1, 14880000,
+                                                               14879927, 0)
+            mock__measure_and_update_state.return_value = \
+                MultipleLossRatioSearch.ProgressState(ending_result, -1, 30,
+                                                      0.2, 0.0, 14880000,
+                                                      14880000)
+            previous_state = MultipleLossRatioSearch.ProgressState(
+                starting_result, -1, 30, 0.005, 0.0, 100000,
+                14880000)
+            state = algorithm.ndrpdr(previous_state)
+            self.assertIsInstance(state, MultipleLossRatioSearch.ProgressState)
+        self.assertIsInstance(state, MultipleLossRatioSearch.ProgressState)
+        self.assertEqual(30, state.result.ndr_interval.measured_low.duration)
+        self.assertEqual(12880000.0,
+                         state.result.ndr_interval.measured_low.target_tr)
+        self.assertEqual(12879927,
+                         state.result.ndr_interval.measured_low.transmit_count)
+        self.assertEqual(100000,
+                         state.result.ndr_interval.measured_low.loss_count)
+        self.assertEqual(12779927,
+                         state.result.ndr_interval.measured_low.receive_count)
+        self.assertEqual(429330.9,
+                         state.result.ndr_interval.measured_low.transmit_rate)
+        self.assertEqual(3333.33333,
+                         state.result.ndr_interval.measured_low.loss_rate)
+        self.assertEqual(425997.56667,
+                         state.result.ndr_interval.measured_low.receive_rate)
+        self.assertEqual(0.00776,
+                         state.result.ndr_interval.measured_low.loss_fraction)
+        self.assertEqual(30, state.result.ndr_interval.measured_high.duration)
+        self.assertEqual(14880000.0,
+                         state.result.ndr_interval.measured_high.target_tr)
+        self.assertEqual(14879927,
+                         state.result.ndr_interval.measured_high.transmit_count)
+        self.assertEqual(100000,
+                         state.result.ndr_interval.measured_high.loss_count)
+        self.assertEqual(14779927,
+                         state.result.ndr_interval.measured_high.receive_count)
+        self.assertEqual(495997.56667,
+                         state.result.ndr_interval.measured_high.transmit_rate)
+        self.assertEqual(3333.33333,
+                         state.result.ndr_interval.measured_high.loss_rate)
+        self.assertEqual(492664.23333,
+                         state.result.ndr_interval.measured_high.receive_rate)
+        self.assertEqual(0.00672,
+                         state.result.ndr_interval.measured_high.loss_fraction)
+        self.assertEqual(30, state.result.pdr_interval.measured_low.duration)
+        self.assertEqual(12880000.0,
+                         state.result.pdr_interval.measured_low.target_tr)
+        self.assertEqual(12879927,
+                         state.result.pdr_interval.measured_low.transmit_count)
+        self.assertEqual(100000,
+                         state.result.pdr_interval.measured_low.loss_count)
+        self.assertEqual(12779927,
+                         state.result.pdr_interval.measured_low.receive_count)
+        self.assertEqual(429330.9,
+                         state.result.pdr_interval.measured_low.transmit_rate)
+        self.assertEqual(3333.33333,
+                         state.result.pdr_interval.measured_low.loss_rate)
+        self.assertEqual(425997.56667,
+                         state.result.pdr_interval.measured_low.receive_rate)
+        self.assertEqual(0.00776,
+                         state.result.pdr_interval.measured_low.loss_fraction)
+        self.assertEqual(30, state.result.pdr_interval.measured_high.duration)
+        self.assertEqual(14880000,
+                         state.result.pdr_interval.measured_high.target_tr)
+        self.assertEqual(14879927,
+                         state.result.pdr_interval.measured_high.transmit_count)
+        self.assertEqual(100000,
+                         state.result.pdr_interval.measured_high.loss_count)
+        self.assertEqual(14779927,
+                         state.result.pdr_interval.measured_high.receive_count)
+        self.assertEqual(495997.56667,
+                         state.result.pdr_interval.measured_high.transmit_rate)
+        self.assertEqual(3333.33333,
+                         state.result.pdr_interval.measured_high.loss_rate)
+        self.assertEqual(492664.23333,
+                         state.result.pdr_interval.measured_high.receive_rate)
+        self.assertEqual(0.00672,
+                         state.result.pdr_interval.measured_high.loss_fraction)
+        self.assertEqual(-1, state.phases)
+        self.assertEqual(30, state.duration)
+        self.assertEqual(0.2, state.width_goal)
+        self.assertEqual(0.0, state.packet_loss_ratio)
+        self.assertEqual(14880000, state.minimum_transmit_rate)
+        self.assertEqual(14880000, state.maximum_transmit_rate)
+
+    def test_ndrpdr_update_state_pdr_lo(self):
+        algorithm = MultipleLossRatioSearch(measurer=mock.Mock(), latency=True,
+                                            pkt_size=64,
+                                            final_trial_duration=30,
+                                            final_relative_width=0.005,
+                                            number_of_intermediate_phases=2,
+                                            initial_trial_duration=1,
+                                            timeout=720)
+        ports = [0, 1]
+        port_pg_id = PortPgIDMap()
+        port_pg_id.add_port(0)
+        port_pg_id.add_port(1)
+        self.assertIsNone(
+            algorithm.init_generator(ports, port_pg_id, mock.Mock(), mock.Mock,
+                                     mock.Mock()))
+        with mock.patch.object(algorithm, 'measure') as \
+                mock_measure, \
+                mock.patch.object(algorithm, '_measure_and_update_state') as \
+                        mock__measure_and_update_state:
+            ndr_measured_low = ReceiveRateMeasurement(30, 10880000, 10879927,
+                                                      0)
+            ndr_measured_high = ReceiveRateMeasurement(30, 12880000, 12879927,
+                                                       0)
+            ndr_measured_low.latency = ['1000/3081/3962', '500/3149/3730']
+            ndr_measured_high.latency = ['1000/3081/3962', '500/3149/3730']
+            pdr_measured_low = ReceiveRateMeasurement(30, 12880000, 12879927,
+                                                      100000)
+            pdr_measured_high = ReceiveRateMeasurement(30, 14880000, 14879927,
+                                                       100000)
+            pdr_measured_low.latency = ['1000/3081/3962', '500/3149/3730']
+            pdr_measured_high.latency = ['1000/3081/3962', '500/3149/3730']
+            ndr_interval = ReceiveRateInterval(ndr_measured_low,
+                                               ndr_measured_high)
+            pdr_interval = ReceiveRateInterval(pdr_measured_low,
+                                               pdr_measured_high)
+            starting_result = NdrPdrResult(ndr_interval, pdr_interval)
+            ending_result = NdrPdrResult(pdr_interval, pdr_interval)
+            mock_measure.return_value = ReceiveRateMeasurement(1, 14880000,
+                                                               14879927, 0)
+            mock__measure_and_update_state.return_value = \
+                MultipleLossRatioSearch.ProgressState(ending_result, -1, 30,
+                                                      0.2, 0.0, 14880000,
+                                                      14880000)
+            previous_state = MultipleLossRatioSearch.ProgressState(
+                starting_result, -1, 30, 0.005, 0.0, 100000,
+                14880000)
+            state = algorithm.ndrpdr(previous_state)
+            self.assertIsInstance(state, MultipleLossRatioSearch.ProgressState)
+        self.assertIsInstance(state, MultipleLossRatioSearch.ProgressState)
+        self.assertEqual(30, state.result.ndr_interval.measured_low.duration)
+        self.assertEqual(12880000.0,
+                         state.result.ndr_interval.measured_low.target_tr)
+        self.assertEqual(12879927,
+                         state.result.ndr_interval.measured_low.transmit_count)
+        self.assertEqual(100000,
+                         state.result.ndr_interval.measured_low.loss_count)
+        self.assertEqual(12779927,
+                         state.result.ndr_interval.measured_low.receive_count)
+        self.assertEqual(429330.9,
+                         state.result.ndr_interval.measured_low.transmit_rate)
+        self.assertEqual(3333.33333,
+                         state.result.ndr_interval.measured_low.loss_rate)
+        self.assertEqual(425997.56667,
+                         state.result.ndr_interval.measured_low.receive_rate)
+        self.assertEqual(0.00776,
+                         state.result.ndr_interval.measured_low.loss_fraction)
+        self.assertEqual(30, state.result.ndr_interval.measured_high.duration)
+        self.assertEqual(14880000.0,
+                         state.result.ndr_interval.measured_high.target_tr)
+        self.assertEqual(14879927,
+                         state.result.ndr_interval.measured_high.transmit_count)
+        self.assertEqual(100000,
+                         state.result.ndr_interval.measured_high.loss_count)
+        self.assertEqual(14779927,
+                         state.result.ndr_interval.measured_high.receive_count)
+        self.assertEqual(495997.56667,
+                         state.result.ndr_interval.measured_high.transmit_rate)
+        self.assertEqual(3333.33333,
+                         state.result.ndr_interval.measured_high.loss_rate)
+        self.assertEqual(492664.23333,
+                         state.result.ndr_interval.measured_high.receive_rate)
+        self.assertEqual(0.00672,
+                         state.result.ndr_interval.measured_high.loss_fraction)
+        self.assertEqual(30, state.result.pdr_interval.measured_low.duration)
+        self.assertEqual(12880000.0,
+                         state.result.pdr_interval.measured_low.target_tr)
+        self.assertEqual(12879927,
+                         state.result.pdr_interval.measured_low.transmit_count)
+        self.assertEqual(100000,
+                         state.result.pdr_interval.measured_low.loss_count)
+        self.assertEqual(12779927,
+                         state.result.pdr_interval.measured_low.receive_count)
+        self.assertEqual(429330.9,
+                         state.result.pdr_interval.measured_low.transmit_rate)
+        self.assertEqual(3333.33333,
+                         state.result.pdr_interval.measured_low.loss_rate)
+        self.assertEqual(425997.56667,
+                         state.result.pdr_interval.measured_low.receive_rate)
+        self.assertEqual(0.00776,
+                         state.result.pdr_interval.measured_low.loss_fraction)
+        self.assertEqual(30, state.result.pdr_interval.measured_high.duration)
+        self.assertEqual(14880000,
+                         state.result.pdr_interval.measured_high.target_tr)
+        self.assertEqual(14879927,
+                         state.result.pdr_interval.measured_high.transmit_count)
+        self.assertEqual(100000,
+                         state.result.pdr_interval.measured_high.loss_count)
+        self.assertEqual(14779927,
+                         state.result.pdr_interval.measured_high.receive_count)
+        self.assertEqual(495997.56667,
+                         state.result.pdr_interval.measured_high.transmit_rate)
+        self.assertEqual(3333.33333,
+                         state.result.pdr_interval.measured_high.loss_rate)
+        self.assertEqual(492664.23333,
+                         state.result.pdr_interval.measured_high.receive_rate)
+        self.assertEqual(0.00672,
+                         state.result.pdr_interval.measured_high.loss_fraction)
+        self.assertEqual(-1, state.phases)
+        self.assertEqual(30, state.duration)
+        self.assertEqual(0.2, state.width_goal)
+        self.assertEqual(0.0, state.packet_loss_ratio)
+        self.assertEqual(14880000, state.minimum_transmit_rate)
+        self.assertEqual(14880000, state.maximum_transmit_rate)
+
+    def test_ndrpdr_update_state_pdr_lo_duration(self):
+        algorithm = MultipleLossRatioSearch(measurer=mock.Mock(), latency=True,
+                                            pkt_size=64,
+                                            final_trial_duration=30,
+                                            final_relative_width=0.005,
+                                            number_of_intermediate_phases=2,
+                                            initial_trial_duration=1,
+                                            timeout=720)
+        ports = [0, 1]
+        port_pg_id = PortPgIDMap()
+        port_pg_id.add_port(0)
+        port_pg_id.add_port(1)
+        self.assertIsNone(
+            algorithm.init_generator(ports, port_pg_id, mock.Mock(), mock.Mock,
+                                     mock.Mock()))
+        with mock.patch.object(algorithm, 'measure') as \
+                mock_measure, \
+                mock.patch.object(algorithm, '_measure_and_update_state') as \
+                        mock__measure_and_update_state:
+            ndr_measured_low = ReceiveRateMeasurement(30, 10880000, 10879927,
+                                                      0)
+            ndr_measured_high = ReceiveRateMeasurement(30, 12880000, 12879927,
+                                                       0)
+            ndr_measured_low.latency = ['1000/3081/3962', '500/3149/3730']
+            ndr_measured_high.latency = ['1000/3081/3962', '500/3149/3730']
+            pdr_measured_low = ReceiveRateMeasurement(30, 12880000, 12879927,
+                                                      100000)
+            pdr_measured_high = ReceiveRateMeasurement(30, 14880000, 14879927,
+                                                       100000)
+            pdr_measured_low.latency = ['1000/3081/3962', '500/3149/3730']
+            pdr_measured_high.latency = ['1000/3081/3962', '500/3149/3730']
+            ndr_interval = ReceiveRateInterval(ndr_measured_low,
+                                               ndr_measured_high)
+            pdr_interval = ReceiveRateInterval(pdr_measured_low,
+                                               pdr_measured_high)
+            starting_result = NdrPdrResult(ndr_interval, pdr_interval)
+            ending_result = NdrPdrResult(pdr_interval, pdr_interval)
+            mock_measure.return_value = ReceiveRateMeasurement(1, 14880000,
+                                                               14879927, 0)
+            mock__measure_and_update_state.return_value = \
+                MultipleLossRatioSearch.ProgressState(ending_result, -1, 30,
+                                                      0.2, 0.0, 14880000,
+                                                      14880000)
+            previous_state = MultipleLossRatioSearch.ProgressState(
+                starting_result, -1, 50, 0.005, 0.0, 14880000,
+                14880000)
+            state = algorithm.ndrpdr(previous_state)
+            self.assertIsInstance(state, MultipleLossRatioSearch.ProgressState)
+        self.assertIsInstance(state, MultipleLossRatioSearch.ProgressState)
+        self.assertEqual(30, state.result.ndr_interval.measured_low.duration)
+        self.assertEqual(12880000.0,
+                         state.result.ndr_interval.measured_low.target_tr)
+        self.assertEqual(12879927,
+                         state.result.ndr_interval.measured_low.transmit_count)
+        self.assertEqual(100000,
+                         state.result.ndr_interval.measured_low.loss_count)
+        self.assertEqual(12779927,
+                         state.result.ndr_interval.measured_low.receive_count)
+        self.assertEqual(429330.9,
+                         state.result.ndr_interval.measured_low.transmit_rate)
+        self.assertEqual(3333.33333,
+                         state.result.ndr_interval.measured_low.loss_rate)
+        self.assertEqual(425997.56667,
+                         state.result.ndr_interval.measured_low.receive_rate)
+        self.assertEqual(0.00776,
+                         state.result.ndr_interval.measured_low.loss_fraction)
+        self.assertEqual(30, state.result.ndr_interval.measured_high.duration)
+        self.assertEqual(14880000.0,
+                         state.result.ndr_interval.measured_high.target_tr)
+        self.assertEqual(14879927,
+                         state.result.ndr_interval.measured_high.transmit_count)
+        self.assertEqual(100000,
+                         state.result.ndr_interval.measured_high.loss_count)
+        self.assertEqual(14779927,
+                         state.result.ndr_interval.measured_high.receive_count)
+        self.assertEqual(495997.56667,
+                         state.result.ndr_interval.measured_high.transmit_rate)
+        self.assertEqual(3333.33333,
+                         state.result.ndr_interval.measured_high.loss_rate)
+        self.assertEqual(492664.23333,
+                         state.result.ndr_interval.measured_high.receive_rate)
+        self.assertEqual(0.00672,
+                         state.result.ndr_interval.measured_high.loss_fraction)
+        self.assertEqual(30, state.result.pdr_interval.measured_low.duration)
+        self.assertEqual(12880000.0,
+                         state.result.pdr_interval.measured_low.target_tr)
+        self.assertEqual(12879927,
+                         state.result.pdr_interval.measured_low.transmit_count)
+        self.assertEqual(100000,
+                         state.result.pdr_interval.measured_low.loss_count)
+        self.assertEqual(12779927,
+                         state.result.pdr_interval.measured_low.receive_count)
+        self.assertEqual(429330.9,
+                         state.result.pdr_interval.measured_low.transmit_rate)
+        self.assertEqual(3333.33333,
+                         state.result.pdr_interval.measured_low.loss_rate)
+        self.assertEqual(425997.56667,
+                         state.result.pdr_interval.measured_low.receive_rate)
+        self.assertEqual(0.00776,
+                         state.result.pdr_interval.measured_low.loss_fraction)
+        self.assertEqual(30, state.result.pdr_interval.measured_high.duration)
+        self.assertEqual(14880000,
+                         state.result.pdr_interval.measured_high.target_tr)
+        self.assertEqual(14879927,
+                         state.result.pdr_interval.measured_high.transmit_count)
+        self.assertEqual(100000,
+                         state.result.pdr_interval.measured_high.loss_count)
+        self.assertEqual(14779927,
+                         state.result.pdr_interval.measured_high.receive_count)
+        self.assertEqual(495997.56667,
+                         state.result.pdr_interval.measured_high.transmit_rate)
+        self.assertEqual(3333.33333,
+                         state.result.pdr_interval.measured_high.loss_rate)
+        self.assertEqual(492664.23333,
+                         state.result.pdr_interval.measured_high.receive_rate)
+        self.assertEqual(0.00672,
+                         state.result.pdr_interval.measured_high.loss_fraction)
+        self.assertEqual(-1, state.phases)
+        self.assertEqual(30, state.duration)
+        self.assertEqual(0.2, state.width_goal)
+        self.assertEqual(0.0, state.packet_loss_ratio)
+        self.assertEqual(14880000, state.minimum_transmit_rate)
+        self.assertEqual(14880000, state.maximum_transmit_rate)
+
+    def test_ndrpdr_update_state_pdr_hi(self):
+        algorithm = MultipleLossRatioSearch(measurer=mock.Mock(), latency=True,
+                                            pkt_size=64,
+                                            final_trial_duration=30,
+                                            final_relative_width=0.005,
+                                            number_of_intermediate_phases=2,
+                                            initial_trial_duration=1,
+                                            timeout=720)
+        ports = [0, 1]
+        port_pg_id = PortPgIDMap()
+        port_pg_id.add_port(0)
+        port_pg_id.add_port(1)
+        self.assertIsNone(
+            algorithm.init_generator(ports, port_pg_id, mock.Mock(), mock.Mock,
+                                     mock.Mock()))
+        with mock.patch.object(algorithm, 'measure') as \
+                mock_measure, \
+                mock.patch.object(algorithm, '_measure_and_update_state') as \
+                        mock__measure_and_update_state:
+            ndr_measured_low = ReceiveRateMeasurement(30, 10880000, 10879927,
+                                                      0)
+            ndr_measured_high = ReceiveRateMeasurement(30, 12880000, 12879927,
+                                                       100000)
+            ndr_measured_low.latency = ['1000/3081/3962', '500/3149/3730']
+            ndr_measured_high.latency = ['1000/3081/3962', '500/3149/3730']
+            pdr_measured_low = ReceiveRateMeasurement(30, 12880000, 12879927,
+                                                      0)
+            pdr_measured_high = ReceiveRateMeasurement(30, 13880000, 14879927,
+                                                       0)
+            pdr_measured_low.latency = ['1000/3081/3962', '500/3149/3730']
+            pdr_measured_high.latency = ['1000/3081/3962', '500/3149/3730']
+            ndr_interval = ReceiveRateInterval(ndr_measured_low,
+                                               ndr_measured_high)
+            pdr_interval = ReceiveRateInterval(pdr_measured_low,
+                                               pdr_measured_high)
+            starting_result = NdrPdrResult(ndr_interval, pdr_interval)
+            ending_result = NdrPdrResult(ndr_interval, ndr_interval)
+            mock_measure.return_value = ReceiveRateMeasurement(1, 14880000,
+                                                               14879927, 0)
+            mock__measure_and_update_state.return_value = \
+                MultipleLossRatioSearch.ProgressState(ending_result, -1, 30,
+                                                      0.2, 0.0, 14880000,
+                                                      14880000)
+            previous_state = MultipleLossRatioSearch.ProgressState(
+                starting_result, -1, 30, 0.005, 0.0, 100000,
+                14880000)
+            state = algorithm.ndrpdr(previous_state)
+            self.assertIsInstance(state, MultipleLossRatioSearch.ProgressState)
+        self.assertIsInstance(state, MultipleLossRatioSearch.ProgressState)
+        self.assertEqual(30, state.result.ndr_interval.measured_low.duration)
+        self.assertEqual(10880000.0,
+                         state.result.ndr_interval.measured_low.target_tr)
+        self.assertEqual(10879927,
+                         state.result.ndr_interval.measured_low.transmit_count)
+        self.assertEqual(0,
+                         state.result.ndr_interval.measured_low.loss_count)
+        self.assertEqual(10879927,
+                         state.result.ndr_interval.measured_low.receive_count)
+        self.assertEqual(362664.23333,
+                         state.result.ndr_interval.measured_low.transmit_rate)
+        self.assertEqual(0.0,
+                         state.result.ndr_interval.measured_low.loss_rate)
+        self.assertEqual(362664.23333,
+                         state.result.ndr_interval.measured_low.receive_rate)
+        self.assertEqual(0.0,
+                         state.result.ndr_interval.measured_low.loss_fraction)
+        self.assertEqual(30, state.result.ndr_interval.measured_high.duration)
+        self.assertEqual(12880000.0,
+                         state.result.ndr_interval.measured_high.target_tr)
+        self.assertEqual(12879927,
+                         state.result.ndr_interval.measured_high.transmit_count)
+        self.assertEqual(100000,
+                         state.result.ndr_interval.measured_high.loss_count)
+        self.assertEqual(12779927,
+                         state.result.ndr_interval.measured_high.receive_count)
+        self.assertEqual(429330.9,
+                         state.result.ndr_interval.measured_high.transmit_rate)
+        self.assertEqual(3333.33333,
+                         state.result.ndr_interval.measured_high.loss_rate)
+        self.assertEqual(425997.56667,
+                         state.result.ndr_interval.measured_high.receive_rate)
+        self.assertEqual(0.00776,
+                         state.result.ndr_interval.measured_high.loss_fraction)
+        self.assertEqual(30, state.result.pdr_interval.measured_low.duration)
+        self.assertEqual(10880000.0,
+                         state.result.pdr_interval.measured_low.target_tr)
+        self.assertEqual(10879927,
+                         state.result.pdr_interval.measured_low.transmit_count)
+        self.assertEqual(0,
+                         state.result.pdr_interval.measured_low.loss_count)
+        self.assertEqual(10879927,
+                         state.result.pdr_interval.measured_low.receive_count)
+        self.assertEqual(362664.23333,
+                         state.result.pdr_interval.measured_low.transmit_rate)
+        self.assertEqual(0.0,
+                         state.result.pdr_interval.measured_low.loss_rate)
+        self.assertEqual(362664.23333,
+                         state.result.pdr_interval.measured_low.receive_rate)
+        self.assertEqual(0.0,
+                         state.result.pdr_interval.measured_low.loss_fraction)
+        self.assertEqual(30, state.result.pdr_interval.measured_high.duration)
+        self.assertEqual(12880000,
+                         state.result.pdr_interval.measured_high.target_tr)
+        self.assertEqual(12879927,
+                         state.result.pdr_interval.measured_high.transmit_count)
+        self.assertEqual(100000,
+                         state.result.pdr_interval.measured_high.loss_count)
+        self.assertEqual(12779927,
+                         state.result.pdr_interval.measured_high.receive_count)
+        self.assertEqual(429330.9,
+                         state.result.pdr_interval.measured_high.transmit_rate)
+        self.assertEqual(3333.33333,
+                         state.result.pdr_interval.measured_high.loss_rate)
+        self.assertEqual(425997.56667,
+                         state.result.pdr_interval.measured_high.receive_rate)
+        self.assertEqual(0.00776,
+                         state.result.pdr_interval.measured_high.loss_fraction)
+        self.assertEqual(-1, state.phases)
+        self.assertEqual(30, state.duration)
+        self.assertEqual(0.2, state.width_goal)
+        self.assertEqual(0.0, state.packet_loss_ratio)
+        self.assertEqual(14880000, state.minimum_transmit_rate)
+        self.assertEqual(14880000, state.maximum_transmit_rate)
+
+    def test_ndrpdr_update_state_pdr_hi_duration(self):
+        algorithm = MultipleLossRatioSearch(measurer=mock.Mock(), latency=True,
+                                            pkt_size=64,
+                                            final_trial_duration=30,
+                                            final_relative_width=0.005,
+                                            number_of_intermediate_phases=2,
+                                            initial_trial_duration=1,
+                                            timeout=720)
+        ports = [0, 1]
+        port_pg_id = PortPgIDMap()
+        port_pg_id.add_port(0)
+        port_pg_id.add_port(1)
+        self.assertIsNone(
+            algorithm.init_generator(ports, port_pg_id, mock.Mock(), mock.Mock,
+                                     mock.Mock()))
+        with mock.patch.object(algorithm, 'measure') as \
+                mock_measure, \
+                mock.patch.object(algorithm, '_measure_and_update_state') as \
+                        mock__measure_and_update_state:
+            ndr_measured_low = ReceiveRateMeasurement(30, 10880000, 10879927,
+                                                      0)
+            ndr_measured_high = ReceiveRateMeasurement(30, 12880000, 12879927,
+                                                       100000)
+            ndr_measured_low.latency = ['1000/3081/3962', '500/3149/3730']
+            ndr_measured_high.latency = ['1000/3081/3962', '500/3149/3730']
+            pdr_measured_low = ReceiveRateMeasurement(30, 12880000, 12879927,
+                                                      0)
+            pdr_measured_high = ReceiveRateMeasurement(30, 13880000, 14879927,
+                                                       0)
+            pdr_measured_low.latency = ['1000/3081/3962', '500/3149/3730']
+            pdr_measured_high.latency = ['1000/3081/3962', '500/3149/3730']
+            ndr_interval = ReceiveRateInterval(ndr_measured_low,
+                                               ndr_measured_high)
+            pdr_interval = ReceiveRateInterval(pdr_measured_low,
+                                               pdr_measured_high)
+            starting_result = NdrPdrResult(ndr_interval, pdr_interval)
+            ending_result = NdrPdrResult(ndr_interval, ndr_interval)
+            mock_measure.return_value = ReceiveRateMeasurement(1, 14880000,
+                                                               14879927, 0)
+            mock__measure_and_update_state.return_value = \
+                MultipleLossRatioSearch.ProgressState(ending_result, -1, 30,
+                                                      0.2, 0.0, 14880000,
+                                                      14880000)
+            previous_state = MultipleLossRatioSearch.ProgressState(
+                starting_result, -1, 50, 0.005, 0.0, 100000,
+                10880000)
+            state = algorithm.ndrpdr(previous_state)
+            self.assertIsInstance(state, MultipleLossRatioSearch.ProgressState)
+        self.assertIsInstance(state, MultipleLossRatioSearch.ProgressState)
+        self.assertEqual(30, state.result.ndr_interval.measured_low.duration)
+        self.assertEqual(10880000.0,
+                         state.result.ndr_interval.measured_low.target_tr)
+        self.assertEqual(10879927,
+                         state.result.ndr_interval.measured_low.transmit_count)
+        self.assertEqual(0,
+                         state.result.ndr_interval.measured_low.loss_count)
+        self.assertEqual(10879927,
+                         state.result.ndr_interval.measured_low.receive_count)
+        self.assertEqual(362664.23333,
+                         state.result.ndr_interval.measured_low.transmit_rate)
+        self.assertEqual(0.0,
+                         state.result.ndr_interval.measured_low.loss_rate)
+        self.assertEqual(362664.23333,
+                         state.result.ndr_interval.measured_low.receive_rate)
+        self.assertEqual(0.0,
+                         state.result.ndr_interval.measured_low.loss_fraction)
+        self.assertEqual(30, state.result.ndr_interval.measured_high.duration)
+        self.assertEqual(12880000.0,
+                         state.result.ndr_interval.measured_high.target_tr)
+        self.assertEqual(12879927,
+                         state.result.ndr_interval.measured_high.transmit_count)
+        self.assertEqual(100000,
+                         state.result.ndr_interval.measured_high.loss_count)
+        self.assertEqual(12779927,
+                         state.result.ndr_interval.measured_high.receive_count)
+        self.assertEqual(429330.9,
+                         state.result.ndr_interval.measured_high.transmit_rate)
+        self.assertEqual(3333.33333,
+                         state.result.ndr_interval.measured_high.loss_rate)
+        self.assertEqual(425997.56667,
+                         state.result.ndr_interval.measured_high.receive_rate)
+        self.assertEqual(0.00776,
+                         state.result.ndr_interval.measured_high.loss_fraction)
+        self.assertEqual(30, state.result.pdr_interval.measured_low.duration)
+        self.assertEqual(10880000.0,
+                         state.result.pdr_interval.measured_low.target_tr)
+        self.assertEqual(10879927,
+                         state.result.pdr_interval.measured_low.transmit_count)
+        self.assertEqual(0,
+                         state.result.pdr_interval.measured_low.loss_count)
+        self.assertEqual(10879927,
+                         state.result.pdr_interval.measured_low.receive_count)
+        self.assertEqual(362664.23333,
+                         state.result.pdr_interval.measured_low.transmit_rate)
+        self.assertEqual(0.0,
+                         state.result.pdr_interval.measured_low.loss_rate)
+        self.assertEqual(362664.23333,
+                         state.result.pdr_interval.measured_low.receive_rate)
+        self.assertEqual(0.0,
+                         state.result.pdr_interval.measured_low.loss_fraction)
+        self.assertEqual(30, state.result.pdr_interval.measured_high.duration)
+        self.assertEqual(12880000,
+                         state.result.pdr_interval.measured_high.target_tr)
+        self.assertEqual(12879927,
+                         state.result.pdr_interval.measured_high.transmit_count)
+        self.assertEqual(100000,
+                         state.result.pdr_interval.measured_high.loss_count)
+        self.assertEqual(12779927,
+                         state.result.pdr_interval.measured_high.receive_count)
+        self.assertEqual(429330.9,
+                         state.result.pdr_interval.measured_high.transmit_rate)
+        self.assertEqual(3333.33333,
+                         state.result.pdr_interval.measured_high.loss_rate)
+        self.assertEqual(425997.56667,
+                         state.result.pdr_interval.measured_high.receive_rate)
+        self.assertEqual(0.00776,
+                         state.result.pdr_interval.measured_high.loss_fraction)
+        self.assertEqual(-1, state.phases)
+        self.assertEqual(30, state.duration)
+        self.assertEqual(0.2, state.width_goal)
+        self.assertEqual(0.0, state.packet_loss_ratio)
+        self.assertEqual(14880000, state.minimum_transmit_rate)
+        self.assertEqual(14880000, state.maximum_transmit_rate)
+
+    def test_measure(self):
+        measurer = mock.MagicMock()
+        measurer.sent = 102563094
+        measurer.loss = 30502
+        algorithm = MultipleLossRatioSearch(measurer=measurer, latency=True,
+                                            pkt_size=64,
+                                            final_trial_duration=30,
+                                            final_relative_width=0.005,
+                                            number_of_intermediate_phases=2,
+                                            initial_trial_duration=1,
+                                            timeout=720)
+        ports = [0, 1]
+        port_pg_id = PortPgIDMap()
+        port_pg_id.add_port(0)
+        port_pg_id.add_port(1)
+        self.assertIsNone(
+            algorithm.init_generator(ports, port_pg_id, mock.MagicMock(),
+                                     mock.Mock, mock.Mock()))
+        measurement = algorithm.measure(30, 3418770.3425, True)
+        self.assertIsInstance(measurement, ReceiveRateMeasurement)
+        self.assertEqual(30, measurement.duration)
+        self.assertEqual(3418770.3425, measurement.target_tr)
+        self.assertEqual(102563094, measurement.transmit_count)
+        self.assertEqual(30502, measurement.loss_count)
+        self.assertEqual(102532592, measurement.receive_count)
+        self.assertEqual(3418769.8, measurement.transmit_rate)
+        self.assertEqual(1016.73333, measurement.loss_rate)
+        self.assertEqual(3417753.06667, measurement.receive_rate)
+        self.assertEqual(0.0003, measurement.loss_fraction)
+
+    def test_perform_additional_measurements_based_on_ndrpdr_result(self):
+        algorithm = MultipleLossRatioSearch(measurer=mock.Mock(), latency=True,
+                                            pkt_size=64,
+                                            final_trial_duration=30,
+                                            final_relative_width=0.005,
+                                            number_of_intermediate_phases=2,
+                                            initial_trial_duration=1,
+                                            timeout=720)
+        ports = [0, 1]
+        port_pg_id = PortPgIDMap()
+        port_pg_id.add_port(0)
+        port_pg_id.add_port(1)
+        self.assertIsNone(
+            algorithm.init_generator(ports, port_pg_id, mock.Mock, mock.Mock,
+                                     mock.Mock()))
+        result = mock.MagicMock()
+        result.ndr_interval.measured_low.target_tr.return_result = 100000
+        self.assertIsNone(
+            algorithm.perform_additional_measurements_based_on_ndrpdr_result(
+                result))
+
+    def test_display_single_bound(self):
+        algorithm = MultipleLossRatioSearch(measurer=mock.Mock(), latency=True,
+                                            pkt_size=64,
+                                            final_trial_duration=30,
+                                            final_relative_width=0.005,
+                                            number_of_intermediate_phases=2,
+                                            initial_trial_duration=1,
+                                            timeout=720)
+        result_samples = {}
+        self.assertIsNone(
+            algorithm.display_single_bound(result_samples, 'NDR_LOWER',
+                                           4857361, 64,
+                                           ['20/849/1069', '40/69/183']))
+        self.assertEqual(
+            {'Result_NDR_LOWER': {'bandwidth_total_Gbps': 3.264146592,
+                                  'rate_total_pps': 4857361.0},
+             'Result_stream0_NDR_LOWER': {'avg_latency': 849.0,
+                                          'max_latency': 1069.0,
+                                          'min_latency': 20.0},
+             'Result_stream1_NDR_LOWER': {'avg_latency': 69.0,
+                                          'max_latency': 183.0,
+                                          'min_latency': 40.0}},
+            result_samples)
+
+    def test_check_ndrpdr_interval_validity(self):
+        algorithm = MultipleLossRatioSearch(measurer=mock.Mock(), latency=True,
+                                            pkt_size=64,
+                                            final_trial_duration=30,
+                                            final_relative_width=0.005,
+                                            number_of_intermediate_phases=2,
+                                            initial_trial_duration=1,
+                                            timeout=720)
+        result_samples = {}
+        measured_low = ReceiveRateMeasurement(1, 4857361, 4857339, 0)
+        measured_high = ReceiveRateMeasurement(1, 4977343, 4977320, 0)
+        receive_rate_interval = ReceiveRateInterval(measured_low,
+                                                    measured_high)
+        self.assertEqual('Minimal rate loss fraction 0.0 reach target 0.0',
+                         algorithm.check_ndrpdr_interval_validity(
+                             result_samples, 'NDR_LOWER',
+                             receive_rate_interval))
+        self.assertEqual(
+            {'Result_NDR_LOWER_packets_lost': {'packet_loss_ratio': 0.0,
+                                               'packets_lost': 0.0}},
+            result_samples)
+
+    def test_check_ndrpdr_interval_validity_fail(self):
+        algorithm = MultipleLossRatioSearch(measurer=mock.Mock(), latency=True,
+                                            pkt_size=64,
+                                            final_trial_duration=30,
+                                            final_relative_width=0.005,
+                                            number_of_intermediate_phases=2,
+                                            initial_trial_duration=1,
+                                            timeout=720)
+        result_samples = {}
+        measured_low = ReceiveRateMeasurement(1, 4857361, 4857339, 84965)
+        measured_high = ReceiveRateMeasurement(1, 4977343, 4977320, 119959)
+        receive_rate_interval = ReceiveRateInterval(measured_low,
+                                                    measured_high)
+        self.assertEqual(
+            'Minimal rate loss fraction 0.01749 does not reach target 0.005\n84965 packets lost.',
+            algorithm.check_ndrpdr_interval_validity(result_samples,
+                                                     'NDR_LOWER',
+                                                     receive_rate_interval,
+                                                     0.005))
+        self.assertEqual({'Result_NDR_LOWER_packets_lost': {
+            'packet_loss_ratio': 0.01749,
+            'packets_lost': 84965.0}}, result_samples)
diff --git a/yardstick/tests/unit/network_services/helpers/vpp_helpers/test_ndr_pdr_result.py b/yardstick/tests/unit/network_services/helpers/vpp_helpers/test_ndr_pdr_result.py
new file mode 100644 (file)
index 0000000..ea9c39a
--- /dev/null
@@ -0,0 +1,91 @@
+# Copyright (c) 2019 Viosoft 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.
+
+import unittest
+
+import mock
+
+from yardstick.network_services.helpers.vpp_helpers.ndr_pdr_result import \
+    NdrPdrResult
+from yardstick.network_services.helpers.vpp_helpers.receive_rate_interval import \
+    ReceiveRateInterval
+from yardstick.network_services.helpers.vpp_helpers.receive_rate_measurement import \
+    ReceiveRateMeasurement
+
+
+class TestNdrPdrResult(unittest.TestCase):
+
+    def test___init__(self):
+        measured_low = ReceiveRateMeasurement(1, 4857361, 4857339, 84965)
+        measured_high = ReceiveRateMeasurement(1, 4977343, 4977320, 119959)
+        starting_interval = ReceiveRateInterval(measured_low, measured_high)
+        ndrpdr_result = NdrPdrResult(starting_interval, starting_interval)
+        self.assertIsInstance(ndrpdr_result.ndr_interval, ReceiveRateInterval)
+        self.assertIsInstance(ndrpdr_result.pdr_interval, ReceiveRateInterval)
+
+    def test___init__ndr_error(self):
+        starting_interval = mock.MagicMock()
+        measured_low = ReceiveRateMeasurement(1, 4857361, 4857339, 84965)
+        measured_high = ReceiveRateMeasurement(1, 4977343, 4977320, 119959)
+        end_interval = ReceiveRateInterval(measured_low, measured_high)
+        with self.assertRaises(TypeError) as raised:
+            NdrPdrResult(starting_interval, end_interval)
+        self.assertIn('ndr_interval, is not a ReceiveRateInterval: ',
+                      str(raised.exception))
+
+    def test___init__pdr_error(self):
+        measured_low = ReceiveRateMeasurement(1, 4857361, 4857339, 84965)
+        measured_high = ReceiveRateMeasurement(1, 4977343, 4977320, 119959)
+        starting_interval = ReceiveRateInterval(measured_low, measured_high)
+        end_interval = mock.MagicMock()
+        with self.assertRaises(TypeError) as raised:
+            NdrPdrResult(starting_interval, end_interval)
+        self.assertIn('pdr_interval, is not a ReceiveRateInterval: ',
+                      str(raised.exception))
+
+    def test_width_in_goals(self):
+        measured_low = ReceiveRateMeasurement(1, 4857361, 4857339, 84965)
+        measured_high = ReceiveRateMeasurement(1, 4977343, 4977320, 119959)
+        starting_interval = ReceiveRateInterval(measured_low, measured_high)
+        ndrpdr_result = NdrPdrResult(starting_interval, starting_interval)
+        self.assertEqual('ndr 4.86887; pdr 4.86887',
+                         ndrpdr_result.width_in_goals(0.005))
+
+    def test___str__(self):
+        measured_low = ReceiveRateMeasurement(1, 4857361, 4857339, 84965)
+        measured_high = ReceiveRateMeasurement(1, 4977343, 4977320, 119959)
+        starting_interval = ReceiveRateInterval(measured_low, measured_high)
+        ndrpdr_result = NdrPdrResult(starting_interval, starting_interval)
+        self.assertEqual(
+            'NDR=[d=1.0,Tr=4857361.0,Df=0.01749;d=1.0,Tr=4977343.0,Df=0.0241);'
+            'PDR=[d=1.0,Tr=4857361.0,Df=0.01749;d=1.0,Tr=4977343.0,Df=0.0241)',
+            ndrpdr_result.__str__())
+
+    def test___repr__(self):
+        measured_low = ReceiveRateMeasurement(1, 4857361, 4857339, 84965)
+        measured_high = ReceiveRateMeasurement(1, 4977343, 4977320, 119959)
+        starting_interval = ReceiveRateInterval(measured_low, measured_high)
+        ndrpdr_result = NdrPdrResult(starting_interval, starting_interval)
+        self.assertEqual(
+            'NdrPdrResult(ndr_interval=ReceiveRateInterval(measured_low=' \
+            'ReceiveRateMeasurement(duration=1.0,target_tr=4857361.0,' \
+            'transmit_count=4857339,loss_count=84965),measured_high=' \
+            'ReceiveRateMeasurement(duration=1.0,target_tr=4977343.0,' \
+            'transmit_count=4977320,loss_count=119959)),pdr_interval=' \
+            'ReceiveRateInterval(measured_low=ReceiveRateMeasurement' \
+            '(duration=1.0,target_tr=4857361.0,transmit_count=4857339,' \
+            'loss_count=84965),measured_high=ReceiveRateMeasurement' \
+            '(duration=1.0,target_tr=4977343.0,transmit_count=4977320,' \
+            'loss_count=119959)))',
+            ndrpdr_result.__repr__())
diff --git a/yardstick/tests/unit/network_services/helpers/vpp_helpers/test_receive_rate_interval.py b/yardstick/tests/unit/network_services/helpers/vpp_helpers/test_receive_rate_interval.py
new file mode 100644 (file)
index 0000000..bbf2416
--- /dev/null
@@ -0,0 +1,100 @@
+# Copyright (c) 2019 Viosoft 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.
+
+import unittest
+
+import mock
+
+from yardstick.network_services.helpers.vpp_helpers.receive_rate_interval import \
+    ReceiveRateInterval
+from yardstick.network_services.helpers.vpp_helpers.receive_rate_measurement import \
+    ReceiveRateMeasurement
+
+
+class TestReceiveRateInterval(unittest.TestCase):
+
+    def test__init__(self):
+        measured_low = ReceiveRateMeasurement(1, 4857361, 4857339, 84965)
+        measured_high = ReceiveRateMeasurement(1, 4977343, 4977320, 119959)
+        receive_rate_interval = ReceiveRateInterval(measured_low,
+                                                    measured_high)
+        self.assertIsInstance(receive_rate_interval.measured_low,
+                              ReceiveRateMeasurement)
+        self.assertIsInstance(receive_rate_interval.measured_high,
+                              ReceiveRateMeasurement)
+
+    def test__init__measured_low_error(self):
+        measured_low = mock.MagicMock()
+        measured_high = ReceiveRateMeasurement(1, 4977343, 4977320, 119959)
+        with self.assertRaises(TypeError) as raised:
+            ReceiveRateInterval(measured_low, measured_high)
+        self.assertIn('measured_low is not a ReceiveRateMeasurement: ',
+                      str(raised.exception))
+
+    def test__init__measured_high_error(self):
+        measured_low = ReceiveRateMeasurement(1, 4857361, 4857339, 84965)
+        measured_high = mock.MagicMock()
+        with self.assertRaises(TypeError) as raised:
+            ReceiveRateInterval(measured_low, measured_high)
+        self.assertIn('measured_high is not a ReceiveRateMeasurement: ',
+                      str(raised.exception))
+
+    def test_sort(self):
+        measured_low = ReceiveRateMeasurement(1, 4857361, 4857339, 84965)
+        measured_high = ReceiveRateMeasurement(1, 4977343, 4977320, 119959)
+        receive_rate_interval = ReceiveRateInterval(measured_low,
+                                                    measured_high)
+        self.assertIsNone(receive_rate_interval.sort())
+        self.assertEqual(119982.0, receive_rate_interval.abs_tr_width)
+        self.assertEqual(0.02411,
+                         receive_rate_interval.rel_tr_width)
+
+    def test_sort_swap(self):
+        measured_low = ReceiveRateMeasurement(1, 14857361, 14857339, 184965)
+        measured_high = ReceiveRateMeasurement(1, 4977343, 4977320, 119959)
+        receive_rate_interval = ReceiveRateInterval(measured_low,
+                                                    measured_high)
+        self.assertIsNone(receive_rate_interval.sort())
+        self.assertEqual(9880018.0, receive_rate_interval.abs_tr_width)
+        self.assertEqual(0.66499,
+                         receive_rate_interval.rel_tr_width)
+
+    def test_width_in_goals(self):
+        measured_low = ReceiveRateMeasurement(1, 4857361, 4857339, 84965)
+        measured_high = ReceiveRateMeasurement(1, 4977343, 4977320, 119959)
+        receive_rate_interval = ReceiveRateInterval(measured_low,
+                                                    measured_high)
+        self.assertEqual(4.86887,
+                         receive_rate_interval.width_in_goals(0.005))
+
+    def test___str__(self):
+        measured_low = ReceiveRateMeasurement(1, 4857361, 4857339, 84965)
+        measured_high = ReceiveRateMeasurement(1, 4977343, 4977320, 119959)
+        receive_rate_interval = ReceiveRateInterval(measured_low,
+                                                    measured_high)
+        self.assertEqual(
+            '[d=1.0,Tr=4857361.0,Df=0.01749;d=1.0,Tr=4977343.0,Df=0.0241)',
+            receive_rate_interval.__str__())
+
+    def test___repr__(self):
+        measured_low = ReceiveRateMeasurement(1, 4857361, 4857339, 84965)
+        measured_high = ReceiveRateMeasurement(1, 4977343, 4977320, 119959)
+        receive_rate_interval = ReceiveRateInterval(measured_low,
+                                                    measured_high)
+        self.assertEqual('ReceiveRateInterval(measured_low=' \
+                         'ReceiveRateMeasurement(duration=1.0,target_tr=4857361.0,' \
+                         'transmit_count=4857339,loss_count=84965),measured_high=' \
+                         'ReceiveRateMeasurement(duration=1.0,target_tr=4977343.0,' \
+                         'transmit_count=4977320,loss_count=119959))',
+                         receive_rate_interval.__repr__())
diff --git a/yardstick/tests/unit/network_services/helpers/vpp_helpers/test_receive_rate_measurement.py b/yardstick/tests/unit/network_services/helpers/vpp_helpers/test_receive_rate_measurement.py
new file mode 100644 (file)
index 0000000..d4e2d79
--- /dev/null
@@ -0,0 +1,44 @@
+# Copyright (c) 2019 Viosoft 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.
+
+import unittest
+
+from yardstick.network_services.helpers.vpp_helpers.receive_rate_measurement import \
+    ReceiveRateMeasurement
+
+
+class TestReceiveRateMeasurement(unittest.TestCase):
+
+    def test__init__(self):
+        measured = ReceiveRateMeasurement(1, 4857361, 4857339, 84965)
+        self.assertEqual(1, measured.duration)
+        self.assertEqual(4857361, measured.target_tr)
+        self.assertEqual(4857339, measured.transmit_count)
+        self.assertEqual(84965, measured.loss_count)
+        self.assertEqual(4772374, measured.receive_count)
+        self.assertEqual(4857339, measured.transmit_rate)
+        self.assertEqual(84965.0, measured.loss_rate)
+        self.assertEqual(4772374.0, measured.receive_rate)
+        self.assertEqual(0.01749, measured.loss_fraction)
+
+    def test___str__(self):
+        measured = ReceiveRateMeasurement(1, 4857361, 4857339, 84965)
+        self.assertEqual('d=1.0,Tr=4857361.0,Df=0.01749',
+                         measured.__str__())
+
+    def test___repr__(self):
+        measured = ReceiveRateMeasurement(1, 4857361, 4857339, 84965)
+        self.assertEqual('ReceiveRateMeasurement(duration=1.0,' \
+                         'target_tr=4857361.0,transmit_count=4857339,loss_count=84965)',
+                         measured.__repr__())
index 4e5a0f7..8ad17b5 100644 (file)
@@ -18,8 +18,17 @@ from trex_stl_lib import trex_stl_packet_builder_scapy
 from trex_stl_lib import trex_stl_streams
 
 from yardstick.common import constants
+from yardstick.network_services.helpers.vpp_helpers.multiple_loss_ratio_search import \
+    MultipleLossRatioSearch
+from yardstick.network_services.helpers.vpp_helpers.ndr_pdr_result import \
+    NdrPdrResult
+from yardstick.network_services.helpers.vpp_helpers.receive_rate_interval import \
+    ReceiveRateInterval
+from yardstick.network_services.helpers.vpp_helpers.receive_rate_measurement import \
+    ReceiveRateMeasurement
 from yardstick.network_services.traffic_profile import base as tp_base
 from yardstick.network_services.traffic_profile import rfc2544, vpp_rfc2544
+from yardstick.network_services.traffic_profile.rfc2544 import PortPgIDMap
 from yardstick.tests.unit import base
 
 
@@ -129,6 +138,7 @@ class TestVppRFC2544Profile(base.BaseUnitTestCase):
         self.assertEqual(vpp_rfc2544_profile.max_rate,
                          vpp_rfc2544_profile.rate)
         self.assertEqual(0, vpp_rfc2544_profile.min_rate)
+        self.assertEqual(2, vpp_rfc2544_profile.number_of_intermediate_phases)
         self.assertEqual(30, vpp_rfc2544_profile.duration)
         self.assertEqual(0.1, vpp_rfc2544_profile.precision)
         self.assertEqual(1.0, vpp_rfc2544_profile.lower_bound)
@@ -287,6 +297,7 @@ class TestVppRFC2544Profile(base.BaseUnitTestCase):
         vpp_rfc2544_profile = vpp_rfc2544.VppRFC2544Profile(
             self.TRAFFIC_PROFILE_MAX_RATE)
         vpp_rfc2544_profile.init_queue(mock.MagicMock())
+        vpp_rfc2544_profile.pkt_size = 64
         vpp_rfc2544_profile.params = {
             'downlink_0': 'profile1',
             'uplink_0': 'profile2'}
@@ -480,6 +491,7 @@ class TestVppRFC2544Profile(base.BaseUnitTestCase):
         mock_latency.side_effect = ['latency1', 'latency2']
         vpp_rfc2544_profile = vpp_rfc2544.VppRFC2544Profile(
             self.TRAFFIC_PROFILE_MAX_RATE)
+        vpp_rfc2544_profile.pkt_size = 64
         vpp_rfc2544_profile.port_pg_id = rfc2544.PortPgIDMap()
         vpp_rfc2544_profile.port_pg_id.add_port(1)
         with mock.patch.object(vpp_rfc2544_profile, '_create_single_packet') as \
@@ -523,10 +535,69 @@ class TestVppRFC2544Profile(base.BaseUnitTestCase):
     def test_binary_search_with_optimized(self):
         vpp_rfc2544_profile = vpp_rfc2544.VppRFC2544Profile(
             self.TRAFFIC_PROFILE)
+        vpp_rfc2544_profile.pkt_size = 64
+        vpp_rfc2544_profile.init_queue(mock.MagicMock())
         mock_generator = mock.MagicMock()
-        self.assertIsNone(
-            vpp_rfc2544_profile.binary_search_with_optimized(mock_generator,
-                                                             30, 720, ''))
+        mock_generator.vnfd_helper.interfaces = [
+            {"name": "xe0"}, {"name": "xe0"}
+        ]
+
+        vpp_rfc2544_profile.ports = [0, 1]
+        vpp_rfc2544_profile.port_pg_id = PortPgIDMap()
+        vpp_rfc2544_profile.port_pg_id.add_port(0)
+        vpp_rfc2544_profile.port_pg_id.add_port(1)
+        vpp_rfc2544_profile.profiles = mock.MagicMock()
+        vpp_rfc2544_profile.test_data = mock.MagicMock()
+        vpp_rfc2544_profile.queue = mock.MagicMock()
+
+        with mock.patch.object(MultipleLossRatioSearch, 'measure') as \
+                mock_measure, \
+                mock.patch.object(MultipleLossRatioSearch, 'ndrpdr') as \
+                        mock_ndrpdr:
+            measured_low = ReceiveRateMeasurement(1, 14880000, 14879927, 0)
+            measured_high = ReceiveRateMeasurement(1, 14880000, 14879927, 0)
+            measured_low.latency = ['1000/3081/3962', '500/3149/3730']
+            measured_high.latency = ['1000/3081/3962', '500/3149/3730']
+            starting_interval = ReceiveRateInterval(measured_low,
+                                                    measured_high)
+            starting_result = NdrPdrResult(starting_interval,
+                                           starting_interval)
+            mock_measure.return_value = ReceiveRateMeasurement(1, 14880000,
+                                                               14879927, 0)
+            mock_ndrpdr.return_value = MultipleLossRatioSearch.ProgressState(
+                starting_result, 2, 30, 0.005, 0.0,
+                4857361, 4977343)
+
+            result_samples = vpp_rfc2544_profile.binary_search_with_optimized(
+                traffic_generator=mock_generator, duration=30,
+                timeout=720,
+                test_data={})
+
+        expected = {'Result_NDR_LOWER': {'bandwidth_total_Gbps': 9.999310944,
+                                         'rate_total_pps': 14879927.0},
+                    'Result_NDR_UPPER': {'bandwidth_total_Gbps': 9.999310944,
+                                         'rate_total_pps': 14879927.0},
+                    'Result_NDR_packets_lost': {'packet_loss_ratio': 0.0,
+                                                'packets_lost': 0.0},
+                    'Result_PDR_LOWER': {'bandwidth_total_Gbps': 9.999310944,
+                                         'rate_total_pps': 14879927.0},
+                    'Result_PDR_UPPER': {'bandwidth_total_Gbps': 9.999310944,
+                                         'rate_total_pps': 14879927.0},
+                    'Result_PDR_packets_lost': {'packet_loss_ratio': 0.0,
+                                                'packets_lost': 0.0},
+                    'Result_stream0_NDR_LOWER': {'avg_latency': 3081.0,
+                                                 'max_latency': 3962.0,
+                                                 'min_latency': 1000.0},
+                    'Result_stream0_PDR_LOWER': {'avg_latency': 3081.0,
+                                                 'max_latency': 3962.0,
+                                                 'min_latency': 1000.0},
+                    'Result_stream1_NDR_LOWER': {'avg_latency': 3149.0,
+                                                 'max_latency': 3730.0,
+                                                 'min_latency': 500.0},
+                    'Result_stream1_PDR_LOWER': {'avg_latency': 3149.0,
+                                                 'max_latency': 3730.0,
+                                                 'min_latency': 500.0}}
+        self.assertEqual(expected, result_samples)
 
     def test_binary_search(self):
         vpp_rfc2544_profile = vpp_rfc2544.VppRFC2544Profile(
diff --git a/yardstick/tests/unit/network_services/vnf_generic/vnf/test_tg_vcmts_pktgen.py b/yardstick/tests/unit/network_services/vnf_generic/vnf/test_tg_vcmts_pktgen.py
new file mode 100755 (executable)
index 0000000..3b226d3
--- /dev/null
@@ -0,0 +1,652 @@
+# Copyright (c) 2019 Viosoft 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.
+
+import unittest
+import mock
+import socket
+import threading
+import time
+import os
+import copy
+
+from yardstick.benchmark.contexts import base as ctx_base
+from yardstick.network_services.vnf_generic.vnf.base import VnfdHelper
+from yardstick.network_services.vnf_generic.vnf import tg_vcmts_pktgen
+from yardstick.common import exceptions
+
+
+NAME = "tg__0"
+
+
+class TestPktgenHelper(unittest.TestCase):
+
+    def test___init__(self):
+        pktgen_helper = tg_vcmts_pktgen.PktgenHelper("localhost", 23000)
+        self.assertEqual(pktgen_helper.host, "localhost")
+        self.assertEqual(pktgen_helper.port, 23000)
+        self.assertFalse(pktgen_helper.connected)
+
+    def _run_fake_server(self):
+        server_sock = socket.socket()
+        server_sock.bind(('localhost', 23000))
+        server_sock.listen(0)
+        client_socket, _ = server_sock.accept()
+        client_socket.close()
+        server_sock.close()
+
+    def test__connect(self):
+        pktgen_helper = tg_vcmts_pktgen.PktgenHelper("localhost", 23000)
+        self.assertFalse(pktgen_helper._connect())
+        server_thread = threading.Thread(target=self._run_fake_server)
+        server_thread.start()
+        time.sleep(0.5)
+        self.assertTrue(pktgen_helper._connect())
+        pktgen_helper._sock.close()
+        server_thread.join()
+
+    @mock.patch('yardstick.network_services.vnf_generic.vnf.tg_vcmts_pktgen.time')
+    def test_connect(self, *args):
+        pktgen_helper = tg_vcmts_pktgen.PktgenHelper("localhost", 23000)
+        pktgen_helper.connected = True
+        self.assertTrue(pktgen_helper.connect())
+        pktgen_helper.connected = False
+
+        pktgen_helper._connect = mock.MagicMock(return_value=True)
+        self.assertTrue(pktgen_helper.connect())
+        self.assertTrue(pktgen_helper.connected)
+
+        pktgen_helper = tg_vcmts_pktgen.PktgenHelper("localhost", 23000)
+        pktgen_helper._connect = mock.MagicMock(return_value=False)
+        self.assertFalse(pktgen_helper.connect())
+        self.assertFalse(pktgen_helper.connected)
+
+    def test_send_command(self):
+        pktgen_helper = tg_vcmts_pktgen.PktgenHelper("localhost", 23000)
+        self.assertFalse(pktgen_helper.send_command(""))
+
+        pktgen_helper.connected = True
+        pktgen_helper._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        self.assertFalse(pktgen_helper.send_command(""))
+
+        pktgen_helper._sock = mock.MagicMock()
+        self.assertTrue(pktgen_helper.send_command(""))
+
+
+class TestVcmtsPktgenSetupEnvHelper(unittest.TestCase):
+
+    PKTGEN_PARAMETERS = "export LUA_PATH=/vcmts/Pktgen.lua;"\
+                        "export CMK_PROC_FS=/host/proc;"\
+                        "  /pktgen-config/setup.sh 0 4 18:02.0 "\
+                        "18:02.1 18:02.2 18:02.3 00:00.0 00:00.0 "\
+                        "00:00.0 00:00.0 imix1_100cms_1ofdm.pcap "\
+                        "imix1_100cms_1ofdm.pcap imix1_100cms_1ofdm.pcap "\
+                        "imix1_100cms_1ofdm.pcap imix1_100cms_1ofdm.pcap "\
+                        "imix1_100cms_1ofdm.pcap imix1_100cms_1ofdm.pcap "\
+                        "imix1_100cms_1ofdm.pcap"
+
+    OPTIONS = {
+        "pktgen_values": "/tmp/pktgen_values.yaml",
+        "tg__0": {
+            "pktgen_id": 0
+        },
+        "vcmts_influxdb_ip": "10.80.5.150",
+        "vcmts_influxdb_port": 8086,
+        "vcmtsd_values": "/tmp/vcmtsd_values.yaml",
+        "vnf__0": {
+            "sg_id": 0,
+            "stream_dir": "us"
+        },
+        "vnf__1": {
+            "sg_id": 0,
+            "stream_dir": "ds"
+        }
+    }
+
+    def setUp(self):
+        vnfd_helper = VnfdHelper(
+            TestVcmtsPktgen.VNFD['vnfd:vnfd-catalog']['vnfd'][0])
+        ssh_helper = mock.Mock()
+        scenario_helper = mock.Mock()
+        scenario_helper.options = self.OPTIONS
+
+        self.setup_helper = tg_vcmts_pktgen.VcmtsPktgenSetupEnvHelper(
+            vnfd_helper, ssh_helper, scenario_helper)
+
+    def test_generate_pcap_filename(self):
+        pcap_file_name = self.setup_helper.generate_pcap_filename(\
+                            TestVcmtsPktgen.PKTGEN_POD_VALUES[0]['ports'][0])
+        self.assertEquals(pcap_file_name, "imix1_100cms_1ofdm.pcap")
+
+    def test_find_port_cfg(self):
+        port_cfg = self.setup_helper.find_port_cfg(\
+                TestVcmtsPktgen.PKTGEN_POD_VALUES[0]['ports'], "port_0")
+        self.assertIsNotNone(port_cfg)
+
+        port_cfg = self.setup_helper.find_port_cfg(\
+                TestVcmtsPktgen.PKTGEN_POD_VALUES[0]['ports'], "port_8")
+        self.assertIsNone(port_cfg)
+
+    def test_build_pktgen_parameters(self):
+        parameters = self.setup_helper.build_pktgen_parameters(
+            TestVcmtsPktgen.PKTGEN_POD_VALUES[0])
+        self.assertEquals(parameters, self.PKTGEN_PARAMETERS)
+
+    def test_start_pktgen(self):
+        self.setup_helper.ssh_helper = mock.MagicMock()
+        self.setup_helper.start_pktgen(TestVcmtsPktgen.PKTGEN_POD_VALUES[0])
+        self.setup_helper.ssh_helper.send_command.assert_called_with(
+            self.PKTGEN_PARAMETERS)
+
+    def test_setup_vnf_environment(self):
+        self.assertIsNone(self.setup_helper.setup_vnf_environment())
+
+class TestVcmtsPktgen(unittest.TestCase):
+
+    VNFD = {'vnfd:vnfd-catalog':
+            {'vnfd':
+            [{
+            "benchmark": {
+                "kpi": [
+                    "upstream/bits_per_second"
+                ]
+            },
+            "connection-point": [
+                {
+                    "name": "xe0",
+                    "type": "VPORT"
+                },
+                {
+                    "name": "xe1",
+                    "type": "VPORT"
+                }
+            ],
+            "description": "vCMTS Pktgen Kubernetes",
+            "id": "VcmtsPktgen",
+            "mgmt-interface": {
+                "ip": "192.168.24.150",
+                "key_filename": "/tmp/yardstick_key-a3b663c2",
+                "user": "root",
+                "vdu-id": "vcmtspktgen-kubernetes"
+            },
+            "name": "vcmtspktgen",
+            "short-name": "vcmtspktgen",
+            "vdu": [
+                {
+                    "description": "vCMTS Pktgen Kubernetes",
+                    "external-interface": [],
+                    "id": "vcmtspktgen-kubernetes",
+                    "name": "vcmtspktgen-kubernetes"
+                }
+            ],
+            "vm-flavor": {
+                "memory-mb": "4096",
+                "vcpu-count": "4"
+            }
+        }]
+    }}
+
+    PKTGEN_POD_VALUES = [
+        {
+            "num_ports": "4",
+            "pktgen_id": "0",
+            "ports": [
+                {
+                    "net_pktgen": "18:02.0",
+                    "num_ofdm": "1",
+                    "num_subs": "100",
+                    "port_0": "",
+                    "traffic_type": "imix1"
+                },
+                {
+                    "net_pktgen": "18:02.1",
+                    "num_ofdm": "1",
+                    "num_subs": "100",
+                    "port_1": "",
+                    "traffic_type": "imix1"
+                },
+                {
+                    "net_pktgen": "18:02.2",
+                    "num_ofdm": "1",
+                    "num_subs": "100",
+                    "port_2": "",
+                    "traffic_type": "imix1"
+                },
+                {
+                    "net_pktgen": "18:02.3",
+                    "num_ofdm": "1",
+                    "num_subs": "100",
+                    "port_3": "",
+                    "traffic_type": "imix1"
+                },
+                {
+                    "net_pktgen": "00:00.0",
+                    "num_ofdm": "1",
+                    "num_subs": "100",
+                    "port_4": "",
+                    "traffic_type": "imix1"
+                },
+                {
+                    "net_pktgen": "00:00.0",
+                    "num_ofdm": "1",
+                    "num_subs": "100",
+                    "port_5": "",
+                    "traffic_type": "imix1"
+                },
+                {
+                    "net_pktgen": "00:00.0",
+                    "num_ofdm": "1",
+                    "num_subs": "100",
+                    "port_6": "",
+                    "traffic_type": "imix1"
+                },
+                {
+                    "net_pktgen": "00:00.0",
+                    "num_ofdm": "1",
+                    "num_subs": "100",
+                    "port_7": "",
+                    "traffic_type": "imix1"
+                }
+            ]
+        },
+        {
+            "num_ports": 4,
+            "pktgen_id": 1,
+            "ports": [
+                {
+                    "net_pktgen": "18:0a.0",
+                    "num_ofdm": "1",
+                    "num_subs": "100",
+                    "port_0": "",
+                    "traffic_type": "imix1"
+                },
+                {
+                    "net_pktgen": "18:0a.1",
+                    "num_ofdm": "1",
+                    "num_subs": "100",
+                    "port_1": "",
+                    "traffic_type": "imix1"
+                },
+                {
+                    "net_pktgen": "18:0a.2",
+                    "num_ofdm": "1",
+                    "num_subs": "100",
+                    "port_2": "",
+                    "traffic_type": "imix1"
+                },
+                {
+                    "net_pktgen": "18:0a.3",
+                    "num_ofdm": "1",
+                    "num_subs": "100",
+                    "port_3": "",
+                    "traffic_type": "imix1"
+                },
+                {
+                    "net_pktgen": "00:00.0",
+                    "num_ofdm": "1",
+                    "num_subs": "100",
+                    "port_4": "",
+                    "traffic_type": "imix1"
+                },
+                {
+                    "net_pktgen": "00:00.0",
+                    "num_ofdm": "1",
+                    "num_subs": "100",
+                    "port_5": "",
+                    "traffic_type": "imix1"
+                },
+                {
+                    "net_pktgen": "00:00.0",
+                    "num_ofdm": "1",
+                    "num_subs": "100",
+                    "port_6": "",
+                    "traffic_type": "imix1"
+                },
+                {
+                    "net_pktgen": "00:00.0",
+                    "num_ofdm": "1",
+                    "num_subs": "100",
+                    "port_7": "",
+                    "traffic_type": "imix1"
+                }
+            ]
+        }
+    ]
+
+    SCENARIO_CFG = {
+        "nodes": {
+            "tg__0": "pktgen0-k8syardstick-a3b663c2",
+            "vnf__0": "vnf0us-k8syardstick-a3b663c2",
+            "vnf__1": "vnf0ds-k8syardstick-a3b663c2"
+        },
+        "options": {
+            "pktgen_values": "/tmp/pktgen_values.yaml",
+            "tg__0": {
+                "pktgen_id": 0
+            },
+            "vcmts_influxdb_ip": "10.80.5.150",
+            "vcmts_influxdb_port": 8086,
+            "vcmtsd_values": "/tmp/vcmtsd_values.yaml",
+            "vnf__0": {
+                "sg_id": 0,
+                "stream_dir": "us"
+            },
+            "vnf__1": {
+                "sg_id": 0,
+                "stream_dir": "ds"
+            }
+        },
+        "task_id": "a3b663c2-e616-4777-b6d0-ec2ea7a06f42",
+        "task_path": "samples/vnf_samples/nsut/cmts",
+        "tc": "tc_vcmts_k8s_pktgen",
+        "topology": "k8s_vcmts_topology.yaml",
+        "traffic_profile": "../../traffic_profiles/fixed.yaml",
+        "type": "NSPerf"
+    }
+
+    CONTEXT_CFG = {
+        "networks": {
+            "flannel": {
+                "name": "flannel"
+            },
+            "xe0": {
+                "name": "xe0"
+            },
+            "xe1": {
+                "name": "xe1"
+            }
+        },
+        "nodes": {
+            "tg__0": {
+                "VNF model": "../../vnf_descriptors/tg_vcmts_tpl.yaml",
+                "interfaces": {
+                    "flannel": {
+                        "local_ip": "192.168.24.150",
+                        "local_mac": None,
+                        "network_name": "flannel"
+                    },
+                    "xe0": {
+                        "local_ip": "192.168.24.150",
+                        "local_mac": None,
+                        "network_name": "xe0"
+                    },
+                    "xe1": {
+                        "local_ip": "192.168.24.150",
+                        "local_mac": None,
+                        "network_name": "xe1"
+                    }
+                },
+                "ip": "192.168.24.150",
+                "key_filename": "/tmp/yardstick_key-a3b663c2",
+                "member-vnf-index": "1",
+                "name": "pktgen0-k8syardstick-a3b663c2",
+                "private_ip": "192.168.24.150",
+                "service_ports": [
+                    {
+                        "name": "ssh",
+                        "node_port": 60270,
+                        "port": 22,
+                        "protocol": "TCP",
+                        "target_port": 22
+                    },
+                    {
+                        "name": "lua",
+                        "node_port": 43619,
+                        "port": 22022,
+                        "protocol": "TCP",
+                        "target_port": 22022
+                    }
+                ],
+                "ssh_port": 60270,
+                "user": "root",
+                "vnfd-id-ref": "tg__0"
+            },
+            "vnf__0": {
+                "VNF model": "../../vnf_descriptors/vnf_vcmts_tpl.yaml",
+                "interfaces": {
+                    "flannel": {
+                        "local_ip": "192.168.100.132",
+                        "local_mac": None,
+                        "network_name": "flannel"
+                    },
+                    "xe0": {
+                        "local_ip": "192.168.100.132",
+                        "local_mac": None,
+                        "network_name": "xe0"
+                    },
+                    "xe1": {
+                        "local_ip": "192.168.100.132",
+                        "local_mac": None,
+                        "network_name": "xe1"
+                    }
+                },
+                "ip": "192.168.100.132",
+                "key_filename": "/tmp/yardstick_key-a3b663c2",
+                "member-vnf-index": "3",
+                "name": "vnf0us-k8syardstick-a3b663c2",
+                "private_ip": "192.168.100.132",
+                "service_ports": [
+                    {
+                        "name": "ssh",
+                        "node_port": 57057,
+                        "port": 22,
+                        "protocol": "TCP",
+                        "target_port": 22
+                    },
+                    {
+                        "name": "lua",
+                        "node_port": 29700,
+                        "port": 22022,
+                        "protocol": "TCP",
+                        "target_port": 22022
+                    }
+                ],
+                "ssh_port": 57057,
+                "user": "root",
+                "vnfd-id-ref": "vnf__0"
+            },
+            "vnf__1": {
+                "VNF model": "../../vnf_descriptors/vnf_vcmts_tpl.yaml",
+                "interfaces": {
+                    "flannel": {
+                        "local_ip": "192.168.100.134",
+                        "local_mac": None,
+                        "network_name": "flannel"
+                    },
+                    "xe0": {
+                        "local_ip": "192.168.100.134",
+                        "local_mac": None,
+                        "network_name": "xe0"
+                    },
+                    "xe1": {
+                        "local_ip": "192.168.100.134",
+                        "local_mac": None,
+                        "network_name": "xe1"
+                    }
+                },
+                "ip": "192.168.100.134",
+                "key_filename": "/tmp/yardstick_key-a3b663c2",
+                "member-vnf-index": "4",
+                "name": "vnf0ds-k8syardstick-a3b663c2",
+                "private_ip": "192.168.100.134",
+                "service_ports": [
+                    {
+                        "name": "ssh",
+                        "node_port": 18581,
+                        "port": 22,
+                        "protocol": "TCP",
+                        "target_port": 22
+                    },
+                    {
+                        "name": "lua",
+                        "node_port": 18469,
+                        "port": 22022,
+                        "protocol": "TCP",
+                        "target_port": 22022
+                    }
+                ],
+                "ssh_port": 18581,
+                "user": "root",
+                "vnfd-id-ref": "vnf__1"
+            }
+        }
+    }
+
+    PKTGEN_VALUES_PATH = "/tmp/pktgen_values.yaml"
+
+    PKTGEN_VALUES = \
+            "serviceAccount: cmk-serviceaccount\n" \
+            "images:\n" \
+            "  vcmts_pktgen: vcmts-pktgen:v18.10\n" \
+            "topology:\n" \
+            "  pktgen_replicas: 8\n" \
+            "  pktgen_pods:\n" \
+            "    - pktgen_id: 0\n" \
+            "      num_ports: 4\n" \
+            "      ports:\n" \
+            "        - port_0:\n" \
+            "          traffic_type: 'imix2'\n" \
+            "          num_ofdm: 4\n" \
+            "          num_subs: 300\n" \
+            "          net_pktgen: 8a:02.0\n" \
+            "        - port_1:\n" \
+            "          traffic_type: 'imix2'\n" \
+            "          num_ofdm: 4\n" \
+            "          num_subs: 300\n" \
+            "          net_pktgen: 8a:02.1\n" \
+            "        - port_2:\n" \
+            "          traffic_type: 'imix2'\n" \
+            "          num_ofdm: 4\n" \
+            "          num_subs: 300\n" \
+            "          net_pktgen: 8a:02.2\n" \
+            "        - port_3:\n" \
+            "          traffic_type: 'imix2'\n" \
+            "          num_ofdm: 4\n" \
+            "          num_subs: 300\n" \
+            "          net_pktgen: 8a:02.3\n" \
+            "        - port_4:\n" \
+            "          traffic_type: 'imix2'\n" \
+            "          num_ofdm: 4\n" \
+            "          num_subs: 300\n" \
+            "          net_pktgen: 8a:02.4\n" \
+            "        - port_5:\n" \
+            "          traffic_type: 'imix2'\n" \
+            "          num_ofdm: 4\n" \
+            "          num_subs: 300\n" \
+            "          net_pktgen: 8a:02.5\n" \
+            "        - port_6:\n" \
+            "          traffic_type: 'imix2'\n" \
+            "          num_ofdm: 4\n" \
+            "          num_subs: 300\n" \
+            "          net_pktgen: 8a:02.6\n" \
+            "        - port_7:\n" \
+            "          traffic_type: 'imix2'\n" \
+            "          num_ofdm: 4\n" \
+            "          num_subs: 300\n" \
+            "          net_pktgen: 8a:02.7\n"
+
+    def setUp(self):
+        vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0]
+        self.vcmts_pktgen = tg_vcmts_pktgen.VcmtsPktgen(NAME, vnfd)
+        self.vcmts_pktgen._start_server = mock.Mock(return_value=0)
+        self.vcmts_pktgen.resource_helper = mock.MagicMock()
+        self.vcmts_pktgen.setup_helper = mock.MagicMock()
+
+    def test___init__(self):
+        self.assertFalse(self.vcmts_pktgen.traffic_finished)
+        self.assertIsNotNone(self.vcmts_pktgen.setup_helper)
+        self.assertIsNotNone(self.vcmts_pktgen.resource_helper)
+
+    def test_extract_pod_cfg(self):
+        pod_cfg = self.vcmts_pktgen.extract_pod_cfg(self.PKTGEN_POD_VALUES, "0")
+        self.assertIsNotNone(pod_cfg)
+        self.assertEqual(pod_cfg["pktgen_id"], "0")
+        pod_cfg = self.vcmts_pktgen.extract_pod_cfg(self.PKTGEN_POD_VALUES, "4")
+        self.assertIsNone(pod_cfg)
+
+    @mock.patch.object(ctx_base.Context, 'get_context_from_server',
+                       return_value='fake_context')
+    def test_instantiate_missing_pktgen_values_key(self, *args):
+        err_scenario_cfg = copy.deepcopy(self.SCENARIO_CFG)
+        err_scenario_cfg['options'].pop('pktgen_values', None)
+        with self.assertRaises(KeyError):
+            self.vcmts_pktgen.instantiate(err_scenario_cfg, self.CONTEXT_CFG)
+
+    @mock.patch.object(ctx_base.Context, 'get_context_from_server',
+                       return_value='fake_context')
+    def test_instantiate_missing_pktgen_values_file(self, *args):
+        if os.path.isfile(self.PKTGEN_VALUES_PATH):
+            os.remove(self.PKTGEN_VALUES_PATH)
+        err_scenario_cfg = copy.deepcopy(self.SCENARIO_CFG)
+        err_scenario_cfg['options']['pktgen_values'] = self.PKTGEN_VALUES_PATH
+        with self.assertRaises(RuntimeError):
+            self.vcmts_pktgen.instantiate(err_scenario_cfg, self.CONTEXT_CFG)
+
+    @mock.patch.object(ctx_base.Context, 'get_context_from_server',
+                       return_value='fake_context')
+    def test_instantiate_empty_pktgen_values_file(self, *args):
+        yaml_sample = open(self.PKTGEN_VALUES_PATH, 'w')
+        yaml_sample.write("")
+        yaml_sample.close()
+
+        err_scenario_cfg = copy.deepcopy(self.SCENARIO_CFG)
+        err_scenario_cfg['options']['pktgen_values'] = self.PKTGEN_VALUES_PATH
+        with self.assertRaises(RuntimeError):
+            self.vcmts_pktgen.instantiate(err_scenario_cfg, self.CONTEXT_CFG)
+
+        if os.path.isfile(self.PKTGEN_VALUES_PATH):
+            os.remove(self.PKTGEN_VALUES_PATH)
+
+    @mock.patch.object(ctx_base.Context, 'get_context_from_server',
+                       return_value='fake_context')
+    def test_instantiate_invalid_pktgen_id(self, *args):
+        yaml_sample = open(self.PKTGEN_VALUES_PATH, 'w')
+        yaml_sample.write(self.PKTGEN_VALUES)
+        yaml_sample.close()
+
+        err_scenario_cfg = copy.deepcopy(self.SCENARIO_CFG)
+        err_scenario_cfg['options'][NAME]['pktgen_id'] = 12
+        with self.assertRaises(KeyError):
+            self.vcmts_pktgen.instantiate(err_scenario_cfg, self.CONTEXT_CFG)
+
+        if os.path.isfile(self.PKTGEN_VALUES_PATH):
+            os.remove(self.PKTGEN_VALUES_PATH)
+
+    @mock.patch.object(ctx_base.Context, 'get_context_from_server',
+                       return_value='fake_context')
+    def test_instantiate_all_valid(self, *args):
+        yaml_sample = open(self.PKTGEN_VALUES_PATH, 'w')
+        yaml_sample.write(self.PKTGEN_VALUES)
+        yaml_sample.close()
+
+        self.vcmts_pktgen.instantiate(self.SCENARIO_CFG, self.CONTEXT_CFG)
+        self.assertIsNotNone(self.vcmts_pktgen.pod_cfg)
+        self.assertEqual(self.vcmts_pktgen.pod_cfg["pktgen_id"], "0")
+
+        if os.path.isfile(self.PKTGEN_VALUES_PATH):
+            os.remove(self.PKTGEN_VALUES_PATH)
+
+    def test_run_traffic_failed_connect(self):
+        self.vcmts_pktgen.pktgen_helper = mock.MagicMock()
+        self.vcmts_pktgen.pktgen_helper.connect.return_value = False
+        with self.assertRaises(exceptions.PktgenActionError):
+            self.vcmts_pktgen.run_traffic({})
+
+    def test_run_traffic_successful_connect(self):
+        self.vcmts_pktgen.pktgen_helper = mock.MagicMock()
+        self.vcmts_pktgen.pktgen_helper.connect.return_value = True
+        self.vcmts_pktgen.pktgen_rate = 8.0
+        self.assertTrue(self.vcmts_pktgen.run_traffic({}))
+        self.vcmts_pktgen.pktgen_helper.connect.assert_called_once()
+        self.vcmts_pktgen.pktgen_helper.send_command.assert_called_with(
+            'pktgen.start("all");')
diff --git a/yardstick/tests/unit/network_services/vnf_generic/vnf/test_vcmts_vnf.py b/yardstick/tests/unit/network_services/vnf_generic/vnf/test_vcmts_vnf.py
new file mode 100755 (executable)
index 0000000..11e3d6e
--- /dev/null
@@ -0,0 +1,651 @@
+# Copyright (c) 2019 Viosoft 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.
+
+import unittest
+import mock
+import copy
+import os
+
+from yardstick.tests.unit.network_services.vnf_generic.vnf.test_base import mock_ssh
+from yardstick.network_services.vnf_generic.vnf.base import VnfdHelper
+from yardstick.network_services.vnf_generic.vnf import vcmts_vnf
+from yardstick.common import exceptions
+
+from influxdb.resultset import ResultSet
+
+NAME = "vnf__0"
+
+
+class TestInfluxDBHelper(unittest.TestCase):
+
+    def test___init__(self):
+        influxdb_helper = vcmts_vnf.InfluxDBHelper("localhost", 8086)
+        self.assertEqual(influxdb_helper._vcmts_influxdb_ip, "localhost")
+        self.assertEqual(influxdb_helper._vcmts_influxdb_port, 8086)
+        self.assertIsNotNone(influxdb_helper._last_upstream_rx)
+        self.assertIsNotNone(influxdb_helper._last_values_time)
+
+    def test_start(self):
+        influxdb_helper = vcmts_vnf.InfluxDBHelper("localhost", 8086)
+        influxdb_helper.start()
+        self.assertIsNotNone(influxdb_helper._read_client)
+        self.assertIsNotNone(influxdb_helper._write_client)
+
+    def test__get_last_value_time(self):
+        influxdb_helper = vcmts_vnf.InfluxDBHelper("localhost", 8086)
+        self.assertEqual(influxdb_helper._get_last_value_time('cpu_value'),
+                         vcmts_vnf.InfluxDBHelper.INITIAL_VALUE)
+
+        influxdb_helper._last_values_time['cpu_value'] = "RANDOM"
+        self.assertEqual(influxdb_helper._get_last_value_time('cpu_value'),
+                         "RANDOM")
+
+    def test__set_last_value_time(self):
+        influxdb_helper = vcmts_vnf.InfluxDBHelper("localhost", 8086)
+        influxdb_helper._set_last_value_time('cpu_value', '00:00')
+        self.assertEqual(influxdb_helper._last_values_time['cpu_value'],
+                         "'00:00'")
+
+    def test__query_measurement(self):
+        influxdb_helper = vcmts_vnf.InfluxDBHelper("localhost", 8086)
+        influxdb_helper._read_client = mock.MagicMock()
+
+        resulted_generator = mock.MagicMock()
+        resulted_generator.keys.return_value = []
+        influxdb_helper._read_client.query.return_value = resulted_generator
+        query_result = influxdb_helper._query_measurement('cpu_value')
+        self.assertIsNone(query_result)
+
+        resulted_generator = mock.MagicMock()
+        resulted_generator.keys.return_value = ["", ""]
+        resulted_generator.get_points.return_value = ResultSet({"":""})
+        influxdb_helper._read_client.query.return_value = resulted_generator
+        query_result = influxdb_helper._query_measurement('cpu_value')
+        self.assertIsNotNone(query_result)
+
+    def test__rw_measurment(self):
+        influxdb_helper = vcmts_vnf.InfluxDBHelper("localhost", 8086)
+        influxdb_helper._query_measurement = mock.MagicMock()
+        influxdb_helper._query_measurement.return_value = None
+        influxdb_helper._rw_measurment('cpu_value', [])
+        self.assertEqual(len(influxdb_helper._last_values_time), 0)
+
+        entry = {
+            "type":"type",
+            "host":"host",
+            "time":"time",
+            "id": "1",
+            "value": "1.0"
+        }
+        influxdb_helper._query_measurement.return_value = [entry]
+        influxdb_helper._write_client = mock.MagicMock()
+        influxdb_helper._rw_measurment('cpu_value', ["id", "value"])
+        self.assertEqual(len(influxdb_helper._last_values_time), 1)
+        influxdb_helper._write_client.write_points.assert_called_once()
+
+    def test_copy_kpi(self):
+        influxdb_helper = vcmts_vnf.InfluxDBHelper("localhost", 8086)
+        influxdb_helper._rw_measurment = mock.MagicMock()
+        influxdb_helper.copy_kpi()
+        influxdb_helper._rw_measurment.assert_called()
+
+
+class TestVcmtsdSetupEnvHelper(unittest.TestCase):
+    POD_CFG = {
+        "cm_crypto": "aes",
+        "cpu_socket_id": "0",
+        "ds_core_pool_index": "2",
+        "ds_core_type": "exclusive",
+        "net_ds": "1a:02.1",
+        "net_us": "1a:02.0",
+        "num_ofdm": "1",
+        "num_subs": "100",
+        "power_mgmt": "pm_on",
+        "qat": "qat_off",
+        "service_group_config": "",
+        "sg_id": "0",
+        "vcmtsd_image": "vcmts-d:perf"
+    }
+
+    OPTIONS = {
+        "pktgen_values": "/tmp/pktgen_values.yaml",
+        "tg__0": {
+            "pktgen_id": 0
+        },
+        "vcmts_influxdb_ip": "10.80.5.150",
+        "vcmts_influxdb_port": 8086,
+        "vcmtsd_values": "/tmp/vcmtsd_values.yaml",
+        "vnf__0": {
+            "sg_id": 0,
+            "stream_dir": "us"
+        },
+        "vnf__1": {
+            "sg_id": 0,
+            "stream_dir": "ds"
+        }
+    }
+
+    def setUp(self):
+        vnfd_helper = VnfdHelper(
+            TestVcmtsVNF.VNFD['vnfd:vnfd-catalog']['vnfd'][0])
+        ssh_helper = mock.Mock()
+        scenario_helper = mock.Mock()
+        scenario_helper.options = self.OPTIONS
+
+        self.setup_helper = vcmts_vnf.VcmtsdSetupEnvHelper(
+            vnfd_helper, ssh_helper, scenario_helper)
+
+    def _build_us_parameters(self):
+        return vcmts_vnf.VcmtsdSetupEnvHelper.BASE_PARAMETERS + " " \
+             + " /opt/bin/cmk isolate --conf-dir=/etc/cmk" \
+             + " --socket-id=" + str(self.POD_CFG['cpu_socket_id']) \
+             + " --pool=shared" \
+             + " /vcmts-config/run_upstream.sh " + self.POD_CFG['sg_id'] \
+             + " " + self.POD_CFG['ds_core_type'] \
+             + " " + str(self.POD_CFG['num_ofdm']) + "ofdm" \
+             + " " + str(self.POD_CFG['num_subs']) + "cm" \
+             + " " + self.POD_CFG['cm_crypto'] \
+             + " " + self.POD_CFG['qat'] \
+             + " " + self.POD_CFG['net_us'] \
+             + " " + self.POD_CFG['power_mgmt']
+
+    def test_build_us_parameters(self):
+        constructed = self._build_us_parameters()
+        result = self.setup_helper.build_us_parameters(self.POD_CFG)
+        self.assertEqual(constructed, result)
+
+    def _build_ds_parameters(self):
+        return vcmts_vnf.VcmtsdSetupEnvHelper.BASE_PARAMETERS + " " \
+             + " /opt/bin/cmk isolate --conf-dir=/etc/cmk" \
+             + " --socket-id=" + str(self.POD_CFG['cpu_socket_id']) \
+             + " --pool=" + self.POD_CFG['ds_core_type'] \
+             + " /vcmts-config/run_downstream.sh " + self.POD_CFG['sg_id'] \
+             + " " + self.POD_CFG['ds_core_type'] \
+             + " " + str(self.POD_CFG['ds_core_pool_index']) \
+             + " " + str(self.POD_CFG['num_ofdm']) + "ofdm" \
+             + " " + str(self.POD_CFG['num_subs']) + "cm" \
+             + " " + self.POD_CFG['cm_crypto'] \
+             + " " + self.POD_CFG['qat'] \
+             + " " + self.POD_CFG['net_ds'] \
+             + " " + self.POD_CFG['power_mgmt']
+
+    def test_build_ds_parameters(self):
+        constructed = self._build_ds_parameters()
+        result = self.setup_helper.build_ds_parameters(self.POD_CFG)
+        self.assertEqual(constructed, result)
+
+    def test_build_cmd(self):
+        us_constructed = self._build_us_parameters()
+        us_result = self.setup_helper.build_cmd('us', self.POD_CFG)
+        self.assertEqual(us_constructed, us_result)
+        ds_constructed = self._build_ds_parameters()
+        ds_result = self.setup_helper.build_cmd('ds', self.POD_CFG)
+        self.assertEqual(ds_constructed, ds_result)
+
+    def test_run_vcmtsd(self):
+        us_constructed = self._build_us_parameters()
+
+        vnfd_helper = VnfdHelper(
+            TestVcmtsVNF.VNFD['vnfd:vnfd-catalog']['vnfd'][0])
+        ssh_helper = mock.MagicMock()
+        scenario_helper = mock.Mock()
+        scenario_helper.options = self.OPTIONS
+
+        setup_helper = vcmts_vnf.VcmtsdSetupEnvHelper(
+            vnfd_helper, ssh_helper, scenario_helper)
+
+        setup_helper.run_vcmtsd('us', self.POD_CFG)
+        ssh_helper.send_command.assert_called_with(us_constructed)
+
+    def test_setup_vnf_environment(self):
+        self.assertIsNone(self.setup_helper.setup_vnf_environment())
+
+class TestVcmtsVNF(unittest.TestCase):
+
+    VNFD = {'vnfd:vnfd-catalog':
+            {'vnfd':
+            [{
+            "benchmark": {
+                "kpi": [
+                    "upstream/bits_per_second"
+                ]
+            },
+            "connection-point": [
+                {
+                    "name": "xe0",
+                    "type": "VPORT"
+                },
+                {
+                    "name": "xe1",
+                    "type": "VPORT"
+                }
+            ],
+            "description": "vCMTS Upstream-Downstream Kubernetes",
+            "id": "VcmtsVNF",
+            "mgmt-interface": {
+                "ip": "192.168.100.35",
+                "key_filename": "/tmp/yardstick_key-81dcca91",
+                "user": "root",
+                "vdu-id": "vcmtsvnf-kubernetes"
+            },
+            "name": "vcmtsvnf",
+            "short-name": "vcmtsvnf",
+            "vdu": [
+                {
+                    "description": "vCMTS Upstream-Downstream Kubernetes",
+                    "external-interface": [],
+                    "id": "vcmtsvnf-kubernetes",
+                    "name": "vcmtsvnf-kubernetes"
+                }
+            ],
+            "vm-flavor": {
+                "memory-mb": "4096",
+                "vcpu-count": "4"
+            }
+        }]
+        }
+    }
+
+    POD_CFG = [
+        {
+            "cm_crypto": "aes",
+            "cpu_socket_id": "0",
+            "ds_core_pool_index": "2",
+            "ds_core_type": "exclusive",
+            "net_ds": "1a:02.1",
+            "net_us": "1a:02.0",
+            "num_ofdm": "1",
+            "num_subs": "100",
+            "power_mgmt": "pm_on",
+            "qat": "qat_off",
+            "service_group_config": "",
+            "sg_id": "0",
+            "vcmtsd_image": "vcmts-d:perf"
+        },
+    ]
+
+    SCENARIO_CFG = {
+        "nodes": {
+            "tg__0": "pktgen0-k8syardstick-afae18b2",
+            "vnf__0": "vnf0us-k8syardstick-afae18b2",
+            "vnf__1": "vnf0ds-k8syardstick-afae18b2"
+        },
+        "options": {
+            "pktgen_values": "/tmp/pktgen_values.yaml",
+            "tg__0": {
+                "pktgen_id": 0
+            },
+            "vcmts_influxdb_ip": "10.80.5.150",
+            "vcmts_influxdb_port": 8086,
+            "vcmtsd_values": "/tmp/vcmtsd_values.yaml",
+            "vnf__0": {
+                "sg_id": 0,
+                "stream_dir": "us"
+            },
+            "vnf__1": {
+                "sg_id": 0,
+                "stream_dir": "ds"
+            }
+        },
+        "task_id": "afae18b2-9902-477f-8128-49afde7c3040",
+        "task_path": "samples/vnf_samples/nsut/cmts",
+        "tc": "tc_vcmts_k8s_pktgen",
+        "topology": "k8s_vcmts_topology.yaml",
+        "traffic_profile": "../../traffic_profiles/fixed.yaml",
+        "type": "NSPerf"
+    }
+
+    CONTEXT_CFG = {
+        "networks": {
+            "flannel": {
+                "name": "flannel"
+            },
+            "xe0": {
+                "name": "xe0"
+            },
+            "xe1": {
+                "name": "xe1"
+            }
+        },
+        "nodes": {
+            "tg__0": {
+                "VNF model": "../../vnf_descriptors/tg_vcmts_tpl.yaml",
+                "interfaces": {
+                    "flannel": {
+                        "local_ip": "192.168.24.110",
+                        "local_mac": None,
+                        "network_name": "flannel"
+                    },
+                    "xe0": {
+                        "local_ip": "192.168.24.110",
+                        "local_mac": None,
+                        "network_name": "xe0"
+                    },
+                    "xe1": {
+                        "local_ip": "192.168.24.110",
+                        "local_mac": None,
+                        "network_name": "xe1"
+                    }
+                },
+                "ip": "192.168.24.110",
+                "key_filename": "/tmp/yardstick_key-afae18b2",
+                "member-vnf-index": "1",
+                "name": "pktgen0-k8syardstick-afae18b2",
+                "private_ip": "192.168.24.110",
+                "service_ports": [
+                    {
+                        "name": "ssh",
+                        "node_port": 17153,
+                        "port": 22,
+                        "protocol": "TCP",
+                        "target_port": 22
+                    },
+                    {
+                        "name": "lua",
+                        "node_port": 51250,
+                        "port": 22022,
+                        "protocol": "TCP",
+                        "target_port": 22022
+                    }
+                ],
+                "ssh_port": 17153,
+                "user": "root",
+                "vnfd-id-ref": "tg__0"
+            },
+            "vnf__0": {
+                "VNF model": "../../vnf_descriptors/vnf_vcmts_tpl.yaml",
+                "interfaces": {
+                    "flannel": {
+                        "local_ip": "192.168.100.53",
+                        "local_mac": None,
+                        "network_name": "flannel"
+                    },
+                    "xe0": {
+                        "local_ip": "192.168.100.53",
+                        "local_mac": None,
+                        "network_name": "xe0"
+                    },
+                    "xe1": {
+                        "local_ip": "192.168.100.53",
+                        "local_mac": None,
+                        "network_name": "xe1"
+                    }
+                },
+                "ip": "192.168.100.53",
+                "key_filename": "/tmp/yardstick_key-afae18b2",
+                "member-vnf-index": "3",
+                "name": "vnf0us-k8syardstick-afae18b2",
+                "private_ip": "192.168.100.53",
+                "service_ports": [
+                    {
+                        "name": "ssh",
+                        "node_port": 34027,
+                        "port": 22,
+                        "protocol": "TCP",
+                        "target_port": 22
+                    },
+                    {
+                        "name": "lua",
+                        "node_port": 32580,
+                        "port": 22022,
+                        "protocol": "TCP",
+                        "target_port": 22022
+                    }
+                ],
+                "ssh_port": 34027,
+                "user": "root",
+                "vnfd-id-ref": "vnf__0"
+            },
+            "vnf__1": {
+                "VNF model": "../../vnf_descriptors/vnf_vcmts_tpl.yaml",
+                "interfaces": {
+                    "flannel": {
+                        "local_ip": "192.168.100.52",
+                        "local_mac": None,
+                        "network_name": "flannel"
+                    },
+                    "xe0": {
+                        "local_ip": "192.168.100.52",
+                        "local_mac": None,
+                        "network_name": "xe0"
+                    },
+                    "xe1": {
+                        "local_ip": "192.168.100.52",
+                        "local_mac": None,
+                        "network_name": "xe1"
+                    }
+                },
+                "ip": "192.168.100.52",
+                "key_filename": "/tmp/yardstick_key-afae18b2",
+                "member-vnf-index": "4",
+                "name": "vnf0ds-k8syardstick-afae18b2",
+                "private_ip": "192.168.100.52",
+                "service_ports": [
+                    {
+                        "name": "ssh",
+                        "node_port": 58661,
+                        "port": 22,
+                        "protocol": "TCP",
+                        "target_port": 22
+                    },
+                    {
+                        "name": "lua",
+                        "node_port": 58233,
+                        "port": 22022,
+                        "protocol": "TCP",
+                        "target_port": 22022
+                    }
+                ],
+                "ssh_port": 58661,
+                "user": "root",
+                "vnfd-id-ref": "vnf__1"
+            },
+        }
+    }
+
+    VCMTSD_VALUES_PATH = "/tmp/vcmtsd_values.yaml"
+
+    VCMTSD_VALUES = \
+            "serviceAccount: cmk-serviceaccount\n" \
+            "topology:\n" \
+            "  vcmts_replicas: 16\n" \
+            "  vcmts_pods:\n" \
+            "    - service_group_config:\n" \
+            "      sg_id: 0\n" \
+            "      net_us: 18:02.0\n" \
+            "      net_ds: 18:02.1\n" \
+            "      num_ofdm: 4\n" \
+            "      num_subs: 300\n" \
+            "      cm_crypto: aes\n" \
+            "      qat: qat_off\n" \
+            "      power_mgmt: pm_on\n" \
+            "      cpu_socket_id: 0\n" \
+            "      ds_core_type: exclusive\n" \
+            "      ds_core_pool_index: 0\n" \
+            "      vcmtsd_image: vcmts-d:feat"
+
+    VCMTSD_VALUES_INCOMPLETE = \
+            "serviceAccount: cmk-serviceaccount\n" \
+            "topology:\n" \
+            "  vcmts_replicas: 16"
+
+    def setUp(self):
+        vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0]
+        self.vnf = vcmts_vnf.VcmtsVNF(NAME, vnfd)
+
+    def test___init__(self, *args):
+        self.assertIsNotNone(self.vnf.setup_helper)
+
+    def test_extract_pod_cfg(self):
+        pod_cfg = self.vnf.extract_pod_cfg(self.POD_CFG, "0")
+        self.assertIsNotNone(pod_cfg)
+        self.assertEqual(pod_cfg['sg_id'], '0')
+        pod_cfg = self.vnf.extract_pod_cfg(self.POD_CFG, "1")
+        self.assertIsNone(pod_cfg)
+
+    def test_instantiate_missing_influxdb_info(self):
+        err_scenario_cfg = copy.deepcopy(self.SCENARIO_CFG)
+        err_scenario_cfg['options'].pop('vcmts_influxdb_ip', None)
+        with self.assertRaises(KeyError):
+            self.vnf.instantiate(err_scenario_cfg, self.CONTEXT_CFG)
+
+    def test_instantiate_missing_vcmtsd_values_file(self):
+        if os.path.isfile(self.VCMTSD_VALUES_PATH):
+            os.remove(self.VCMTSD_VALUES_PATH)
+        err_scenario_cfg = copy.deepcopy(self.SCENARIO_CFG)
+        err_scenario_cfg['options']['vcmtsd_values'] = self.VCMTSD_VALUES_PATH
+        with self.assertRaises(RuntimeError):
+            self.vnf.instantiate(err_scenario_cfg, self.CONTEXT_CFG)
+
+    def test_instantiate_empty_vcmtsd_values_file(self):
+        yaml_sample = open(self.VCMTSD_VALUES_PATH, 'w')
+        yaml_sample.write("")
+        yaml_sample.close()
+
+        err_scenario_cfg = copy.deepcopy(self.SCENARIO_CFG)
+        err_scenario_cfg['options']['vcmtsd_values'] = self.VCMTSD_VALUES_PATH
+        with self.assertRaises(RuntimeError):
+            self.vnf.instantiate(err_scenario_cfg, self.CONTEXT_CFG)
+
+        if os.path.isfile(self.VCMTSD_VALUES_PATH):
+            os.remove(self.VCMTSD_VALUES_PATH)
+
+    def test_instantiate_missing_vcmtsd_values_key(self):
+        err_scenario_cfg = copy.deepcopy(self.SCENARIO_CFG)
+        err_scenario_cfg['options'].pop('vcmtsd_values', None)
+        with self.assertRaises(KeyError):
+            self.vnf.instantiate(err_scenario_cfg, self.CONTEXT_CFG)
+
+    def test_instantiate_invalid_vcmtsd_values(self):
+        yaml_sample = open(self.VCMTSD_VALUES_PATH, 'w')
+        yaml_sample.write(self.VCMTSD_VALUES_INCOMPLETE)
+        yaml_sample.close()
+
+        err_scenario_cfg = copy.deepcopy(self.SCENARIO_CFG)
+        with self.assertRaises(KeyError):
+            self.vnf.instantiate(err_scenario_cfg, self.CONTEXT_CFG)
+
+        if os.path.isfile(self.VCMTSD_VALUES_PATH):
+            os.remove(self.VCMTSD_VALUES_PATH)
+
+    def test_instantiate_invalid_sg_id(self):
+        yaml_sample = open(self.VCMTSD_VALUES_PATH, 'w')
+        yaml_sample.write(self.VCMTSD_VALUES)
+        yaml_sample.close()
+
+        err_scenario_cfg = copy.deepcopy(self.SCENARIO_CFG)
+        err_scenario_cfg['options'][NAME]['sg_id'] = 8
+        with self.assertRaises(exceptions.IncorrectConfig):
+            self.vnf.instantiate(err_scenario_cfg, self.CONTEXT_CFG)
+
+        if os.path.isfile(self.VCMTSD_VALUES_PATH):
+            os.remove(self.VCMTSD_VALUES_PATH)
+
+    @mock.patch('yardstick.network_services.vnf_generic.vnf.vcmts_vnf.VnfSshHelper')
+    def test_instantiate_all_valid(self, ssh, *args):
+        mock_ssh(ssh)
+
+        vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0]
+        vnf = vcmts_vnf.VcmtsVNF(NAME, vnfd)
+
+        yaml_sample = open(self.VCMTSD_VALUES_PATH, 'w')
+        yaml_sample.write(self.VCMTSD_VALUES)
+        yaml_sample.close()
+
+        vnf.instantiate(self.SCENARIO_CFG, self.CONTEXT_CFG)
+        self.assertEqual(vnf.vcmts_influxdb_ip, "10.80.5.150")
+        self.assertEqual(vnf.vcmts_influxdb_port, 8086)
+
+        if os.path.isfile(self.VCMTSD_VALUES_PATH):
+            os.remove(self.VCMTSD_VALUES_PATH)
+
+    def test__update_collectd_options(self):
+        scenario_cfg = {'options':
+                            {'collectd':
+                                 {'interval': 3,
+                                  'plugins':
+                                      {'plugin3': {'param': 3}}},
+                             'vnf__0':
+                                 {'collectd':
+                                      {'interval': 2,
+                                       'plugins':
+                                           {'plugin3': {'param': 2},
+                                            'plugin2': {'param': 2}}}}}}
+        context_cfg = {'nodes':
+                           {'vnf__0':
+                                {'collectd':
+                                     {'interval': 1,
+                                      'plugins':
+                                          {'plugin3': {'param': 1},
+                                           'plugin2': {'param': 1},
+                                           'plugin1': {'param': 1}}}}}}
+        expected = {'interval': 1,
+                    'plugins':
+                        {'plugin3': {'param': 1},
+                         'plugin2': {'param': 1},
+                         'plugin1': {'param': 1}}}
+
+        self.vnf._update_collectd_options(scenario_cfg, context_cfg)
+        self.assertEqual(self.vnf.setup_helper.collectd_options, expected)
+
+    def test__update_options(self):
+        options1 = {'interval': 1,
+                    'param1': 'value1',
+                    'plugins':
+                        {'plugin3': {'param': 3},
+                         'plugin2': {'param': 1},
+                         'plugin1': {'param': 1}}}
+        options2 = {'interval': 2,
+                    'param2': 'value2',
+                    'plugins':
+                        {'plugin4': {'param': 4},
+                         'plugin2': {'param': 2},
+                         'plugin1': {'param': 2}}}
+        expected = {'interval': 1,
+                    'param1': 'value1',
+                    'param2': 'value2',
+                    'plugins':
+                        {'plugin4': {'param': 4},
+                         'plugin3': {'param': 3},
+                         'plugin2': {'param': 1},
+                         'plugin1': {'param': 1}}}
+
+        vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0]
+        vnf = vcmts_vnf.VcmtsVNF('vnf1', vnfd)
+        vnf._update_options(options2, options1)
+        self.assertEqual(options2, expected)
+
+    def test_wait_for_instantiate(self):
+        self.assertIsNone(self.vnf.wait_for_instantiate())
+
+    def test_terminate(self):
+        self.assertIsNone(self.vnf.terminate())
+
+    def test_scale(self):
+        self.assertIsNone(self.vnf.scale())
+
+    def test_collect_kpi(self):
+        self.vnf.influxdb_helper = mock.MagicMock()
+        self.vnf.collect_kpi()
+        self.vnf.influxdb_helper.copy_kpi.assert_called_once()
+
+    def test_start_collect(self):
+        self.vnf.vcmts_influxdb_ip = "localhost"
+        self.vnf.vcmts_influxdb_port = 8800
+
+        self.assertIsNone(self.vnf.start_collect())
+        self.assertIsNotNone(self.vnf.influxdb_helper)
+
+    def test_stop_collect(self):
+        self.assertIsNone(self.vnf.stop_collect())