Add lispflowmapping performance tests 30/26930/8
authorLorand Jakab <lojakab@cisco.com>
Thu, 1 Oct 2015 14:44:42 +0000 (17:44 +0300)
committerGerrit Code Review <gerrit@opendaylight.org>
Tue, 6 Oct 2015 23:58:55 +0000 (23:58 +0000)
Add test to detect regressions in mapping resolution performance of the
Mapping Service when used through the LISP Southbound Plugin.

First, add a script (loosely based on Jan Medved's
flow_config_blaster.py) to create 10.000 simple IPv4-to-IPv4 mappings to
ODL through RPCs.

Second and a script to generate a .pcap file containing 10.000
Map-Requests for each of those mappings in a randomized order. The .pcap
file can then be replayed at arbitrary speeds.

Third, use `udpreplay` to send the prerecorded lookup requests in the
.pcap file at a rate of 100.000 packets/second for 1000 times for a
total of 100 seconds.

Finally, compute the reply rate by reading the Tx packet counter from
the southbound statisitics using RPC and dividing it by the number of
seconds the packet generater was active.

Change-Id: Ib4de1782aa32b7e010f22e12fbd04b2f53579fb1
Signed-off-by: Lorand Jakab <lojakab@cisco.com>
csit/libraries/Utils.robot
csit/suites/lispflowmapping/performance/010_Southbound_MapRequest.robot [new file with mode: 0644]
csit/variables/Variables.py
tools/odl-lispflowmapping-performance-tests/create_map_request_pcap.py [new file with mode: 0755]
tools/odl-lispflowmapping-performance-tests/mapping_blaster.py [new file with mode: 0755]

index 04f1ca6e6b719a85c377f0286791b2e27d32e0ae..dd7efed84279cfa3a5626a2f0d751bbb67b16dcb 100644 (file)
@@ -2,6 +2,7 @@
 Library           SSHLibrary
 Library           String
 Library           DateTime
+Library           Process
 Library           ./UtilLibrary.py
 Resource          KarafKeywords.robot
 Variables           ../variables/Variables.py
@@ -304,3 +305,12 @@ Post Elements To URI From File
     ${body}    OperatingSystem.Get File    ${data_file}
     ${resp}    RequestsLibrary.Post    session    ${dest_uri}    data=${body}    headers=${headers}
     Should Be Equal As Strings    ${resp.status_code}    200
