blob: 2686d0047e5d94660b2bd6bc99b8f803088467e7 [file] [log] [blame]
Umesh Krishnaswamy345ee992012-12-13 20:29:48 -08001package net.floodlightcontroller.core.module;
2
3import java.io.File;
4import java.io.FileInputStream;
5import java.io.IOException;
6import java.io.InputStream;
7import java.util.ArrayList;
8import java.util.Arrays;
9import java.util.Collection;
10import java.util.Enumeration;
11import java.util.HashMap;
12import java.util.HashSet;
13import java.util.Iterator;
14import java.util.LinkedList;
15import java.util.Map;
16import java.util.Map.Entry;
17import java.util.Properties;
18import java.util.Queue;
19import java.util.ServiceConfigurationError;
20import java.util.ServiceLoader;
21import java.util.Set;
22
23import net.floodlightcontroller.core.annotations.LogMessageDoc;
24import net.floodlightcontroller.core.annotations.LogMessageDocs;
25
26import org.slf4j.Logger;
27import org.slf4j.LoggerFactory;
28
29/**
30 * Finds all Floodlight modules in the class path and loads/starts them.
31 * @author alexreimers
32 *
33 */
34public class FloodlightModuleLoader {
Yuta HIGUCHI6ac8d182013-10-22 15:24:56 -070035 protected final static Logger logger =
Umesh Krishnaswamy345ee992012-12-13 20:29:48 -080036 LoggerFactory.getLogger(FloodlightModuleLoader.class);
37
38 protected static Map<Class<? extends IFloodlightService>,
39 Collection<IFloodlightModule>> serviceMap;
40 protected static Map<IFloodlightModule,
41 Collection<Class<? extends
42 IFloodlightService>>> moduleServiceMap;
43 protected static Map<String, IFloodlightModule> moduleNameMap;
44 protected static Object lock = new Object();
45
46 protected FloodlightModuleContext floodlightModuleContext;
47
48 public static final String COMPILED_CONF_FILE =
49 "floodlightdefault.properties";
50 public static final String FLOODLIGHT_MODULES_KEY =
51 "floodlight.modules";
52
53 public FloodlightModuleLoader() {
54 floodlightModuleContext = new FloodlightModuleContext();
55 }
56
57 /**
58 * Finds all IFloodlightModule(s) in the classpath. It creates 3 Maps.
59 * serviceMap -> Maps a service to a module
60 * moduleServiceMap -> Maps a module to all the services it provides
61 * moduleNameMap -> Maps the string name to the module
62 * @throws FloodlightModuleException If two modules are specified in the configuration
63 * that provide the same service.
64 */
65 protected static void findAllModules(Collection<String> mList) throws FloodlightModuleException {
66 synchronized (lock) {
67 if (serviceMap != null) return;
68 serviceMap =
69 new HashMap<Class<? extends IFloodlightService>,
70 Collection<IFloodlightModule>>();
71 moduleServiceMap =
72 new HashMap<IFloodlightModule,
73 Collection<Class<? extends
74 IFloodlightService>>>();
75 moduleNameMap = new HashMap<String, IFloodlightModule>();
76
77 // Get all the current modules in the classpath
78 ClassLoader cl = Thread.currentThread().getContextClassLoader();
79 ServiceLoader<IFloodlightModule> moduleLoader
80 = ServiceLoader.load(IFloodlightModule.class, cl);
81 // Iterate for each module, iterate through and add it's services
82 Iterator<IFloodlightModule> moduleIter = moduleLoader.iterator();
83 while (moduleIter.hasNext()) {
84 IFloodlightModule m = null;
85 try {
86 m = moduleIter.next();
87 } catch (ServiceConfigurationError sce) {
Jonathan Hart29809182013-02-26 10:32:47 -080088 logger.debug("Could not find module: {}", sce.getMessage());
Umesh Krishnaswamy345ee992012-12-13 20:29:48 -080089 //moduleIter.remove();
90 continue;
91 }
92 //}
93 //for (IFloodlightModule m : moduleLoader) {
94 if (logger.isDebugEnabled()) {
95 logger.debug("Found module " + m.getClass().getName());
96 }
97
98 // Set up moduleNameMap
99 moduleNameMap.put(m.getClass().getCanonicalName(), m);
100
101 // Set up serviceMap
102 Collection<Class<? extends IFloodlightService>> servs =
103 m.getModuleServices();
104 if (servs != null) {
105 moduleServiceMap.put(m, servs);
106 for (Class<? extends IFloodlightService> s : servs) {
107 Collection<IFloodlightModule> mods =
108 serviceMap.get(s);
109 if (mods == null) {
110 mods = new ArrayList<IFloodlightModule>();
111 serviceMap.put(s, mods);
112 }
113 mods.add(m);
114 // Make sure they haven't specified duplicate modules in the config
115 int dupInConf = 0;
116 for (IFloodlightModule cMod : mods) {
117 if (mList.contains(cMod.getClass().getCanonicalName()))
118 dupInConf += 1;
119 }
120
121 if (dupInConf > 1) {
122 String duplicateMods = "";
123 for (IFloodlightModule mod : mods) {
124 duplicateMods += mod.getClass().getCanonicalName() + ", ";
125 }
126 throw new FloodlightModuleException("ERROR! The configuraiton" +
127 " file specifies more than one module that provides the service " +
128 s.getCanonicalName() +". Please specify only ONE of the " +
129 "following modules in the config file: " + duplicateMods);
130 }
131 }
132 }
133 }
134 }
135 }
136
137 /**
138 * Loads the modules from a specified configuration file.
139 * @param fName The configuration file path
140 * @return An IFloodlightModuleContext with all the modules to be started
141 * @throws FloodlightModuleException
142 */
143 @LogMessageDocs({
144 @LogMessageDoc(level="INFO",
145 message="Loading modules from file {file name}",
146 explanation="The controller is initializing its module " +
147 "configuration from the specified properties file"),
148 @LogMessageDoc(level="INFO",
149 message="Loading default modules",
150 explanation="The controller is initializing its module " +
151 "configuration to the default configuration"),
152 @LogMessageDoc(level="ERROR",
153 message="Could not load module configuration file",
154 explanation="The controller failed to read the " +
155 "module configuration file",
156 recommendation="Verify that the module configuration is " +
157 "present. " + LogMessageDoc.CHECK_CONTROLLER),
158 @LogMessageDoc(level="ERROR",
159 message="Could not load default modules",
160 explanation="The controller failed to read the default " +
161 "module configuration",
162 recommendation=LogMessageDoc.CHECK_CONTROLLER)
163 })
164 public IFloodlightModuleContext loadModulesFromConfig(String fName)
165 throws FloodlightModuleException {
166 Properties prop = new Properties();
167
168 File f = new File(fName);
169 if (f.isFile()) {
170 logger.info("Loading modules from file {}", fName);
171 try {
172 prop.load(new FileInputStream(fName));
173 } catch (Exception e) {
174 logger.error("Could not load module configuration file", e);
175 System.exit(1);
176 }
177 } else {
178 logger.info("Loading default modules");
179 InputStream is = this.getClass().getClassLoader().
180 getResourceAsStream(COMPILED_CONF_FILE);
181 try {
182 prop.load(is);
183 } catch (IOException e) {
184 logger.error("Could not load default modules", e);
185 System.exit(1);
186 }
187 }
188
189 String moduleList = prop.getProperty(FLOODLIGHT_MODULES_KEY)
190 .replaceAll("\\s", "");
191 Collection<String> configMods = new ArrayList<String>();
192 configMods.addAll(Arrays.asList(moduleList.split(",")));
193 return loadModulesFromList(configMods, prop);
194 }
195
196 /**
197 * Loads modules (and their dependencies) specified in the list
198 * @param mList The array of fully qualified module names
199 * @param ignoreList The list of Floodlight services NOT to
200 * load modules for. Used for unit testing.
201 * @return The ModuleContext containing all the loaded modules
202 * @throws FloodlightModuleException
203 */
204 protected IFloodlightModuleContext loadModulesFromList(Collection<String> configMods, Properties prop,
205 Collection<IFloodlightService> ignoreList) throws FloodlightModuleException {
206 logger.debug("Starting module loader");
207 if (logger.isDebugEnabled() && ignoreList != null)
208 logger.debug("Not loading module services " + ignoreList.toString());
209
210 findAllModules(configMods);
211
212 Collection<IFloodlightModule> moduleSet = new ArrayList<IFloodlightModule>();
213 Map<Class<? extends IFloodlightService>, IFloodlightModule> moduleMap =
214 new HashMap<Class<? extends IFloodlightService>,
215 IFloodlightModule>();
216
217 Queue<String> moduleQ = new LinkedList<String>();
218 // Add the explicitly configured modules to the q
219 moduleQ.addAll(configMods);
220 Set<String> modsVisited = new HashSet<String>();
221
222 while (!moduleQ.isEmpty()) {
223 String moduleName = moduleQ.remove();
224 if (modsVisited.contains(moduleName))
225 continue;
226 modsVisited.add(moduleName);
227 IFloodlightModule module = moduleNameMap.get(moduleName);
228 if (module == null) {
229 throw new FloodlightModuleException("Module " +
230 moduleName + " not found");
231 }
232 // If the module provies a service that is in the
233 // services ignorelist don't load it.
234 if ((ignoreList != null) && (module.getModuleServices() != null)) {
235 for (IFloodlightService ifs : ignoreList) {
236 for (Class<?> intsIgnore : ifs.getClass().getInterfaces()) {
237 //System.out.println(intsIgnore.getName());
238 // Check that the interface extends IFloodlightService
239 //if (intsIgnore.isAssignableFrom(IFloodlightService.class)) {
240 //System.out.println(module.getClass().getName());
241 if (intsIgnore.isAssignableFrom(module.getClass())) {
242 // We now ignore loading this module.
243 logger.debug("Not loading module " +
244 module.getClass().getCanonicalName() +
245 " because interface " +
246 intsIgnore.getCanonicalName() +
247 " is in the ignore list.");
248
249 continue;
250 }
251 //}
252 }
253 }
254 }
255
256 // Add the module to be loaded
257 addModule(moduleMap, moduleSet, module);
258 // Add it's dep's to the queue
259 Collection<Class<? extends IFloodlightService>> deps =
260 module.getModuleDependencies();
261 if (deps != null) {
262 for (Class<? extends IFloodlightService> c : deps) {
263 IFloodlightModule m = moduleMap.get(c);
264 if (m == null) {
265 Collection<IFloodlightModule> mods = serviceMap.get(c);
266 // Make sure only one module is loaded
267 if ((mods == null) || (mods.size() == 0)) {
268 throw new FloodlightModuleException("ERROR! Could not " +
269 "find an IFloodlightModule that provides service " +
270 c.toString());
271 } else if (mods.size() == 1) {
272 IFloodlightModule mod = mods.iterator().next();
273 if (!modsVisited.contains(mod.getClass().getCanonicalName()))
274 moduleQ.add(mod.getClass().getCanonicalName());
275 } else {
276 boolean found = false;
277 for (IFloodlightModule moduleDep : mods) {
278 if (configMods.contains(moduleDep.getClass().getCanonicalName())) {
279 // Module will be loaded, we can continue
280 found = true;
281 break;
282 }
283 }
284 if (!found) {
285 String duplicateMods = "";
286 for (IFloodlightModule mod : mods) {
287 duplicateMods += mod.getClass().getCanonicalName() + ", ";
288 }
289 throw new FloodlightModuleException("ERROR! Found more " +
290 "than one (" + mods.size() + ") IFloodlightModules that provides " +
291 "service " + c.toString() +
292 ". Please specify one of the following modules in the config: " +
293 duplicateMods);
294 }
295 }
296 }
297 }
298 }
299 }
300
301 floodlightModuleContext.setModuleSet(moduleSet);
302 parseConfigParameters(prop);
303 initModules(moduleSet);
304 startupModules(moduleSet);
305
306 return floodlightModuleContext;
307 }
308
309 /**
310 * Loads modules (and their dependencies) specified in the list.
311 * @param configMods The collection of fully qualified module names to load.
312 * @param prop The list of properties that are configuration options.
313 * @return The ModuleContext containing all the loaded modules.
314 * @throws FloodlightModuleException
315 */
316 public IFloodlightModuleContext loadModulesFromList(Collection<String> configMods, Properties prop)
317 throws FloodlightModuleException {
318 return loadModulesFromList(configMods, prop, null);
319 }
320
321 /**
322 * Add a module to the set of modules to load and register its services
323 * @param moduleMap the module map
324 * @param moduleSet the module set
325 * @param module the module to add
326 */
327 protected void addModule(Map<Class<? extends IFloodlightService>,
328 IFloodlightModule> moduleMap,
329 Collection<IFloodlightModule> moduleSet,
330 IFloodlightModule module) {
331 if (!moduleSet.contains(module)) {
332 Collection<Class<? extends IFloodlightService>> servs =
333 moduleServiceMap.get(module);
334 if (servs != null) {
335 for (Class<? extends IFloodlightService> c : servs)
336 moduleMap.put(c, module);
337 }
338 moduleSet.add(module);
339 }
340 }
341
342 /**
343 * Allocate service implementations and then init all the modules
344 * @param moduleSet The set of modules to call their init function on
345 * @throws FloodlightModuleException If a module can not properly be loaded
346 */
347 protected void initModules(Collection<IFloodlightModule> moduleSet)
348 throws FloodlightModuleException {
349 for (IFloodlightModule module : moduleSet) {
350 // Get the module's service instance(s)
351 Map<Class<? extends IFloodlightService>,
352 IFloodlightService> simpls = module.getServiceImpls();
353
354 // add its services to the context
355 if (simpls != null) {
356 for (Entry<Class<? extends IFloodlightService>,
357 IFloodlightService> s : simpls.entrySet()) {
358 if (logger.isDebugEnabled()) {
359 logger.debug("Setting " + s.getValue() +
360 " as provider for " +
361 s.getKey().getCanonicalName());
362 }
363 if (floodlightModuleContext.getServiceImpl(s.getKey()) == null) {
364 floodlightModuleContext.addService(s.getKey(),
365 s.getValue());
366 } else {
367 throw new FloodlightModuleException("Cannot set "
368 + s.getValue()
369 + " as the provider for "
370 + s.getKey().getCanonicalName()
371 + " because "
372 + floodlightModuleContext.getServiceImpl(s.getKey())
373 + " already provides it");
374 }
375 }
376 }
377 }
378
379 for (IFloodlightModule module : moduleSet) {
380 // init the module
381 if (logger.isDebugEnabled()) {
382 logger.debug("Initializing " +
383 module.getClass().getCanonicalName());
384 }
385 module.init(floodlightModuleContext);
386 }
387 }
388
389 /**
390 * Call each loaded module's startup method
391 * @param moduleSet the module set to start up
392 */
393 protected void startupModules(Collection<IFloodlightModule> moduleSet) {
394 for (IFloodlightModule m : moduleSet) {
395 if (logger.isDebugEnabled()) {
396 logger.debug("Starting " + m.getClass().getCanonicalName());
397 }
398 m.startUp(floodlightModuleContext);
399 }
400 }
401
402 /**
403 * Parses configuration parameters for each module
404 * @param prop The properties file to use
405 */
406 @LogMessageDoc(level="WARN",
407 message="Module {module} not found or loaded. " +
408 "Not adding configuration option {key} = {value}",
409 explanation="Ignoring a configuration parameter for a " +
410 "module that is not loaded.")
411 protected void parseConfigParameters(Properties prop) {
412 if (prop == null) return;
413
414 Enumeration<?> e = prop.propertyNames();
415 while (e.hasMoreElements()) {
416 String key = (String) e.nextElement();
417 // Ignore module list key
418 if (key.equals(FLOODLIGHT_MODULES_KEY)) {
419 continue;
420 }
421
422 String configValue = null;
423 int lastPeriod = key.lastIndexOf(".");
424 String moduleName = key.substring(0, lastPeriod);
425 String configKey = key.substring(lastPeriod + 1);
426 // Check to see if it's overridden on the command line
427 String systemKey = System.getProperty(key);
428 if (systemKey != null) {
429 configValue = systemKey;
430 } else {
431 configValue = prop.getProperty(key);
432 }
433
434 IFloodlightModule mod = moduleNameMap.get(moduleName);
435 if (mod == null) {
436 logger.warn("Module {} not found or loaded. " +
437 "Not adding configuration option {} = {}",
438 new Object[]{moduleName, configKey, configValue});
439 } else {
440 floodlightModuleContext.addConfigParam(mod, configKey, configValue);
441 }
442 }
443 }
444}