Example #1
0
 def start_pcap(self, filename):
     assert (self.pcap_writer == None)
     self.pcap_writer = PcapWriter(filename)
Example #2
0
class DataPlane(Thread):
    """
    This class provides methods to send and receive packets on the dataplane.
    It uses the DataPlanePort class, or an alternative implementation of that
    interface, to do IO on a particular port. A background thread is used to
    read packets from the dataplane ports and enqueue them to be read by the
    test. The kill() method must be called to shutdown this thread.
    """

    MAX_QUEUE_LEN = 100

    # When poll() fails to find a matching packet and reports the error, it
    # includes up to this many recent packets as context.
    POLL_MAX_RECENT_PACKETS = 3

    def __init__(self, config=None):
        Thread.__init__(self)

        # dict from device number, port number to port object
        self.ports = {}

        # dict from device number, port number to list of (timestamp, packet)
        self.packet_queues = {}

        # counters of received packets (may include packets which were dropped due to queue overflow)
        self.rx_counters = defaultdict(int)

        # counters of transmited packets
        self.tx_counters = defaultdict(int)

        # cvar serves double duty as a regular top level lock and
        # as a condition variable
        self.cvar = Condition()

        # Used to wake up the event loop from another thread
        self.waker = ptfutils.EventDescriptor()
        self.killed = False

        self.logger = logging.getLogger("dataplane")
        self.pcap_writer = None

        if config is None:
            self.config = {}
        else:
            self.config = config

        ############################################################
        #
        # The platform/config can provide a custom DataPlanePort class
        # here if you have a custom implementation with different
        # behavior.
        #
        # Set config.dataplane.portclass = MyDataPlanePortClass
        # where MyDataPlanePortClass has the same interface as the class
        # DataPlanePort defined here.
        #
        if self.config["platform"] == "nn":
            # assert is ok here because this is caught earlier in ptf
            assert (with_nnpy == True)
            self.dppclass = DataPlanePortNN
        elif "dataplane" in self.config and "portclass" in self.config[
                "dataplane"]:
            self.dppclass = self.config["dataplane"]["portclass"]
        elif "linux" in sys.platform:
            self.dppclass = DataPlanePortLinux
        elif have_pypcap:
            self.dppclass = DataPlanePortPcap
        else:
            self.logger.warning(
                "Missing pypcap, VLAN tests may fail. See README for installation instructions."
            )
            self.dppclass = DataPlanePort

        if "qlen" in self.config:
            self.qlen = self.config["qlen"]
        else:
            self.qlen = self.MAX_QUEUE_LEN

        self.start()

    def run(self):
        """
        Activity function for class
        """
        while not self.killed:
            sockets = set([p.get_packet_source() for p in self.ports.values()])
            sockets.add(self.waker)
            try:
                sel_in, sel_out, sel_err = select.select(sockets, [], [], 1)
            except:
                print sys.exc_info()
                self.logger.error("Select error, exiting")
                break

            with self.cvar:
                for sel in sel_in:
                    if sel == self.waker:
                        self.waker.wait()
                        continue
                    else:
                        # Enqueue packet
                        t = sel.recv()
                        if t is None:
                            continue
                        device_number, port_number, pkt, timestamp = t
                        self.logger.debug(
                            "Pkt len %d in on device %d, port %d", len(pkt),
                            device_number, port_number)
                        if self.pcap_writer:
                            self.pcap_writer.write(pkt, timestamp,
                                                   device_number, port_number)
                        queue = self.packet_queues[(device_number,
                                                    port_number)]
                        if len(queue) >= self.qlen:
                            # Queue full, throw away oldest
                            queue.pop(0)
                            self.logger.debug(
                                "Discarding oldest packet to make room")
                        queue.append((pkt, timestamp))
                        self.rx_counters[(device_number, port_number)] += 1
                self.cvar.notify_all()

        self.logger.info("Thread exit")

    def set_qlen(self, qlen):
        self.qlen = qlen

    def port_add(self, interface_name, device_number, port_number):
        """
        Add a port to the dataplane
        @param interface_name The name of the physical interface like eth1
        @param device_number The device id used to refer to the device
        @param port_number The port number used to refer to the port
        Stashes the port number on the created port object.
        """
        port_id = (device_number, port_number)
        with self.cvar:
            self.ports[port_id] = self.dppclass(interface_name, device_number,
                                                port_number, self.config)
            self.ports[port_id]._port_number = port_number
            self.ports[port_id]._device_number = device_number
            self.packet_queues[port_id] = []
            # Need to wake up event loop to change the sockets being selected
            # on.
        self.waker.notify()

    # Returns true if success
    def port_remove(self, device_number, port_number):
        port_id = (device_number, port_number)
        with self.cvar:
            if port_id not in self.ports:
                self.logger.warn(
                    "Invalid port_remove: no port {} for device {}".format(
                        port_number, device_number))
                return False
            del self.ports[port_id]
            del self.packet_queues[port_id]
        self.waker.notify()
        return True

    def send(self, device_number, port_number, packet):
        """
        Send a packet to the given port
        @param device_number, port_number The port to send the data to
        @param packet Raw packet data to send to port
        """
        if (device_number, port_number) not in self.ports:
            self.logger.error("send: no port %d for device %d", port_number,
                              device_number)
            return 0
        self.logger.debug("Sending %d bytes to device %d, port %d" %
                          (len(packet), device_number, port_number))
        if len(packet) < 15 and sys.platform.startswith('linux'):
            self.logger.warn(
                "The %s kernel may not send packets smaller than 15 bytes",
                sys.platform)
        if self.pcap_writer:
            self.pcap_writer.write(packet, time.time(), device_number,
                                   port_number)
        bytes = self.ports[(device_number, port_number)].send(packet)
        self.tx_counters[(device_number, port_number)] += 1
        if bytes != len(packet):
            self.logger.error(
                "Unhandled send error, length mismatch %d != %d" %
                (bytes, len(packet)))
        return bytes

    def oldest_port_number(self, device):
        """
        Returns the port number with the oldest packet,
        or None if no packets are queued.
        """
        min_port_number = None
        min_time = float('inf')
        for (port_id, queue) in self.packet_queues.items():
            if port_id[0] != device:
                continue
            if queue and queue[0][1] < min_time:
                min_time = queue[0][1]
                min_port_number = port_id[1]
        return min_port_number

    # Dequeues and yields packets in the order they were received.
    # Yields (port, packet, received time).
    # If port is not specified yields packets from all ports.
    def packets(self, device, port=None):
        while True:
            if port is None:
                rcv_port = self.oldest_port_number(device)
            else:
                rcv_port = port

            if rcv_port == None:
                self.logger.debug("Out of packets on all ports")
                break

            queue = self.packet_queues[(device, rcv_port)]

            if len(queue) == 0:
                self.logger.debug("Out of packets on device %d, port %d",
                                  device, rcv_port)
                break

            pkt, time = queue.pop(0)
            yield (rcv_port, pkt, time)

    PollResult = namedtuple('PollResult', ['device', 'port', 'packet', 'time'])
    """
    A base class used for the result of poll(). No matter what kind of
    additional information subclasses include, for backwards compatibility
    callers must be able to unpack them as if they were a tuple with this
    format. The pair of the @device number and the @port number indicate where
    the packet was received, @packet is the packet itself, and @time is the time
    it was received.
    """

    # On success, poll() returns a PollSuccess object which contains the device
    # number and port number on which a matching packet was found, the packet
    # itself, and the time at which it was received.
    class PollSuccess(PollResult):
        """ Returned by poll() when it successfully finds a matching packet. """
        def __new__(cls, device, port, packet, expected_packet, time):
            """ Initialize. (We're an immutable tuple, so we can't use __init__.) """
            self = super(DataPlane.PollSuccess,
                         cls).__new__(cls, device, port, packet, time)
            self.expected_packet = expected_packet
            return self

        def format(self):
            """
            Returns a string containing a nice (but verbose) representation of
            this packet. If the expected packet is a scapy packet, it's used to
            include detailed information about the fields in the packet.
            """
            try:
                # The scapy packet dissection methods print directly to stdout,
                # so we have to redirect stdout to a string.
                sys.stdout = StringIO()

                print "========== RECEIVED =========="
                if isinstance(self.expected_packet, scapy.packet.Packet):
                    # Dissect this packet as if it were an instance of
                    # the expected packet's class.
                    scapy.packet.ls(self.expected_packet.__class__(
                        self.packet))
                    print '--'
                scapy.utils.hexdump(self.packet)
                print "=============================="

                return sys.stdout.getvalue()
            finally:
                sys.stdout.close()
                sys.stdout = sys.__stdout__  # Restore the original stdout.

    class PollFailure(PollResult):
        """
        Returned by poll() when it fails to match any packets. Contains metadata
        which can be used to diagnose the failure; callers will often want to
        include the result of format() in assertion failure messages.

        For backwards compatibility, when a PollFailure is treated as a tuple
        and unpacked, all PollResult fields are 'None'.
        """
        def __new__(cls, expected_packet, recent_packets, packet_count):
            """ Initialize. (We're an immutable tuple, so we can't use __init__.) """
            self = super(DataPlane.PollFailure,
                         cls).__new__(cls, None, None, None, None)
            self.expected_packet = expected_packet
            self.recent_packets = recent_packets
            self.packet_count = packet_count
            return self

        def format(self):
            """
            Returns a string containing a nice (but verbose) error report based
            on this PollFailure. If there was an expected packet, it's included
            in the output. If the expected packet is a scapy packet object, the
            output will include information about the fields in the packet.
            """
            try:
                # The scapy packet dissection methods print directly to stdout,
                # so we have to redirect stdout to a string.
                sys.stdout = StringIO()

                if self.expected_packet is not None:
                    print "========== EXPECTED =========="
                    if isinstance(self.expected_packet, scapy.packet.Packet):
                        scapy.packet.ls(self.expected_packet)
                        print '--'
                        scapy.utils.hexdump(self.expected_packet)
                    elif isinstance(self.expected_packet, mask.Mask):
                        print 'Mask:', str(self.expected_packet)
                    else:
                        scapy.utils.hexdump(self.expected_packet)

                print "========== RECEIVED =========="
                if self.recent_packets:
                    print "%d total packets. Displaying most recent %d packets:" \
                            % (self.packet_count, len(self.recent_packets))
                    for packet in self.recent_packets:
                        print "------------------------------"
                        if isinstance(self.expected_packet,
                                      scapy.packet.Packet):
                            # Dissect this packet as if it were an instance of
                            # the expected packet's class.
                            scapy.packet.ls(
                                self.expected_packet.__class__(packet))
                            print '--'
                        scapy.utils.hexdump(packet)
                else:
                    print "%d total packets." % self.packet_count
                print "=============================="

                return sys.stdout.getvalue()
            finally:
                sys.stdout.close()
                sys.stdout = sys.__stdout__  # Restore the original stdout.

    def poll(self,
             device_number=0,
             port_number=None,
             timeout=-1,
             exp_pkt=None,
             filters=[]):
        """
        Poll one or all dataplane ports for a packet

        If port_number is given, get the oldest packet from that port (and for
        that device).
        Otherwise, find the port with the oldest packet and return
        that packet.

        If exp_pkt is true, discard all packets until that one is found

        @param device_number Get packet from this device
        @param port_number If set, get packet from this port
        @param timeout If positive and no packet is available, block
        until a packet is received or for this many seconds
        @param exp_pkt If not None, look for this packet and ignore any
        others received.  Note that if port_number is None, all packets
        from all ports will be discarded until the exp_pkt is found

        @return A PollSuccess object on success, or a PollFailure object on
        failure. See the definitions of those classes for more details.
        """
        def filter_check(pkt):
            for f in filters:
                if not f(pkt): return False
            return True

        if exp_pkt and (port_number is None):
            self.logger.warn("Dataplane poll with exp_pkt but no port number")

        # A nested function can't assign to variables in its enclosing function
        # in Python 2, so the conventional hack is to put them in a dict.
        grab_log = {
            # A ring buffer to hold recent non-matching packets.
            'recent_packets': deque(maxlen=DataPlane.POLL_MAX_RECENT_PACKETS),

            # A count of the total packets received. Since 'recent_packets' is a
            # ring buffer, we can't simply check its length.
            'packet_count': 0
        }

        # Retrieve the packet. Returns (device number, port number, packet, time).
        def grab():
            self.logger.debug("Grabbing packet")
            for (rcv_port_number, pkt,
                 time) in self.packets(device_number, port_number):
                rcv_device_number = device_number
                grab_log['recent_packets'].append(pkt)
                grab_log['packet_count'] += 1
                self.logger.debug("Checking packet from device %d, port %d",
                                  rcv_device_number, rcv_port_number)
                if not filter_check(pkt):
                    self.logger.debug(
                        "Paket does not match filter, discarding")
                    continue
                if not exp_pkt or match_exp_pkt(exp_pkt, pkt):
                    return DataPlane.PollSuccess(rcv_device_number,
                                                 rcv_port_number, pkt, exp_pkt,
                                                 time)

            self.logger.debug("Did not find packet")
            return None

        with self.cvar:
            ret = ptfutils.timed_wait(self.cvar, grab, timeout=timeout)

        if ret is None:
            self.logger.debug(
                "Poll timeout, no packet from device %d, port %r",
                device_number, port_number)
            return DataPlane.PollFailure(exp_pkt, grab_log['recent_packets'],
                                         grab_log['packet_count'])

        return ret

    def kill(self):
        """
        Stop the dataplane thread.
        """
        self.killed = True
        self.waker.notify()
        self.join()
        # Explicitly release ports to ensure we don't run out of sockets
        # even if someone keeps holding a reference to the dataplane.
        del self.ports

    def port_down(self, device_number, port_number):
        """Brings the specified port down"""
        self.ports[(device_number, port_number)].down()

    def port_up(self, device_number, port_number):
        """Brings the specified port up"""
        self.ports[(device_number, port_number)].up()

    def get_mac(self, device_number, port_number):
        """Get the specified mac"""
        return self.ports[(device_number, port_number)].mac()

    def get_counters(self, device_number, port_number):
        """Get the counters mac"""
        return self.rx_counters[(device_number, port_number)], \
               self.tx_counters[(device_number, port_number)]

    def get_nn_counters(self, device_number, port_number):
        """Get the specified port counters from nn agent """
        return self.ports[(device_number, port_number)].nn_counters()

    def flush(self):
        """
        Drop any queued packets.
        """
        for port_id in self.packet_queues.keys():
            self.packet_queues[port_id] = []

    def start_pcap(self, filename):
        assert (self.pcap_writer == None)
        self.pcap_writer = PcapWriter(filename)

    def stop_pcap(self):
        if self.pcap_writer:
            with self.cvar:
                self.pcap_writer.close()
                self.pcap_writer = None
                self.cvar.notify_all()
