org.opendaylight.vtn.manager.internal.packet.VTNPacketService.java Source code

Java tutorial

Introduction

Here is the source code for org.opendaylight.vtn.manager.internal.packet.VTNPacketService.java

Source

/*
 * Copyright (c) 2015, 2016 NEC Corporation. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 */

package org.opendaylight.vtn.manager.internal.packet;

import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicReference;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.apache.commons.lang3.tuple.Pair;

import org.opendaylight.vtn.manager.VTNException;
import org.opendaylight.vtn.manager.packet.Packet;
import org.opendaylight.vtn.manager.util.EtherTypes;

import org.opendaylight.vtn.manager.internal.VTNManagerProvider;
import org.opendaylight.vtn.manager.internal.inventory.VTNInventoryListener;
import org.opendaylight.vtn.manager.internal.inventory.VtnNodeEvent;
import org.opendaylight.vtn.manager.internal.inventory.VtnPortEvent;
import org.opendaylight.vtn.manager.internal.util.SalNotificationListener;
import org.opendaylight.vtn.manager.internal.util.inventory.InventoryReader;
import org.opendaylight.vtn.manager.internal.util.inventory.NodeRpcErrorCallback;
import org.opendaylight.vtn.manager.internal.util.inventory.SalPort;

import org.opendaylight.controller.md.sal.binding.api.NotificationService;

import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.types.rev150209.VtnUpdateType;

import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorRef;
import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketProcessingListener;
import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketProcessingService;
import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketReceived;
import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.TransmitPacketInputBuilder;

/**
 * Provider of internal packet services.
 */
