blob: 025b6d7524ad0152d2d563eea96523e64fcab445 [file] [log] [blame]
package net.onrc.onos.core.intent.runtime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.LinkedBlockingQueue;
import net.floodlightcontroller.core.FloodlightContext;
import net.floodlightcontroller.core.IFloodlightProviderService;
import net.floodlightcontroller.core.IOFMessageListener;
import net.floodlightcontroller.core.IOFSwitch;
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.onrc.onos.core.datagrid.IDatagridService;
import net.onrc.onos.core.datagrid.IEventChannel;
import net.onrc.onos.core.datagrid.IEventChannelListener;
import net.onrc.onos.core.flowprogrammer.IFlowPusherService;
import net.onrc.onos.core.intent.FlowEntry;
import net.onrc.onos.core.intent.Intent;
import net.onrc.onos.core.intent.Intent.IntentState;
import net.onrc.onos.core.intent.IntentOperation;
import net.onrc.onos.core.intent.IntentOperationList;
import net.onrc.onos.core.intent.PathIntent;
import net.onrc.onos.core.intent.ShortestPathIntent;
import net.onrc.onos.core.topology.ITopologyService;
import org.openflow.protocol.OFFlowRemoved;
import org.openflow.protocol.OFMessage;
import org.openflow.protocol.OFType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The PlanInstallModule contains the PlanCalcRuntime and PlanInstallRuntime.
* <p>
* It is responsible for converting Intents into FlowMods and seeing that
* they are properly installed.
*/
public class PlanInstallModule implements IFloodlightModule, IOFMessageListener {
protected volatile IFloodlightProviderService floodlightProvider;
protected volatile ITopologyService topologyService;
protected volatile IDatagridService datagridService;
protected volatile IFlowPusherService flowPusher;
private PlanCalcRuntime planCalc;
private PlanInstallRuntime planInstall;
private EventListener eventListener;
private IEventChannel<Long, IntentStateList> intentStateChannel;
private static final Logger log = LoggerFactory.getLogger(PlanInstallModule.class);
private static final String PATH_INTENT_CHANNEL_NAME = "onos.pathintent";
private static final String INTENT_STATE_EVENT_CHANNEL_NAME = "onos.pathintent_state";
private ConcurrentMap<String, Intent> parentIntentMap = new ConcurrentHashMap<String, Intent>();
/**
* EventListener for Intent updates from the PathCalcRuntime module.
*/
class EventListener extends Thread
implements IEventChannelListener<Long, IntentOperationList> {
private BlockingQueue<IntentOperationList> intentQueue = new LinkedBlockingQueue<>();
private Long key = Long.valueOf(0);
/**
* Poll the Intent queue for events.
*/
@Override
public void run() {
while (true) {
try {
IntentOperationList intents = intentQueue.take();
//TODO: consider draining the remaining intent lists
// and processing in one big batch
processIntents(intents);
} catch (InterruptedException e) {
log.warn("Error taking from intent queue: {}", e.getMessage());
}
}
}
/**
* Process events received from the event queue.
* <p>
* First, compute a plan, and then install it.
*
* @param intents list of new Intent events
*/
private void processIntents(IntentOperationList intents) {
log("start_processIntents");
log.debug("Processing OperationList {}", intents);
log("begin_computePlan");
List<Set<FlowEntry>> plan = planCalc.computePlan(intents);
log("end_computePlan");
log.debug("Plan: {}", plan);
log("begin_installPlan");
boolean success = planInstall.installPlan(plan);
log("end_installPlan");
Set<Long> domainSwitchDpids = floodlightProvider.getSwitches().keySet();
log("begin_sendInstallNotif");
sendNotifications(intents, true, success, domainSwitchDpids);
log("end_sendInstallNotif");
log("finish");
}
/***
* This function is for sending intent state notification to other ONOS instances.
* The argument of "domainSwitchDpids" is required for dispatching this ONOS's managed switches.
*
* @param intents list of intents
* @param installed true if Intents were installed
* @param success true if updates succeeded
* @param domainSwitchDpids set of affected switches
*/
private void sendNotifications(IntentOperationList intents,
boolean installed, boolean success, Set<Long> domainSwitchDpids) {
IntentStateList states = new IntentStateList();
for (IntentOperation i : intents) {
IntentState newState;
switch (i.operator) {
case REMOVE:
if (installed) {
newState = success ? IntentState.DEL_ACK : IntentState.DEL_PENDING;
} else {
newState = IntentState.DEL_REQ;
}
break;
case ADD:
default:
if (installed) {
if (domainSwitchDpids != null) {
states.domainSwitchDpids.addAll(domainSwitchDpids);
}
newState = success ? IntentState.INST_ACK : IntentState.INST_NACK;
} else {
newState = IntentState.INST_REQ;
}
break;
}
states.put(i.intent.getId(), newState);
}
if (log.isTraceEnabled()) {
log.trace("sendNotifications, states {}, domainSwitchDpids {}",
states, states.domainSwitchDpids);
}
// Send notifications using the same key every time
// and receive them by entryAdded() and entryUpdated()
intentStateChannel.addTransientEntry(key, states);
}
/**
* Uses the entryUpdated() method. This should only be called once
* because we always use the same key.
*/
@Override
public void entryAdded(IntentOperationList value) {
entryUpdated(value);
}
/*
* (non-Javadoc)
* @see net.onrc.onos.core.datagrid.IEventChannelListener#entryRemoved(java.lang.Object)
*/
@Override
public void entryRemoved(IntentOperationList value) {
// This channel is a queue, so this method is not needed
}
/**
* Adds the new intents to the event queue.
*
* @param value list of new intents
*/
@Override
public void entryUpdated(IntentOperationList value) {
putIntentOpsInfoInParentMap(value);
log("start_intentNotifRecv");
log("begin_sendReceivedNotif");
sendNotifications(value, false, false, null);
log("end_sendReceivedNotif");
log("finish");
log.debug("Added OperationList {}", value);
try {
intentQueue.put(value);
} catch (InterruptedException e) {
log.warn("Error putting to intent queue: {}", e.getMessage());
}
}
/**
* Add intent information to parent map.
*
* @param intentOps list of Intents and new states
*/
private void putIntentOpsInfoInParentMap(IntentOperationList intentOps) {
for (IntentOperation i : intentOps) {
if (!(i.intent instanceof PathIntent)) {
log.warn("Not a path intent: {}", i);
continue;
}
PathIntent intent = (PathIntent) i.intent;
Intent parent = intent.getParentIntent();
if (parent instanceof ShortestPathIntent) {
parentIntentMap.put(parent.getId(), parent);
} else {
log.warn("Unsupported Intent: {}", parent);
continue;
}
}
}
}
/**
* Formatted log for debugging.
* TODO: merge this into debugging framework
*
* @param step the step of computation
*/
public static void log(String step) {
log.debug("Time:{}, Step:{}", System.nanoTime(), step);
}
/*
* (non-Javadoc)
* @see net.floodlightcontroller.core.module.IFloodlightModule#init
* (net.floodlightcontroller.core.module.FloodlightModuleContext)
*/
@Override
public void init(FloodlightModuleContext context)
throws FloodlightModuleException {
floodlightProvider = context.getServiceImpl(IFloodlightProviderService.class);
topologyService = context.getServiceImpl(ITopologyService.class);
datagridService = context.getServiceImpl(IDatagridService.class);
flowPusher = context.getServiceImpl(IFlowPusherService.class);
planCalc = new PlanCalcRuntime();
planInstall = new PlanInstallRuntime(floodlightProvider, flowPusher);
eventListener = new EventListener();
}
/*
* (non-Javadoc)
* @see net.floodlightcontroller.core.module.IFloodlightModule#startUp
* (net.floodlightcontroller.core.module.FloodlightModuleContext)
*/
@Override
public void startUp(FloodlightModuleContext context) {
// start subscriber
datagridService.addListener(PATH_INTENT_CHANNEL_NAME,
eventListener,
Long.class,
IntentOperationList.class);
eventListener.start();
// start publisher
intentStateChannel = datagridService.createChannel(INTENT_STATE_EVENT_CHANNEL_NAME,
Long.class,
IntentStateList.class);
floodlightProvider.addOFMessageListener(OFType.FLOW_REMOVED, this);
}
/*
* (non-Javadoc)
* @see net.floodlightcontroller.core.module.IFloodlightModule#getModuleDependencies()
*/
@Override
public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
Collection<Class<? extends IFloodlightService>> l =
new ArrayList<Class<? extends IFloodlightService>>();
l.add(IFloodlightProviderService.class);
l.add(ITopologyService.class);
l.add(IDatagridService.class);
l.add(IFlowPusherService.class);
return l;
}
/*
* (non-Javadoc)
* @see net.floodlightcontroller.core.module.IFloodlightModule#getModuleServices()
*/
@Override
public Collection<Class<? extends IFloodlightService>> getModuleServices() {
// no services, for now
return null;
}
/*
* (non-Javadoc)
* @see net.floodlightcontroller.core.module.IFloodlightModule#getServiceImpls()
*/
@Override
public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() {
// no services, for now
return null;
}
/**
* Handles FlowRemoved messages from the switches.
*/
@Override
public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {
if (msg.getType().equals(OFType.FLOW_REMOVED) &&
(msg instanceof OFFlowRemoved)) {
OFFlowRemoved flowRemovedMsg = (OFFlowRemoved) msg;
if (log.isTraceEnabled()) {
log.trace("Receive flowRemoved from sw {} : Cookie {}",
sw.getId(), flowRemovedMsg.getCookie());
}
String intentParentId = Long.toString(flowRemovedMsg.getCookie());
Intent intent = parentIntentMap.get(intentParentId);
//We assume if the path src sw flow entry is expired,
//the path is expired.
if (!isFlowSrcRemoved(sw.getId(), intentParentId)) {
return Command.CONTINUE;
}
ShortestPathIntent spfIntent = null;
if (!(intent instanceof ShortestPathIntent)) {
return Command.CONTINUE;
}
spfIntent = (ShortestPathIntent) intent;
String pathIntentId = spfIntent.getPathIntentId();
IntentStateList states = new IntentStateList();
IntentState newState = IntentState.DEL_ACK;
states.put(pathIntentId, newState);
Set<Long> domainSwitchDpids = floodlightProvider.getSwitches().keySet();
if (domainSwitchDpids != null) {
states.domainSwitchDpids.addAll(domainSwitchDpids);
}
parentIntentMap.remove(intentParentId);
log.debug("addEntry to intentStateChannel intentId {}, states {}",
pathIntentId, newState);
intentStateChannel.addTransientEntry(flowRemovedMsg.getCookie(), states);
}
return Command.CONTINUE;
}
/**
* Check to see if the flow's source switch entry is removed/expired.
*
* @param dpid DPID of affected switch
* @param shortestPathIntentId Intent to check
* @return true if the flow's source switch entry is removed or expired,
* otherwise false.
*/
private boolean isFlowSrcRemoved(long dpid, String shortestPathIntentId) {
Intent intent = parentIntentMap.get(shortestPathIntentId);
ShortestPathIntent spfIntent = null;
if (intent instanceof ShortestPathIntent) {
spfIntent = (ShortestPathIntent) intent;
}
if (spfIntent == null) {
return false;
}
long srcSwDpid = spfIntent.getSrcSwitchDpid();
if (srcSwDpid == dpid) {
return true;
}
return false;
}
/*
* (non-Javadoc)
* @see net.floodlightcontroller.core.IListener#getName()
*/
@Override
public String getName() {
// TODO Auto-generated method stub
return null;
}
/*
* (non-Javadoc)
* @see net.floodlightcontroller.core.IListener#isCallbackOrderingPrereq(java.lang.Object, java.lang.String)
*/
@Override
public boolean isCallbackOrderingPrereq(OFType type, String name) {
// TODO Auto-generated method stub
return false;
}
/*
* (non-Javadoc)
* @see net.floodlightcontroller.core.IListener#isCallbackOrderingPostreq(java.lang.Object, java.lang.String)
*/
@Override
public boolean isCallbackOrderingPostreq(OFType type, String name) {
// TODO Auto-generated method stub
return false;
}
}