get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

GET /api/patches/87187/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 87187,
    "url": "https://patches.dpdk.org/api/patches/87187/?format=api",
    "web_url": "https://patches.dpdk.org/project/dts/patch/20210125084414.8503-12-yufengx.mo@intel.com/",
    "project": {
        "id": 3,
        "url": "https://patches.dpdk.org/api/projects/3/?format=api",
        "name": "DTS",
        "link_name": "dts",
        "list_id": "dts.dpdk.org",
        "list_email": "dts@dpdk.org",
        "web_url": "",
        "scm_url": "git://dpdk.org/tools/dts",
        "webscm_url": "http://git.dpdk.org/tools/dts/",
        "list_archive_url": "https://inbox.dpdk.org/dts",
        "list_archive_url_format": "https://inbox.dpdk.org/dts/{}",
        "commit_url_format": ""
    },
    "msgid": "<20210125084414.8503-12-yufengx.mo@intel.com>",
    "list_archive_url": "https://inbox.dpdk.org/dts/20210125084414.8503-12-yufengx.mo@intel.com",
    "date": "2021-01-25T08:43:58",
    "name": "[V1,11/27] framework/pktgen: enable ixNetwork",
    "commit_ref": null,
    "pull_url": null,
    "state": "accepted",
    "archived": false,
    "hash": "5487acadf283b79362e3f596639556e0adc192ba",
    "submitter": {
        "id": 1342,
        "url": "https://patches.dpdk.org/api/people/1342/?format=api",
        "name": "Yufen.Mo",
        "email": "yufengx.mo@intel.com"
    },
    "delegate": {
        "id": 23642,
        "url": "https://patches.dpdk.org/api/users/23642/?format=api",
        "username": "lijuantu",
        "first_name": "LIjuan",
        "last_name": "Tu",
        "email": "lijuan.tu@intel.com"
    },
    "mbox": "https://patches.dpdk.org/project/dts/patch/20210125084414.8503-12-yufengx.mo@intel.com/mbox/",
    "series": [
        {
            "id": 14926,
            "url": "https://patches.dpdk.org/api/series/14926/?format=api",
            "web_url": "https://patches.dpdk.org/project/dts/list/?series=14926",
            "date": "2021-01-25T08:43:47",
            "name": "dts: enable IxNetwork and enhance perf testing",
            "version": 1,
            "mbox": "https://patches.dpdk.org/series/14926/mbox/"
        }
    ],
    "comments": "https://patches.dpdk.org/api/patches/87187/comments/",
    "check": "pending",
    "checks": "https://patches.dpdk.org/api/patches/87187/checks/",
    "tags": {},
    "related": [],
    "headers": {
        "Return-Path": "<dts-bounces@dpdk.org>",
        "X-Original-To": "patchwork@inbox.dpdk.org",
        "Delivered-To": "patchwork@inbox.dpdk.org",
        "Received": [
            "from mails.dpdk.org (mails.dpdk.org [217.70.189.124])\n\tby inbox.dpdk.org (Postfix) with ESMTP id 9D28FA052A;\n\tMon, 25 Jan 2021 09:51:44 +0100 (CET)",
            "from [217.70.189.124] (localhost [127.0.0.1])\n\tby mails.dpdk.org (Postfix) with ESMTP id 976A5140DE1;\n\tMon, 25 Jan 2021 09:51:44 +0100 (CET)",
            "from mga06.intel.com (mga06.intel.com [134.134.136.31])\n by mails.dpdk.org (Postfix) with ESMTP id 50D9C140DBB\n for <dts@dpdk.org>; Mon, 25 Jan 2021 09:51:42 +0100 (CET)",
            "from fmsmga001.fm.intel.com ([10.253.24.23])\n by orsmga104.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384;\n 25 Jan 2021 00:51:41 -0800",
            "from dpdk-moyufen06.sh.intel.com ([10.67.116.208])\n by fmsmga001.fm.intel.com with ESMTP; 25 Jan 2021 00:51:40 -0800"
        ],
        "IronPort-SDR": [
            "\n HIuPLaLWfm50EOjylGtwsdwqPchGHjv9deQmKDhW/DbRqNFMCfibIJ2XFWkHVk0kNLg28RuCGx\n 5VKDVHZC9Gug==",
            "\n NQj4Lr9/VImNN6E6va2L397GCqGEzywwB6h/yqt0tBEg4yULgwonO1673DmeGf9KJ1F38jOuCg\n GRsemIv5cbRA=="
        ],
        "X-IronPort-AV": [
            "E=McAfee;i=\"6000,8403,9874\"; a=\"241224426\"",
            "E=Sophos;i=\"5.79,373,1602572400\"; d=\"scan'208\";a=\"241224426\"",
            "E=Sophos;i=\"5.79,373,1602572400\"; d=\"scan'208\";a=\"471978444\""
        ],
        "X-ExtLoop1": "1",
        "From": "yufengmx <yufengx.mo@intel.com>",
        "To": "dts@dpdk.org,\n\tlijuan.tu@intel.com",
        "Cc": "yufengmx <yufengx.mo@intel.com>",
        "Date": "Mon, 25 Jan 2021 16:43:58 +0800",
        "Message-Id": "<20210125084414.8503-12-yufengx.mo@intel.com>",
        "X-Mailer": "git-send-email 2.21.0",
        "In-Reply-To": "<20210125084414.8503-1-yufengx.mo@intel.com>",
        "References": "<20210125084414.8503-1-yufengx.mo@intel.com>",
        "MIME-Version": "1.0",
        "Content-Transfer-Encoding": "8bit",
        "Subject": "[dts] [PATCH V1 11/27] framework/pktgen: enable ixNetwork",
        "X-BeenThere": "dts@dpdk.org",
        "X-Mailman-Version": "2.1.29",
        "Precedence": "list",
        "List-Id": "test suite reviews and discussions <dts.dpdk.org>",
        "List-Unsubscribe": "<https://mails.dpdk.org/options/dts>,\n <mailto:dts-request@dpdk.org?subject=unsubscribe>",
        "List-Archive": "<http://mails.dpdk.org/archives/dts/>",
        "List-Post": "<mailto:dts@dpdk.org>",
        "List-Help": "<mailto:dts-request@dpdk.org?subject=help>",
        "List-Subscribe": "<https://mails.dpdk.org/listinfo/dts>,\n <mailto:dts-request@dpdk.org?subject=subscribe>",
        "Errors-To": "dts-bounces@dpdk.org",
        "Sender": "\"dts\" <dts-bounces@dpdk.org>"
    },
    "content": "ixNetwork api server restful interface.\n\nSigned-off-by: yufengmx <yufengx.mo@intel.com>\n---\n framework/ixia_network/ixnet.py | 844 ++++++++++++++++++++++++++++++++\n 1 file changed, 844 insertions(+)\n create mode 100644 framework/ixia_network/ixnet.py",
    "diff": "diff --git a/framework/ixia_network/ixnet.py b/framework/ixia_network/ixnet.py\nnew file mode 100644\nindex 00000000..a8de15ca\n--- /dev/null\n+++ b/framework/ixia_network/ixnet.py\n@@ -0,0 +1,844 @@\n+# BSD LICENSE\n+#\n+# Copyright(c) 2010-2021 Intel Corporation. All rights reserved.\n+# All rights reserved.\n+#\n+# Redistribution and use in source and binary forms, with or without\n+# modification, are permitted provided that the following conditions\n+# are met:\n+#\n+#   * Redistributions of source code must retain the above copyright\n+#     notice, this list of conditions and the following disclaimer.\n+#   * Redistributions in binary form must reproduce the above copyright\n+#     notice, this list of conditions and the following disclaimer in\n+#     the documentation and/or other materials provided with the\n+#     distribution.\n+#   * Neither the name of Intel Corporation nor the names of its\n+#     contributors may be used to endorse or promote products derived\n+#     from this software without specific prior written permission.\n+#\n+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n+# \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n+\"\"\"\n+This module implant from pei,yulong ixNetwork tool.\n+\"\"\"\n+\n+import os\n+import time\n+import re\n+import requests\n+import json\n+import csv\n+from collections import OrderedDict\n+from datetime import datetime\n+\n+\n+# local lib deps\n+from .packet_parser import PacketParser\n+from .ixnet_stream import IxnetConfigStream\n+\n+\n+class IxnetTrafficGenerator(object):\n+    \"\"\"ixNetwork Traffic Generator.\"\"\"\n+    json_header = {'content-type': 'application/json'}\n+\n+    def __init__(self, config, logger):\n+        # disable SSL warnings\n+        requests.packages.urllib3.disable_warnings()\n+        self.logger = logger\n+        self.tg_ip = config.tg_ip\n+        self.tg_ports = config.tg_ports\n+        port = config.tg_ip_port or '11009'\n+        # id will always be 1 when using windows api server\n+        self.api_server = 'http://{0}:{1}'.format(self.tg_ip, port)\n+        self.session = requests.session()\n+        self.session_id = self.get_session_id(self.api_server)\n+        self.session_url = \"{0}/api/v1/sessions/{1}\".format(\n+            self.api_server, self.session_id)\n+        # initialize ixNetwork\n+        self.new_blank_config()\n+        self.tg_vports = self.assign_ports(self.tg_ports)\n+\n+    def get_session_id(self, api_server):\n+        url = '{server}/api/v1/sessions'.format(server=api_server)\n+        response = self.session.post(\n+            url, headers=self.json_header, verify=False)\n+        session_id = response.json()['links'][0]['href'].split('/')[-1]\n+        msg = \"{0}: Session ID is {1}\".format(api_server, session_id)\n+        self.logger.info(msg)\n+        return session_id\n+\n+    def destroy_config(self, name):\n+        json_header = {\n+            'content-type': 'application/json',\n+            'X-HTTP-Method-Override': 'DELETE',\n+        }\n+        response = self.session.post(name, headers=json_header, verify=False)\n+        return response\n+\n+    def __get_ports(self):\n+        \"\"\"Return available tg vports list\"\"\"\n+        return self.tg_vports\n+\n+    def disable_port_misdirected(self):\n+        msg = 'close mismatched flag'\n+        self.logger.debug(msg)\n+        url = \"{0}/ixnetwork/traffic\".format(self.session_url)\n+        data = {\n+            \"detectMisdirectedOnAllPorts\": False,\n+            \"disablePortLevelMisdirected\": True,\n+        }\n+        response = self.session.patch(\n+            url, data=json.dumps(data), headers=self.json_header, verify=False)\n+\n+    def delete_session(self):\n+        \"\"\"delete session after test done\"\"\"\n+        try:\n+            url = self.session_url\n+            response = self.destroy_config(url)\n+            self.logger.debug(\"STATUS CODE: %s\" % response.status_code)\n+        except requests.exceptions.RequestException as err_msg:\n+            raise Exception('DELETE error: {0}\\n'.format(err_msg))\n+\n+    def configure_streams(self, pkt, field_config=None):\n+        hParser = PacketParser()\n+        hParser._parse_pcap(pkt)\n+        hConfig = IxnetConfigStream(\n+            hParser.packetLayers, field_config, hParser.framesize)\n+        return hConfig.ixnet_packet\n+\n+    def regenerate_trafficitems(self, trafficItemList):\n+        \"\"\"\n+        Parameter\n+            trafficItemList: ['/api/v1/sessions/1/ixnetwork/traffic/trafficItem/1', ...]\n+        \"\"\"\n+        url = \"{0}/ixnetwork/traffic/trafficItem/operations/generate\".format(\n+            self.session_url)\n+        data = {\"arg1\": trafficItemList}\n+        self.logger.info('Regenerating traffic items: %s' % trafficItemList)\n+        response = self.session.post(\n+            url, data=json.dumps(data), headers=self.json_header, verify=False)\n+        self.wait_for_complete(response, url + '/' + response.json()['id'])\n+\n+    def apply_traffic(self):\n+        \"\"\"Apply the configured traffic.\"\"\"\n+        url = \"{0}/ixnetwork/traffic/operations/apply\".format(self.session_url)\n+        data = {\"arg1\": f\"/api/v1/sessions/{self.session_id}/ixnetwork/traffic\"}\n+        response = self.session.post(\n+            url, data=json.dumps(data), headers=self.json_header, verify=False)\n+        self.wait_for_complete(response, url + '/' + response.json()['id'])\n+\n+    def start_traffic(self):\n+        \"\"\"start the configured traffic.\"\"\"\n+        self.logger.info(\"Traffic starting...\")\n+        url = \"{0}/ixnetwork/traffic/operations/start\".format(self.session_url)\n+        data = {\"arg1\": f\"/api/v1/sessions/{self.session_id}/ixnetwork/traffic\"}\n+        response = self.session.post(\n+            url, data=json.dumps(data), headers=self.json_header, verify=False)\n+        self.check_traffic_state(\n+            expectedState=['started', 'startedWaitingForStats'], timeout=45)\n+        self.logger.info(\"Traffic started Successfully.\")\n+\n+    def stop_traffic(self):\n+        \"\"\"stop the configured traffic.\"\"\"\n+        url = \"{0}/ixnetwork/traffic/operations/stop\".format(self.session_url)\n+        data = {\"arg1\": f\"/api/v1/sessions/{self.session_id}/ixnetwork/traffic\"}\n+        response = self.session.post(\n+            url, data=json.dumps(data), headers=self.json_header, verify=False)\n+        self.check_traffic_state(\n+            expectedState=['stopped', 'stoppedWaitingForStats'])\n+        time.sleep(5)\n+\n+    def check_traffic_state(self, expectedState=['stopped'], timeout=45):\n+        \"\"\"\n+        Description\n+            Check the traffic state for the expected state.\n+\n+        Traffic states are:\n+            startedWaitingForStats, startedWaitingForStreams, started, stopped,\n+            stoppedWaitingForStats, txStopWatchExpected, locked, unapplied\n+\n+        Parameters\n+            expectedState = Input a list of expected traffic state.\n+                            Example: ['started', startedWaitingForStats']\n+            timeout = The amount of seconds you want to wait for the expected traffic state.\n+                      Defaults to 45 seconds.\n+                      In a situation where you have more than 10 pages of stats, you will\n+                      need to increase the timeout time.\n+        \"\"\"\n+        if type(expectedState) != list:\n+            expectedState.split(' ')\n+\n+        self.logger.info(\n+            'check_traffic_state: expecting traffic state {0}'.format(expectedState))\n+        for counter in range(1, timeout + 1):\n+            url = \"{0}/ixnetwork/traffic\".format(self.session_url)\n+            response = self.session.get(\n+                url, headers=self.json_header, verify=False)\n+            current_traffic_state = response.json()['state']\n+            self.logger.info('check_traffic_state: {trafficstate}: Waited {counter}/{timeout} seconds'.format(\n+                trafficstate=current_traffic_state,\n+                counter=counter,\n+                timeout=timeout))\n+            if counter < timeout and current_traffic_state not in expectedState:\n+                time.sleep(1)\n+                continue\n+            if counter < timeout and current_traffic_state in expectedState:\n+                time.sleep(8)\n+                self.logger.info(\n+                    'check_traffic_state: got expected [ %s ], Done' % current_traffic_state)\n+                return 0\n+\n+        raise Exception(\n+            'Traffic state did not reach the expected state (%s):' % expectedState)\n+\n+    def _get_stats(self, viewName='Flow Statistics', csvFile=None, csvEnableFileTimestamp=False):\n+        \"\"\"\n+         sessionUrl: http://10.219.x.x:11009/api/v1/sessions/1/ixnetwork\n+\n+         csvFile = None or <filename.csv>.\n+                   None will not create a CSV file.\n+                   Provide a <filename>.csv to record all stats to a CSV file.\n+                   Example: _get_stats(sessionUrl, csvFile='Flow_Statistics.csv')\n+\n+         csvEnableFileTimestamp = True or False. If True, timestamp will be appended to the filename.\n+\n+         viewName options (Not case sensitive):\n+\n+            'Port Statistics'\n+            'Tx-Rx Frame Rate Statistics'\n+            'Port CPU Statistics'\n+            'Global Protocol Statistics'\n+            'Protocols Summary'\n+            'Port Summary'\n+            'OSPFv2-RTR Drill Down'\n+            'OSPFv2-RTR Per Port'\n+            'IPv4 Drill Down'\n+            'L2-L3 Test Summary Statistics'\n+            'Flow Statistics'\n+            'Traffic Item Statistics'\n+            'IGMP Host Drill Down'\n+            'IGMP Host Per Port'\n+            'IPv6 Drill Down'\n+            'MLD Host Drill Down'\n+            'MLD Host Per Port'\n+            'PIMv6 IF Drill Down'\n+            'PIMv6 IF Per Port'\n+\n+         Note: Not all of the viewNames are listed here. You have to get the exact names from\n+               the IxNetwork GUI in statistics based on your protocol(s).\n+\n+         Return you a dictionary of all the stats: statDict[rowNumber][columnName] == statValue\n+           Get stats on row 2 for 'Tx Frames' = statDict[2]['Tx Frames']\n+        \"\"\"\n+        url = \"{0}/ixnetwork/statistics/view\".format(self.session_url)\n+        viewList = self.session.get(\n+            url, headers=self.json_header, verify=False)\n+        views = ['{0}/{1}'.format(url, str(i['id'])) for i in viewList.json()]\n+\n+        for view in views:\n+            # GetAttribute\n+            response = self.session.get(\n+                view, headers=self.json_header, verify=False)\n+            if response.status_code != 200:\n+                raise Exception('getStats: Failed: %s' % response.text)\n+            captionMatch = re.match(viewName, response.json()['caption'], re.I)\n+            if captionMatch:\n+                # viewObj: sessionUrl + /statistics/view/11'\n+                viewObj = view\n+                break\n+\n+        self.logger.info(\"viewName: %s, %s\" % (viewName, viewObj))\n+\n+        try:\n+            response = self.session.patch(viewObj, data=json.dumps(\n+                {'enabled': 'true'}), headers=self.json_header, verify=False)\n+        except Exception as e:\n+            raise Exception('get_stats error: No stats available')\n+\n+        for counter in range(0, 31):\n+            response = self.session.get(\n+                viewObj + '/page', headers=self.json_header, verify=False)\n+            totalPages = response.json()['totalPages']\n+            if totalPages == 'null':\n+                self.logger.info(\n+                    'Getting total pages is not ready yet. Waiting %d/30 seconds' % counter)\n+                time.sleep(1)\n+            if totalPages != 'null':\n+                break\n+            if totalPages == 'null' and counter == 30:\n+                raise Exception('getStats: failed to get total pages')\n+\n+        if csvFile is not None:\n+            csvFileName = csvFile.replace(' ', '_')\n+            if csvEnableFileTimestamp:\n+                timestamp = datetime.now().strftime('%H%M%S')\n+                if '.' in csvFileName:\n+                    csvFileNameTemp = csvFileName.split('.')[0]\n+                    csvFileNameExtension = csvFileName.split('.')[1]\n+                    csvFileName = csvFileNameTemp + '_' + \\\n+                        timestamp + '.' + csvFileNameExtension\n+                else:\n+                    csvFileName = csvFileName + '_' + timestamp\n+\n+            csvFile = open(csvFileName, 'w')\n+            csvWriteObj = csv.writer(csvFile)\n+\n+        # Get the stat column names\n+        columnList = response.json()['columnCaptions']\n+        if csvFile is not None:\n+            csvWriteObj.writerow(columnList)\n+\n+        statDict = {}\n+        flowNumber = 1\n+        # Get the stat values\n+        for pageNumber in range(1, totalPages + 1):\n+            self.session.patch(viewObj + '/page', data=json.dumps(\n+                {'currentPage': pageNumber}), headers=self.json_header, verify=False)\n+            response = self.session.get(\n+                viewObj + '/page', headers=self.json_header, verify=False)\n+            statValueList = response.json()['pageValues']\n+            for statValue in statValueList:\n+                if csvFile is not None:\n+                    csvWriteObj.writerow(statValue[0])\n+\n+                self.logger.info('Row: %d' % flowNumber)\n+                statDict[flowNumber] = {}\n+                index = 0\n+                for statValue in statValue[0]:\n+                    statName = columnList[index]\n+                    statDict[flowNumber].update({statName: statValue})\n+                    self.logger.info('%s: %s' % (statName, statValue))\n+                    index += 1\n+                flowNumber += 1\n+\n+        if csvFile is not None:\n+            csvFile.close()\n+        return statDict\n+        # Flow Statistics dictionary output example\n+        \"\"\"\n+        Flow: 50\n+            Tx Port: Ethernet - 002\n+            Rx Port: Ethernet - 001\n+            Traffic Item: OSPF T1 to T2\n+            Source/Dest Value Pair: 2.0.21.1-1.0.21.1\n+            Flow Group: OSPF T1 to T2-FlowGroup-1 - Flow Group 0002\n+            Tx Frames: 35873\n+            Rx Frames: 35873\n+            Frames Delta: 0\n+            Loss %: 0\n+            Tx Frame Rate: 3643.5\n+            Rx Frame Rate: 3643.5\n+            Tx L1 Rate (bps): 4313904\n+            Rx L1 Rate (bps): 4313904\n+            Rx Bytes: 4591744\n+            Tx Rate (Bps): 466368\n+            Rx Rate (Bps): 466368\n+            Tx Rate (bps): 3730944\n+            Rx Rate (bps): 3730944\n+            Tx Rate (Kbps): 3730.944\n+            Rx Rate (Kbps): 3730.944\n+            Tx Rate (Mbps): 3.731\n+            Rx Rate (Mbps): 3.731\n+            Store-Forward Avg Latency (ns): 0\n+            Store-Forward Min Latency (ns): 0\n+            Store-Forward Max Latency (ns): 0\n+            First TimeStamp: 00:00:00.722\n+            Last TimeStamp: 00:00:10.568\n+        \"\"\"\n+\n+    def new_blank_config(self):\n+        \"\"\"\n+        Start a new blank configuration.\n+        \"\"\"\n+        url = \"{0}/ixnetwork/operations/newconfig\".format(self.session_url)\n+        self.logger.info('newBlankConfig: %s' % url)\n+        response = self.session.post(url, verify=False)\n+        url = \"{0}/{1}\".format(url, response.json()['id'])\n+        self.wait_for_complete(response, url)\n+\n+    def wait_for_complete(self, response='', url='', timeout=120):\n+        \"\"\"\n+        Wait for an operation progress to complete.\n+        response: The POST action response.\n+        \"\"\"\n+        if response.json() == '' and response.json()['state'] == 'SUCCESS':\n+            self.logger.info('State: SUCCESS')\n+            return\n+\n+        if response.json() == []:\n+            raise Exception('waitForComplete: response is empty.')\n+\n+        if 'errors' in response.json():\n+            raise Exception(response.json()[\"errors\"][0])\n+\n+        if response.json()['state'] in [\"ERROR\", \"EXCEPTION\"]:\n+            raise Exception('WaitForComplete: STATE=%s: %s' %\n+                            (response.json()['state'], response.text))\n+\n+        self.logger.info(\"%s\" % url)\n+        self.logger.info(\"State: %s\" % (response.json()[\"state\"]))\n+        while response.json()[\"state\"] == \"IN_PROGRESS\" or response.json()[\"state\"] == \"down\":\n+            if timeout == 0:\n+                raise Exception('%s' % response.text)\n+            time.sleep(1)\n+            response = self.session.get(\n+                url, headers=self.json_header, verify=False)\n+            self.logger.info(\"State: %s\" % (response.json()[\"state\"]))\n+            if response.json()[\"state\"] == 'SUCCESS':\n+                return\n+            timeout = timeout - 1\n+\n+    def create_vports(self, portList=None, rawTrafficVport=True):\n+        \"\"\"\n+        This creates virtual ports based on a portList.\n+        portList:  Pass in a list of ports in the format of ixChassisIp, slotNumber, portNumber\n+          portList = [[ixChassisIp, '1', '1'],\n+                      [ixChassisIp, '2', '1']]\n+        rawTrafficVport = For raw Traffic Item src/dest endpoints, vports must be in format:\n+                               /api/v1/sessions1/vport/{id}/protocols\n+        Next step is to call assign_port.\n+        Return: A list of vports\n+        \"\"\"\n+        createdVportList = []\n+        for index in range(0, len(portList)):\n+            url = \"{0}/ixnetwork/vport\".format(self.session_url)\n+\n+            card = portList[index][1]\n+            port = portList[index][2]\n+            portNumber = str(card) + '/' + str(port)\n+            self.logger.info('Name: %s' % portNumber)\n+            data = {'name': portNumber}\n+            response = self.session.post(\n+                url, data=json.dumps(data), headers=self.json_header, verify=False)\n+            vportObj = response.json()['links'][0]['href']\n+            self.logger.info('createVports: %s' % vportObj)\n+            if rawTrafficVport:\n+                createdVportList.append(vportObj + '/protocols')\n+            else:\n+                createdVportList.append(vportObj)\n+\n+        if createdVportList == []:\n+            raise Exception('No vports created')\n+\n+        self.logger.info('createVports: %s' % createdVportList)\n+        return createdVportList\n+\n+    def assign_ports(self, portList, createVports=True, rawTraffic=True, timeout=90):\n+        \"\"\"\n+        Description\n+            Use this to assign physical ports to the virtual ports.\n+\n+        Parameters\n+            portList: [ [ixChassisIp, '1','1'], [ixChassisIp, '1','2'] ]\n+            vportList: list return by create_vports.\n+            timeout: Timeout for port up.\n+\n+        Syntaxes\n+            POST: http://{apiServerIp:port}/api/v1/sessions/{id}/ixnetwork/operations/assignports\n+                  data={arg1: [{arg1: ixChassisIp, arg2: 1, arg3: 1}, {arg1: ixChassisIp, arg2: 1, arg3: 2}],\n+                        arg2: [],\n+                        arg3: ['/api/v1/sessions/{1}/ixnetwork/vport/1',\n+                               '/api/v1/sessions/{1}/ixnetwork/vport/2'],\n+                        arg4: true}  <-- True will clear port ownership\n+                  headers={'content-type': 'application/json'}\n+            GET:  http://{apiServerIp:port}/api/v1/sessions/{id}/ixnetwork/operations/assignports/1\n+                  data={}\n+                  headers={}\n+            Expecting:   RESPONSE:  SUCCESS\n+        \"\"\"\n+        if createVports:\n+            vportList = self.create_vports(portList, rawTrafficVport=False)\n+        url = \"{0}/ixnetwork/operations/assignports\".format(self.session_url)\n+        data = {\"arg1\": [], \"arg2\": [], \"arg3\": vportList, \"arg4\": \"true\"}\n+        [data[\"arg1\"].append({\"arg1\": str(chassis), \"arg2\": str(\n+            card), \"arg3\": str(port)}) for chassis, card, port in portList]\n+        response = self.session.post(\n+            url, data=json.dumps(data), headers=self.json_header, verify=False)\n+        self.logger.info('%s' % response.json())\n+        url = \"{0}/{1}\".format(url, response.json()['id'])\n+        self.wait_for_complete(response, url)\n+\n+        for vport in vportList:\n+            url = \"{0}{1}/l1Config\".format(self.api_server, vport)\n+            response = self.session.get(\n+                url, headers=self.json_header, verify=False)\n+            url = url + '/' + response.json()['currentType']\n+            data = {\"enabledFlowControl\": False}\n+            response = self.session.patch(\n+                url, data=json.dumps(data), headers=self.json_header, verify=False)\n+\n+        if rawTraffic:\n+            vportList_protocol = []\n+            for vport in vportList:\n+                vportList_protocol.append(vport + '/protocols')\n+            self.logger.info('vports: %s' % vportList_protocol)\n+            return vportList_protocol\n+        else:\n+            self.logger.info('vports: %s' % vportList)\n+            return vportList\n+\n+    def destroy_assign_ports(self, vportList):\n+        msg = \"release {}\".format(vportList)\n+        self.logger.info(msg)\n+        for vport_url in vportList:\n+            url = self.api_server + \"/\".join(vport_url.split(\"/\")[:-1])\n+            self.destroy_config(url)\n+\n+    def config_config_elements(self, config_element_obj, config_elements):\n+        \"\"\"\n+        Parameters\n+        config_element_obj: /api/v1/sessions/1/ixnetwork/traffic/trafficItem/{id}/configElement/{id}\n+        \"\"\"\n+        url = self.api_server + config_element_obj + '/transmissionControl'\n+        if 'transmissionType' in config_elements:\n+            data = {'type': config_elements['transmissionType']}\n+            self.session.patch(\n+                url, data=json.dumps(data), headers=self.json_header, verify=False)\n+\n+        if 'burstPacketCount' in config_elements:\n+            data = {\n+                'burstPacketCount': int(config_elements['burstPacketCount'])}\n+            self.session.patch(\n+                url, data=json.dumps(data), headers=self.json_header, verify=False)\n+\n+        if 'frameCount' in config_elements:\n+            data = {'frameCount': int(config_elements['frameCount'])}\n+            self.session.patch(\n+                url, data=json.dumps(data), headers=self.json_header, verify=False)\n+\n+        if 'duration' in config_elements:\n+            data = {'duration': int(config_elements['duration'])}\n+            self.session.patch(\n+                url, data=json.dumps(data), headers=self.json_header, verify=False)\n+\n+        url = self.api_server + config_element_obj + '/frameRate'\n+        if 'frameRate' in config_elements:\n+            data = {'rate': int(config_elements['frameRate'])}\n+            self.session.patch(\n+                url, data=json.dumps(data), headers=self.json_header, verify=False)\n+\n+        if 'frameRateType' in config_elements:\n+            data = {'type': config_elements['frameRateType']}\n+            self.session.patch(\n+                url, data=json.dumps(data), headers=self.json_header, verify=False)\n+\n+        url = self.api_server + config_element_obj + '/frameSize'\n+        if 'frameSize' in config_elements:\n+            data = {'fixedSize': int(config_elements['frameSize'])}\n+            self.session.patch(\n+                url, data=json.dumps(data), headers=self.json_header, verify=False)\n+\n+    def import_json_config_obj(self, data_obj):\n+        \"\"\"\n+        Parameter\n+            data_obj: The JSON config object.\n+        Note\n+            arg2 value must be a string of JSON data: '{\"xpath\": \"/traffic/trafficItem[1]\", \"enabled\": false}'\n+        \"\"\"\n+        data = {\"arg1\": \"/api/v1/sessions/1/ixnetwork/resourceManager\",\n+                \"arg2\": json.dumps(data_obj),\n+                \"arg3\": False}\n+        url = \"{0}/ixnetwork/resourceManager/operations/importconfig\".format(\n+            self.session_url)\n+        response = self.session.post(\n+            url, data=json.dumps(data), headers=self.json_header, verify=False)\n+        url = \"{0}/{1}\".format(url, response.json()['id'])\n+        self.wait_for_complete(response, url)\n+\n+    def send_rfc2544_throughput(self, options):\n+        \"\"\"Send traffic per RFC2544 throughput test specifications.\n+        Send packets at a variable rate, using ``traffic_list`` configuration,\n+        until minimum rate at which no packet loss is detected is found.\n+        \"\"\"\n+        # new added parameters\n+        duration = options.get('duration') or 10\n+        initialBinaryLoadRate = max_rate = options.get('max_rate') or 100.0\n+        min_rate = options.get('min_rate') or 0.0\n+        accuracy = options.get('accuracy') or 0.001\n+        permit_loss_rate = options.get('pdr') or 0.0\n+        # old parameters\n+        traffic_list = options.get('traffic_list')\n+        if traffic_list is None:\n+            raise Exception('traffic_list is empty.')\n+\n+        # close port mismatched statistics\n+        self.disable_port_misdirected()\n+\n+        url = \"{0}/ixnetwork/traffic/trafficItem\".format(self.session_url)\n+        response = self.session.get(\n+            url, headers=self.json_header, verify=False)\n+        if response.json() != []:\n+            for item in response.json():\n+                url = \"{0}{1}\".format(\n+                    self.api_server, item['links'][0]['href'])\n+                response = self.destroy_config(url)\n+                if response.status_code != 200:\n+                    raise Exception(\"remove trafficitem failed\")\n+\n+        trafficitem_list = []\n+        index = 0\n+        for traffic in traffic_list:\n+            index = index + 1\n+            # create trafficitem\n+            url = \"{0}/ixnetwork/traffic/trafficItem\".format(self.session_url)\n+            data = {\"name\": \"Traffic Item \" + str(index), \"trafficType\": \"raw\"}\n+            response = self.session.post(\n+                url, data=json.dumps(data), headers=self.json_header, verify=False)\n+            trafficitem_obj = response.json()['links'][0]['href']\n+            self.logger.info('create traffic item: %s' % trafficitem_obj)\n+            trafficitem_list.append(trafficitem_obj)\n+            # create endpointset\n+            url = \"{0}{1}/endpointSet\".format(self.api_server, trafficitem_obj)\n+            data = {\n+                \"sources\": [traffic[0]],\n+                \"destinations\": [traffic[1]]\n+            }\n+            response = self.session.post(\n+                url, data=json.dumps(data), headers=self.json_header, verify=False)\n+            # packet config\n+            config_stack_obj = eval(\n+                str(traffic[2]).replace('trafficItem[1]', 'trafficItem[' + str(index) + ']'))\n+            self.import_json_config_obj(config_stack_obj)\n+            # get framesize\n+            url = \"{0}{1}/configElement/1/frameSize\".format(\n+                self.api_server, trafficitem_obj)\n+            response = self.session.get(\n+                url, headers=self.json_header, verify=False)\n+            frame_size = response.json()['fixedSize']\n+\n+        self.regenerate_trafficitems(trafficitem_list)\n+\n+        # query existing quick test\n+        url = \"{0}/ixnetwork/quickTest/rfc2544throughput\".format(\n+            self.session_url)\n+        response = self.session.get(\n+            url, headers=self.json_header, verify=False)\n+        if response.json() != []:\n+            for qt in response.json():\n+                url = \"{0}{1}\".format(self.api_server, qt['links'][0]['href'])\n+                response = self.destroy_config(url)\n+                if response.status_code != 200:\n+                    raise Exception(\"remove quick test failed\")\n+        # create quick test\n+        url = \"{0}/ixnetwork/quickTest/rfc2544throughput\".format(\n+            self.session_url)\n+        data = [{\"name\": \"QuickTest1\", \"mode\": \"existingMode\"}]\n+        response = self.session.post(\n+            url, data=json.dumps(data), headers=self.json_header, verify=False)\n+        quicktest_obj = response.json()['links'][0]['href']\n+        self.logger.info('create quick test: %s' % quicktest_obj)\n+        # add trafficitems\n+        url = \"{0}{1}/trafficSelection\".format(self.api_server, quicktest_obj)\n+        data = [{\"__id__\": item_obj} for item_obj in trafficitem_list]\n+        response = self.session.post(\n+            url, data=json.dumps(data), headers=self.json_header, verify=False)\n+        self.logger.info(\"add traffic item status: %s\" % response.content)\n+        # modify quick test config\n+        url = \"{0}{1}/testConfig\".format(self.api_server, quicktest_obj)\n+        data = {\n+            # If Enabled, The minimum size of the frame is used .\n+            \"enableMinFrameSize\": True,\n+            # This attribute is the frame size mode for the Quad Gaussian.\n+            # Possible values includes:\n+            \"frameSizeMode\": \"custom\",\n+            # The list of the available frame size.\n+            \"framesizeList\": [str(frame_size)],\n+            # The minimum delay between successive packets.\n+            \"txDelay\": 5,\n+            # Specifies the amount of delay after every transmit\n+            \"delayAfterTransmit\": 5,\n+            # sec\n+            \"duration\": duration,\n+            # The initial binary value of the load rate\n+            \"initialBinaryLoadRate\": initialBinaryLoadRate,\n+            # The upper bound of the iteration rates for each frame size during\n+            # a binary search\n+            \"maxBinaryLoadRate\": max_rate,\n+            # Specifies the minimum rate of the binary algorithm.\n+            \"minBinaryLoadRate\": min_rate,\n+            # The frame loss unit for traffic in binary.\n+            # Specifies the resolution of the iteration. The difference between\n+            # the real rate transmission in two consecutive iterations, expressed\n+            # as a percentage, is compared with the resolution value. When the\n+            # difference is smaller than the value specified for the\n+            # resolution, the test stops .\n+            \"resolution\": accuracy * 100,\n+            # The load unit value in binary.\n+            \"binaryFrameLossUnit\": \"%\",\n+            # The binary tolerance level.\n+            \"binaryTolerance\": permit_loss_rate,\n+        }\n+        response = self.session.patch(\n+            url, data=json.dumps(data), headers=self.json_header, verify=False)\n+        if response.status_code != 200:\n+            raise Exception(\"change quick test config failed\")\n+        # run the quick test\n+        url = \"{0}{1}/operations/run\".format(self.api_server, quicktest_obj)\n+        data = {\"arg1\": quicktest_obj, \"arg2\": \"\"}\n+        response = self.session.post(\n+            url, data=json.dumps(data), headers=self.json_header, verify=False)\n+        url = url + '/' + response.json()['id']\n+        state = response.json()[\"state\"]\n+        self.logger.info(\"Quicktest State: %s\" % state)\n+        while state == \"IN_PROGRESS\":\n+            response = self.session.get(\n+                url, headers=self.json_header, verify=False)\n+            state = response.json()[\"state\"]\n+            self.logger.info(\"Quicktest State: %s\" % state)\n+            time.sleep(5)\n+\n+        timestamp = datetime.now().strftime(\"%Y%m%d_%H%M%S\")\n+        copy_to_path = os.sep.join([\n+            self.OUTPUT_DIR,\n+            'ixnet' + datetime.now().strftime(\"%Y%m%d_%H%M%S\")])\n+        if not os.path.exists(copy_to_path):\n+            os.makedirs(copy_to_path)\n+        self.get_quicktest_csvfiles(quicktest_obj, copy_to_path, csvfile='all')\n+        qt_result_csv = \"{0}/AggregateResults.csv\".format(copy_to_path)\n+        return self.parse_quicktest_results(qt_result_csv)\n+\n+    def parse_quicktest_results(self, path_file):\n+        \"\"\" parse csv filte and return quicktest result \"\"\"\n+        results = OrderedDict()\n+\n+        if not os.path.exists(path_file):\n+            msg = \"failed to get result file from windows api server\"\n+            self.logger.error(msg)\n+            return results\n+\n+        ret_result = []\n+        with open(path_file, \"r\") as f:\n+            qt_result = csv.DictReader(f)\n+            for row in qt_result:\n+                ret_result.append(row)\n+                results['framesize'] = row['Framesize']\n+                results['throughput'] = row['Agg Rx Throughput (fps)']\n+                results['linerate%'] = row['Agg Rx Throughput (% Line Rate)']\n+                results['min_latency'] = row['Min Latency (ns)']\n+                results['max_latency'] = row['Max Latency (ns)']\n+                results['avg_latency'] = row['Avg Latency (ns)']\n+\n+        return ret_result\n+\n+    def get_quicktest_resultpath(self, quicktest_obj):\n+        \"\"\"\n+        quicktest_obj = /api/v1/sessions/1/ixnetwork/quickTest/rfc2544throughput/2\n+        \"\"\"\n+        url = \"{0}{1}/results\".format(self.api_server, quicktest_obj)\n+        response = self.session.get(\n+            url, headers=self.json_header, verify=False)\n+        return response.json()['resultPath']\n+\n+    def get_quicktest_csvfiles(self, quicktest_obj, copy_to_path, csvfile='all'):\n+        \"\"\"\n+        Description\n+            Copy Quick Test CSV result files to a specified path on either Windows or Linux.\n+            Note: Currently only supports copying from Windows.\n+        quicktest_obj: The Quick Test handle.\n+        copy_to_path: The destination path to copy to.\n+                    If copy to Windows: c:\\\\Results\\\\Path\n+                    If copy to Linux: /home/user1/results/path\n+        csvfile: A list of CSV files to get: 'all', one or more CSV files to get:\n+                 AggregateResults.csv, iteration.csv, results.csv, logFile.txt, portMap.csv\n+        \"\"\"\n+        results_path = self.get_quicktest_resultpath(quicktest_obj)\n+        self.logger.info('get_quickTest_csvfiles: %s' % results_path)\n+        if csvfile == 'all':\n+            get_csv_files = [\n+                'AggregateResults.csv', 'iteration.csv', 'results.csv', 'logFile.txt', 'portMap.csv']\n+        else:\n+            if type(csvfile) is not list:\n+                get_csv_files = [csvfile]\n+            else:\n+                get_csv_files = csvfile\n+\n+        for each_csvfile in get_csv_files:\n+            # Backslash indicates the results resides on a Windows OS.\n+            if '\\\\' in results_path:\n+                cnt = 0\n+                while cnt < 5:\n+                    try:\n+                        self.copyfile_windows2linux(\n+                            results_path + '\\\\{0}'.format(each_csvfile), copy_to_path)\n+                        break\n+                    except Exception as e:\n+                        time.sleep(5)\n+                        cnt += 1\n+                        msg = \"No.{} retry to get result from windows\".format(cnt)\n+                        self.logger.warning(msg)\n+                        continue\n+            else:\n+                # TODO:Copy from Linux to Windows and Linux to Linux.\n+                pass\n+\n+    def copyfile_windows2linux(self, winPathFile, linuxPath, includeTimestamp=False):\n+        \"\"\"\n+        Description\n+            Copy files from the IxNetwork API Server c: drive to local Linux filesystem.\n+            You could also include a timestamp for the destination file.\n+        Parameters\n+            winPathFile: (str): The full path and filename to retrieve from Windows client.\n+            linuxPath: (str): The Linux destination path to put the file to.\n+            includeTimestamp: (bool):  If False, each time you copy the same file will be overwritten.\n+        Syntax\n+            post: /api/v1/sessions/1/ixnetwork/operations/copyfile\n+            data: {'arg1': winPathFile, 'arg2': '/api/v1/sessions/1/ixnetwork/files/'+fileName'}\n+        \"\"\"\n+        self.logger.info('copyfile From: %s to %s' % (winPathFile, linuxPath))\n+        fileName = winPathFile.split('\\\\')[-1]\n+        fileName = fileName.replace(' ', '_')\n+        destinationPath = '/api/v1/sessions/1/ixnetwork/files/' + fileName\n+        currentTimestamp = datetime.now().strftime('%H%M%S')\n+\n+        # Step 1 of 2:\n+        url = \"{0}/ixnetwork/operations/copyfile\".format(self.session_url)\n+        data = {\"arg1\": winPathFile, \"arg2\": destinationPath}\n+        response = self.session.post(\n+            url, data=json.dumps(data), headers=self.json_header, verify=False)\n+\n+        # Step 2 of 2:\n+        url = \"{0}/ixnetwork/files/{1}\".format(self.session_url, fileName)\n+        requestStatus = self.session.get(\n+            url, stream=True, headers=self.json_header, verify=False)\n+        if requestStatus.status_code == 200:\n+            contents = requestStatus.raw.read()\n+\n+            if includeTimestamp:\n+                tempFileName = fileName.split('.')\n+                if len(tempFileName) > 1:\n+                    extension = fileName.split('.')[-1]\n+                    fileName = tempFileName[0] + '_' + currentTimestamp + '.' + extension\n+                else:\n+                    fileName = tempFileName[0] + '_' + currentTimestamp\n+\n+                linuxPath = linuxPath + '/' + fileName\n+            else:\n+                linuxPath = linuxPath + '/' + fileName\n+\n+            with open(linuxPath, 'wb') as downloadedFileContents:\n+                downloadedFileContents.write(contents)\n+\n+            url = \"{0}/ixnetwork/files\".format(self.session_url)\n+            response = self.session.get(\n+                url, headers=self.json_header, verify=False)\n+            self.logger.info('A copy of saved file is in: %s' % (winPathFile))\n+            self.logger.info(\n+                'copyfile_windows2linux: The copyfile is in %s' % linuxPath)\n+        else:\n+            raise Exception(\n+                \"copyfile_windows2linux: Failed to download file from IxNetwork API Server.\")\n+\n+    def tear_down(self):\n+        \"\"\"do needed clean up\"\"\"\n+        self.destroy_assign_ports(self.tg_vports)\n+        self.session.close()\n",
    "prefixes": [
        "V1",
        "11/27"
    ]
}