package net.floodlightcontroller.hand; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.PriorityBlockingQueue; import java.util.Properties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import net.floodlightcontroller.core.IFloodlightProviderService; 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.restserver.IRestApiService; import net.floodlightcontroller.staticflowentry.IStaticFlowEntryPusherService; import net.floodlightcontroller.storage.IStorageSourceService; import net.floodlightcontroller.devicemanager.IDevice; import net.floodlightcontroller.devicemanager.IDeviceService; import net.floodlightcontroller.devicemanager.SwitchPort; /** * HAND: Host Aware Network Decisions * "NetworkAware Network Decisions based on Ganglia metrics and clusters" * @author ryan wallner * * ***All host that join HAND must be running GMOND*** in order to work. **/ public class HAND implements IHANDService, IFloodlightModule { // services needed protected IFloodlightProviderService floodlightProvider; protected IStaticFlowEntryPusherService flowPusher; protected IStorageSourceService storageSource; protected IRestApiService restApi; protected static Logger logger; protected IDeviceService devices; protected ArrayList<HANDRule> hostRules; protected ArrayList<HANDGangliaHost> gangliaHosts; protected ArrayList<String> messages; protected PriorityBlockingQueue<HANDRule> ruleQueue; //used to prioritize rules //Base path for RRDs public static String gangliaBasePath; /** * Table for hosts in storage source */ public static final String HOSTS_TABLE_NAME = "hand_hosts"; public static final String COLUMN_HID = "hostid"; public static final String COLUMN_CLUSTER = "cluster"; public static final String COLUMN_NAME = "hostname"; public static final String COLUMN_IPADD = "ip_address"; public static final String COLUMN_MACADD = "mac_address"; public static final String COLUMN_FIRSTHOPS = "first_hop_switches"; public static final String COLUMN_TIMEADDED = "time_added"; public static String HostColumnNames[] = { COLUMN_HID, COLUMN_CLUSTER, COLUMN_NAME, COLUMN_IPADD, COLUMN_MACADD, COLUMN_FIRSTHOPS, COLUMN_TIMEADDED }; /** * Table for rules in storage source */ public static final String RULES_TABLE_NAME = "hand_rules"; public static final String COLUMN_RID = "ruleid"; public static final String COLUMN_RNAME = "rule_name"; public static final String COLUMN_PRIOR = "priority"; public static final String COLUMN_POLLTIME = "polling_time"; public static final String COLUMN_TIMEADD = "time_added"; public static final String COLUMN_NEXTCHECK = "next_check_time"; public static final String COLUMN_ACTIVE = "active"; public static final String COLUMN_ASSOC = "host_association"; public static final String COLUMN_METRICS = "metrics"; public static final String COLUMN_FRULE = "firewall_rule"; public static final String COLUMN_QOS = "qos_rule"; public static final String COLUMN_STATICFLOW = "static_flow"; public static final String COLUMN_CHECKLIMIT = "check_limit"; public static final String COLUMN_CURRCHECK = "current_check"; public static final String COLUMN_ACTION = "action"; public static String RuleColumnNames[] = { COLUMN_RID, COLUMN_RNAME, COLUMN_PRIOR, COLUMN_POLLTIME, COLUMN_TIMEADD, COLUMN_NEXTCHECK, COLUMN_ACTIVE, COLUMN_ASSOC, COLUMN_METRICS, COLUMN_FRULE, COLUMN_QOS, COLUMN_STATICFLOW, COLUMN_CHECKLIMIT, COLUMN_CURRCHECK, COLUMN_ACTION, }; public boolean enabled; @Override public String getName(){ return "HostAwareNetorkingDecisions"; } @Override public Boolean isHANDEnabled(){ return this.enabled; } @Override public void enableHAND(boolean enabled){ logger.info("Setting Host Aware Networking Decisions to {}", enabled); this.enabled = enabled; // when enabled, we need to start the timed polling mechanism if (enabled) this.startTimerPollingTask(); if (!(enabled)){ logger.info("HAND disabled"); } } /** * @return */ public ArrayList<String> getMessages(){ return this.messages; } /** * * @return */ public ArrayList<HANDRule> getRules(){ return this.hostRules; } /** * * @return */ public ArrayList<HANDGangliaHost> getHosts(){ return this.gangliaHosts; } /** * This will start a scheduled task to run every second. * -this will run a procedure to check the current time, the * subsequently check the current rules for there @nextCheckTime * and see if they match. If the do, the appropriate action will be * taken to consume the needed metrics for the host's rule and * analyze the rules to see if any actions need be taken. * When the rules is check it will appear in the Messaged queue with * a time stamp. * * //This is inefficient at large scale, but to get things working * //on a small scale it will be used. */ public void startTimerPollingTask(){ long delay = 1000; // delay for 1 sec. long period = 1000; // repeat every sec. final boolean enabled = this.isHANDEnabled(); //Create a new timer and add a task to it. Our task is // right inside using the run method of a new TimerTask. final Timer timer = new Timer(); timer.scheduleAtFixedRate(new TimerTask(){ public void run(){ if(!(enabled)){ //Cancel //This is important to check before each second //in case HAND becomes disabled at any point. timer.cancel(); }else{ //Stamp this with time in seconds since Jan. 1 1970 midnight :) long currentTime = System.currentTimeMillis() / 1000l; logger.info("Checking Rules : "+currentTime); //TODO /** * Logic here needs carry out the above description * * -Queuing, possibly threading, and concurrency methods need to be thought of here. * -For the above use PriorrityBlockingQueue ir * a PriorityQueue for action on matches */ // Current time should be updated on rules when checked, if // checking time, is < pollingTime then pass. //1.call function to check rules bases on priority //2.Rules are sent into priorityqueue bases on .. priority setting //3.Rule is checked against associated host metrics and thresholds in rule //4.If threshold is met, rule.action is carried out. //5. maybe a intoActionFunc(Action action) to carry out rules actions to controller. } } }, delay, period); } @Override public Collection<Class<? extends IFloodlightService>> getModuleServices() { Collection<Class<? extends IFloodlightService>> l = new ArrayList<Class<? extends IFloodlightService>>(); l.add(IHANDService.class); return l; } @Override public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() { Map<Class<? extends IFloodlightService>, IFloodlightService> m = new HashMap<Class<? extends IFloodlightService>, IFloodlightService>(); // Implements the QoS service m.put(IHANDService.class, this); return m; } @Override public Collection<Class<? extends IFloodlightService>> getModuleDependencies() { //This module should depend on FloodlightProviderService, // IStorageSourceProviderService, IRestApiService, & // IStaticFlowEntryPusherService Collection<Class<? extends IFloodlightService>> l = new ArrayList<Class<? extends IFloodlightService>>(); l.add(IFloodlightProviderService.class); l.add(IStorageSourceService.class); l.add(IRestApiService.class); l.add(IStaticFlowEntryPusherService.class); l.add(IDeviceService.class); return l; } @Override public void init(FloodlightModuleContext context) throws FloodlightModuleException { floodlightProvider = context .getServiceImpl(IFloodlightProviderService.class); storageSource = context.getServiceImpl(IStorageSourceService.class); restApi = context.getServiceImpl(IRestApiService.class); devices = context.getServiceImpl(IDeviceService.class); hostRules = new ArrayList<HANDRule>(); gangliaHosts = new ArrayList<HANDGangliaHost>(); messages = new ArrayList<String>(); logger = LoggerFactory.getLogger(HAND.class); // start disabled enabled = false; //true for testing //fetch config getGangliaRRDLocation(); logger.info("RRD Location: {}", HAND.gangliaBasePath); } @Override public void startUp(FloodlightModuleContext context) { //initialize // register REST interface restApi.addRestletRoutable(new HANDWebRoutable()); // storage for hosts storageSource.createTable(HOSTS_TABLE_NAME, null); storageSource.setTablePrimaryKeyName(HOSTS_TABLE_NAME, COLUMN_HID); synchronized (gangliaHosts) { this.gangliaHosts = readHostsFromStorage(); } //storage for rules storageSource.createTable(RULES_TABLE_NAME, null); storageSource.setTablePrimaryKeyName(RULES_TABLE_NAME, COLUMN_RID); synchronized (hostRules) { this.hostRules = readRulesFromStorage(); } // One of the things we need to do is start our timing // mechanism when the module is started. if(this.isHANDEnabled()){ this.startTimerPollingTask(); } else{ logger.info("Disabled, waiting to enable to start polling hosts."); } } /** * Adds a host to the controller in an ordered manner, * hosts are ordered High --> Low by time they were added. * @param host */ public void addGangliaHost(HANDGangliaHost host){ if(hostSeenByFloodlight(host)){ logger.debug("All checks complete, adding Ganglia Host to Controller"); //Generate unique identifier. host.hostId = host.genUniqueId(); int pos = 0; int h = 0; for(h = 0; h < this.gangliaHosts.size(); h ++){ if(this.gangliaHosts.isEmpty()){ break; //just add it. } //Add High to Low. (newer hosts are checked first) if(this.gangliaHosts.get(h).timeAdded <= host.timeAdded){ pos = h; break; //want to add here. H -> L } } if(pos <= this.gangliaHosts.size()){ this.gangliaHosts.add(pos, host); } else{ this.gangliaHosts.add(host); } /** * Add the host to the storageSource. * COLUMN_HID, * COLUMN_CLUSTER, * COLUMN_NAME, * COLUMN_IPADD, * COLUMN_MACADD, * COLUMN_FIRSTHOPS, * COLUMN_TIMEADDED */ Map<String, Object> hostEntry = new HashMap<String, Object>(); hostEntry.put(COLUMN_HID, Long.toString(host.hostId)); hostEntry.put(COLUMN_CLUSTER, host.cluster); hostEntry.put(COLUMN_NAME, host.hostName); hostEntry.put(COLUMN_IPADD, Integer.toString(host.ipAddress)); hostEntry.put(COLUMN_MACADD, Long.toString(host.macAddress)); hostEntry.put(COLUMN_FIRSTHOPS, host.firstHops.toString()); hostEntry.put(COLUMN_TIMEADDED, Long.toString(host.timeAdded)); storageSource.insertRow(HOSTS_TABLE_NAME, hostEntry); } else{ logger.error("Ganglia host not added. Reason:"+ "HAND Could not detect that Floodlight has seen this host before,"+ "Please check to make sure host in visible to the controller."); } } //TODO /** * Add host (DONE!) 5/1/2013 * * Remove host //TODO **************************** */ //TODO /** * Add/Remove rules * * Rules deal with all other modules in Floodlight. * LB - Load Balancer * AFR / DFR - Add/Remove Firewall Rule * PSF - Push Static Flow * AQOS / DQOS - Add/Remove QoS * * ***Rules must be check every time data is polled for a specific host*** * */ /** * Adds a Host Aware Rule to the controller * @param rule */ public void addHostAwareRule(HANDRule rule){ //TODO } //TODO /** * Needs a way to carry out the rules and a way to * time synchronize HANDRule.getPollingTime() with the system clock * Hosts need to be polled every ^^time^^ that is set. * * If the metrics add up the carry out the rule. */ //TODO /** * need storage source for *Metrics last polled* if a rule states * "If the last polled metrics is above X amount 5 times in a row, carry out rule Y" * The last 4 polls need to be stored. */ /** * This method takes in a user defined host and * makes sure that Floodlight has seen the host. If * Floodlight cannot see the host, the host information * is useless to the SDN network and the controller will * not be able to control the flows to / from the host. * * The method also adds vital information about the host * if Floodlight has seen it in its networks before. * * @param host * @return boolean */ public boolean hostSeenByFloodlight(HANDGangliaHost host){ boolean isSeen = false; Iterator<? extends IDevice> hosts; if(host.macAddress != 0){ hosts = devices.queryDevices(host.macAddress, null, host.ipAddress,null, null); } else{ hosts = devices.queryDevices(null, null, host.ipAddress, null, null); } while(hosts.hasNext()){ IDevice device = hosts.next(); Integer[] deviceAddresses = device.getIPv4Addresses(); for(Integer address : deviceAddresses){ if(address == host.ipAddress){ isSeen = true; logger.info("******Host confirmed by Floodlight******"); /** * some of the host information is optional, * if the user doesn't enter it, Floodlight * knows about it. */ if(host.firstHops.isEmpty()){ ArrayList<Long> switches = new ArrayList<Long>(); for(SwitchPort p : device.getAttachmentPoints()){ switches.add(p.getSwitchDPID()); logger.info("***Adding first hop***"); } host.firstHops = switches; } if(host.macAddress == 0){ host.macAddress = device.getMACAddress(); } break; }else{ logger.error("******Host ["+host.ipAddress+"/"+host.hostName+"] Not seen by Floodlight"); isSeen = false; } } } //return whether Floodlight has seen the host. return isSeen; } /** * Reads the policies from the storage and creates an * ArrayList of GangliaHost's from them. * @return */ public ArrayList<HANDGangliaHost> readHostsFromStorage(){ ArrayList<HANDGangliaHost> list = new ArrayList<HANDGangliaHost>(); //TODO read gangliaHosts from storageSource; return list; } /** * Reads the policies from the storage and creates a sorted * ArrayList of Rule's from them. * @return */ public ArrayList<HANDRule> readRulesFromStorage(){ ArrayList<HANDRule> list = new ArrayList<HANDRule>(); //TODO read rules from storageSource, sorted based on priority of rule. return list; } /** * Only needed by init. * Gets the rrd UNIX file locations for clusters and RRD's */ private void getGangliaRRDLocation() { Properties props = new Properties(); try { props.load(new FileInputStream("src/main/resources/floodlightdefault.properties")); gangliaBasePath = props.getProperty("net.floodlightcontroller.hand.GangliaBasePath"); //System.out.println("GANGLIA BASE PATH AS: "+gangliaBasePath); //debug } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }