blob: e555ab5bffe62c73a5359a206e5b05482924172a [file] [log] [blame]
package net.onrc.onos.core.hostmanager;
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.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.packet.ARP;
import net.onrc.onos.core.packet.Ethernet;
import net.onrc.onos.core.packet.IPv4;
import net.onrc.onos.core.topology.ITopologyService;
import net.onrc.onos.core.topology.MutableTopology;
import net.onrc.onos.core.topology.Port;
import net.onrc.onos.core.util.Dpid;
import net.onrc.onos.core.util.PortNumber;
import net.onrc.onos.core.util.PortNumberUtils;
import org.projectfloodlight.openflow.protocol.OFMessage;
import org.projectfloodlight.openflow.protocol.OFPacketIn;
import org.projectfloodlight.openflow.protocol.OFType;
import org.projectfloodlight.openflow.types.IPv4Address;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HostManager implements IFloodlightModule,
IOFMessageListener,
IHostService {
private static final Logger log = LoggerFactory.getLogger(HostManager.class);
private static final long HOST_CLEANING_INITIAL_DELAY = 30;
private int cleanupSecondConfig = 60 * 60;
private int agingMillisecConfig = 60 * 60 * 1000;
private CopyOnWriteArrayList<IHostListener> hostListeners;
private IFloodlightProviderService floodlightProvider;
private static final ScheduledExecutorService EXECUTOR_SERVICE =
Executors.newSingleThreadScheduledExecutor();
private ITopologyService topologyService;
private MutableTopology mutableTopology;
public enum HostUpdateType {
ADD, DELETE, UPDATE;
}
private class HostUpdate implements IUpdate {
private final Host host;
private final HostUpdateType type;
public HostUpdate(Host host, HostUpdateType type) {
this.host = host;
this.type = type;
}
@Override
public void dispatch() {
if (type == HostUpdateType.ADD) {
for (IHostListener listener : hostListeners) {
listener.hostAdded(host);
}
} else if (type == HostUpdateType.DELETE) {
for (IHostListener listener : hostListeners) {
listener.hostRemoved(host);
}
}
}
}
@Override
public String getName() {
return "hostmanager";
}
@Override
public boolean isCallbackOrderingPrereq(OFType type, String name) {
// We want link discovery to consume LLDP first otherwise we'll
// end up reading bad host 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) &&
(msg instanceof OFPacketIn)) {
OFPacketIn pi = (OFPacketIn) msg;
Ethernet eth = IFloodlightProviderService.bcStore.
get(cntx, IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
short inport = (short) cntx.getStorage()
.get(IFloodlightProviderService.CONTEXT_PI_INPORT);
return processPacketIn(sw, pi, eth, inport);
}
return Command.CONTINUE;
}
// This "protected" modifier is for unit test.
// The above "receive" method couldn't be tested
// because of IFloodlightProviderService static final field.
protected Command processPacketIn(IOFSwitch sw, OFPacketIn pi, Ethernet eth,
short inport) {
if (log.isTraceEnabled()) {
log.trace("Receive PACKET_IN swId {}, portId {}", sw.getId(), inport);
}
final Dpid dpid = new Dpid(sw.getId());
// FIXME method signature needs to be fixed. losing port number precision
final PortNumber portNum = PortNumberUtils
.openFlow(sw.getOFVersion(), inport);
Host srcHost =
getSourceHostFromPacket(eth, dpid.value(), portNum.value());
if (srcHost == null) {
return Command.STOP;
}
// If the switch port we try to attach a new host already has a link,
// then don't add the host
// TODO We probably don't need to check this here, it should be done in
// the Topology module.
mutableTopology.acquireReadLock();
try {
if (mutableTopology.getOutgoingLink(dpid, portNum) != null ||
mutableTopology.getIncomingLink(dpid, portNum) != null) {
log.debug("Not adding host {} as " +
"there is a link on the port: dpid {} port {}",
srcHost.getMacAddress(), dpid, portNum);
return Command.CONTINUE;
}
} finally {
mutableTopology.releaseReadLock();
}
Long mac = eth.getSourceMAC().toLong();
addHost(mac, srcHost);
if (log.isTraceEnabled()) {
log.trace("Add host info: {}", srcHost);
}
return Command.CONTINUE;
}
// Thread to delete hosts periodically.
// Remove all hosts from the map first and then finally delete hosts
// from the DB.
// TODO This should be sharded based on host 'owner' (i.e. the instance
// that owns the switch it is attached to). Currently any instance can
// issue deletes for any host, which permits race conditions and could
// cause the Topology replicas to diverge.
private class HostCleaner implements Runnable {
@Override
public void run() {
log.debug("called HostCleaner");
mutableTopology.acquireReadLock();
try {
Set<net.onrc.onos.core.topology.Host> deleteSet = new HashSet<>();
for (net.onrc.onos.core.topology.Host host : mutableTopology.getHosts()) {
long now = System.currentTimeMillis();
if ((now - host.getLastSeenTime() > agingMillisecConfig)) {
if (log.isTraceEnabled()) {
log.trace("Removing host info: mac {}, now {}, lastSeenTime {}, diff {}",
host.getMacAddress(), now, host.getLastSeenTime(), now - host.getLastSeenTime());
}
deleteSet.add(host);
}
}
for (net.onrc.onos.core.topology.Host host : deleteSet) {
deleteHostByMac(host.getMacAddress());
}
} catch (Exception e) {
// Any exception thrown by the task will prevent the Executor
// from running the next iteration, so we need to catch and log
// all exceptions here.
log.error("Exception in host cleanup thread:", e);
} finally {
mutableTopology.releaseReadLock();
}
}
}
/**
* Parse a host from an {@link Ethernet} packet.
*
* @param eth the packet to parse
* @param swdpid the switch on which the packet arrived
* @param port the port on which the packet arrived
* @return the host from the packet
*/
protected Host getSourceHostFromPacket(Ethernet eth,
long swdpid, long port) {
MACAddress sourceMac = eth.getSourceMAC();
int sourceIp = 0;
if (eth.getEtherType() == Ethernet.TYPE_IPV4) {
IPv4 ipv4 = (IPv4)eth.getPayload();
sourceIp = ipv4.getSourceAddress();
}
else if (eth.getEtherType() == Ethernet.TYPE_ARP) {
ARP arp = (ARP)eth.getPayload();
sourceIp = IPv4Address.of(arp.getSenderProtocolAddress()).getInt();
}
// Ignore broadcast/multicast source
if (sourceMac.isBroadcast() || sourceMac.isMulticast()) {
return null;
}
short vlan = eth.getVlanID();
return new Host(sourceMac,
sourceIp,
((vlan >= 0) ? vlan : null),
swdpid,
port,
new Date());
}
@Override
public Collection<Class<? extends IFloodlightService>> getModuleServices() {
List<Class<? extends IFloodlightService>> services =
new ArrayList<Class<? extends IFloodlightService>>();
services.add(IHostService.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(IHostService.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(ITopologyService.class);
return dependencies;
}
@Override
public void init(FloodlightModuleContext context)
throws FloodlightModuleException {
floodlightProvider = context.getServiceImpl(IFloodlightProviderService.class);
hostListeners = new CopyOnWriteArrayList<IHostListener>();
topologyService = context.getServiceImpl(ITopologyService.class);
mutableTopology = topologyService.getTopology();
setHostManagerProperties(context);
}
@Override
public void startUp(FloodlightModuleContext context) {
floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this);
EXECUTOR_SERVICE.scheduleAtFixedRate(new HostCleaner(),
HOST_CLEANING_INITIAL_DELAY, cleanupSecondConfig, TimeUnit.SECONDS);
}
@Override
public void deleteHost(Host host) {
floodlightProvider.publishUpdate(
new HostUpdate(host, HostUpdateType.DELETE));
}
@Override
public void deleteHostByMac(MACAddress mac) {
Host deleteHost = null;
mutableTopology.acquireReadLock();
try {
net.onrc.onos.core.topology.Host host = mutableTopology.getHostByMac(mac);
for (Port switchPort : host.getAttachmentPoints()) {
// We don't handle vlan now and multiple attachment points.
deleteHost = new Host(host.getMacAddress(),
host.getIpAddress(),
null,
switchPort.getDpid().value(),
switchPort.getNumber().value(),
new Date(host.getLastSeenTime()));
// FIXME: remove NOPMD flag after multiple attachment points are implemented
break; // NOPMD
}
} finally {
mutableTopology.releaseReadLock();
}
if (deleteHost != null) {
deleteHost(deleteHost);
}
}
@Override
public void addHost(Long mac, Host host) {
floodlightProvider.publishUpdate(
new HostUpdate(host, HostUpdateType.ADD));
}
@Override
public void addHostListener(IHostListener listener) {
hostListeners.add(listener);
}
@Override
public void removeHostListener(IHostListener listener) {
hostListeners.remove(listener);
}
private void setHostManagerProperties(FloodlightModuleContext context) {
Map<String, String> configOptions = context.getConfigParams(this);
String cleanupsec = configOptions.get("cleanupsec");
String agingmsec = configOptions.get("agingmsec");
if (cleanupsec != null) {
cleanupSecondConfig = Integer.parseInt(cleanupsec);
log.debug("CLEANUP_SECOND is set to {}", cleanupSecondConfig);
}
if (agingmsec != null) {
agingMillisecConfig = Integer.parseInt(agingmsec);
log.debug("AGEING_MILLSEC is set to {}", agingMillisecConfig);
}
}
}