Example #3
0
class DataPlane(Thread):
    """
    This class provides methods to send and receive packets on the dataplane.
    It uses the DataPlanePort class, or an alternative implementation of that
    interface, to do IO on a particular port. A background thread is used to
    read packets from the dataplane ports and enqueue them to be read by the
    test. The kill() method must be called to shutdown this thread.
    """

    MAX_QUEUE_LEN = 100

    def __init__(self, config=None):
        Thread.__init__(self)

        # dict from port number to port object
        self.ports = {}

        # dict from port number to list of (timestamp, packet)
        self.packet_queues = {}

        # cvar serves double duty as a regular top level lock and
        # as a condition variable
        self.cvar = Condition()

        # Used to wake up the event loop from another thread
        self.waker = ofutils.EventDescriptor()
        self.killed = False

        self.logger = logging.getLogger("dataplane")
        self.pcap_writer = None

        if config is None:
            self.config = {}
        else:
            self.config = config

        ############################################################
        #
        # The platform/config can provide a custom DataPlanePort class
        # here if you have a custom implementation with different
        # behavior.
        #
        # Set config.dataplane.portclass = MyDataPlanePortClass
        # where MyDataPlanePortClass has the same interface as the class
        # DataPlanePort defined here.
        #
        if "dataplane" in self.config and "portclass" in self.config[
                "dataplane"]:
            self.dppclass = self.config["dataplane"]["portclass"]
        elif have_pypcap:
            self.dppclass = DataPlanePortPcap
        else:
            self.logger.warning(
                "Missing pypcap, VLAN tests may fail. See README for installation instructions."
            )
            self.dppclass = DataPlanePort

        self.start()

    def run(self):
        """
        Activity function for class
        """
        while not self.killed:
            sockets = [self.waker] + self.ports.values()
            try:
                sel_in, sel_out, sel_err = select.select(sockets, [], [], 1)
            except:
                print sys.exc_info()
                self.logger.error("Select error, exiting")
                break

            with self.cvar:
                for port in sel_in:
                    if port == self.waker:
                        self.waker.wait()
                        continue
                    else:
                        # Enqueue packet
                        pkt, timestamp = port.recv()
                        port_number = port._port_number
                        self.logger.debug("Pkt len %d in on port %d", len(pkt),
                                          port_number)
                        if self.pcap_writer:
                            self.pcap_writer.write(pkt, timestamp, port_number)
                        queue = self.packet_queues[port_number]
                        if len(queue) >= self.MAX_QUEUE_LEN:
                            # Queue full, throw away oldest
                            queue.pop(0)
                            self.logger.debug(
                                "Discarding oldest packet to make room")
                        queue.append((pkt, timestamp))
                self.cvar.notify_all()

        self.logger.info("Thread exit")

    def port_add(self, interface_name, port_number):
        """
        Add a port to the dataplane
        @param interface_name The name of the physical interface like eth1
        @param port_number The port number used to refer to the port
        Stashes the port number on the created port object.
        """
        self.ports[port_number] = self.dppclass(interface_name, port_number)
        self.ports[port_number]._port_number = port_number
        self.packet_queues[port_number] = []
        # Need to wake up event loop to change the sockets being selected on.
        self.waker.notify()

    def send(self, port_number, packet):
        """
        Send a packet to the given port
        @param port_number The port to send the data to
        @param packet Raw packet data to send to port
        """
        self.logger.debug("Sending %d bytes to port %d" %
                          (len(packet), port_number))
        if self.pcap_writer:
            self.pcap_writer.write(packet, time.time(), port_number)
        bytes = self.ports[port_number].send(packet)
        if bytes != len(packet):
            self.logger.error(
                "Unhandled send error, length mismatch %d != %d" %
                (bytes, len(packet)))
        return bytes

    def oldest_port_number(self):
        """
        Returns the port number with the oldest packet, or
        None if no packets are queued.
        """
        min_port_number = None
        min_time = float('inf')
        for (port_number, queue) in self.packet_queues.items():
            if queue and queue[0][1] < min_time:
                min_time = queue[0][1]
                min_port_number = port_number
        return min_port_number

    # Dequeues and yields packets in the order they were received.
    # Yields (port number, packet, received time).
    # If port_number is not specified yields packets from all ports.
    def packets(self, port_number=None):
        while True:
            if port_number is None:
                rcv_port_number = self.oldest_port_number()
            else:
                rcv_port_number = port_number

            if rcv_port_number == None:
                self.logger.debug("Out of packets on all ports")
                break

            queue = self.packet_queues[rcv_port_number]

            if len(queue) == 0:
                self.logger.debug("Out of packets on port %d", rcv_port_number)
                break

            pkt, time = queue.pop(0)
            yield (rcv_port_number, pkt, time)

    def poll(self, port_number=None, timeout=-1, exp_pkt=None):
        """
        Poll one or all dataplane ports for a packet

        If port_number is given, get the oldest packet from that port.
        Otherwise, find the port with the oldest packet and return
        that packet.

        If exp_pkt is true, discard all packets until that one is found

        @param port_number If set, get packet from this port
        @param timeout If positive and no packet is available, block
        until a packet is received or for this many seconds
        @param exp_pkt If not None, look for this packet and ignore any
        others received.  Note that if port_number is None, all packets
        from all ports will be discarded until the exp_pkt is found
        @return The triple port_number, packet, pkt_time where packet
        is received from port_number at time pkt_time.  If a timeout
        occurs, return None, None, None
        """

        if exp_pkt and (port_number is None):
            self.logger.warn("Dataplane poll with exp_pkt but no port number")

        # Retrieve the packet. Returns (port number, packet, time).
        def grab():
            self.logger.debug("Grabbing packet")
            for (rcv_port_number, pkt, time) in self.packets(port_number):
                self.logger.debug("Checking packet from port %d",
                                  rcv_port_number)
                if not exp_pkt or match_exp_pkt(exp_pkt, pkt):
                    return (rcv_port_number, pkt, time)
            self.logger.debug("Did not find packet")
            return None

        with self.cvar:
            ret = ofutils.timed_wait(self.cvar, grab, timeout=timeout)

        if ret != None:
            return ret
        else:
            self.logger.debug("Poll time out, no packet from " +
                              str(port_number))
            return (None, None, None)

    def kill(self):
        """
        Stop the dataplane thread.
        """
        self.killed = True
        self.waker.notify()
        self.join()
        # Explicitly release ports to ensure we don't run out of sockets
        # even if someone keeps holding a reference to the dataplane.
        del self.ports

    def port_down(self, port_number):
        """Brings the specified port down"""
        self.ports[port_number].down()

    def port_up(self, port_number):
        """Brings the specified port up"""
        self.ports[port_number].up()

    def flush(self):
        """
        Drop any queued packets.
        """
        for port_number in self.packet_queues.keys():
            self.packet_queues[port_number] = []

    def start_pcap(self, filename):
        assert (self.pcap_writer == None)
        self.pcap_writer = PcapWriter(filename)

    def stop_pcap(self):
        if self.pcap_writer:
            self.pcap_writer.close()
            self.pcap_writer = None
