blob: 7507fd5fb9d93f64e721157c0d9290cf1a784bb7 [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 }
Pavlin Radoslavovc35229e2014-02-06 16:19:37 -0800300
301 //
302 // Reorder the moduleSet to take into account the module dependencies:
303 // If a module depends on the service provided by another module, the
304 // latter should be included before the former.
305 //
306 Collection<IFloodlightModule> orderedModuleSet =
307 new ArrayList<IFloodlightModule>();
308 while (! moduleSet.isEmpty()) {
309 //
310 // Evaluate each module in the unsorted collection: if all its
311 // dependencies are in the orderedModuleSet, then add it to the
312 // orderedModuleSet.
313 //
314 boolean moduleWasSorted = false;
315 for (IFloodlightModule module : moduleSet) {
316 Collection<Class<? extends IFloodlightService>> deps =
317 module.getModuleDependencies();
318 boolean allDepsFound = true;
319 if (deps != null) {
320 for (Class<? extends IFloodlightService> c : deps) {
321 IFloodlightModule m = moduleMap.get(c);
322 // NOTE: Earlier we checked that the module exists
323 assert(m != null);
324 if (! orderedModuleSet.contains(m)) {
325 allDepsFound = false;
326 break;
327 }
328 }
329 }
330
331 // Move the module to the sorted collection
332 if (allDepsFound) {
333 orderedModuleSet.add(module);
334 moduleSet.remove(module);
335 moduleWasSorted = true;
336 break;
337 }
338 }
339
340 //
341 // Test for cyclic depenency:
342 // If no module was sorted, but there are still unsorted modules
343 // then there is cyclic dependency.
344 //
345 if ((! moduleWasSorted) && (! moduleSet.isEmpty())) {
346 String errorMsg = "";
347 for (IFloodlightModule module : moduleSet) {
348 if (! errorMsg.isEmpty())
349 errorMsg += ", ";
350 errorMsg += module.getClass().getName();
351 }
352 errorMsg = "ERROR! Cyclic service dependency/dependencies among the following modules: " + errorMsg;
353 throw new FloodlightModuleException(errorMsg);
354 }
355 }
356 moduleSet = orderedModuleSet;
357
Umesh Krishnaswamy345ee992012-12-13 20:29:48 -0800358 floodlightModuleContext.setModuleSet(moduleSet);
359 parseConfigParameters(prop);
360 initModules(moduleSet);
361 startupModules(moduleSet);
362
363 return floodlightModuleContext;
364 }
365
366 /**
367 * Loads modules (and their dependencies) specified in the list.
368 * @param configMods The collection of fully qualified module names to load.
369 * @param prop The list of properties that are configuration options.
370 * @return The ModuleContext containing all the loaded modules.
371 * @throws FloodlightModuleException
372 */
373 public IFloodlightModuleContext loadModulesFromList(Collection<String> configMods, Properties prop)
374 throws FloodlightModuleException {
375 return loadModulesFromList(configMods, prop, null);
376 }
377
378 /**
379 * Add a module to the set of modules to load and register its services
380 * @param moduleMap the module map
381 * @param moduleSet the module set
382 * @param module the module to add
383 */
384 protected void addModule(Map<Class<? extends IFloodlightService>,
385 IFloodlightModule> moduleMap,
386 Collection<IFloodlightModule> moduleSet,
387 IFloodlightModule module) {
388 if (!moduleSet.contains(module)) {
389 Collection<Class<? extends IFloodlightService>> servs =
390 moduleServiceMap.get(module);
391 if (servs != null) {
392 for (Class<? extends IFloodlightService> c : servs)
393 moduleMap.put(c, module);
394 }
395 moduleSet.add(module);
396 }
397 }
398
399 /**
400 * Allocate service implementations and then init all the modules
401 * @param moduleSet The set of modules to call their init function on
402 * @throws FloodlightModuleException If a module can not properly be loaded
403 */
404 protected void initModules(Collection<IFloodlightModule> moduleSet)
405 throws FloodlightModuleException {
406 for (IFloodlightModule module : moduleSet) {
407 // Get the module's service instance(s)
408 Map<Class<? extends IFloodlightService>,
409 IFloodlightService> simpls = module.getServiceImpls();
410
411 // add its services to the context
412 if (simpls != null) {
413 for (Entry<Class<? extends IFloodlightService>,
414 IFloodlightService> s : simpls.entrySet()) {
415 if (logger.isDebugEnabled()) {
416 logger.debug("Setting " + s.getValue() +
417 " as provider for " +
418 s.getKey().getCanonicalName());
419 }
420 if (floodlightModuleContext.getServiceImpl(s.getKey()) == null) {
421 floodlightModuleContext.addService(s.getKey(),
422 s.getValue());
423 } else {
424 throw new FloodlightModuleException("Cannot set "
425 + s.getValue()
426 + " as the provider for "
427 + s.getKey().getCanonicalName()
428 + " because "
429 + floodlightModuleContext.getServiceImpl(s.getKey())
430 + " already provides it");
431 }
432 }
433 }
434 }
435
436 for (IFloodlightModule module : moduleSet) {
437 // init the module
438 if (logger.isDebugEnabled()) {
439 logger.debug("Initializing " +
440 module.getClass().getCanonicalName());
441 }
442 module.init(floodlightModuleContext);
443 }
444 }
445
446 /**
447 * Call each loaded module's startup method
448 * @param moduleSet the module set to start up
449 */
450 protected void startupModules(Collection<IFloodlightModule> moduleSet) {
451 for (IFloodlightModule m : moduleSet) {
452 if (logger.isDebugEnabled()) {
453 logger.debug("Starting " + m.getClass().getCanonicalName());
454 }
455 m.startUp(floodlightModuleContext);
456 }
457 }
458
459 /**
460 * Parses configuration parameters for each module
461 * @param prop The properties file to use
462 */
463 @LogMessageDoc(level="WARN",
464 message="Module {module} not found or loaded. " +
465 "Not adding configuration option {key} = {value}",
466 explanation="Ignoring a configuration parameter for a " +
467 "module that is not loaded.")
468 protected void parseConfigParameters(Properties prop) {
469 if (prop == null) return;
470
471 Enumeration<?> e = prop.propertyNames();
472 while (e.hasMoreElements()) {
473 String key = (String) e.nextElement();
474 // Ignore module list key
475 if (key.equals(FLOODLIGHT_MODULES_KEY)) {
476 continue;
477 }
478
479 String configValue = null;
480 int lastPeriod = key.lastIndexOf(".");
481 String moduleName = key.substring(0, lastPeriod);
482 String configKey = key.substring(lastPeriod + 1);
483 // Check to see if it's overridden on the command line
484 String systemKey = System.getProperty(key);
485 if (systemKey != null) {
486 configValue = systemKey;
487 } else {
488 configValue = prop.getProperty(key);
489 }
490
491 IFloodlightModule mod = moduleNameMap.get(moduleName);
492 if (mod == null) {
493 logger.warn("Module {} not found or loaded. " +
494 "Not adding configuration option {} = {}",
495 new Object[]{moduleName, configKey, configValue});
496 } else {
497 floodlightModuleContext.addConfigParam(mod, configKey, configValue);
498 }
499 }
500 }
501}