Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions tests/common/ixia/ixia_fixtures.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import pytest
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add docstrings for all the methods in this module

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the doc string

import pprint
# from common.devices import SonicHost, Localhost, PTFHost, EosHost, FanoutHost
from common.devices import FanoutHost
"""
In an IXIA testbed, there is no PTF docker.
Hence, we use ptf_ip field to store IXIA API server.
This fixture returns the IP address of the IXIA API server.
"""
@pytest.fixture(scope = "module")
def ixia_api_serv_ip(testbed):
return testbed['ptf_ip']

"""
Return the username of IXIA API server
"""
@pytest.fixture(scope = "module")
def ixia_api_serv_user(duthost):
return duthost.host.options['variable_manager']._hostvars[duthost.hostname]['secret_group_vars']['ixia_api_server']['user']

"""
Return the password of IXIA API server
"""
@pytest.fixture(scope = "module")
def ixia_api_serv_passwd(duthost):
return duthost.host.options['variable_manager']._hostvars[duthost.hostname]['secret_group_vars']['ixia_api_server']['password']

"""
Return REST port.
"""
@pytest.fixture(scope = "module")
def ixia_api_serv_port(duthost):
return duthost.host.options['variable_manager']._hostvars[duthost.hostname]['secret_group_vars']['ixia_api_server']['rest_port']

"""
IXIA PTF can spawn multiple session on the same REST port. Optional for LINUX, Rewuired for windows
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'Required'

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected the typo

Return the session ID.
"""
@pytest.fixture(scope = "module")
def ixia_api_serv_session_id(duthost):
return duthost.host.options['variable_manager']._hostvars[duthost.hostname]['secret_group_vars']['ixia_api_server']['session_id']
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if users do not specify session_id in configuration file? What will this function return?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Session Id is presumed as a mandatory parameter in the configuration file. So if it is not there we may get a key error (KeyError: 'session_Id'). If we don’t want to connect to particular session we give "None" as sessionID. No Change is needed.


"""
IXIA chassis are leaf fanout switches in the testbed.
This fixture returns the hostnames and IP addresses of the IXIA chassis in the dictionary format.
"""
@pytest.fixture(scope = "module")
def ixia_dev(duthost, ansible_adhoc, conn_graph_facts, creds):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as discussed in the meeting, this needs to be part of the fanouthosts fixture and abstracted in common/devices.py. Please look at the EosHost or OnyxHost for examples

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have removed this fixture from ixia_fixtures.py, instead I am taking fanout_graph_facts as input and designed new class "IxiaFanoutManager" defined in ixia_helpers.py to handle and extract chassis IP address.

dev_conn = conn_graph_facts['device_conn'] if 'device_conn' in conn_graph_facts else {}
fanout_hosts = {}
result = dict()
# WA for virtual testbed which has no fanout
try:
for dut_port in dev_conn.keys():
fanout_rec = dev_conn[dut_port]
fanout_host = fanout_rec['peerdevice']
fanout_port = fanout_rec['peerport']
if fanout_host in fanout_hosts.keys():
fanout = fanout_hosts[fanout_host]
else:
user = pswd = None
host_vars = ansible_adhoc().options['inventory_manager'].get_host(fanout_host).vars
os_type = 'eos' if 'os' not in host_vars else host_vars['os']
if os_type == "ixia":
fanout = FanoutHost(ansible_adhoc, os_type, fanout_host, 'FanoutLeaf', user, pswd)
fanout_hosts[fanout_host] = fanout
fanout.add_port_map(dut_port, fanout_port)
except:
pass

ixia_dev_hostnames = fanout_hosts.keys()
for hostname in ixia_dev_hostnames:
result[hostname] = duthost.host.options['inventory_manager'].get_host(hostname).get_vars()['ansible_host']

return result
59 changes: 59 additions & 0 deletions tests/common/ixia/ixia_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import re

"""
@summary: given a DUT interface, return the management IP address of its neighbor IXIA device
@param intf: DUT interface
@param conn_graph_facts: testbed connectivity graph
@param ixia_dev: the mapping of hostname to IP address of IXIA devices
@return the management IP address of its neighbor IXIA device or None if we cannot find it
"""
def get_neigh_ixia_mgmt_ip(intf, conn_graph_facts, ixia_dev):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use fanout_graph_facts intead of conn_graph_facts. that will return all the fanouts(ixia chassis) associated with the dut as a dict which will have all the details for each of the fanouts

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have used fanout_graph_facts to get chassis/card/port information.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see any changes in this method. If we aren't using these, please remove them