+
+Run Process With Logging And Status Check
+    [Arguments]    @{proc_args}
+    [Documentation]    Execute an OS command, log STDOUT and STDERR output and check exit code to be 0
+    ${result}=    Run Process    @{proc_args}
+    Log    ${result.stdout}
+    Log    ${result.stderr}
+    Should Be Equal As Integers    ${result.rc}    0
+    [Return]    ${result}
diff --git a/csit/suites/lispflowmapping/performance/010_Southbound_MapRequest.robot b/csit/suites/lispflowmapping/performance/010_Southbound_MapRequest.robot
new file mode 100644 (file)
index 0000000..ac512da
--- /dev/null
@@ -0,0 +1,70 @@
+*** Settings ***
+Documentation     Test suite to determine the southbound Map-Request serving rate
+Suite Setup       Prepare Environment
+Suite Teardown    Destroy Environment
+Library           Collections
+Library           OperatingSystem
+Library           RequestsLibrary
+Library           String
+Resource          ../../../libraries/Utils.robot
+Variables         ../../../variables/Variables.py
+
+*** Variables ***
+${MAPPINGS}=          10000
+${LISP_SCAPY}         https://raw.githubusercontent.com/intouch/py-lispnetworking/master/lisp.py
+${TOOLS_DIR}          ${CURDIR}/../../../../tools/odl-lispflowmapping-performance-tests/
+${PCAP_CREATOR}       ${TOOLS_DIR}/create_map_request_pcap.py
+${MAPPING_BLASTER}    ${TOOLS_DIR}/mapping_blaster.py
+${REPLAY_PPS}         100000
+${REPLAY_CNT}         1000
+${REPLAY_FILE}        ${CURDIR}/encapsulated-map-requests-sequential.pcap
+${RESULTS_FILE}       pps.csv
+
+*** Test Cases ***
+Add Simple IPv4 Mappings
+    Run Process With Logging And Status Check    ${MAPPING_BLASTER}    --host    ${CONTROLLER}    --mappings    ${MAPPINGS}
+
+Generate Test Traffic
+    Reset Stats
+    ${result}=    Run Process With Logging And Status Check    /usr/local/bin/udpreplay    --pps    ${REPLAY_PPS}    --repeat    ${REPLAY_CNT}    --port    4342    ${REPLAY_FILE}
+    ${partial}=    Fetch From Left    ${result.stdout}    s =
+    Log    ${partial}
+    ${seconds}=    Fetch From Right    ${partial}    ${SPACE}
+    ${seconds}=    Convert To Number    ${seconds}
+    Log    ${seconds}
+    Set Suite Variable    ${seconds}
+
+Compute And Export MapReply Rate
+    ${txmrep}=    Get Transmitted Map-Requests Stats
+    ${pps}=    Evaluate    ${txmrep}/${seconds}
+    Log    ${pps}
+    Create File    ${RESULTS_FILE}    replies/s\n
+    Append To File    ${RESULTS_FILE}    ${pps}\n
+
+*** Keywords ***
+Reset Stats
+    ${resp}=    RequestsLibrary.Post    session    ${LFM_SB_RPC_API}:reset-stats
+    Log    ${resp.content}
+    Should Be Equal As Strings    ${resp.status_code}    200
+
+Get Transmitted Map-Requests Stats
+    ${resp}=    RequestsLibrary.Post    session    ${LFM_SB_RPC_API}:get-stats
+    Log    ${resp.content}
+    ${output}=     Get From Dictionary    ${resp.json()}    output
+    ${stats}=      Get From Dictionary    ${output}         control-message-stats
+    ${ctrlmsg}=    Get From Dictionary    ${stats}          control-message
+    ${replies}=    Get From List          ${ctrlmsg}        2
+    ${txmrep}=     Get From Dictionary    ${replies}        tx-count
+    ${txmrep}=     Convert To Integer     ${txmrep}
+    Log    ${txmrep}
+    [Return]    ${txmrep}
+
+Prepare Environment
+    Create Session    session    http://${CONTROLLER}:${RESTCONFPORT}    auth=${AUTH}    headers=${HEADERS}
+    Run Process With Logging And Status Check    wget    -P    ${TOOLS_DIR}    ${LISP_SCAPY}
+    Run Process With Logging And Status Check    ${PCAP_CREATOR}    --requests    ${MAPPINGS}
+
+Destroy Environment
+    Delete All Sessions
+    Remove File    ${TOOLS_DIR}/lisp.py*
+    Remove File    ${REPLAY_FILE}
index bc5273851243bd6256a3954cc344bbe2bb144bd7..f0ba059e7dca97c8302c67700a74ccc9fec8e19a 100644 (file)
@@ -134,3 +134,4 @@ GBP_TUNNELS_API = "/restconf/config/opendaylight-inventory:nodes"
 # LISP Flow Mapping variables
 LFM_RPC_API = "/restconf/operations/mappingservice"
 LFM_RPC_API_LI = "/restconf/operations/lfm-mapping-database"
