From: sandeepg Date: Tue, 19 May 2015 23:22:01 +0000 (-0700) Subject: OpenflowPlugin link scalability test X-Git-Tag: release/lithium~59 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=commitdiff_plain;h=d04c05b2f2c8ad813e4dad160a019552a9ab5325;p=integration%2Ftest.git OpenflowPlugin link scalability test This test incrementally connects switches in fully mesh topology and checks if the controller is able to discover links between them. Test exits(max switch scale value found) when there is an out of memory, null pointer exception or if expected links are unavailable in operational DB. Change-Id: Iffed85112f89efc328a9bc8a095884e39aff6d30 Signed-off-by: sandeepg --- diff --git a/test/csit/libraries/MininetTopo/create_fullymesh.py b/test/csit/libraries/MininetTopo/create_fullymesh.py new file mode 100644 index 0000000000..b1191a1746 --- /dev/null +++ b/test/csit/libraries/MininetTopo/create_fullymesh.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python + +import sys +import netaddr + +__author__ = "Sandeep Gangadharan" +__copyright__ = "(c) Copyright [2015] Hewlett-Packard \ + Development Company, L.P." +__license__ = "Eclipse Public License " +__email__ = "sandeep.gangadharan@hp.com" +__created__ = "19 March 2014" + +""" + create_fullymesh.py: + Description : Creates Fully mesh mininet topology. + Input : switch_count, host count per switch, base mac address, + base ip address + Output : switch.py (A python topology file) + Note : This is a fully mesh network. Not available in + mininet by default. Hence generating a python file + dynamically which represents the topology. + +""" + +if len(sys.argv) < 5: + print("Please povide correct inputs. Exiting!!!") + print "{0} \ + ".format(sys.argv[0].split('/')[-1]) + print "Dpid of switches is derived from base mac and \ + host ip address is derived from base ip" + sys.exit(1) + +switch_count = int(sys.argv[1]) +host_per_switch = int(sys.argv[2]) +base_mac = sys.argv[3] +base_host_ip = sys.argv[4] + +base_host_mac = base_mac.split(':') +base_host_mac[0] = '10' +base_host_mac = (':').join(base_host_mac) +dpid_mac = base_mac.split(':') +dpid_mac = ('').join(dpid_mac) + + +def new_mac(mac, offset): + """ + Description: This function increments an existing mac address by offset + and returns the new mac + :param mac: Mac address with each hex separated by a colon + (Eg: 00:01:02:03:04:05 + :param offset: Increment the mac to this offset value + :return: new mac in same format as input mac. + """ + mac = netaddr.EUI(mac).value + mac = mac + offset + mac = str(netaddr.EUI(mac)).replace('-', ':') + return mac + + +def new_ip(ip, offset): + """ + Description: This function increments an existing ip address by offset + and returns the new ip + :param ip: Ipv4 address string + :param offset: increment value of IP + :rtype : new Ipv4 address string after incrementing by offset + """ + ip = netaddr.IPAddress(ip) + return ip.__add__(offset) + + +def new_dpid(mac, offset): + """ + Description: This function is for returns a new dpid by + incrementing a mac address by offset value. + :param mac: mac address separated by colon + :param offset: increment value + :return: New dpid + """ + mac = netaddr.EUI(mac).value + mac = mac + offset + mac = str(netaddr.EUI(mac)).replace('-', ':') + dpid_mac = mac.split(':') + dpid_mac = ('').join(dpid_mac) + DPID = "0000" + dpid_mac + return DPID + + +if __name__ == "__main__": + DPID = new_dpid(base_mac, 1) + HMAC = new_mac(base_host_mac, 1) + HIP = new_ip(base_host_ip, 1) + prefix = 8 + configfile = open("switch.py", 'w') + configfile.write('\"\"\"@author: sandeep gangadharan\n \ + This topology has {0:d} switches {1:d} hosts \ + \nThis topology is made out of {2:s} script \ + \nThis is a fully mesh topology. Not available in mininet by default.\ + \nHence generating this python file dynamically\"\"\" \ + \nfrom mininet.topo import Topo\nclass DemoTopo(Topo): \ + \n'.format(switch_count, switch_count * host_per_switch, sys.argv[0])) + print "This topology has %d switches %d hosts" \ + % (switch_count, switch_count * host_per_switch) + configfile.write(" def __init__(self):\n ") + configfile.write(" # Initialize topology\n") + configfile.write(" Topo.__init__(self)\n") + configfile.write(" # Add Switches\n") + # Add switches + for i in range(1, switch_count + 1): + configfile.write(" s{0:d} = self.addSwitch(\'s{1:d}\',dpid=\'{2:s}\')\ + \n".format(i, i, DPID)) + DPID = new_dpid(base_mac, i + 1) + + # Add hosts + configfile.write(" # Add Hosts\n") + for i in range(1, switch_count + 1): + for j in range(1, host_per_switch + 1): + configfile.write(" self.addLink(s{0:d}, \ + self.addHost('s{1:d}h{2:d}',\ + ip='{3:s}',mac='{4:s}',prefixLen='{5:d}'))\n" + .format(i, i, j, HIP, HMAC, prefix)) + HMAC = new_mac(HMAC, 1) + HIP = new_ip(HIP, 1) + + # Add Links + configfile.write(" # Add Links\n") + count = 0 + for i in range(1, switch_count + 1): + if i == 1: + continue + for j in range(1, i + 1): + if i != j: + configfile.write(" self.addLink(s{0:d}, s{1:d})\ + \n".format(i, j)) + configfile.write("topos = { 'demotopo': ( lambda: DemoTopo() ) }") + configfile.close() diff --git a/test/csit/libraries/Scalability.txt b/test/csit/libraries/Scalability.txt index 08029e2d13..c7b6f63092 100644 --- a/test/csit/libraries/Scalability.txt +++ b/test/csit/libraries/Scalability.txt @@ -5,10 +5,11 @@ Library String Library Collections Variables ../variables/Variables.py Library RequestsLibrary - +Library SwitchClasses/BaseSwitch.py *** Variables *** ${linux_prompt} > + *** Keywords *** Find Max Switches [Arguments] ${start} ${stop} ${step} @@ -37,6 +38,36 @@ Find Max Switches \ ${max-switches} Convert To String ${switches} [Return] ${max-switches} +Find Max Links + [Arguments] ${begin} ${stop} ${step} + [Documentation] Will find out max switches in fully mesh topology starting from ${start} till reaching ${stop} and in steps defined by ${step} + ${max_switches} Set Variable ${0} + ${stop} Convert to Integer ${stop} + ${step} Convert to Integer ${step} + : FOR ${switches} IN RANGE ${begin} ${stop+1} ${step} + \ Start Mininet With Custom Topology ${CREATE_FULLYMESH_TOPOLOGY_FILE} ${switches} ${BASE_MAC_1} ${BASE_IP_1} ${0} ${switches*20} + \ ${status} ${result} Run Keyword And Ignore Error Verify Controller Is Not Dead ${CONTROLLER} + \ Exit For Loop If '${status}' == 'FAIL' + \ ${status} ${result} Run Keyword And Ignore Error Verify Controller Has No Null Pointer Exceptions ${CONTROLLER} + \ Exit For Loop If '${status}' == 'FAIL' + \ ${status} ${result} Run Keyword And Ignore Error Wait Until Keyword Succeeds 120 10s + \ ... Check Every Switch ${switches} ${BASE_MAC_1} + \ Exit For Loop If '${status}' == 'FAIL' + \ ${max-links}= Evaluate ${switches}*${switches-1} + \ ${status} ${result} Run Keyword And Ignore Error Wait Until Keyword Succeeds 120 10s + \ ... Check Number Of Links ${max-links} + \ Exit For Loop If '${status}' == 'FAIL' + \ Stop Mininet + \ ${status} ${result} Run Keyword And Ignore Error Wait Until Keyword Succeeds 120 10s + \ ... Check No Switches ${switches} + \ Exit For Loop If '${status}' == 'FAIL' + \ ${status} ${result} Run Keyword And Ignore Error Wait Until Keyword Succeeds 120 10s + \ ... Check No Topology ${switches} + \ Exit For Loop If '${status}' == 'FAIL' + \ ${max_switches} Set Variable ${switches} + ${max-links}= Evaluate ${max_switches}*${max_switches-1} + [Return] ${max-links} + Find Max Hosts [Arguments] ${begin} ${stop} ${step} [Documentation] Will find out max hosts starting from ${begin} till reaching ${stop} and in steps defined by ${step} @@ -97,13 +128,23 @@ Start Mininet With One Switch And ${hosts} hosts Check Number Of Hosts [Arguments] ${hosts} [Documentation] Check number of hosts in inventory - ${resp} RequestsLibrary.Get session ${OPERATIONAL_TOPO_API} + ${resp}= RequestsLibrary.Get session ${OPERATIONAL_TOPO_API} Log Check number of hosts in inventory Log To Console Check number of hosts in inventory Should Be Equal As Strings ${resp.status_code} 200 ${count}= Get Count ${resp.content} "node-id":"host: Should Be Equal As Integers ${count} ${hosts} +Check Number Of Links + [Arguments] ${links} + [Documentation] Check number of links in inventory is ${links} + ${resp}= RequestsLibrary.Get session ${OPERATIONAL_TOPO_API} + Log Check number of links in inventory is ${links} + Log To Console Check number of links in inventory is ${links} + Should Be Equal As Strings ${resp.status_code} 200 + ${count}= Get Count ${resp.content} "link-id":"openflow: + Should Be Equal As Integers ${count} ${links} + Ping Two Hosts [Arguments] ${host1} ${host2} ${pingcount}=2 ${connection_index}=${EMPTY} ${connection_alias}=${EMPTY} [Documentation] Ping between mininet hosts. Must be used only after a mininet session is in place.Returns non zero value if there is 100% packet loss. @@ -117,7 +158,7 @@ Ping Two Hosts Check No Hosts [Documentation] Check if all hosts are deleted from inventory - ${resp} RequestsLibrary.Get session ${OPERATIONAL_TOPO_API} + ${resp}= RequestsLibrary.Get session ${OPERATIONAL_TOPO_API} Log To Console Checking no hosts are present in operational database Log Checking no hosts are present in operational database Should Be Equal As Strings ${resp.status_code} 200 @@ -134,20 +175,41 @@ Start Mininet Linear Read Until mininet> Sleep 6 +Start Mininet With Custom Topology + [Arguments] ${topology_file} ${switches} ${base_mac}=00:00:00:00:00:00 ${base_ip}=1.1.1.1 ${hosts}=0 ${mininet_start_time}=100 + [Documentation] Start a custom mininet topology. + Log To Console Start a custom mininet topology with ${switches} nodes + ${mininet_conn_id}= Open Connection ${MININET} prompt=${linux_prompt} timeout=${mininet_start_time} + Set Suite Variable ${mininet_conn_id} + Login With Public Key ${MININET_USER} ${USER_HOME}/.ssh/id_rsa any + Write python ${topology_file} ${switches} ${hosts} ${base_mac} ${base_ip} + Read Until ${linux_prompt} + Write sudo mn --controller=remote,ip=${CONTROLLER} --custom switch.py --topo demotopo --switch ovsk,protocols=OpenFlow13 + Read Until mininet> + Write sh ovs-vsctl show + ${output}= Read Until mininet> + # Ovsdb connection is sometimes lost after mininet is started. Checking if the connection is alive before proceeding. + Should Not Contain ${output} database connection failed + Log To Console Mininet Started with ${switches} nodes + Check Every Switch - [Arguments] ${switches} + [Arguments] ${switches} ${base_mac}=00:00:00:00:00:00 [Documentation] Check all switches and stats in operational inventory + ${mac}= Replace String Using Regexp ${base_mac} : ${EMPTY} + ${mac}= Convert Hex To Decimal As String ${mac} + ${mac}= Convert To Integer ${mac} : FOR ${switch} IN RANGE 1 ${switches+1} - \ ${resp} RequestsLibrary.Get session /restconf/operational/opendaylight-inventory:nodes/node/openflow:${switch} - \ Log To Console Checking Switch ${switch} + \ ${dpid_decimal}= Evaluate ${mac}+${switch} + \ ${resp} RequestsLibrary.Get session ${OPERATIONAL_NODES_API}/node/openflow:${dpid_decimal} \ Should Be Equal As Strings ${resp.status_code} 200 + \ Log To Console Checking Switch ${switch} \ Should Contain ${resp.content} flow-capable-node-connector-statistics \ Should Contain ${resp.content} flow-table-statistics Check Linear Topology [Arguments] ${switches} [Documentation] Check Linear topology given ${switches} - ${resp} RequestsLibrary.Get session /restconf/operational/network-topology:network-topology/ + ${resp} RequestsLibrary.Get session ${OPERATIONAL_TOPO_API} Log To Console Checking Topology Should Be Equal As Strings ${resp.status_code} 200 : FOR ${switch} IN RANGE 1 ${switches+1} @@ -190,4 +252,4 @@ Stop Mininet Scalability Suite Teardown Delete All Sessions - Clean Mininet System \ No newline at end of file + Clean Mininet System diff --git a/test/csit/libraries/SwitchClasses/BaseSwitch.py b/test/csit/libraries/SwitchClasses/BaseSwitch.py index 057fe60ebf..b09c91d6ad 100644 --- a/test/csit/libraries/SwitchClasses/BaseSwitch.py +++ b/test/csit/libraries/SwitchClasses/BaseSwitch.py @@ -75,44 +75,54 @@ class BaseSwitch(object): def create_flow_match_elements(self, flow_xml): flow_tree = fromstring(flow_xml) - - self.table_id = flow_tree.find('{urn:opendaylight:flow:inventory}table_id').text - - instructions_element = flow_tree.find('{urn:opendaylight:flow:inventory}instructions') - instruction_element = instructions_element.find('{urn:opendaylight:flow:inventory}instruction') - apply_actions = instruction_element.find('{urn:opendaylight:flow:inventory}apply-actions') - action = apply_actions.find('{urn:opendaylight:flow:inventory}action') - output_action = action.find('{urn:opendaylight:flow:inventory}output-action') - output_node_connector = output_action.find('{urn:opendaylight:flow:inventory}output-node-connector') + self.table_id = flow_tree.\ + find('{urn:opendaylight:flow:inventory}table_id').text + instructions_element = flow_tree.\ + find('{urn:opendaylight:flow:inventory}instructions') + instruction_element = instructions_element.\ + find('{urn:opendaylight:flow:inventory}instruction') + apply_actions = instruction_element.\ + find('{urn:opendaylight:flow:inventory}apply-actions') + action = apply_actions.\ + find('{urn:opendaylight:flow:inventory}action') + output_action = action.\ + find('{urn:opendaylight:flow:inventory}output-action') + output_node_connector = \ + output_action.find('{urn:opendaylight:' + 'flow:inventory}output-node-connector') self.action = output_node_connector.text - - match_element = flow_tree.find('{urn:opendaylight:flow:inventory}match') - ethernet_match_element = match_element.find('{urn:opendaylight:flow:inventory}ethernet-match') - - ethernet_source = ethernet_match_element.find('{urn:opendaylight:flow:inventory}ethernet-source') - ethernet_source_address = ethernet_source.find('{urn:opendaylight:flow:inventory}address') + match_element = flow_tree.\ + find('{urn:opendaylight:flow:inventory}match') + ethernet_match_element = match_element.\ + find('{urn:opendaylight:flow:inventory}ethernet-match') + ethernet_source = ethernet_match_element.\ + find('{urn:opendaylight:flow:inventory}ethernet-source') + ethernet_source_address = ethernet_source.\ + find('{urn:opendaylight:flow:inventory}address') self.src_mac = ethernet_source_address.text - - ethernet_destination = ethernet_match_element.find('{urn:opendaylight:flow:inventory}ethernet-destination') - ethernet_destination_address = ethernet_destination.find('{urn:opendaylight:flow:inventory}address') + ethernet_destination = ethernet_match_element.\ + find('{urn:opendaylight:flow:inventory}ethernet-destination') + ethernet_destination_address = ethernet_destination.\ + find('{urn:opendaylight:flow:inventory}address') self.dst_mac = ethernet_destination_address.text - - self.ip_src = match_element.find('{urn:opendaylight:flow:inventory}ipv4-source').text - self.ip_dst = match_element.find('{urn:opendaylight:flow:inventory}ipv4-destination').text + self.ip_src = match_element.\ + find('{urn:opendaylight:flow:inventory}ipv4-source').text + self.ip_dst = match_element.\ + find('{urn:opendaylight:flow:inventory}ipv4-destination').text def convert_hex_to_decimal_as_string(self, hex_string): # TODO: need to add error checking in case the hex_string is - # not fully hex + # not fully hex return str(int(hex_string, 16)) def get_switch(self, switch_type): - ''' + """ Generic method that will allow Robot Code to pass a string to this "keyword - Get Switch" and create an object of that type. (EX: Get Switch OVS) - ''' + """ # TODO: what if the module "switch_type" does not exist. Need some - # error checking for that. + # error checking for that. module = importlib.import_module(switch_type) return getattr(module, switch_type)() diff --git a/test/csit/libraries/Utils.txt b/test/csit/libraries/Utils.txt index 38f284c334..f186dbb596 100644 --- a/test/csit/libraries/Utils.txt +++ b/test/csit/libraries/Utils.txt @@ -156,6 +156,11 @@ Verify Controller Is Not Dead ... Out Of Memory Execptions. Check Karaf Log File Does Not Have Messages ${controller_ip} java.lang.OutOfMemoryError +Verify Controller Has No Null Pointer Exceptions + [Arguments] ${controller_ip}=${CONTROLLER} + [Documentation] Will execute any tests to verify the controller is not having any null pointer eceptions. + Check Karaf Log File Does Not Have Messages ${controller_ip} java.lang.NullPointerException + Get Epoch Time [Arguments] ${time} [Documentation] Get the Epoc time from MM/DD/YYYY HH:MM:SS diff --git a/test/csit/suites/openflowplugin/Maximum_Links/010__finding_max_links.robot b/test/csit/suites/openflowplugin/Maximum_Links/010__finding_max_links.robot new file mode 100644 index 0000000000..4e905ca569 --- /dev/null +++ b/test/csit/suites/openflowplugin/Maximum_Links/010__finding_max_links.robot @@ -0,0 +1,34 @@ +*** Settings *** +Documentation Test suite for finding out max number of Links +Suite Setup Link Scale Suite Setup +Suite Teardown Scalability Suite Teardown +Library OperatingSystem +Library RequestsLibrary +Variables ../../../variables/Variables.py +Resource ../../../libraries/Scalability.txt + +*** Variables *** +${MIN_SWITCHES} 10 +${MAX_SWITCHES} 200 +${STEP_SWITCHES} 5 +${LINKS_RESULT_FILE} links.csv + + +*** Test Cases *** +Find Max Switch Links + [Documentation] Find max number of Links supported. Fully mesh topology starting from + ... ${MIN_SWITCHES} switches till ${MAX_SWITCHES} switches will be attempted in steps of ${STEP_SWITCHES} + Append To File ${LINKS_RESULT_FILE} Max Links \n + ${max-links} Find Max Links ${MIN_SWITCHES} ${MAX_SWITCHES} ${STEP_SWITCHES} + Log ${max-links} + Append To File ${LINKS_RESULT_FILE} ${max-links}\n + +*** Keywords *** +Link Scale Suite Setup + [Documentation] Do initial steps for link scale tests + Create Session session http://${CONTROLLER}:${RESTCONFPORT} auth=${AUTH} headers=${HEADERS_XML} + ${mininet_conn_id}= Open Connection ${MININET} prompt=${linux_prompt} + Login With Public Key ${MININET_USER} ${USER_HOME}/.ssh/id_rsa any + Log Copying ${CREATE_FULLYMESH_TOPOLOGY_FILE_PATH} file to Mininet VM + Put File ${CREATE_FULLYMESH_TOPOLOGY_FILE_PATH} + Close Connection \ No newline at end of file diff --git a/test/csit/testplans/openflowplugin-link-scalability-daily.txt b/test/csit/testplans/openflowplugin-link-scalability-daily.txt new file mode 100644 index 0000000000..abb02cc592 --- /dev/null +++ b/test/csit/testplans/openflowplugin-link-scalability-daily.txt @@ -0,0 +1,2 @@ +# Place the suites in run order: +integration/test/csit/suites/openflowplugin/Maximum_Links diff --git a/test/csit/variables/Variables.py b/test/csit/variables/Variables.py index e60d514394..5328ed88d0 100644 --- a/test/csit/variables/Variables.py +++ b/test/csit/variables/Variables.py @@ -37,7 +37,8 @@ LINUX_PROMPT = '>' VTNC = '127.0.0.1' VTNCPORT = '8083' VTNC_PREFIX = 'http://' + VTNC + ':' + VTNCPORT -VTNC_HEADERS = {'Content-Type': 'application/json', 'username': 'admin', 'password': 'adminpass'} +VTNC_HEADERS = {'Content-Type': 'application/json', + 'username': 'admin', 'password': 'adminpass'} VTNWEBAPI = '/vtn-webapi' # controllers URL @@ -69,11 +70,25 @@ PORTS = 'ports/detail.json' # Common APIs CONFIG_NODES_API = '/restconf/config/opendaylight-inventory:nodes' OPERATIONAL_NODES_API = '/restconf/operational/opendaylight-inventory:nodes' -OPERATIONAL_TOPO_API = '/restconf/operational/network-topology:network-topology' +OPERATIONAL_TOPO_API = '/restconf/operational/network-topology:' \ + 'network-topology' CONFIG_TOPO_API = '/restconf/config/network-topology:network-topology' -CONTROLLER_CONFIG_MOUNT = ('/restconf/config/network-topology:network-topology/topology' - '/topology-netconf/node/controller-config/yang-ext:mount') +CONTROLLER_CONFIG_MOUNT = ('/restconf/config/network-topology:' + 'network-topology/topology' + '/topology-netconf/node/' + 'controller-config/yang-ext:mount') # TOKEN AUTH_TOKEN_API = '/oauth2/token' REVOKE_TOKEN_API = '/oauth2/revoke' + +# Base Mininet Mac address. DPID of mininet switches will be derived from this. +BASE_MAC_1 = '00:4b:00:00:00:00' +# Base IP of mininet hosts +BASE_IP_1 = '75.75.0.0' + +# Mininet Custom Topology Path and File +CREATE_FULLYMESH_TOPOLOGY_FILE = "create_fullymesh.py" +CREATE_FULLYMESH_TOPOLOGY_FILE_PATH = "integration/test/csit/" +\ + "libraries/MininetTopo/" +\ + CREATE_FULLYMESH_TOPOLOGY_FILE