Example #4
0
 def start_pcap(self, filename):
     assert(self.pcap_writer == None)
     self.pcap_writer = PcapWriter(filename)
Example #5
0
class DataPlane(Thread):
    """
    This class provides methods to send and receive packets on the dataplane.
    It uses the DataPlanePort class, or an alternative implementation of that
    interface, to do IO on a particular port. A background thread is used to
    read packets from the dataplane ports and enqueue them to be read by the
    test. The kill() method must be called to shutdown this thread.
    """

    MAX_QUEUE_LEN = 100

    def __init__(self, config=None):
        Thread.__init__(self)

        # dict from port number to port object
        self.ports = {}

        # dict from port number to list of (timestamp, packet)
        self.packet_queues = {}

        # cvar serves double duty as a regular top level lock and
        # as a condition variable
        self.cvar = Condition()

        # Used to wake up the event loop from another thread
        self.waker = ofutils.EventDescriptor()
        self.killed = False

        self.logger = logging.getLogger("dataplane")
        self.pcap_writer = None

        if config is None:
            self.config = {}
        else:
            self.config = config; 

        ############################################################
        #
        # The platform/config can provide a custom DataPlanePort class
        # here if you have a custom implementation with different
        # behavior. 
        #
        # Set config.dataplane.portclass = MyDataPlanePortClass
        # where MyDataPlanePortClass has the same interface as the class
        # DataPlanePort defined here. 
        #
        if "dataplane" in self.config and "portclass" in self.config["dataplane"]:
            self.dppclass = self.config["dataplane"]["portclass"]
        elif have_pypcap:
            self.dppclass = DataPlanePortPcap
        else:
            self.logger.warning("Missing pypcap, VLAN tests may fail. See README for installation instructions.")
            self.dppclass = DataPlanePort

        self.start()

    def run(self):
        """
        Activity function for class
        """
        while not self.killed:
            sockets = [self.waker] + self.ports.values()
            try:
                sel_in, sel_out, sel_err = select.select(sockets, [], [], 1)
            except:
                print sys.exc_info()
                self.logger.error("Select error, exiting")
                break

            with self.cvar:
                for port in sel_in:
                    if port == self.waker:
                        self.waker.wait()
                        continue
                    else:
                        # Enqueue packet
                        pkt, timestamp = port.recv()
                        port_number = port._port_number
                        self.logger.debug("Pkt len %d in on port %d",
                                          len(pkt), port_number)
                        if self.pcap_writer:
                            self.pcap_writer.write(pkt, timestamp, port_number)
                        queue = self.packet_queues[port_number]
                        if len(queue) >= self.MAX_QUEUE_LEN:
                            # Queue full, throw away oldest
                            queue.pop(0)
                            self.logger.debug("Discarding oldest packet to make room")
                        queue.append((pkt, timestamp))
                self.cvar.notify_all()

        self.logger.info("Thread exit")

    def port_add(self, interface_name, port_number):
        """
        Add a port to the dataplane
        @param interface_name The name of the physical interface like eth1
        @param port_number The port number used to refer to the port
        Stashes the port number on the created port object.
        """
        self.ports[port_number] = self.dppclass(interface_name, port_number)
        self.ports[port_number]._port_number = port_number
        self.packet_queues[port_number] = []
        # Need to wake up event loop to change the sockets being selected on.
        self.waker.notify()

    def send(self, port_number, packet):
        """
        Send a packet to the given port
        @param port_number The port to send the data to
        @param packet Raw packet data to send to port
        """
        self.logger.debug("Sending %d bytes to port %d" %
                          (len(packet), port_number))
        if self.pcap_writer:
            self.pcap_writer.write(packet, time.time(), port_number)
        bytes = self.ports[port_number].send(packet)
        if bytes != len(packet):
            self.logger.error("Unhandled send error, length mismatch %d != %d" %
                     (bytes, len(packet)))
        return bytes

    def oldest_port_number(self):
        """
        Returns the port number with the oldest packet, or
        None if no packets are queued.
        """
        min_port_number = None
        min_time = float('inf')
        for (port_number, queue) in self.packet_queues.items():
            if queue and queue[0][1] < min_time:
                min_time = queue[0][1]
                min_port_number = port_number
        return min_port_number

    # Dequeues and yields packets in the order they were received.
    # Yields (port number, packet, received time).
    # If port_number is not specified yields packets from all ports.
    def packets(self, port_number=None):
        while True:
            if port_number is None:
                rcv_port_number = self.oldest_port_number()
            else:
                rcv_port_number = port_number

            if rcv_port_number == None:
                self.logger.debug("Out of packets on all ports")
                break

            queue = self.packet_queues[rcv_port_number]

            if len(queue) == 0:
                self.logger.debug("Out of packets on port %d", rcv_port_number)
                break

            pkt, time = queue.pop(0)
            yield (rcv_port_number, pkt, time)

    def poll(self, port_number=None, timeout=-1, exp_pkt=None):
        """
        Poll one or all dataplane ports for a packet

        If port_number is given, get the oldest packet from that port.
        Otherwise, find the port with the oldest packet and return
        that packet.

        If exp_pkt is true, discard all packets until that one is found

        @param port_number If set, get packet from this port
        @param timeout If positive and no packet is available, block
        until a packet is received or for this many seconds
        @param exp_pkt If not None, look for this packet and ignore any
        others received.  Note that if port_number is None, all packets
        from all ports will be discarded until the exp_pkt is found
        @return The triple port_number, packet, pkt_time where packet
        is received from port_number at time pkt_time.  If a timeout
        occurs, return None, None, None
        """

        if exp_pkt and (port_number is None):
            self.logger.warn("Dataplane poll with exp_pkt but no port number")

        # Retrieve the packet. Returns (port number, packet, time).
        def grab():
            self.logger.debug("Grabbing packet")
            for (rcv_port_number, pkt, time) in self.packets(port_number):
                self.logger.debug("Checking packet from port %d", rcv_port_number)
                if not exp_pkt or match_exp_pkt(exp_pkt, pkt):
                    return (rcv_port_number, pkt, time)
            self.logger.debug("Did not find packet")
            return None

        with self.cvar:
            ret = ofutils.timed_wait(self.cvar, grab, timeout=timeout)

        if ret != None:
            return ret
        else:
            self.logger.debug("Poll time out, no packet from " + str(port_number))
            return (None, None, None)

    def kill(self):
        """
        Stop the dataplane thread.
        """
        self.killed = True
        self.waker.notify()
        self.join()
        # Explicitly release ports to ensure we don't run out of sockets
        # even if someone keeps holding a reference to the dataplane.
        del self.ports

    def port_down(self, port_number):
        """Brings the specified port down"""
        self.ports[port_number].down()

    def port_up(self, port_number):
        """Brings the specified port up"""
        self.ports[port_number].up()

    def flush(self):
        """
        Drop any queued packets.
        """
        for port_number in self.packet_queues.keys():
            self.packet_queues[port_number] = []

    def start_pcap(self, filename):
        assert(self.pcap_writer == None)
        self.pcap_writer = PcapWriter(filename)

    def stop_pcap(self):
        if self.pcap_writer:
            self.pcap_writer.close()
            self.pcap_writer = None