device_conn = conn_graph_facts['device_conn']
if intf not in device_conn:
return None

ixia_dev_hostname = device_conn[intf]['peerdevice']
if ixia_dev_hostname not in ixia_dev:
return None

return ixia_dev[ixia_dev_hostname]

"""
@summary: given a DUT interface, return the card of its neighbor IXIA device
@param intf: DUT interface
@param conn_graph_facts: testbed connectivity graph
@return the card of its neighbor IXIA device or None if we cannot find it
"""
def get_neigh_ixia_card(intf, conn_graph_facts):
device_conn = conn_graph_facts['device_conn']
if intf not in device_conn:
return None

ixia_intf = device_conn[intf]['peerport']
pattern = r'Card(\d+)/Port(\d+)'
m = re.match(pattern, ixia_intf)

if m is None:
return None
else:
return m.group(1)

"""
@summary: given a DUT interface, return the port of its neighbor IXIA device
@param intf: DUT interface
@param conn_graph_facts: testbed connectivity graph
@return the port of its neighbor IXIA device or None if we cannot find it
"""
def get_neigh_ixia_port(intf, conn_graph_facts):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

have a method to return a set of ports as well

device_conn = conn_graph_facts['device_conn']
if intf not in device_conn:
return None

ixia_intf = device_conn[intf]['peerport']
pattern = r'Card(\d+)/Port(\d+)'
m = re.match(pattern, ixia_intf)

if m is None:
return None
else:
return m.group(2)
183 changes: 183 additions & 0 deletions tests/ixia/test_ixia_traffic_restpy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
###############################################################################
# This test cases demonstrates:
# * All the fixtures required for running ixia script (please see the
# arguments of the test function)
# * How Ixia chassis card/ports are addressed
# * How you can configure/control ixia devices, start traffic and collect
# statistics using REST API
# * This simple sanity test cases can be used to check if testbed setup
# is correct or not - since it prints a lot of testbed data
###############################################################################