public final class VTNPacketService extends SalNotificationListener
        implements PacketProcessingListener, VTNInventoryListener {
    /**
     * Logger instance.
     */
    private static final Logger LOG = LoggerFactory.getLogger(VTNPacketService.class);

    /**
     * VTN Manager provider service.
     */
    private final VTNManagerProvider vtnProvider;

    /**
     * MD-SAL packet processing service.
     */
    private final AtomicReference<PacketProcessingService> packetService = new AtomicReference<PacketProcessingService>();

    /**
     * A list of VTN packet listeners.
     */
    private final List<VTNPacketListener> listeners = new CopyOnWriteArrayList<>();

    /**
     * A set of physical switch ports waiting for completion of topology
     * detection.
     *
     * <p>
     *   If a port is contained in this map, any packet from that port is
     *   ignored, and no packet is sent to that port.
     * </p>
     */
    private ConcurrentMap<SalPort, TimerTask> topologyWaiting = new ConcurrentHashMap<SalPort, TimerTask>();

    /**
     * Construct a new instance.
     *
     * @param provider  A VTN Manager provider service.
     * @param nsv       A {@link NotificationService} service instance.
     */
    public VTNPacketService(VTNManagerProvider provider, NotificationService nsv) {
        vtnProvider = provider;

        try {
            // Get MD-SAL packet processing service.
            PacketProcessingService pps = provider.getRpcService(PacketProcessingService.class);
            packetService.set(pps);

            // Register MD-SAL notification listener.
            registerListener(nsv);
        } catch (Exception e) {
            String msg = "Failed to initialize VTN packet service.";
            LOG.error(msg, e);
            close();
            throw new IllegalStateException(msg, e);
        }
    }

    /**
     * Add the given VTN packet listener.
     *
     * @param l  A VTN packet listener.
     */
    public void addListener(VTNPacketListener l) {
        listeners.add(l);
    }

    /**
     * Transmit the given packet.
     *
     * @param egress  A {@link SalPort} instance which specifies the egress
     *                switch port.
     * @param packet  A {@link Packet} instance to transmit.
     */
    public void transmit(SalPort egress, Packet packet) {
        PacketProcessingService pps = packetService.get();
        if (pps != null) {
            transmit(pps, egress, packet);
        }
    }

    /**
     * Transmit all the given packets.
     *
     * @param packets  A list of packets to transmit.
     *                 The left of each elements indicates the egress switch
     *                 port, and the right indiciates a packet to transmit.
     */
    public void transmit(List<Pair<SalPort, Packet>> packets) {
        PacketProcessingService pps = packetService.get();
        if (pps != null) {
            for (Pair<SalPort, Packet> pkt : packets) {
                transmit(pps, pkt.getLeft(), pkt.getRight());
            }
        }
    }

    /**
     * Transmit the given packet.
     *
     * @param pps     A packet-procssing service.
     * @param egress  A {@link SalPort} instance which specifies the egress
     *                switch port.
     * @param packet  A {@link Packet} instance to be transmitted.
     */
    private void transmit(PacketProcessingService pps, SalPort egress, Packet packet) {
        if (topologyWaiting.containsKey(egress)) {
            LOG.trace("Ignore packet transmission due to topology " + "detection: {}", egress);
        } else {
            byte[] payload;
            try {
                payload = packet.serialize();
            } catch (Exception e) {
                LOG.error("Failed to transmit packet via " + egress, e);
                return;
            }

            TransmitPacketInputBuilder builder = new TransmitPacketInputBuilder().setNode(egress.getNodeRef())
                    .setEgress(egress.getNodeConnectorRef()).setPayload(payload);
            TransmitPacketRpc rpc = new TransmitPacketRpc(vtnProvider, pps, builder.build());
            NodeRpcErrorCallback<Void> cb = new NodeRpcErrorCallback<>(rpc, LOG, "Failed to transmit packet.");
            vtnProvider.setCallback(rpc.getFuture(), cb);
        }
    }

    /**
     * Add the given port to {@link #topologyWaiting}.
     *
     * <p>
     *   Inter-switch link detection may be still running on a port when that
     *   port status has been changed to UP state. If a PACKET_OUT is sent to
     *   that port, it may cause broadcast packet storm because the controller
     *   can not determine ISL port yet.
     * </p>
     * <p>
     *   That is why we should disable any packet service on a newly enabled
     *   port for a while.
     * </p>
     *
     * @param sport  A newly enabled port.
     */
    private void addTopologyWaiting(final SalPort sport) {
        int topoWait = vtnProvider.getVTNConfig().getTopologyWait();
        if (topoWait <= 0) {
            // Topology wait is disabled.
            return;
        }

        // Create a timer task to remove the port from topologyWaiting.
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                if (topologyWaiting.remove(sport) != null) {
                    LOG.info("{}: Start packet service.", sport);
                }
            }
        };

        if (topologyWaiting.putIfAbsent(sport, task) == null) {
            if (packetService.get() == null) {
                // Packet service is already closed.
                topologyWaiting.remove(sport);
            } else {
                LOG.debug("{}: Wait for completion of topology detection.", sport);
                vtnProvider.getTimer().schedule(task, topoWait);
            }
        }
    }

    /**
     * Construct a new PACKET_IN event.
     *
     * @param listener      A PACKET_IN event listener.
     * @param notification  A PACKET_IN notification.
     * @param ingress       A {@link SalPort} instance which specifies the
     *                      ingress switch port.
     * @return  A {@link PacketInEvent} instance if the given notification
     *          should be delivered to listeners.
     *          {@code null} if the given notification should be ignored.
     */
    private PacketInEvent newPacketInEvent(VTNPacketListener listener, PacketReceived notification,
            SalPort ingress) {
        PacketInEvent ev;
        try {
            ev = new PacketInEvent(listener, notification, ingress);
        } catch (IllegalArgumentException e) {
            LOG.debug("Ignore received packet.", e);
            return null;
        } catch (Exception e) {
            LOG.error("Failed to create PacketInEvent: " + notification, e);
            return null;
        }

        if (ev.getEthernet().getEtherType() == EtherTypes.LLDP.shortValue()) {
            // Ignore LLDP packet.
            return null;
        }

        return ev;
    }

    /**
     * Determine whether the topology wait on the port specified by the
     * given port event should terminate or not.
     *
     * @param ev  A {@link VtnPortEvent} instance which notifies the port
     *            change.
     * @return  A string which indicates the reason why the topology wait
     *          should terminate. {@code null} if the topology wait should not
     *          terminate.
     * @throws VTNException  An error occurred.
     */
    private String shouldTopologyWaitTerminate(VtnPortEvent ev) throws VTNException {
        if (ev.getUpdateType() == VtnUpdateType.REMOVED) {
            return "port has been removed";
        }

        if (Boolean.FALSE.equals(ev.getStateChange())) {
            return "port is down";
        }

        if (Boolean.TRUE.equals(ev.getInterSwitchLinkChange())) {
            return "inter-switch link has been detected";
        }

        InventoryReader reader = ev.getTxContext().getReadSpecific(InventoryReader.class);
        SalPort sport = ev.getSalPort();
        return (reader.isStaticEdgePort(sport)) ? "static edge port" : null;
    }

    // AutoCloseable

    /**
     * Close the internal packet service.
     */
    @Override
    public void close() {
        listeners.clear();
        super.close();
        packetService.set(null);

        for (Iterator<TimerTask> it = topologyWaiting.values().iterator(); it.hasNext();) {
            TimerTask task = it.next();
            task.cancel();
            it.remove();
        }
    }

    // PacketProcessingListener

    /**
     * Invoked when a PACKET_IN message has been notified.
     *
     * @param notification  A PACKET_IN notification.
     */
    @Override
    public void onPacketReceived(PacketReceived notification) {
        if (notification == null) {
            LOG.debug("Ignore null notification.");
            return;
        }

        NodeConnectorRef nref = notification.getIngress();
        SalPort ingress = SalPort.create(nref);
        if (ingress == null) {
            LOG.trace("Ignore packet received on unsupported port: {}", nref);
            return;
        }

        if (topologyWaiting.containsKey(ingress)) {
            LOG.trace("Ignore received packet due to topology detection: {}", ingress);
            return;
        }

        Iterator<VTNPacketListener> it = listeners.iterator();
        if (it.hasNext()) {
            PacketInEvent ev = newPacketInEvent(it.next(), notification, ingress);
            if (ev != null) {
                vtnProvider.post(ev);
                while (it.hasNext()) {
                    vtnProvider.post(new PacketInEvent(it.next(), ev));
                }
            }
        }
    }

    // CloseableContainer

    /**
     * {@inheritDoc}
     */
    @Override
    protected Logger getLogger() {
        return LOG;
    }

    // VTNInventoryListener

    /**
     * Invoked when a node information has been added or removed.
     *
     * @param ev  A {@link VtnNodeEvent} instance.
     */
    @Override
    public void notifyVtnNode(VtnNodeEvent ev) {
        if (ev.getUpdateType() == VtnUpdateType.REMOVED) {
            // Terminate topology waits on ports contained by the removed node.
            long num = ev.getSalNode().getNodeNumber();
            Set<Map.Entry<SalPort, TimerTask>> eset = topologyWaiting.entrySet();
            for (Iterator<Map.Entry<SalPort, TimerTask>> it = eset.iterator(); it.hasNext();) {
                Map.Entry<SalPort, TimerTask> ent = it.next();
                SalPort sport = ent.getKey();
                if (sport.getNodeNumber() == num) {
                    LOG.debug("{}: Terminate topology wait on removed node.", sport);
                    TimerTask task = ent.getValue();
                    task.cancel();
                }
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void notifyVtnPort(VtnPortEvent ev) throws VTNException {
        String reason = shouldTopologyWaitTerminate(ev);
        if (reason != null) {
            SalPort sport = ev.getSalPort();
            TimerTask task = topologyWaiting.remove(sport);
            if (task != null) {
                LOG.debug("{}: Terminate topology wait: reason={}", sport, reason);
                task.cancel();
            }
        } else if (Boolean.TRUE.equals(ev.getStateChange())) {
            // Wait for completion of topology detection on this port.
            addTopologyWaiting(ev.getSalPort());
        }
    }
}