/**
* Copyright 2012 InCNTRE, This file is released under Apache 2.0 license except for component libraries under different licenses
http://www.apache.org/licenses/LICENSE-2.0
*/
package edu.iu.incntre.flowscale;
import org.openflow.protocol.OFBarrierRequest;
import org.openflow.protocol.OFFlowMod;
import org.openflow.protocol.OFMatch;
import org.openflow.protocol.OFMessage;
import org.openflow.protocol.OFPhysicalPort;
import org.openflow.protocol.OFPortStatus;
import org.openflow.protocol.OFPortStatus.OFPortReason;
import org.openflow.protocol.OFType;
import org.openflow.protocol.action.OFAction;
import org.openflow.protocol.action.OFActionOutput;
import org.openflow.protocol.statistics.OFStatistics;
import org.openflow.util.HexString;
import org.openflow.util.U16;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.beaconcontroller.core.IBeaconProvider;
import net.beaconcontroller.core.IOFMessageListener;
import net.beaconcontroller.core.IOFSwitch;
import net.beaconcontroller.core.IOFSwitchListener;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import org.json.simple.JSONObject;
import edu.iu.incntre.flowscale.exception.NoDatabaseException;
import edu.iu.incntre.flowscale.exception.NoSwitchException;
/**
* This class is the main controller and communicates with the switch
*
* @author Ali Khalfan (akhalfan@indiana.edu)
*/
public class FlowscaleController implements IOFSwitchListener,
IOFMessageListener {
protected IBeaconProvider ibeaconProvider;
private HashMap<Long, SwitchDevice> controllerSwitches = new HashMap<Long, SwitchDevice>();
private HashMap<Integer, Group> groupList = new HashMap<Integer, Group>();
private String username, password, connectionString, dbDriverString;
private ArrayList<SwitchDevice> connectedSwitches = new ArrayList<SwitchDevice>();
private String mirroringRules;
private int defaultRulePriority;
private short mirrorPriority;
private HashMap<Long, Integer> maximumFlowsToPushHashMap = new HashMap<Long, Integer>();
HashMap<Long, HashMap<Short, Short>> switchFlowMirrorPortsHashMap;
private int maximumFlowsToPush;
public static Logger logger = LoggerFactory
.getLogger(FlowscaleController.class);
// implementation of the IOFMessage Listener
@Override
public Command receive(IOFSwitch sw, OFMessage msg) {
// TODO Auto-generated method stub
if (msg.getType() == OFType.BARRIER_REPLY) {
logger.info("received barrier info from switch {} with xid {}",
sw.getId(), msg.getXid());
if (msg.getXid() == 2) {
SwitchDevice switchDevice = controllerSwitches.get(sw.getId());
logger.debug("ports on initiation {}", sw.getFeaturesReply()
.getPorts());
switchDevice.setPhysicalPorts(sw.getFeaturesReply().getPorts());
controllerSwitches.put(sw.getId(), switchDevice);
for (Integer groupId : groupList.keySet()) {
Group group = groupList.get(groupId);
if (sw.getId() == group.getOutputSwitchDatapathId()) {
try {
group.switchUpAlert(sw);
} catch (Exception e) {
// TODO Auto-generated catch block
logger.error("{}", e);
}
}
}
logger.info("switch {} added",
HexString.toHexString(sw.getId()));
connectedSwitches.add(switchDevice);
}
}
if (msg.getType() == OFType.PACKET_IN) {
return Command.CONTINUE;
}
if (msg.getType() == OFType.PORT_STATUS) {
logger.info(
"controller received a port status message from switch {}",
HexString.toHexString(sw.getId()));
OFPortStatus ps = (OFPortStatus) msg;
logger.info(
"port {}, with h/w address {} sent a port update message",
ps.getDesc().getPortNumber(),
HexString.toHexString(ps.getDesc().getHardwareAddress()));
logger.info("Status of port is {}",
(ps.getDesc().getState() % 2 == 0) ? "up" : "down");
if (OFPortReason.values()[ps.getReason()] == OFPortReason.OFPPR_MODIFY) {
updateGroupsWithPortStatus(sw, ps.getDesc().getPortNumber(),
ps.getDesc());
}
// update switch as well
SwitchDevice switchDevice = controllerSwitches.get(sw.getId());
if (switchDevice != null) {
switchDevice.updatePort(ps);
}
}
return null;
}
/**
* once a port is updated , all groups that have this port as value must be
* invoked inorder to updated any flow associated with the particular port
*
* @param sw
* @param portNum
* @param physicalPort
*/
public void updateGroupsWithPortStatus(IOFSwitch sw, short portNum,
OFPhysicalPort physicalPort) {
logger.trace("updating groups with port number {} on switch {} ",
portNum, HexString.toHexString(sw.getId()));
for (Integer groupId : groupList.keySet()) {
Group group = groupList.get(groupId);
if ((sw.getId() == group.getInputSwitchDatapathId() || sw.getId() == group
.getOutputSwitchDatapathId())
&& (group.getInputPorts().contains(portNum) || group
.getOutputPorts().contains(portNum))) {
logger.trace(
"group with value {} contains this port ,updating ...",
group.getValues());
group.alert(sw, portNum, physicalPort, null);
}
}
}
// implementation of the IOFSwitchLisnter
@Override
public void addedSwitch(IOFSwitch sw) {
logger.info("in added switch method");
// TODO Auto-generated method stub
try {
logger.debug("controller switches are {}", controllerSwitches);
SwitchDevice switchDevice = controllerSwitches.get(sw.getId());
if (switchDevice == null) {
logger.info("switch {} device is not in list exiting...",
HexString.toHexString(sw.getId()));
return;
}
if (connectedSwitches.contains(switchDevice)) {
logger.info("switch is already connected exiting ");
return;
}
// initiate switch
initiateSwitch(sw);
switchDevice.setOpenFlowSwitch(sw);
} catch (Exception e) {
// TODO Auto-generated catch block
logger.info("adding group exception {}", e);
}
}
/**
* method invoked after the switch is connected to the controller and
* default reles are pushed to the switch in addition, the group rules
* associated with this switch are inserted
*
* @param sw
* IOFSwitch
*/
public void initiateSwitch(IOFSwitch sw) {
try {
// 1) delete all flows
OFFlowMod ofDeleteAll = new OFFlowMod();
OFMatch ofMatchAll = new OFMatch();
ofMatchAll.setWildcards(OFMatch.OFPFW_ALL);
ofDeleteAll.setMatch(ofMatchAll);
ofDeleteAll.setCommand(OFFlowMod.OFPFC_DELETE);
// 2) inset default drop rule to avoid sending any packet to the
// controller
OFFlowMod ofDefaultDropRule = new OFFlowMod();
ofDefaultDropRule.setPriority((short) defaultRulePriority);
ofDefaultDropRule.setMatch(ofMatchAll);
ofDefaultDropRule.setIdleTimeout((short) 0);
ofDefaultDropRule.setHardTimeout((short) 0);
ArrayList<OFAction> emptyActions = new ArrayList<OFAction>();
ofDefaultDropRule.setActions(emptyActions);
ArrayList<OFFlowMod> mirroringOFFlowMods = new ArrayList<OFFlowMod>();
// 3) insert mirroring rules for UISO flowscale
if (mirroringRules == null) {
logger.info("no mirroring rules configured");
} else {
String[] mirrorValues = mirroringRules.split(";");
// loop over comma separated value
OFFlowMod mirrorFlowMod;
OFMatch mirrorMatch;
ArrayList<OFAction> mirrorOutput;
OFActionOutput mirrorAction;
for (String mirrorValue : mirrorValues) {
String[] mirrorIndex = mirrorValue.split("-");
short inputPort = Short.parseShort(mirrorIndex[0]);
String[] mirrorPortValues = mirrorIndex[1].split(",");
// add the flows
mirrorFlowMod = new OFFlowMod();
mirrorMatch = new OFMatch();
mirrorMatch.setWildcards(OFMatch.OFPFW_ALL
^ OFMatch.OFPFW_IN_PORT);
mirrorMatch.setInputPort(inputPort);
mirrorFlowMod.setMatch(mirrorMatch);
mirrorFlowMod.setIdleTimeout((short) 0);
mirrorFlowMod.setHardTimeout((short) 0);
mirrorFlowMod.setPriority(mirrorPriority);
mirrorOutput = new ArrayList<OFAction>();
for (String mirrorPortValue : mirrorPortValues) {
mirrorAction = new OFActionOutput();
mirrorAction.setPort(Short.parseShort(mirrorPortValue));
mirrorOutput.add(mirrorAction);
}
mirrorFlowMod.setActions(mirrorOutput);
mirrorFlowMod.setBufferId(-1);
mirroringOFFlowMods.add(mirrorFlowMod);
}
}
// finally, insert above flows to the switch
sw.getOutputStream().write(ofDeleteAll);
logger.debug("deleting all flows...");
ofDefaultDropRule.setBufferId(-1);
ofDefaultDropRule.setLength(U16.t(OFFlowMod.MINIMUM_LENGTH));
logger.debug("adding default rule {}", ofDefaultDropRule.toString());
sw.getOutputStream().write(ofDefaultDropRule);
for (OFFlowMod mirrorFlowModValue : mirroringOFFlowMods) {
logger.debug("adding mirror rules {}",
mirrorFlowModValue.toString());
sw.getOutputStream().write(mirrorFlowModValue);
}
OFBarrierRequest ofbr = new OFBarrierRequest();
ofbr.setXid(2);
sw.getOutputStream().write(ofbr);
sw.getOutputStream().flush();
} catch (Exception e) {
FlowscaleController.logger.error("{}", e);
}
}
@Override
public void removedSwitch(IOFSwitch sw) {
// TODO Auto-generated method stub
for (Integer groupId : groupList.keySet()) {
Group group = groupList.get(groupId);
if (sw.getId() == group.getOutputSwitchDatapathId()) {
group.switchDownAlert(sw);
}
}
connectedSwitches.remove(controllerSwitches.get(sw.getId()));
try {
sw.getSocketChannel().close();
} catch (IOException e) {
// TODO Auto-generated catch block
logger.error("{}", e);
}
logger.info("switch {} removed", HexString.toHexString(sw.getId()));
}
@Override
public String getName() {
// TODO Auto-generated method stub
return "flowscaleController";
}
// controller listeners
public IBeaconProvider getIBeaconProvider() {
return this.ibeaconProvider;
}
public void setMaximumFlowsToPush(int maximumFlowsToPush) {
if (maximumFlowsToPush == 0) {
this.maximumFlowsToPush = Integer.MAX_VALUE;
} else {
this.maximumFlowsToPush = maximumFlowsToPush;
}
}
public int getMaximumFlowsToPush() {
return this.maximumFlowsToPush;
}
public void setBeaconProvider(IBeaconProvider beaconProvider) {
this.ibeaconProvider = beaconProvider;
}
public void setDefaultRulePriority(short defaultRulePriority) {
this.defaultRulePriority = defaultRulePriority;
}
public void setMirrorPriority(short mirrorPriority) {
this.mirrorPriority = mirrorPriority;
}
/**
* read config file and get the miorroing ports and store them in a HashMap
* , to be used whenever a flow is inserted with the mirrored port, it's
* mirroring port is also inserted
*
* @param flowMirrorPorts
*/
public void setFlowMirrorPorts(String flowMirrorPorts) {
switchFlowMirrorPortsHashMap = new HashMap<Long, HashMap<Short, Short>>();
String[] switchMirrorConfig = flowMirrorPorts.split("-");
for (String switchMirrorConfigValue : switchMirrorConfig) {
String[] switchSplitter = switchMirrorConfigValue.split(":");
long switchDatapathId = HexString.toLong(switchSplitter[0]);
HashMap<Short, Short> mirrors = new HashMap<Short, Short>();
String[] mirrorPorts = switchSplitter[1].split(";");
for (String mirrorPortsValue : mirrorPorts) {
String[] flowMirrors = mirrorPortsValue.split(",");
mirrors.put(Short.parseShort(flowMirrors[0]),
Short.parseShort(flowMirrors[1]));
}
switchFlowMirrorPortsHashMap.put(switchDatapathId, mirrors);
}
}
public void setMirroringRules(String mirroringRules) {
this.mirroringRules = mirroringRules;
}
public HashMap<Long, HashMap<Short, Short>> getSwitchFlowMirrorPortsHashMap() {
return this.switchFlowMirrorPortsHashMap;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public void setConnectionString(String connectionString) {
this.connectionString = connectionString;
}
public void setDbDriverString(String dbDriverString) {
this.dbDriverString = dbDriverString;
}
public void startUp() {
logger.info("starting controller ");
DatabaseUtility db = new DatabaseUtility();
db.setConnection(username, password, connectionString, dbDriverString);
logger.info("initiating controller");
// adding listeners
ibeaconProvider.addOFMessageListener(OFType.PACKET_IN, this);
ibeaconProvider.addOFMessageListener(OFType.FEATURES_REPLY, this);
ibeaconProvider.addOFMessageListener(OFType.ECHO_REQUEST, this);
ibeaconProvider.addOFMessageListener(OFType.ERROR, this);
ibeaconProvider.addOFMessageListener(OFType.PORT_MOD, this);
ibeaconProvider.addOFMessageListener(OFType.PORT_STATUS, this);
ibeaconProvider.addOFMessageListener(OFType.BARRIER_REPLY, this);
logger.info("adding switch listener");
ibeaconProvider.addOFSwitchListener(this);
try {
this.controllerSwitches = db.populateSwitchesFromDatabase(this);
this.groupList = db.populateGroupsFromDatabase(this);
logger.debug("controller switches are {}", controllerSwitches);
} catch (NoDatabaseException e) {
// TODO Auto-generated catch block
logger.error("No Database loaded, please update your configuration and rerun the FlowScale");
ibeaconProvider.notify();
}
logger.debug("groupList has {}", groupList);
for (Integer groupId : groupList.keySet()) {
Group group = groupList.get(groupId);
}
logger.info("controller instance is {}", this.toString());
}
public void shutDown() {
logger.info("controller is shutting down");
ibeaconProvider.removeOFMessageListener(OFType.PACKET_IN, this);
ibeaconProvider.removeOFMessageListener(OFType.ECHO_REQUEST, this);
ibeaconProvider.removeOFMessageListener(OFType.ERROR, this);
ibeaconProvider.removeOFSwitchListener(this);
try {
} catch (Exception e) {
// TODO Auto-generated catch block
logger.error("{}", e
);
}
}
public HashMap<Long, SwitchDevice> getSwitchDevices() {
return this.controllerSwitches;
}
/**
* insterface to add switches , usually called from flowscalehttplistener
*
* @see handle method in flowscalehttplistener
* @param datapathIdString
*/
public void addSwitchFromInterface(String datapathIdString) {
long datapathId = HexString.toLong(datapathIdString);
SwitchDevice switchDevice = new SwitchDevice(datapathId);
IOFSwitch ofSwitch = ibeaconProvider.getSwitches().get(datapathId);
if (ofSwitch != null) {
switchDevice.setOpenFlowSwitch(ofSwitch);
switchDevice.setPhysicalPorts(ofSwitch.getFeaturesReply()
.getPorts());
}
controllerSwitches.put(datapathId, switchDevice);
}
public void removeSwitchFromInterface(String datapathIdString) {
long datapathId = HexString.toLong(datapathIdString);
controllerSwitches.remove(datapathId);
}
/**
* Method called from flowscalehttplistener (may be called also from cli) in
* order to create a new group not that this method will NOT store the deta
* in the database at the time being, that will be done by the calling
* interface itself
*
* @param groupIdString
* @param groupName
* @param inputSwitchDatapathIdString
* @param outputSwitchDatapathIdString
* @param inputPortListString
* @param outputPortListString
* @param typeString
* @param priorityString
* @param valuesString
* @param maximumFlowsAllowedString
* @param networkProtocolString
* @param transportDirectionString
* @return a string that will be presented in JSON format to be interpreted
* by the interface
*/
public String addGroupFromInterface(String groupIdString, String groupName,
String inputSwitchDatapathIdString,
String outputSwitchDatapathIdString, String inputPortListString,
String outputPortListString, String typeString,
String priorityString, String valuesString,
String maximumFlowsAllowedString, String networkProtocolString,
String transportDirectionString) {
Group g = new Group(this);
g.addGroupDetails(groupIdString, groupName,
inputSwitchDatapathIdString, outputSwitchDatapathIdString,
inputPortListString, outputPortListString, typeString,
priorityString, valuesString, maximumFlowsAllowedString,
networkProtocolString, transportDirectionString);
g.pushRules();
groupList.put(Integer.parseInt(groupIdString), g);
JSONObject jsonObject = new JSONObject();
jsonObject.put("result", "group added");
return jsonObject.toJSONString();
}
/**
* method to edit group, avoid for now
*
* @param groupIdString
* @param editTypeString
* @param updateValueString
* @return JSON string to be interpreted by the interface
* @deprecated
*/
@Deprecated
public String editGroupFromInterface(String groupIdString,
String editTypeString, String updateValueString) {
Group g = groupList.get(Integer.parseInt(groupIdString));
g.editGroup(editTypeString, updateValueString);
return null;
}
/**
* Method used to delete a group and remove its flows from the switches,
* note that this method will NOT deal with the database
*
* @param groupIdString
* @return JSON string to be interpreted by interface
*/
public String deleteGroupFromInterface(String groupIdString) {
logger.debug(groupList.toString());
Group g = groupList.get(Integer.parseInt(groupIdString));
g.removeGroup();
groupList.remove(Integer.parseInt(groupIdString));
return null;
}
/**
* get statistics from switch based on type specified from interface
*
* @param datapathIdString
* @param typeString
* @return List<OFStatistics> statistics from switch
* @throws NoSwitchException
* @throws IOException
* @throws InterruptedException
* @throws ExecutionException
* @throws TimeoutException
*/
public List<OFStatistics> getSwitchStatisticsFromInterface(
String datapathIdString, String typeString)
throws NoSwitchException, IOException, InterruptedException,
ExecutionException, TimeoutException {
long datapathId = HexString.toLong(datapathIdString);
SwitchDevice switchDevice = controllerSwitches.get(datapathId);
if (switchDevice == null) {
throw new NoSwitchException(datapathIdString);
}
List<OFStatistics> ofst = switchDevice.getStatistics(typeString);
return ofst;
}
/**
* called by the hot swapping bundle :flowscaleflowupdate , will have the
* modified flows to swap from high loaded ports to low ones
*
* @param ofFlowMods
* flows that are passed by the flowscaleflowupdate bundle
* @param datapathId
* id of concerned switch that we desire to hotswap flows
*/
public void injectFlows(ArrayList<OFFlowMod> ofFlowMods, long datapathId) {
logger.info("injecting flows in controller");
IOFSwitch sw = this.ibeaconProvider.getSwitches().get(datapathId);
if (sw == null) {
logger.error("no switch {} exists", datapathId);
return;
}
for (OFFlowMod ofFlowMod : ofFlowMods) {
logger.info("injecting flow {}", ofFlowMod);
for (Integer groupKey : groupList.keySet()) {
Group group = groupList.get(groupKey);
ArrayList<OFRule> groupRules = (ArrayList<OFRule>) group
.getGroupRules();
for (OFRule rule : groupRules) {
if (rule.getMatch().equals(ofFlowMod.getMatch())
&& group.getOutputSwitchDatapathId() == datapathId) {
logger.info("rule equal", rule.getMatch().toString(),
ofFlowMod.getMatch().toString());
ofFlowMod.setPriority(rule.getPriority());
ArrayList<OFAction> aList = rule.getActions();
aList.clear();
short port = ((OFActionOutput) ofFlowMod.getActions()
.get(0)).getPort();
rule.setPort(port);
Short mirrorPort = 0;
if (switchFlowMirrorPortsHashMap.get(datapathId) != null) {
mirrorPort = switchFlowMirrorPortsHashMap.get(
datapathId).get(port);
}
if (mirrorPort != null) {
rule.setMirrorPort(mirrorPort);
}
logger.info("new rule is {} and port is {}",
rule.getMatch(), rule.getActions().get(0));
}
}
}
try {
sw.getOutputStream().write(ofFlowMod);
} catch (IOException e) {
// TODO Auto-generated catch block
logger.error("{}", e);
}
}
try {
sw.getOutputStream().flush();
} catch (IOException e) {
// TODO Auto-generated catch block
logger.error("{}", e);
}
}
}