**本文通过在代码中注释的方式对Floodlight源码(Version 1.2)进行学习,同时若遇到关键点,会对关键点进行单独的详细分析.
Floodlight是一款开源的SDN控制器,首先先从模块加载开始学习,以下为加载模块的关键代码:**
首先奉上启动流程图,图中对启动过程中关键点进行了描述:
分析1:main函数
public static void main(String[] args) throws FloodlightModuleException {
try {
// Setup logger
System.setProperty("org.restlet.engine.loggerFacadeClass",
"org.restlet.ext.slf4j.Slf4jLoggerFacade");
//实例化一个CmdLineSettings类,该类用于设置配置文件,用户可以用-cf命令指定自定义的配置文件,若没有指定则使用默认字段
CmdLineSettings settings = new CmdLineSettings();
//实例化一个CmdLineParser类,该类来自于arg4j,arg4j是一款用于解析命令行参数的插件
CmdLineParser parser = new CmdLineParser(settings);
try {
//解析命令行参数,并设置到settings当中
parser.parseArgument(args);
} catch (CmdLineException e) {
parser.printUsage(System.out);
System.exit(1);
}
// Load modules
//创建一个Floodlight的模块加载器
FloodlightModuleLoader fml = new FloodlightModuleLoader();
try {
//调用模块加载器的loadModulesFromConfig函数,入参为用户的配置文件,详细分析参见2
IFloodlightModuleContext moduleContext = fml.loadModulesFromConfig(settings.getModuleFile());
//从模块上线文环境获取RestApi的服务,并启动
IRestApiService restApi = moduleContext.getServiceImpl(IRestApiService.class);
restApi.run();
} catch (FloodlightModuleConfigFileNotFoundException e) {
// we really want to log the message, not the stack trace
logger.error("Could not read config file: {}", e.getMessage());
System.exit(1);
}
try {
//启动controller和其它模块,详见分析6
fml.runModules(); // run the controller module and all modules
} catch (FloodlightModuleException e) {
logger.error("Failed to run controller modules", e);
System.exit(1);
}
} catch (Exception e) {
logger.error("Exception in main", e);
System.exit(1);
}
}
分析2:loadModulesFromConfig函数
public IFloodlightModuleContext loadModulesFromConfig(String fName)
throws FloodlightModuleException {
//实例化一个Properties类用于解析配置文档中非模块配置的属性
Properties prop = new Properties();
//创建一个String类型的集合用于存放配置文件中配置的模块
Collection configMods = new ArrayList<>();
//若入参的配置文件为null,则加载COMPILED_CONF_FILE,这是一个默认的配置文件
if (fName == null) {
logger.info("Loading default modules");
InputStream is = this.getClass().getClassLoader().
getResourceAsStream(COMPILED_CONF_FILE);
//这个函数用于将配置文件解析到prop和configMods当中
mergeProperties(is, null, configMods, prop);
//若入参的配置文件不为空,则解析配置文件或者目录下面的Properties文件
} else {
File confFile = new File(fName);
if (! confFile.exists())
throw new FloodlightModuleConfigFileNotFoundException(fName);
logger.info("Loading modules from {}", confFile.getPath());
if (confFile.isFile()) {
mergeProperties(null, confFile,
configMods, prop);
} else {
File[] files = confFile.listFiles();
Arrays.sort(files);
for (File f : files) {
logger.debug("Loading conf.d file {}", f.getPath());
if (f.isFile() &&
f.getName().matches(".*\\.properties$")) {
mergeProperties(null, f, configMods, prop);
}
}
}
}
//最后调用loadModulesFromList函数,详见分析3
return loadModulesFromList(configMods, prop);
}
分析3:loadModulesFromList函数
public synchronized IFloodlightModuleContext
loadModulesFromList(Collection configMods,
Properties prop)
throws FloodlightModuleException {
logger.debug("Starting module loader");
//将configMods解析到模块加载器的三个成员变量中,三个成员变量分别为:serviceMap,moduleServiceMap,moduleNameMap.详见分析4
findAllModules(configMods);
//创建一个IFloodlightModule类型的集合,用于存放需要加载的模块
ArrayList moduleList = new ArrayList<>();
Map<Class extends IFloodlightService>, IFloodlightModule> moduleMap =
new HashMap<>();
//创建一个set用于存在访问过的模块名称
HashSet<String> modsVisited = new HashSet<>();
//将configMods转换为一个队列
ArrayDeque<String> modsToLoad = new ArrayDeque<>(configMods);
//循环从队列中取出模块,解析模块之间的依赖关系,同时按照顺序的放入moduleList
while (!modsToLoad.isEmpty()) {
String moduleName = modsToLoad.removeFirst();
//该函数的功能是保证模块之间的依赖关系,并按照顺序不重复的放入moduleList
traverseDeps(moduleName, modsToLoad,
moduleList, moduleMap, modsVisited);
}
//将配置属性解析并放入到模块的上下文环境中floodlightModuleContext
parseConfigParameters(prop);
loadedModuleList = moduleList;
//初始化模块,详见分析5
initModules(moduleList);
//查看startupModules状态,如果为true则调用模块实现的startUp方法,因为在FloodlightModuleLoader初始化时候,已经将startupModules置为true
if(startupModules)
startupModules(moduleList);
//返回模块的上线文环境中
return floodlightModuleContext;
}
分析4:findAllModules函数
这个函数主要的作用是将configMods解析到加载模块的三个变量中,这三个变量是:
1.serviceMap
模块提供的服务与实现模块实例的关系,模块实现的服务是通过模块中getModuleServices中获得,这里模块提供的服务我理解是模块对外提供的服务类,比如MemoryStorageSource模块提供了IStorageSourceService服务接口,这些服务接口都需要实现最基础的IFloodlightService
2.moduleServiceMap
这个是与serviceMap相反的,实模块实例和模块所提供服务接口的映射关系
3.moduleNameMap:
提供模块名称和模块实例的映射关系
protected synchronized void findAllModules(Collection mList)
throws FloodlightModuleException {
if (serviceMap != null)
return;
serviceMap = new HashMap<>();
moduleServiceMap = new HashMap<>();
moduleNameMap = new HashMap<>();
//动态加载目前实现了IFloodlightModule的模块,形成一个一个的实例
ClassLoader cl = Thread.currentThread().getContextClassLoader();
ServiceLoader moduleLoader =
ServiceLoader.load(IFloodlightModule.class, cl);
//遍历模块的实例,解析出上面提到的成员变量
Iterator moduleIter = moduleLoader.iterator();
while (moduleIter.hasNext()) {
IFloodlightModule m = null;
try {
m = moduleIter.next();
} catch (ServiceConfigurationError sce) {
logger.error("Could not find module: {}", sce.getMessage());
continue;
}
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 mods =
serviceMap.get(s);
if (mods == null) {
mods = new ArrayList();
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) {
StringBuilder sb = new StringBuilder();
for (IFloodlightModule mod : mods) {
sb.append(mod.getClass().getCanonicalName());
sb.append(", ");
}
String duplicateMods = sb.toString();
String mess = "ERROR! The configuration 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;
throw new FloodlightModuleException(mess);
}
}
}
}
}
分析5:initModules函数
protected void initModules(Collection moduleSet)
throws FloodlightModuleException {
for (IFloodlightModule module : moduleSet) {
// 获取模块提供服务的实例
Map<Class extends IFloodlightService>,
IFloodlightService> simpls = module.getServiceImpls();
// 将模块提供服务实例放入上下文环境变量中
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");
}
}
}
}
//遍历需要加载的模块,并调用模块实现的init方法,该方法实现将模块依赖的服务注入到模块的引用中
for (IFloodlightModule module : moduleSet) {
// init the module
if (logger.isDebugEnabled()) {
logger.debug("Initializing " +
module.getClass().getCanonicalName());
}
module.init(floodlightModuleContext);
}
}
分析6:runModules方法
public void runModules() throws FloodlightModuleException {
List mainLoopMethods = Lists.newArrayList();
//遍历需要加载的模块
for (IFloodlightModule m : getModuleList()) {
for (Method method : m.getClass().getDeclaredMethods()) {
//找到模块方法中标识了@Run注解的方法
Run runAnnotation = method.getAnnotation(Run.class);
if (runAnnotation != null) {
RunMethod runMethod = new RunMethod(m, method);
if(runAnnotation.mainLoop()) {
mainLoopMethods.add(runMethod);
} else {
//调用方法
runMethod.run();
}
}
}
}
//标注了mainLoop的方法只能有一个
if(mainLoopMethods.size() == 1) {
mainLoopMethods.get(0).run();
} else if (mainLoopMethods.size() > 1) {
throw new FloodlightModuleException("Invalid module configuration -- "
+ "multiple run methods annotated with mainLoop detected: " + mainLoopMethods);
}
}
至此Floodlight启动流程中主要涉及的方法如上。