blob: 30ad722112888676b167204b8f128ad2443a668d [file] [log] [blame]
package net.onrc.onos.core.devicemanager;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import net.floodlightcontroller.core.FloodlightContext;
import net.floodlightcontroller.core.IFloodlightProviderService;
import net.floodlightcontroller.core.IOFMessageListener;
import net.floodlightcontroller.core.IOFSwitch;
import net.floodlightcontroller.core.IUpdate;
import net.floodlightcontroller.core.module.FloodlightModuleContext;
import net.floodlightcontroller.core.module.FloodlightModuleException;
import net.floodlightcontroller.core.module.IFloodlightModule;
import net.floodlightcontroller.core.module.IFloodlightService;
import net.floodlightcontroller.util.MACAddress;
import net.onrc.onos.core.datagrid.IDatagridService;
import net.onrc.onos.core.datagrid.IEventChannel;
import net.onrc.onos.core.datagrid.IEventChannelListener;
import net.onrc.onos.core.packet.ARP;
import net.onrc.onos.core.packet.DHCP;
import net.onrc.onos.core.packet.Ethernet;
import net.onrc.onos.core.packet.IPv4;
import net.onrc.onos.core.packet.UDP;
import net.onrc.onos.core.topology.INetworkGraphService;
import net.onrc.onos.core.topology.NetworkGraph;
import org.openflow.protocol.OFMessage;
import org.openflow.protocol.OFPacketIn;
import org.openflow.protocol.OFType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class OnosDeviceManager implements IFloodlightModule,
IOFMessageListener,
IOnosDeviceService,
IEventChannelListener<Long, OnosDevice> {
private static final Logger log = LoggerFactory.getLogger(OnosDeviceManager.class);
private static final int CLEANUP_SECOND = 60 * 60;
private static final int AGEING_MILLSEC = 60 * 60 * 1000;
private CopyOnWriteArrayList<IOnosDeviceListener> deviceListeners;
private IFloodlightProviderService floodlightProvider;
private static final ScheduledExecutorService EXECUTOR_SERVICE = Executors.newSingleThreadScheduledExecutor();
private IDatagridService datagrid;
private IEventChannel<Long, OnosDevice> eventChannel;
private static final String DEVICE_CHANNEL_NAME = "onos.device";
private Map<Long, OnosDevice> mapDevice = new ConcurrentHashMap<Long, OnosDevice>();
private INetworkGraphService networkGraphService;
private NetworkGraph networkGraph;
public enum OnosDeviceUpdateType {
ADD, DELETE, UPDATE;
}
private class OnosDeviceUpdate implements IUpdate {
private OnosDevice device;
private OnosDeviceUpdateType type;
public OnosDeviceUpdate(OnosDevice device, OnosDeviceUpdateType type) {
this.device = device;
this.type = type;
}
@Override
public void dispatch() {
if (type == OnosDeviceUpdateType.ADD) {
for (IOnosDeviceListener listener : deviceListeners) {
listener.onosDeviceAdded(device);
}
} else if (type == OnosDeviceUpdateType.DELETE) {
for (IOnosDeviceListener listener : deviceListeners) {
listener.onosDeviceRemoved(device);
}
}
}
}
@Override
public String getName() {
return "onosdevicemanager";
}
@Override
public boolean isCallbackOrderingPrereq(OFType type, String name) {
// We want link discovery to consume LLDP first otherwise we'll
// end up reading bad device info from LLDP packets
return type == OFType.PACKET_IN && "linkdiscovery".equals(name);
}
@Override
public boolean isCallbackOrderingPostreq(OFType type, String name) {
return type == OFType.PACKET_IN &&
("proxyarpmanager".equals(name) || "onosforwarding".equals(name));
}
@Override
public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {
if (msg.getType().equals(OFType.PACKET_IN)) {
OFPacketIn pi = (OFPacketIn) msg;
Ethernet eth = IFloodlightProviderService.bcStore.
get(cntx, IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
return processPacketIn(sw, pi, eth);
}
return Command.CONTINUE;
}
private Command processPacketIn(IOFSwitch sw, OFPacketIn pi, Ethernet eth) {
long dpid = sw.getId();
short portId = pi.getInPort();
Long mac = eth.getSourceMAC().toLong();
OnosDevice srcDevice =
getSourceDeviceFromPacket(eth, dpid, portId);
if (srcDevice == null) {
return Command.STOP;
}
//We check if it is the same device in datagrid to suppress the device update
OnosDevice exDev = mapDevice.get(mac);
if (exDev != null) {
if (exDev.equals(srcDevice)) {
//There is the same existing device. Update only ActiveSince time.
exDev.setLastSeenTimestamp(new Date());
if (log.isTraceEnabled()) {
log.debug("In the datagrid, there is the same device."
+ "Only update last seen time. dpid {}, port {}, mac {}, ip {}, lastSeenTime {}",
dpid, portId, srcDevice.getMacAddress(), srcDevice.getIpv4Address(), srcDevice.getLastSeenTimestamp().getTime());
}
return Command.CONTINUE;
} else if (srcDevice.getIpv4Address() == null &&
exDev.getSwitchDPID().equals(srcDevice.getSwitchDPID()) &&
exDev.getSwitchPort() == srcDevice.getSwitchPort()) {
//Vlan should be handled based on the Onos spec. Until then, don't handle it.
//Device attachment point and mac address are the same
//but the packet does not have an ip address.
exDev.setLastSeenTimestamp(new Date());
if (log.isTraceEnabled()) {
log.debug("In the datagrid, there is the same device with no ip."
+ "Keep ip and update last seen time. dpid {}, port {}, mac {}, ip {} lastSeenTime {}",
dpid, portId, srcDevice.getMacAddress(), exDev.getIpv4Address(), srcDevice.getLastSeenTimestamp().getTime());
}
return Command.CONTINUE;
}
}
//If the switch port we try to attach a new device already has a link, then stop adding device
if (networkGraph.getLink(dpid, (long) portId) != null) {
if (log.isTraceEnabled()) {
log.debug("Stop adding OnosDevice {} due to there is a link to: dpid {} port {}",
srcDevice.getMacAddress(), dpid, portId);
}
return Command.CONTINUE;
}
addOnosDevice(mac, srcDevice);
if (log.isTraceEnabled()) {
log.debug("Add device info in the set. dpid {}, port {}, mac {}, ip {}, lastSeenTime {}",
dpid, portId, srcDevice.getMacAddress(), srcDevice.getIpv4Address(), srcDevice.getLastSeenTimestamp().getTime());
}
return Command.CONTINUE;
}
//Thread to delete devices periodically.
//Remove all devices from the map first and then finally delete devices from the DB.
private class CleanDevice implements Runnable {
@Override
public void run() {
log.debug("called CleanDevice");
try {
Set<OnosDevice> deleteSet = new HashSet<OnosDevice>();
for (OnosDevice dev : mapDevice.values()) {
long now = new Date().getTime();
if ((now - dev.getLastSeenTimestamp().getTime() > AGEING_MILLSEC)) {
if (log.isTraceEnabled()) {
log.debug("Remove device info in the datagrid. dpid {}, port {}, mac {}, ip {}, lastSeenTime {}, diff {}",
dev.getSwitchDPID(), dev.getSwitchPort(), dev.getMacAddress(), dev.getIpv4Address(),
dev.getLastSeenTimestamp().getTime(), now - dev.getLastSeenTimestamp().getTime());
}
deleteSet.add(dev);
}
}
for (OnosDevice dev : deleteSet) {
deleteOnosDevice(dev);
}
} catch (Exception e) {
log.error("Error:", e);
}
}
}
/**
* Get IP address from packet if the packet is either an ARP
* or a DHCP packet
*
* @param eth
* @param dlAddr
* @return
*/
private int getSrcNwAddr(Ethernet eth, long dlAddr) {
if (eth.getPayload() instanceof ARP) {
ARP arp = (ARP) eth.getPayload();
if ((arp.getProtocolType() == ARP.PROTO_TYPE_IP) &&
(Ethernet.toLong(arp.getSenderHardwareAddress()) == dlAddr)) {
return IPv4.toIPv4Address(arp.getSenderProtocolAddress());
}
} else if (eth.getPayload() instanceof IPv4) {
IPv4 ipv4 = (IPv4) eth.getPayload();
if (ipv4.getPayload() instanceof UDP) {
UDP udp = (UDP) ipv4.getPayload();
if (udp.getPayload() instanceof DHCP) {
DHCP dhcp = (DHCP) udp.getPayload();
if (dhcp.getOpCode() == DHCP.OPCODE_REPLY) {
return ipv4.getSourceAddress();
}
}
}
}
return 0;
}
/**
* Parse an entity from an {@link Ethernet} packet.
*
* @param eth the packet to parse
* @param sw the switch on which the packet arrived
* @param pi the original packetin
* @return the entity from the packet
*/
private OnosDevice getSourceDeviceFromPacket(Ethernet eth,
long swdpid,
short port) {
byte[] dlAddrArr = eth.getSourceMACAddress();
long dlAddr = Ethernet.toLong(dlAddrArr);
// Ignore broadcast/multicast source
if ((dlAddrArr[0] & 0x1) != 0)
return null;
short vlan = eth.getVlanID();
int nwSrc = getSrcNwAddr(eth, dlAddr);
return new OnosDevice(MACAddress.valueOf(dlAddr),
((vlan >= 0) ? vlan : null),
((nwSrc != 0) ? nwSrc : null),
swdpid,
port,
new Date());
}
@Override
public Collection<Class<? extends IFloodlightService>> getModuleServices() {
List<Class<? extends IFloodlightService>> services =
new ArrayList<Class<? extends IFloodlightService>>();
services.add(IOnosDeviceService.class);
return services;
}
@Override
public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() {
Map<Class<? extends IFloodlightService>, IFloodlightService> impls =
new HashMap<Class<? extends IFloodlightService>, IFloodlightService>();
impls.put(IOnosDeviceService.class, this);
return impls;
}
@Override
public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
List<Class<? extends IFloodlightService>> dependencies =
new ArrayList<Class<? extends IFloodlightService>>();
dependencies.add(IFloodlightProviderService.class);
dependencies.add(INetworkGraphService.class);
dependencies.add(IDatagridService.class);
return dependencies;
}
@Override
public void init(FloodlightModuleContext context)
throws FloodlightModuleException {
floodlightProvider = context.getServiceImpl(IFloodlightProviderService.class);
EXECUTOR_SERVICE.scheduleAtFixedRate(new CleanDevice(), 30, CLEANUP_SECOND, TimeUnit.SECONDS);
deviceListeners = new CopyOnWriteArrayList<IOnosDeviceListener>();
datagrid = context.getServiceImpl(IDatagridService.class);
networkGraphService = context.getServiceImpl(INetworkGraphService.class);
networkGraph = networkGraphService.getNetworkGraph();
}
@Override
public void startUp(FloodlightModuleContext context) {
floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this);
eventChannel = datagrid.addListener(DEVICE_CHANNEL_NAME, this,
Long.class,
OnosDevice.class);
}
@Override
public void deleteOnosDevice(OnosDevice dev) {
Long mac = dev.getMacAddress().toLong();
eventChannel.removeEntry(mac);
floodlightProvider.publishUpdate(new OnosDeviceUpdate(dev, OnosDeviceUpdateType.DELETE));
}
@Override
public void deleteOnosDeviceByMac(MACAddress mac) {
OnosDevice deleteDevice = mapDevice.get(mac);
deleteOnosDevice(deleteDevice);
}
@Override
public void addOnosDevice(Long mac, OnosDevice dev) {
eventChannel.addEntry(mac, dev);
floodlightProvider.publishUpdate(new OnosDeviceUpdate(dev, OnosDeviceUpdateType.ADD));
}
@Override
public void entryAdded(OnosDevice dev) {
Long mac = dev.getMacAddress().toLong();
mapDevice.put(mac, dev);
log.debug("Device added: device mac {}", mac);
}
@Override
public void entryRemoved(OnosDevice dev) {
Long mac = dev.getMacAddress().toLong();
mapDevice.remove(mac);
log.debug("Device removed: device mac {}", mac);
}
@Override
public void entryUpdated(OnosDevice dev) {
Long mac = dev.getMacAddress().toLong();
mapDevice.put(mac, dev);
log.debug("Device updated: device mac {}", mac);
}
@Override
public void addOnosDeviceListener(IOnosDeviceListener listener) {
deviceListeners.add(listener);
}
@Override
public void deleteOnosDeviceListener(IOnosDeviceListener listener) {
deviceListeners.remove(listener);
}
}