+LFM_SB_RPC_API = "/restconf/operations/lisp-sb"
diff --git a/tools/odl-lispflowmapping-performance-tests/create_map_request_pcap.py b/tools/odl-lispflowmapping-performance-tests/create_map_request_pcap.py
new file mode 100755 (executable)
index 0000000..3e099fb
--- /dev/null
@@ -0,0 +1,129 @@
+#!/usr/bin/env python
+"""
+Script to generate a pcap file with n Map-Request packets with EID records
+increasing sequentially and (optionally) another pcap file with n Map-Request
+packets that have random EIDs
+
+Use `./create_map_request_pcap.py --help` to see options
+"""
+
+import argparse
+import netaddr
+import lisp
+import random
+
+__author__ = "Lori Jakab"
+__copyright__ = "Copyright (c) 2015, Cisco Systems, Inc."
+__license__ = "New-style BSD"
+__email__ = "lojakab@cisco.com"
+__version__ = "0.0.2"
+
+
+def generate_eids_random(base, n):
+    """Generate n number of EIDs in random order starting from base
+    Args:
+        :param base: A string used as the first EID
+        :param n: An integer, number of EIDs to generate
+    Returns:
+        :return : returns list containing string EIDs
+    """
+    eids = []
+    for i in range(0, n):
+        eids.append(str(netaddr.IPAddress(base) +
+                        random.randint(0, (n-1)*increment)))
+    return eids
+
+
+def generate_eids_sequential(base, n):
+    """Generate n number of EIDs in sequential order starting from base
+    Args:
+        :param base: A string used as the first EID
+        :param n: An integer, number of EIDs to generate
+    Returns:
+        :return : returns list containing string EIDs
+    """
+    eids = []
+    for i in range(0, n):
+        eids.append(str(netaddr.IPAddress(base) + i*increment))
+    return eids
+
+
+def generate_map_request(eid):
+    """Create a LISP Map-Request packet as a Scapy object
+    Args:
+        :param eid: A string used as the EID record
+    Returns:
+        :return : returns a Scapy Map-Request packet object
+    """
+    sport1 = random.randint(60000, 65000)
+    sport2 = random.randint(60000, 65000)
+    rnonce = random.randint(0, 2**63)
+
+    itr_rloc = [lisp.LISP_AFI_Address(address=src_rloc, afi=1)]
+    record = [lisp.LISP_MapRequestRecord(request_address=eid,
+                                         request_afi=1,
+                                         eid_mask_len=32)]
+
+    packet = lisp.Ether(dst=dst_mac, src=src_mac)
+    packet /= lisp.IP(dst=dst_rloc, src=src_rloc)
+    packet /= lisp.UDP(sport=sport1, dport=4342)
+    packet /= lisp.LISP_Encapsulated_Control_Message(ptype=8)
+    packet /= lisp.IP(dst=eid, src=src_eid)
+    packet /= lisp.UDP(sport=sport2, dport=4342)
+    packet /= lisp.LISP_MapRequest(nonce=rnonce, request_afi=1,
+                                   address=src_eid, ptype=1,
+                                   itr_rloc_records=itr_rloc,
+                                   request_records=record)
+    return packet
+
+parser = argparse.ArgumentParser(description='Create a Map-Request trace file')
+
+parser.add_argument('--dst-mac', default='00:00:00:00:00:00',
+                    help='Map-Request destination MAC address \
+                        (default is 00:00:00:00:00:00)')
+parser.add_argument('--src-mac', default='00:00:00:00:00:00',
+                    help='Map-Request source MAC address \
+                        (default is 00:00:00:00:00:00)')
+parser.add_argument('--dst-rloc', default='127.0.0.1',
+                    help='Send Map-Request to the Map-Server with this RLOC \
+                        (default is 127.0.0.1)')
+parser.add_argument('--src-rloc', default='127.0.0.1',
+                    help='Send Map-Request with this source RLOC \
+                        (default is 127.0.0.1)')
+parser.add_argument('--src-eid', default='192.0.2.1',
+                    help='Send Map-Request with this source EID \
+                        (default is 192.0.2.1)')
+parser.add_argument('--base-eid', default='10.0.0.0',
+                    help='Start incrementing EID from this address \
+                        (default is 10.0.0.0)')
+parser.add_argument('--requests', type=int, default=1,
+                    help='Number of requests to create (default 1)')
+parser.add_argument('--increment', type=int, default=1,
+                    help='Increment EID requests (default 1)')
+parser.add_argument('--random', type=bool, default=False,
+                    help='Create random EID requests (default False)')
+
+in_args = parser.parse_args()
+dst_mac = in_args.dst_mac
+src_mac = in_args.src_mac
+dst_rloc = in_args.dst_rloc
+src_rloc = in_args.src_rloc
+src_eid = in_args.src_eid
+increment = in_args.increment
+
+seq_eids = generate_eids_sequential(in_args.base_eid, in_args.requests)
+seq_pkts = []
+
+for eid in seq_eids:
+    seq_pkts.append(generate_map_request(eid))
+
+lisp.wrpcap("encapsulated-map-requests-sequential.pcap", seq_pkts)
+
+if in_args.random is True:
+    rand_eids = generate_eids_random(in_args.base_eid, in_args.requests)
+    rand_pkts = []
+
+    for eid in rand_eids:
+        rand_pkts.append(generate_map_request(eid))
+
+    lisp.wrpcap("encapsulated-map-requests-random.pcap", rand_pkts)
diff --git a/tools/odl-lispflowmapping-performance-tests/mapping_blaster.py b/tools/odl-lispflowmapping-performance-tests/mapping_blaster.py
new file mode 100755 (executable)
index 0000000..15fc642
--- /dev/null
@@ -0,0 +1,214 @@
+#!/usr/bin/python
+"""
+Script to add LISP mappings to ODL.  Currently it only supports the
+RPC interface.  Support for RESTCONF is planned in a future release.
+
+Use `./mapping_blaster.py --help` to see options
+
+Code inspired from `flow_config_blaster.py` by Jan Medved
+"""
+
+import argparse
+import copy
+import json
+
+import netaddr
+import requests
+
+__author__ = "Lori Jakab"
+__copyright__ = "Copyright (c) 2014, Cisco Systems, Inc."
+__credits__ = ["Jan Medved"]
+__license__ = "New-style BSD"
+__email__ = "lojakab@cisco.com"
+__version__ = "0.0.3"
+
+
+class MappingRPCBlaster(object):
+    putheaders = {'Content-type': 'application/json'}
+    getheaders = {'Accept': 'application/json'}
+
+    RPC_URL_LI = 'restconf/operations/lfm-mapping-database:'
+    RPC_URL_BE = 'restconf/operations/mappingservice:'
+    TIMEOUT = 10
+
+    # Template for adding mappings
+    add_mapping_template = {
+        u'input': {
+            u'recordTtl': 60,
+            u'maskLength': 32,
+            u'authoritative': True,
+            u'action': u'NoAction',
+            u'LispAddressContainer': {
+                u'Ipv4Address': {
+                    u'afi': 1,
+                    u'Ipv4Address': u'10.0.0.0'
+                }
+            },
+            u'LocatorRecord': [
+                {
+                    u'name': u'ipv4:172.16.0.0',
+                    u'priority': 1,
+                    u'weight': 1,
+                    u'multicastPriority': 255,
+                    u'multicastWeight': 0,
+                    u'localLocator': True,
+                    u'rlocProbed': False,
+                    u'routed': True,
+                    u'LispAddressContainer': {
+                        u'Ipv4Address': {
+                            u'afi': 1,
+                            u'Ipv4Address': u'172.16.0.0'
+                        }
+                    }
+                }
+            ]
+        }
+    }
+
+    # Template for getting mappings
+    get_mapping_template = {
+        u'input': {
+            u'LispAddressContainer': {
+                u'Ipv4Address': {
+                    u'afi': 1,
+                    u'Ipv4Address': u'10.0.0.0'
+                }
+            },
+            u'mask-length': 32
+        }
+    }
+
+    def __init__(self, host, port, start_eid, mask, start_rloc, nmappings, v):
+        """
+        Args:
+            :param host: The host running ODL where we want to send the RPCs
+            :param port: The RESTCONF port on the ODL host
+            :param start_eid: The starting EID for adding mappings as an IPv4
+                literal
+            :param mask: The network mask for the EID prefixes to be added
+            :param start_rloc: The starting RLOC for the locators in the
+                mappings as an IPv4 literal
+            :param nmappings: The number of mappings to be generated
+            :param v: Version of the ODL instance (since the RPC URL changed
+                from Lithium to Beryllium
+        """
+        self.session = requests.Session()
+        self.host = host
+        self.port = port
+        self.start_eid = netaddr.IPAddress(start_eid)
+        self.mask = mask
+        self.start_rloc = netaddr.IPAddress(start_rloc)
+        self.nmappings = nmappings
+        if v == "Li" or v == "li":
+            print "Using the Lithium RPC URL"
+            rpc_url = self.RPC_URL_LI
+        else:
+            print "Using the Beryllium and later RPC URL"
+            rpc_url = self.RPC_URL_BE
+
+        self.post_url_template = 'http://' + self.host + ':' \
+            + self.port + '/' + rpc_url
+
+    def mapping_from_tpl(self, eid, mask, rloc):
+        """Create an add-mapping RPC input dictionary from the mapping template
+        Args:
+            :param eid: Replace the default EID in the template with this one
+            :param mask: Replace the default mask in the template with this one
+            :param rloc: Replace the default RLOC in the template with this one
+        Returns:
+            :return dict: mapping - template modified with the arguments
+        """
+        mapping = copy.deepcopy(self.add_mapping_template['input'])
+        mapping['maskLength'] = mask
+        mapping['LispAddressContainer']['Ipv4Address']['Ipv4Address'] \
+            = str(netaddr.IPAddress(eid))
+        mapping['LocatorRecord'][0]['name'] = 'ipv4:' \
+            + str(netaddr.IPAddress(rloc))
+        address_container = mapping['LocatorRecord'][0]['LispAddressContainer']
+        address_container['Ipv4Address']['Ipv4Address'] \
+            = str(netaddr.IPAddress(rloc))
+        return mapping
+
+    def send_rpc(self, session, method, body):
+        """Send an HTTP POST to the RPC URL and return the status code
+        Args:
+            :param session: The HTTP session handle
+            :param method: "add" or "del"ete mapping
+            :param body: the JSON body to be sent
+        Returns:
+            :return int: status_code - HTTP status code
+        """
+        rpc_url = self.post_url_template + method
+        r = session.post(rpc_url, data=body, headers=self.putheaders,
+                         stream=False, auth=('admin', 'admin'),
+                         timeout=self.TIMEOUT)
+        return r.status_code
+
+    def add_n_mappings(self):
+        """Add self.nmappings mappings to ODL"""
+        rpc = dict(self.add_mapping_template)
+        increment = pow(2, 32 - int(self.mask))
+        for i in range(self.nmappings):
+            rpc['input'] = self.mapping_from_tpl(self.start_eid + i *
+                                                 increment, self.mask,
+                                                 self.start_rloc + i)
+            rpc_json = json.dumps(rpc)
+            self.send_rpc(self.session, 'add-mapping', rpc_json)
+        self.session.close()
+
+    def get_n_mappings(self):
+        """Retrieve self.nmappings mappings from ODL
+        """
+        rpc = dict(self.get_mapping_template)
+        increment = pow(2, 32 - int(self.mask))
+        for i in range(self.nmappings):
+            eid = self.start_eid + i * increment
+            rpc['input']['LispAddressContainer']['Ipv4Address']['Ipv4Address']\
+                = str(netaddr.IPAddress(eid))
+            rpc['input']['mask-length'] = self.mask
+            rpc_json = json.dumps(rpc)
+            self.send_rpc(self.session, 'get-mapping', rpc_json)
+        self.session.close()
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(description='Add simple IPv4 \
+        prefix-to-IPv4 locator LISP mappings to OpenDaylight')
+
+    parser.add_argument('--mode', default='add',
+                        help='Operating mode, can be "add" or "get" \
+                            (default is "add")')
+    parser.add_argument('--host', default='127.0.0.1',
+                        help='Host where ODL controller is running (default \
+                            is 127.0.0.1)')
+    parser.add_argument('--port', default='8181',
+                        help='Port on which ODL\'s RESTCONF is listening \
+                            (default is 8181)')
+    parser.add_argument('--start-eid', default='10.0.0.0',
+                        help='Start incrementing EID from this address \
+                            (default is 10.0.0.0)')
+    parser.add_argument('--mask', default='32',
+                        help='Network mask for the IPv4 EID prefixes \
+                            (default is 32)')
+    parser.add_argument('--start-rloc', default='172.16.0.0',
+                        help='Start incrementing RLOC from this address \
+                            (default is 172.16.0.0, ignored for "get")')
+    parser.add_argument('--mappings', type=int, default=1,
+                        help='Number of mappings to add/get (default 1)')
+    parser.add_argument('--odl-version', default='Be',
+                        help='OpenDaylight version, can be "Li" or "Be" \
+                            (default is "Be")')
+
+    in_args = parser.parse_args()
+
+    mapping_rpc_blaster = MappingRPCBlaster(in_args.host, in_args.port,
+                                            in_args.start_eid, in_args.mask,
+                                            in_args.start_rloc,
+                                            in_args.mappings,
+                                            in_args.odl_version)
+
+    if in_args.mode == "add":
+        mapping_rpc_blaster.add_n_mappings()
+    elif in_args.mode == "get":
+        mapping_rpc_blaster.get_n_mappings()
+    else:
+        print "Unsupported mode:", in_args.mode