package net.floodlightcontroller.core.module; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.Queue; import java.util.ServiceConfigurationError; import java.util.ServiceLoader; import java.util.Set; import net.floodlightcontroller.core.annotations.LogMessageDoc; import net.floodlightcontroller.core.annotations.LogMessageDocs; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Finds all Floodlight modules in the class path and loads/starts them. * @author alexreimers * */ public class FloodlightModuleLoader { protected static Logger logger = LoggerFactory.getLogger(FloodlightModuleLoader.class); protected static Map<Class<? extends IFloodlightService>, Collection<IFloodlightModule>> serviceMap; protected static Map<IFloodlightModule, Collection<Class<? extends IFloodlightService>>> moduleServiceMap; protected static Map<String, IFloodlightModule> moduleNameMap; protected static Object lock = new Object(); protected FloodlightModuleContext floodlightModuleContext; public static final String COMPILED_CONF_FILE = "floodlightdefault.properties"; public static final String FLOODLIGHT_MODULES_KEY = "floodlight.modules"; public FloodlightModuleLoader() { floodlightModuleContext = new FloodlightModuleContext(); } /** * Finds all IFloodlightModule(s) in the classpath. It creates 3 Maps. * serviceMap -> Maps a service to a module * moduleServiceMap -> Maps a module to all the services it provides * moduleNameMap -> Maps the string name to the module * @throws FloodlightModuleException If two modules are specified in the configuration * that provide the same service. */ protected static void findAllModules(Collection<String> mList) throws FloodlightModuleException { synchronized (lock) { if (serviceMap != null) return; serviceMap = new HashMap<Class<? extends IFloodlightService>, Collection<IFloodlightModule>>(); moduleServiceMap = new HashMap<IFloodlightModule, Collection<Class<? extends IFloodlightService>>>(); moduleNameMap = new HashMap<String, IFloodlightModule>(); // Get all the current modules in the classpath ClassLoader cl = Thread.currentThread().getContextClassLoader(); ServiceLoader<IFloodlightModule> moduleLoader = ServiceLoader.load(IFloodlightModule.class, cl); // Iterate for each module, iterate through and add it's services Iterator<IFloodlightModule> moduleIter = moduleLoader.iterator(); while (moduleIter.hasNext()) { IFloodlightModule m = null; try { m = moduleIter.next(); } catch (ServiceConfigurationError sce) { logger.debug("Could not find module"); //moduleIter.remove(); continue; } //} //for (IFloodlightModule m : moduleLoader) { if (logger.isDebugEnabled()) { logger.debug("Found module " + m.getClass().getName()); } // Set up moduleNameMap moduleNameMap.put(m.getClass().getCanonicalName(), m); // Set up serviceMap Collection<Class<? extends IFloodlightService>> servs = m.getModuleServices(); if (servs != null) { moduleServiceMap.put(m, servs); for (Class<? extends IFloodlightService> s : servs) { Collection<IFloodlightModule> mods = serviceMap.get(s); if (mods == null) { mods = new ArrayList<IFloodlightModule>(); serviceMap.put(s, mods); } mods.add(m); // Make sure they haven't specified duplicate modules in the config int dupInConf = 0; for (IFloodlightModule cMod : mods) { if (mList.contains(cMod.getClass().getCanonicalName())) dupInConf += 1; } if (dupInConf > 1) { String duplicateMods = ""; for (IFloodlightModule mod : mods) { duplicateMods += mod.getClass().getCanonicalName() + ", "; } throw new FloodlightModuleException("ERROR! The configuraiton" + " file specifies more than one module that provides the service " + s.getCanonicalName() +". Please specify only ONE of the " + "following modules in the config file: " + duplicateMods); } } } } } } /** * Loads the modules from a specified configuration file. * @param fName The configuration file path * @return An IFloodlightModuleContext with all the modules to be started * @throws FloodlightModuleException */ @LogMessageDocs({ @LogMessageDoc(level="INFO", message="Loading modules from file {file name}", explanation="The controller is initializing its module " + "configuration from the specified properties file"), @LogMessageDoc(level="INFO", message="Loading default modules", explanation="The controller is initializing its module " + "configuration to the default configuration"), @LogMessageDoc(level="ERROR", message="Could not load module configuration file", explanation="The controller failed to read the " + "module configuration file", recommendation="Verify that the module configuration is " + "present. " + LogMessageDoc.CHECK_CONTROLLER), @LogMessageDoc(level="ERROR", message="Could not load default modules", explanation="The controller failed to read the default " + "module configuration", recommendation=LogMessageDoc.CHECK_CONTROLLER) }) public IFloodlightModuleContext loadModulesFromConfig(String fName) throws FloodlightModuleException { Properties prop = new Properties(); File f = new File(fName); if (f.isFile()) { logger.info("Loading modules from file {}", fName); try { prop.load(new FileInputStream(fName)); } catch (Exception e) { logger.error("Could not load module configuration file", e); System.exit(1); } } else { logger.info("Loading default modules"); InputStream is = this.getClass().getClassLoader(). getResourceAsStream(COMPILED_CONF_FILE); try { prop.load(is); } catch (IOException e) { logger.error("Could not load default modules", e); System.exit(1); } } String moduleList = prop.getProperty(FLOODLIGHT_MODULES_KEY) .replaceAll("\\s", ""); Collection<String> configMods = new ArrayList<String>(); configMods.addAll(Arrays.asList(moduleList.split(","))); return loadModulesFromList(configMods, prop); } /** * Loads modules (and their dependencies) specified in the list * @param mList The array of fully qualified module names * @param ignoreList The list of Floodlight services NOT to * load modules for. Used for unit testing. * @return The ModuleContext containing all the loaded modules * @throws FloodlightModuleException */ protected IFloodlightModuleContext loadModulesFromList(Collection<String> configMods, Properties prop, Collection<IFloodlightService> ignoreList) throws FloodlightModuleException { logger.debug("Starting module loader"); if (logger.isDebugEnabled() && ignoreList != null) logger.debug("Not loading module services " + ignoreList.toString()); findAllModules(configMods); Collection<IFloodlightModule> moduleSet = new ArrayList<IFloodlightModule>(); Map<Class<? extends IFloodlightService>, IFloodlightModule> moduleMap = new HashMap<Class<? extends IFloodlightService>, IFloodlightModule>(); Queue<String> moduleQ = new LinkedList<String>(); // Add the explicitly configured modules to the q moduleQ.addAll(configMods); Set<String> modsVisited = new HashSet<String>(); while (!moduleQ.isEmpty()) { String moduleName = moduleQ.remove(); if (modsVisited.contains(moduleName)) continue; modsVisited.add(moduleName); IFloodlightModule module = moduleNameMap.get(moduleName); if (module == null) { throw new FloodlightModuleException("Module " + moduleName + " not found"); } // If the module provies a service that is in the // services ignorelist don't load it. if ((ignoreList != null) && (module.getModuleServices() != null)) { for (IFloodlightService ifs : ignoreList) { for (Class<?> intsIgnore : ifs.getClass().getInterfaces()) { //System.out.println(intsIgnore.getName()); // Check that the interface extends IFloodlightService //if (intsIgnore.isAssignableFrom(IFloodlightService.class)) { //System.out.println(module.getClass().getName()); if (intsIgnore.isAssignableFrom(module.getClass())) { // We now ignore loading this module. logger.debug("Not loading module " + module.getClass().getCanonicalName() + " because interface " + intsIgnore.getCanonicalName() + " is in the ignore list."); continue; } //} } } } // Add the module to be loaded addModule(moduleMap, moduleSet, module); // Add it's dep's to the queue Collection<Class<? extends IFloodlightService>> deps = module.getModuleDependencies(); if (deps != null) { for (Class<? extends IFloodlightService> c : deps) { IFloodlightModule m = moduleMap.get(c); if (m == null) { Collection<IFloodlightModule> mods = serviceMap.get(c); // Make sure only one module is loaded if ((mods == null) || (mods.size() == 0)) { throw new FloodlightModuleException("ERROR! Could not " + "find an IFloodlightModule that provides service " + c.toString()); } else if (mods.size() == 1) { IFloodlightModule mod = mods.iterator().next(); if (!modsVisited.contains(mod.getClass().getCanonicalName())) moduleQ.add(mod.getClass().getCanonicalName()); } else { boolean found = false; for (IFloodlightModule moduleDep : mods) { if (configMods.contains(moduleDep.getClass().getCanonicalName())) { // Module will be loaded, we can continue found = true; break; } } if (!found) { String duplicateMods = ""; for (IFloodlightModule mod : mods) { duplicateMods += mod.getClass().getCanonicalName() + ", "; } throw new FloodlightModuleException("ERROR! Found more " + "than one (" + mods.size() + ") IFloodlightModules that provides " + "service " + c.toString() + ". Please specify one of the following modules in the config: " + duplicateMods); } } } } } } floodlightModuleContext.setModuleSet(moduleSet); parseConfigParameters(prop); initModules(moduleSet); startupModules(moduleSet); return floodlightModuleContext; } /** * Loads modules (and their dependencies) specified in the list. * @param configMods The collection of fully qualified module names to load. * @param prop The list of properties that are configuration options. * @return The ModuleContext containing all the loaded modules. * @throws FloodlightModuleException */ public IFloodlightModuleContext loadModulesFromList(Collection<String> configMods, Properties prop) throws FloodlightModuleException { return loadModulesFromList(configMods, prop, null); } /** * Add a module to the set of modules to load and register its services * @param moduleMap the module map * @param moduleSet the module set * @param module the module to add */ protected void addModule(Map<Class<? extends IFloodlightService>, IFloodlightModule> moduleMap, Collection<IFloodlightModule> moduleSet, IFloodlightModule module) { if (!moduleSet.contains(module)) { Collection<Class<? extends IFloodlightService>> servs = moduleServiceMap.get(module); if (servs != null) { for (Class<? extends IFloodlightService> c : servs) moduleMap.put(c, module); } moduleSet.add(module); } } /** * Allocate service implementations and then init all the modules * @param moduleSet The set of modules to call their init function on * @throws FloodlightModuleException If a module can not properly be loaded */ protected void initModules(Collection<IFloodlightModule> moduleSet) throws FloodlightModuleException { for (IFloodlightModule module : moduleSet) { // Get the module's service instance(s) Map<Class<? extends IFloodlightService>, IFloodlightService> simpls = module.getServiceImpls(); // add its services to the context if (simpls != null) { for (Entry<Class<? extends IFloodlightService>, IFloodlightService> s : simpls.entrySet()) { if (logger.isDebugEnabled()) { logger.debug("Setting " + s.getValue() + " as provider for " + s.getKey().getCanonicalName()); } if (floodlightModuleContext.getServiceImpl(s.getKey()) == null) { floodlightModuleContext.addService(s.getKey(), s.getValue()); } else { throw new FloodlightModuleException("Cannot set " + s.getValue() + " as the provider for " + s.getKey().getCanonicalName() + " because " + floodlightModuleContext.getServiceImpl(s.getKey()) + " already provides it"); } } } } for (IFloodlightModule module : moduleSet) { // init the module if (logger.isDebugEnabled()) { logger.debug("Initializing " + module.getClass().getCanonicalName()); } module.init(floodlightModuleContext); } } /** * Call each loaded module's startup method * @param moduleSet the module set to start up */ protected void startupModules(Collection<IFloodlightModule> moduleSet) { for (IFloodlightModule m : moduleSet) { if (logger.isDebugEnabled()) { logger.debug("Starting " + m.getClass().getCanonicalName()); } m.startUp(floodlightModuleContext); } } /** * Parses configuration parameters for each module * @param prop The properties file to use */ @LogMessageDoc(level="WARN", message="Module {module} not found or loaded. " + "Not adding configuration option {key} = {value}", explanation="Ignoring a configuration parameter for a " + "module that is not loaded.") protected void parseConfigParameters(Properties prop) { if (prop == null) return; Enumeration<?> e = prop.propertyNames(); while (e.hasMoreElements()) { String key = (String) e.nextElement(); // Ignore module list key if (key.equals(FLOODLIGHT_MODULES_KEY)) { continue; } String configValue = null; int lastPeriod = key.lastIndexOf("."); String moduleName = key.substring(0, lastPeriod); String configKey = key.substring(lastPeriod + 1); // Check to see if it's overridden on the command line String systemKey = System.getProperty(key); if (systemKey != null) { configValue = systemKey; } else { configValue = prop.getProperty(key); } IFloodlightModule mod = moduleNameMap.get(moduleName); if (mod == null) { logger.warn("Module {} not found or loaded. " + "Not adding configuration option {} = {}", new Object[]{moduleName, configKey, configValue}); } else { floodlightModuleContext.addConfigParam(mod, configKey, configValue); } } } }