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