import logging
import time
import pytest
import pprint
from common.utilities import wait_until
from common.fixtures.conn_graph_facts import conn_graph_facts
from common.platform.interface_utils import check_interface_information
from common.platform.daemon_utils import check_pmon_daemon_status
from common.reboot import *
from common.platform.device_utils import fanout_switch_port_lookup
from common.helpers import assertions
from ixnetwork_restpy import SessionAssistant, Files
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you move all the low level IXIA API functions to ixia_helpers.py?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion. In fact we are in process of designing those high-level wrapper function. See you soon in PRs and meeting and may be design discussion regarding this. Currently I suggest let it be there, to get the ball rolling. As soon we will formulate the new wrapper API we will remove these and replace these with new wrapper functions (which may be bit delayed). Till then let it be here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have moved some of them to the ixia_fixture.py (mainly connection related). Rest of them is still there. Until we come up with some high level wrapper they suppose to be there. This is only a sample script. This will act as a templet for the engineers who will add test-case to act with ixia devices.
Purpose is the get the activity started. Later we may enhance/change or even remove it (after a considerable RDMA test case is added). Right now to stat the activity it is important.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have also moved the ixia libraries to tests/ixia/lib/* and changed the import statements on the top of the script accordingly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have also moved the ixia libraries to "/sonic-mgmt/tests/common/ixia" and changed the import statements on the top of the script accordingly.


from common.ixia_fixtures import ixia_api_serv_ip, ixia_api_serv_user,\
ixia_api_serv_passwd, ixia_dev, ixia_api_serv_port, ixia_api_serv_session_id

from common.ixia_helpers import get_neigh_ixia_mgmt_ip, get_neigh_ixia_card,\
get_neigh_ixia_port

import time

#pytestmark = [pytest.mark.disable_loganalyzer]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cleanup. remove

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have addressed this - the commented line is removed


def returnIxiaChassisIp (chassisDict, no) :
chList = []
for key in chassisDict.keys():
chList.append(chassisDict[key])

return (chList[no - 1])

def parseDeviceConn (device_conn) :
retval = []
dive_con_dict = device_conn['device_conn']
for key in dive_con_dict.keys() :
pp = dive_con_dict[key]['peerport']
string = pp + '/' + key
retval.append(string)
retval.sort()
return(retval)

def getCard(ixiaCardPortList, num) :
card = ixiaCardPortList[num].split('/')[0]
cardNo = int(card.replace('Card', ''))
return(cardNo)

def getPort(ixiaCardPortList, num) :
port = ixiaCardPortList[num].split('/')[1]
portNo = int(port.replace('Port', ''))
return(portNo)

def test_testbed(testbed, conn_graph_facts, duthost, ixia_dev, ixia_api_serv_ip,
ixia_api_serv_user, ixia_api_serv_passwd, ixia_api_serv_port,
ixia_api_serv_session_id):
logger.info("Connection Graph Facts = %s " %(conn_graph_facts))
logger.info("DUT hostname = %s" %(duthost.hostname))
ixiaports = parseDeviceConn(conn_graph_facts)
logger.info("Ixia ports = %s" %(ixiaports))
logger.info("Ixia chassis IP = %s" %(ixia_dev))
logger.info("Ixia API server IP = %s" %(ixia_api_serv_ip))
logger.info("Ixia API server user = %s" %(ixia_api_serv_user))
logger.info("Ixia API server password = %s" %(ixia_api_serv_passwd))
logger.info("Ixia API server port = %s" %(ixia_api_serv_port))
logger.info("Ixia API server sessionId = %s" %(ixia_api_serv_session_id))

clientIp = ixia_api_serv_ip
UserName = ixia_api_serv_user
Password = ixia_api_serv_passwd
RestPort = ixia_api_serv_passwd
SessionId = ixia_api_serv_session_id
chassisIp = returnIxiaChassisIp(ixia_dev, 1)

cardId = getCard(ixiaports, 0)
PortId1 = getPort(ixiaports, 0)
PortId2 = getPort(ixiaports, 1)
PortId3 = getPort(ixiaports, 2)
PortId4 = getPort(ixiaports, 3)
PortId5 = getPort(ixiaports, 4)
PortId6 = getPort(ixiaports, 5)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless 6 ixia connections exist, this test cannot be used by others, can you make this num_of_ports configurable?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All this hard codings are removed. I have used "IxiaFanoutManager" class which can handle variable number of physical ports (as comes in fanout_graph_fact) and creates that number of virtual ports in the ixNetwork configuration. Also port assignments are done accordingly.


if (SessionId != "None") :
session = SessionAssistant(IpAddress = clientIp,
UserName = UserName,
Password = Password,
RestPort = RestPort,
SessionId = SessionId)
else :
session = SessionAssistant(IpAddress = clientIp,
UserName = UserName,
Password = Password,
RestPort = RestPort)

sessionData = session.Session
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all the test setup/cleanup code, should move to a fixture/separate method.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have written a fixture ixia_api_server_session in the ixia_fixtures.py to handle session set up and initial clean up.

ixNetwork = session.Ixnetwork
ixNetwork.NewConfig()
portMap = session.PortMapAssistant()

vPort1 = portMap.Map(chassisIp, cardId, PortId1)
vPort2 = portMap.Map(chassisIp, cardId, PortId2)
vPort3 = portMap.Map(chassisIp, cardId, PortId3)
vPort4 = portMap.Map(chassisIp, cardId, PortId4)
vPort5 = portMap.Map(chassisIp, cardId, PortId5)
vPort6 = portMap.Map(chassisIp, cardId, PortId6)
#print ('connecting to chassis %s' %(chassisIp))

t1 = time.time()
portMap.Connect(ChassisTimeout=1200, ForceOwnership=True)
t2 = time.time()

time_taken = t2 - t1
logger.info("time-taken to connect = %s" %(time_taken))

vPort1.L1Config.NovusHundredGigLan.IeeeL1Defaults = False
vPort1.L1Config.NovusHundredGigLan.EnableAutoNegotiation =False
vPort1.L1Config.NovusHundredGigLan.EnableRsFec = True
vPort1.L1Config.NovusHundredGigLan.EnableRsFecStats = True

vPort2.L1Config.NovusHundredGigLan.IeeeL1Defaults = False
vPort2.L1Config.NovusHundredGigLan.EnableAutoNegotiation =False
vPort2.L1Config.NovusHundredGigLan.EnableRsFec = True
vPort2.L1Config.NovusHundredGigLan.EnableRsFecStats = True

vPort3.L1Config.NovusHundredGigLan.IeeeL1Defaults = False
vPort3.L1Config.NovusHundredGigLan.EnableAutoNegotiation =False
vPort3.L1Config.NovusHundredGigLan.EnableRsFec = True
vPort3.L1Config.NovusHundredGigLan.EnableRsFecStats = True

vPort4.L1Config.NovusHundredGigLan.IeeeL1Defaults = False
vPort4.L1Config.NovusHundredGigLan.EnableAutoNegotiation =False
vPort4.L1Config.NovusHundredGigLan.EnableRsFec = True
vPort4.L1Config.NovusHundredGigLan.EnableRsFecStats = True

vPort5.L1Config.NovusHundredGigLan.IeeeL1Defaults = False
vPort5.L1Config.NovusHundredGigLan.EnableAutoNegotiation =False
vPort5.L1Config.NovusHundredGigLan.EnableRsFec = True
vPort5.L1Config.NovusHundredGigLan.EnableRsFecStats = True

vPort6.L1Config.NovusHundredGigLan.IeeeL1Defaults = False
vPort6.L1Config.NovusHundredGigLan.EnableAutoNegotiation =False
vPort6.L1Config.NovusHundredGigLan.EnableRsFec = True
vPort6.L1Config.NovusHundredGigLan.EnableRsFecStats = True

vPort1.Name = 'Tx1'
vPort4.Name = 'Rx1'
state = vPort1.State

topology1 = ixNetwork.Topology.add(Name='Topo1', Ports=[vPort1, vPort2, vPort3, vPort4, vPort5, vPort6])
deviceGroup1 = topology1.DeviceGroup.add(Name='DG1', Multiplier='1')
ethernet1 = deviceGroup1.Ethernet.add(Name='Eth1')
ipv4 = ethernet1.Ipv4.add(Name='Ipv4')

ipv4.GatewayIp.ValueList(['192.168.1.1','192.168.1.1','192.168.1.1','192.168.1.1', '192.168.1.1', '192.168.1.1'])
ipv4.Address.ValueList(['192.168.1.2','192.168.1.3','192.168.1.4','192.168.1.5', '192.168.1.6', '192.168.1.7'])

ixNetwork.StartAllProtocols()
time.sleep(60)

# Traffic
traffic_item = ixNetwork.Traffic.TrafficItem.add(Name='Traffic Test', TrafficType='ipv4')
dest = [{'arg1': '/api/v1/sessions/1/ixnetwork/topology/1/deviceGroup/1/ethernet/1/ipv4/1', 'arg2': 1, 'arg3': 4, 'arg4': 1 ,'arg5': 1}]
src = [{'arg1': '/api/v1/sessions/1/ixnetwork/topology/1/deviceGroup/1/ethernet/1/ipv4/1', 'arg2': 1, 'arg3': 1, 'arg4': 1, 'arg5': 1}]
endPoint = traffic_item.EndpointSet.add()
traffic_item.Tracking.find().TrackBy = ['trackingenabled0']
endPoint.ScalableSources = src
endPoint.ScalableDestinations = dest
traffic_item.Generate()
ixNetwork.Traffic.Apply()
ixNetwork.Traffic.Start()
time.sleep(10)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the purpose of this sleep?

I don't see IO being stopped, is that on purpose? If so, will it cause issue for next test?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Open IO was not intentional. I will modify the the code to add a line to stop traffic. Generally stop the traffic is good practice. But even if traffic is not stopped it wont effect the next test, because those ports will get rebooted during while getting configured fro the next test.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This issue is addressed. I have added ixNetwork.Traffic.Stop() ad the end of the script

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest use a helper with StartFor(5) seconds instead and get stats then if possible.

logger.info(session.StatViewAssistant('Traffic Item Statistics'))
ixNetwork.Traffic.Stop()

assert 1
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this no-op code.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will do that