This is only for fixed rate runs, the chain analysis table at end of run may contain
incorrect drops.
Change-Id: I6656528ed695a60003c672132624a7284db60497
Signed-off-by: ahothan <ahothan@cisco.com>
#
import time
-
from log import LOG
from network import Network
from packet_analyzer import PacketAnalyzer
class StageManager(object):
+ """A class to stage resources in the systenm under test."""
def __init__(self, config, cred, factory):
self.config = config
self.client.dispose()
-class StatsManager(object):
+class PVPStatsManager(object):
+ """A class to generate traffic and extract results for PVP chains."""
def __init__(self, config, clients, specs, factory, vlans, notifier=None):
self.config = config
def _get_data(self):
return self.worker.get_data() if self.worker else {}
- def _get_network(self, traffic_port, index, stats, reverse=False):
+ def _get_network(self, traffic_port, stats, reverse=False):
+ """Get the Network object corresponding to a given TG port.
+
+ :param traffic_port: must be either 0 or 1
+ :param stats: TG stats for given traffic port
+ :param reverse: specifies if the interface list for this network
+ should go from TG to loopback point (reverse=false) or
+ from loopback point to TG (reverse=true)
+ """
+ # build the interface list in fwd direction (TG To loopback point)
interfaces = [self.clients['traffic'].get_interface(traffic_port, stats)]
if self.worker:
- interfaces.extend(self.worker.get_network_interfaces(index))
+ # if available,
+ # interfaces for workers must be aligned on the TG port number
+ interfaces.extend(self.worker.get_network_interfaces(traffic_port))
+ # let Network reverse the interface order if needed
return Network(interfaces, reverse)
def _config_interfaces(self):
return self.worker.get_version() if self.worker else {}
def run(self):
- """
- Run analysis in both direction and return the analysis
- """
+ """Run analysis in both direction and return the analysis."""
if self.worker:
self.worker.run()
}
# fetch latest stats from traffic gen
- if self.config.no_traffic:
- stats = None
- else:
- stats = self.clients['traffic'].get_stats()
+ stats = self.clients['traffic'].get_stats()
LOG.info('Requesting packet analysis on the forward direction...')
result['packet_analysis']['direction-forward'] = \
- self.get_analysis([self._get_network(0, 0, stats),
- self._get_network(0, 1, stats, reverse=True)])
+ self.get_analysis([self._get_network(0, stats),
+ self._get_network(1, stats, reverse=True)])
LOG.info('Packet analysis on the forward direction completed')
LOG.info('Requesting packet analysis on the reverse direction...')
result['packet_analysis']['direction-reverse'] = \
- self.get_analysis([self._get_network(1, 1, stats),
- self._get_network(1, 0, stats, reverse=True)])
+ self.get_analysis([self._get_network(1, stats),
+ self._get_network(0, stats, reverse=True)])
LOG.info('Packet analysis on the reverse direction completed')
return result
self.worker.close()
-class PVPStatsManager(StatsManager):
-
- def __init__(self, config, clients, specs, factory, vlans, notifier=None):
- StatsManager.__init__(self, config, clients, specs, factory, vlans, notifier)
-
-
-class PVVPStatsManager(StatsManager):
+class PVVPStatsManager(PVPStatsManager):
+ """A Class to generate traffic and extract results for PVVP chains."""
def __init__(self, config, clients, specs, factory, vlans, notifier=None):
- StatsManager.__init__(self, config, clients, specs, factory, vlans, notifier)
+ PVPStatsManager.__init__(self, config, clients, specs, factory, vlans, notifier)
def run(self):
- """
- Run analysis in both direction and return the analysis
- """
+ """Run analysis in both direction and return the analysis."""
fwd_v2v_net, rev_v2v_net = self.worker.run()
stats = self._generate_traffic()
'stats': stats
}
# fetch latest stats from traffic gen
- if self.config.no_traffic:
- stats = None
- else:
- stats = self.clients['traffic'].get_stats()
- fwd_nets = [self._get_network(0, 0, stats)]
+ stats = self.clients['traffic'].get_stats()
+ fwd_nets = [self._get_network(0, stats)]
if fwd_v2v_net:
fwd_nets.append(fwd_v2v_net)
- fwd_nets.append(self._get_network(0, 1, stats, reverse=True))
+ fwd_nets.append(self._get_network(1, stats, reverse=True))
- rev_nets = [self._get_network(1, 1, stats)]
+ rev_nets = [self._get_network(1, stats)]
if rev_v2v_net:
rev_nets.append(rev_v2v_net)
- rev_nets.append(self._get_network(1, 0, stats, reverse=True))
+ rev_nets.append(self._get_network(0, stats, reverse=True))
LOG.info('Requesting packet analysis on the forward direction...')
result['packet_analysis']['direction-forward'] = self.get_analysis(fwd_nets)
return result
-class EXTStatsManager(StatsManager):
+class EXTStatsManager(PVPStatsManager):
+ """A Class to generate traffic and extract results for EXT chains."""
+
def __init__(self, config, clients, specs, factory, vlans, notifier=None):
- StatsManager.__init__(self, config, clients, specs, factory, vlans, notifier)
+ PVPStatsManager.__init__(self, config, clients, specs, factory, vlans, notifier)
def _setup(self):
if self.specs.openstack:
class Interface(object):
+ """A class to hold the RX and TX counters for a virtual or physical interface."""
def __init__(self, name, device, tx_packets, rx_packets):
+ """Create a new interface instance."""
self.name = name
self.device = device
self.packets = {
}
def set_packets(self, tx, rx):
+ """Set tx and rx counters for this interface."""
self.packets = {
'tx': tx,
'rx': rx
}
def set_packets_diff(self, tx, rx):
+ """Subtract current counters from new set of counters and update with results."""
self.packets = {
'tx': tx - self.packets['tx'],
'rx': rx - self.packets['rx'],
}
def is_no_op(self):
+ """Check if this interface is a no-opn interface."""
return self.name is None
def get_packet_count(self, traffic_type):
+ """Get packet count for given direction."""
return self.packets.get(traffic_type, 0)
@staticmethod
def no_op():
+ """Return an interface that doe snot pass any traffic."""
return Interface(None, None, 0, 0)
class Network(object):
+ """This class holds all interfaces that make up a logical neutron network.
+
+ A loopback packet path has exactly 2 networks.
+ The first interface is always one of the 2 traffic gen interface.
+ Subsequent interfaces are sorted along the path from the TG to the loopback point
+ which could be interfaces in a switch, a vswitch or a VM.
+ """
def __init__(self, interfaces=None, reverse=False):
+ """Create a network with initial interface list and direction.
+
+ :param interfaces: initial interface list
+ :param reverse: specifies the order of interfaces returned by get_interfaces
+ """
if interfaces is None:
interfaces = []
self.interfaces = interfaces
self.reverse = reverse
def add_interface(self, interface):
+ """Add one more interface to this network.
+
+ Order if important as interfaces must be added from traffic generator ports towards then
+ looping back device.
+ """
self.interfaces.append(interface)
def get_interfaces(self):
+ """Get interfaces associated to this network.
+
+ Returned interface list is ordered from traffic generator port towards looping device if
+ reverse is false. Else returms the list in the reverse order.
+ """
return self.interfaces[::-1] if self.reverse else self.interfaces
class TrafficClientException(Exception):
+ """Generic traffic client exception."""
+
pass
class TrafficRunner(object):
+ """Serialize various steps required to run traffic."""
+
def __init__(self, client, duration_sec, interval_sec=0):
self.client = client
self.start_time = None
class IpBlock(object):
+ """Manage a block of IP addresses."""
+
def __init__(self, base_ip, step_ip, count_ip):
self.base_ip_int = Device.ip_to_int(base_ip)
self.step = Device.ip_to_int(step_ip)
self.next_free = 0
def get_ip(self, index=0):
- '''Return the IP address at given index
- '''
+ """Return the IP address at given index."""
if index < 0 or index >= self.max_available:
raise IndexError('Index out of bounds')
return Device.int_to_ip(self.base_ip_int + index * self.step)
def reserve_ip_range(self, count):
- '''Reserve a range of count consecutive IP addresses spaced by step
- '''
+ """Reserve a range of count consecutive IP addresses spaced by step."""
if self.next_free + count > self.max_available:
raise IndexError('No more IP addresses next free=%d max_available=%d requested=%d' %
(self.next_free,
class Device(object):
+ """Represent a port device and all information associated to it."""
+
def __init__(self, port, pci, switch_port=None, vtep_vlan=None, ip=None, tg_gateway_ip=None,
gateway_ip=None, ip_addrs_step=None, tg_gateway_ip_addrs_step=None,
gateway_ip_addrs_step=None, udp_src_port=None, udp_dst_port=None,
if mac is None:
raise TrafficClientException('Trying to set traffic generator MAC address as None')
self.mac = mac
+ LOG.info("Port %d: src MAC %s", self.port, self.mac)
def set_destination(self, dst):
self.dst = dst
if self.vlan_tagging and vlan_tag is None:
raise TrafficClientException('Trying to set VLAN tag as None')
self.vlan_tag = vlan_tag
+ LOG.info("Port %d: VLAN %d", self.port, self.vlan_tag)
def get_gw_ip(self, chain_index):
- '''Retrieve the IP address assigned for the gateway of a given chain
- '''
+ """Retrieve the IP address assigned for the gateway of a given chain."""
return self.gw_ip_block.get_ip(chain_index)
def get_stream_configs(self, service_chain):
return configs
def ip_range_overlaps(self):
- '''Check if this device ip range is overlapping with the dst device ip range
- '''
+ """Check if this device ip range is overlapping with the dst device ip range."""
src_base_ip = Device.ip_to_int(self.ip)
dst_base_ip = Device.ip_to_int(self.dst.ip)
src_last_ip = src_base_ip + self.flow_count - 1
class TrafficGeneratorFactory(object):
+ """Factory class to generate a traffic generator."""
+
def __init__(self, config):
self.config = config
class TrafficClient(object):
+ """Traffic generator client."""
+
PORTS = [0, 1]
def __init__(self, config, notifier=None, skip_sleep=False):
return self.gen.get_version()
def ensure_end_to_end(self):
- """
- Ensure traffic generator receives packets it has transmitted.
+ """Ensure traffic generator receives packets it has transmitted.
+
This ensures end to end connectivity and also waits until VMs are ready to forward packets.
VMs that are started and in active state may not pass traffic yet. It is imperative to make
results[tag]['timestamp_sec'] = time.time()
def __range_search(self, left, right, targets, results):
- '''Perform a binary search for a list of targets inside a [left..right] range or rate
+ """Perform a binary search for a list of targets inside a [left..right] range or rate.
left the left side of the range to search as a % the line rate (100 = 100% line rate)
indicating the rate to send on each interface
targets a dict of drop rates to search (0.1 = 0.1%), indexed by the DR name or "tag"
('ndr', 'pdr')
results a dict to store results
- '''
+ """
if not targets:
return
LOG.info('Range search [%s .. %s] targets: %s', left, right, targets)
return config
def get_run_config(self, results):
- """Returns configuration which was used for the last run."""
+ """Return configuration which was used for the last run."""
r = {}
for idx, key in enumerate(["direction-forward", "direction-reverse"]):
tx_rate = results["stats"][idx]["tx"]["total_pkts"] / self.config.duration_sec