Example #6
0
class DataPlane(Thread):
    """
    This class provides methods to send and receive packets on the dataplane.
    It uses the DataPlanePort class, or an alternative implementation of that
    interface, to do IO on a particular port. A background thread is used to
    read packets from the dataplane ports and enqueue them to be read by the
    test. The kill() method must be called to shutdown this thread.
    """

    MAX_QUEUE_LEN = 100

    def __init__(self, config=None):
        Thread.__init__(self)

        # dict from port number to port object
        self.ports = {}

        # dict from port number to list of (timestamp, packet)
        self.packet_queues = {}

        # cvar serves double duty as a regular top level lock and
        # as a condition variable
        self.cvar = Condition()

        # Used to wake up the event loop from another thread
        self.waker = ofutils.EventDescriptor()
        self.killed = False

        self.logger = logging.getLogger("dataplane")
        self.pcap_writer = None

        if config is None:
            self.config = {}
        else:
            self.config = config; 

        ############################################################
        #
        # The platform/config can provide a custom DataPlanePort class
        # here if you have a custom implementation with different
        # behavior. 
        #
        # Set config.dataplane.portclass = MyDataPlanePortClass
        # where MyDataPlanePortClass has the same interface as the class
        # DataPlanePort defined here. 
        #
        if "dataplane" in self.config and "portclass" in self.config["dataplane"]:
            self.dppclass = self.config["dataplane"]["portclass"]
        elif "tstc" in self.config["platform"]:
            self.logger.info( "dataplane use stc adapter")
            self.dppclass = DataPlanePortStc
        elif "test" in self.config["platform"]:
            self.logger.info( "dataplane use stc adapter")
            self.dppclass = DataPlanePortTest
        elif "linux" in sys.platform:
            self.dppclass = DataPlanePortLinux
        else:
            self.dppclass = DataPlanePortPcap

        if "tstc" in self.config["platform"]:
            try: 
                self.stc = stc_proxy.STC(remoteRpcServerIp = self.config["stcSeriverIp"], port = self.config["stcServierPort"])
                self.chassisIp = self.config["chassisIp"]
                self.stc_version = self.stc.get("system1","-Version")  
                self.logger.info("Spirent test center version-%s" % self.stc_version)  
                log_filename_stc = time.strftime("remote_%Y_%m_%d_%H_%M_%S.log",time.localtime(time.time()))
                cResult = self.stc.config("automationoptions -logTo \"" + "log/" + log_filename_stc + "\" -logLevel DEBUG")  
                self.logger.info("enable stc log to file . result %s ." % cResult)

                cResult = self.stc.connect(self.chassisIp)
                self.logger.info("connect to chassis . result %s ." % cResult)
                
                self.project = []
                self.ethPhy = []
                
                prj = self.stc.create("project")
                assert(prj != "")
                self.project.append(prj)
                
            except:
                self.logger.error("Configure remote rpc server failed, Please check whether remote server is running")
                raise
            
        self.start()


    def __del__(self):
        pass
        #Thread.__del__(self)
    
    def run(self):
        """
        Activity function for class
        """
        while not self.killed:
            
            if "tstc" in self.config["platform"] :
                #print time.asctime()
                time.sleep(1)
            else:
                sockets = [self.waker] + self.ports.values()
                try:
                    sel_in, sel_out, sel_err = select.select(sockets, [], [], 1)
                except:
                    print sys.exc_info()
                    self.logger.error("Select error, exiting")
                    break
    
                with self.cvar:
                    for port in sel_in:
                        if port == self.waker:
                            self.waker.wait()
                            continue
                        else:
                            # Enqueue packet
                            pkt, timestamp = port.recv()
                            port_number = port._port_number
                            '''
                            self.logger.debug("Pkt len %d in on port %d",
                                              len(pkt), port_number)
                            '''
                            if self.pcap_writer:
                                self.pcap_writer.write(pkt, timestamp, port_number)
                            queue = self.packet_queues[port_number]
                            if len(queue) >= self.MAX_QUEUE_LEN:
                                # Queue full, throw away oldest
                                queue.pop(0)
                                self.logger.debug("Discarding oldest packet to make room")
                            queue.append((pkt, timestamp))
                    self.cvar.notify_all()

        self.logger.info("Thread exit")

    def port_add(self, interface_name, port_number):
        """
        Add a port to the dataplane
        @param interface_name The name of the physical interface like eth1
        @param port_number The port number used to refer to the port
        Stashes the port number on the created port object.
        """
        if "tstc" in self.config["platform"]:
            
            try:
                cResult = self.stc.reserve( "//" + self.chassisIp + "/" + interface_name)
                self.logger.info( "cResult %s" % cResult)

                stc_port = self.stc.create( "port","-under" , self.project[0])
                self.logger.info( "stc_port %s" % stc_port)
                
                cResult = self.stc.config( stc_port, " -location " , "//" + self.chassisIp + '/' + interface_name)
                self.logger.info( "cResult %s" % cResult)
                
                EthernetCopper = self.stc.create( "EthernetCopper" ,"-under",stc_port, "-Name" ,"ethernetCopper" + str(port_number))
                self.logger.info( "EthernetCopper %s" % EthernetCopper)
                self.ethPhy.append(EthernetCopper)
                
                
            except:
                self.logger.error("add stc port error")
                raise
                        
            self.ports[port_number] = stc_port
            
        else:
            self.ports[port_number] = self.dppclass(interface_name, port_number)
            self.ports[port_number]._port_number = port_number
            self.packet_queues[port_number] = []
            # Need to wake up event loop to change the sockets being selected on.
            self.waker.notify()

    def send(self, port_number, packet):
        """
        Send a packet to the given port
        @param port_number The port to send the data to
        @param packet Raw packet data to send to port
        """
        self.logger.debug("Sending %d bytes to port %d" %
                          (len(packet), port_number))
        if self.pcap_writer:
            self.pcap_writer.write(packet, time.time(), port_number)
        bytes = self.ports[port_number].send(packet)
        if bytes != len(packet):
            self.logger.error("Unhandled send error, length mismatch %d != %d" %
                     (bytes, len(packet)))
        return bytes

    def oldest_port_number(self):
        """
        Returns the port number with the oldest packet, or
        None if no packets are queued.
        """
        min_port_number = None
        min_time = float('inf')
        for (port_number, queue) in self.packet_queues.items():
            if queue and queue[0][1] < min_time:
                min_time = queue[0][1]
                min_port_number = port_number
        return min_port_number

    # Dequeues and yields packets in the order they were received.
    # Yields (port number, packet, received time).
    # If port_number is not specified yields packets from all ports.
    def packets(self, port_number=None):
        while True:
            rcv_port_number = port_number or self.oldest_port_number()

            if rcv_port_number == None:
                self.logger.debug("Out of packets on all ports")
                break

            queue = self.packet_queues[rcv_port_number]

            if len(queue) == 0:
                self.logger.debug("Out of packets on port %d", rcv_port_number)
                break

            pkt, time = queue.pop(0)
            yield (rcv_port_number, pkt, time)

    def poll(self, port_number=None, timeout=-1, exp_pkt=None):
        """
        Poll one or all dataplane ports for a packet

        If port_number is given, get the oldest packet from that port.
        Otherwise, find the port with the oldest packet and return
        that packet.

        If exp_pkt is true, discard all packets until that one is found

        @param port_number If set, get packet from this port
        @param timeout If positive and no packet is available, block
        until a packet is received or for this many seconds
        @param exp_pkt If not None, look for this packet and ignore any
        others received.  Note that if port_number is None, all packets
        from all ports will be discarded until the exp_pkt is found
        @return The triple port_number, packet, pkt_time where packet
        is received from port_number at time pkt_time.  If a timeout
        occurs, return None, None, None
        """

        if exp_pkt and not port_number:
            self.logger.warn("Dataplane poll with exp_pkt but no port number")

        # Retrieve the packet. Returns (port number, packet, time).
        def grab():
            self.logger.debug("Grabbing packet")
            for (rcv_port_number, pkt, time) in self.packets(port_number):
                self.logger.debug("Checking packet from port %d", rcv_port_number)
                if not exp_pkt or match_exp_pkt(exp_pkt, pkt):
                    return (rcv_port_number, pkt, time)
            self.logger.debug("Did not find packet")
            return None

        with self.cvar:
            ret = ofutils.timed_wait(self.cvar, grab, timeout=timeout)

        if ret != None:
            return ret
        else:
            self.logger.debug("Poll time out, no packet from " + str(port_number))
            return (None, None, None)

    def kill(self):
        """
        Stop the dataplane thread.
        """
        
        if "tstc" in self.config["platform"]:
            
            for key in self.ports.keys():
                self.logger.info( "release port : %s " % self.ports[key])
                self.stc.release( self.stc.get(self.ports[key], "-location"))
            
            self.stc.disconnect( self.chassisIp)
            
            #delete project
            for prj in self.project:
                self.logger.info( "delete project : %s " % prj)
                self.stc.delete( prj)
            
            self.stc.perform( "ResetConfig" ,"-config", "system1")
            

            
        self.killed = True
        self.waker.notify()
        self.join()
        
        del self.waker        
        # Explicitly release ports to ensure we don't run out of sockets
        # even if someone keeps holding a reference to the dataplane.
        del self.ports

    def port_down(self, port_number):
        """Brings the specified port down"""
        self.ports[port_number].down()

    def port_up(self, port_number):
        """Brings the specified port up"""
        self.ports[port_number].up()

    def flush(self):
        """
        Drop any queued packets.
        """
        for port_number in self.packet_queues.keys():
            self.packet_queues[port_number] = []

    def start_pcap(self, filename):
        assert(self.pcap_writer == None)
        self.pcap_writer = PcapWriter(filename)

    def stop_pcap(self):
        if self.pcap_writer:
            self.pcap_writer.close()
            self.pcap_writer = None