From 45e82f05b24a2c83ef3c1869f5585645dd99e73c Mon Sep 17 00:00:00 2001 From: "Blake J. Harnden" Date: Mon, 5 Feb 2018 09:01:49 -0800 Subject: [PATCH] attempt to better support dynamic emane models --- daemon/core/emane/emanemanager.py | 19 ++++++- daemon/core/misc/utils.py | 88 +++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 1 deletion(-) diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 69722796..f60b1084 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -29,6 +29,8 @@ from core.misc import utils from core.misc.ipaddress import MacAddress from core.xml import xmlutils +_PATH = os.path.abspath(os.path.dirname(__file__)) + # EMANE 0.7.4/0.8.1 try: import emaneeventservice @@ -91,7 +93,7 @@ class EmaneManager(ConfigurableManager): self._modelclsmap = { self.emane_config.name: self.emane_config } - self.loadmodels() + self.load_models(_PATH) def logversion(self): """ @@ -192,6 +194,21 @@ class EmaneManager(ConfigurableManager): self.session.add_config_object(emane_model.name, emane_model.config_type, emane_model.configure_emane) + def load_models(self, path): + """ + Loads EMANE models into the manager for usage within CORE. + + :param str path: path to retrieve model from + :return: nothing + """ + emane_models = utils.load_classes(path, EmaneModel) + for emane_model in emane_models: + logger.info("loading emane model: (%s) %s - %s", + emane_model, emane_model.name, RegisterTlvs(emane_model.config_type)) + self._modelclsmap[emane_model.name] = emane_model + self.session.add_config_object(emane_model.name, emane_model.config_type, + emane_model.configure_emane) + def addobj(self, obj): """ add a new EmaneNode object to this Emane controller object diff --git a/daemon/core/misc/utils.py b/daemon/core/misc/utils.py index 62dccd0c..7eb9281c 100644 --- a/daemon/core/misc/utils.py +++ b/daemon/core/misc/utils.py @@ -2,8 +2,11 @@ Miscellaneous utility functions, wrappers around some subprocess procedures. """ +import importlib +import inspect import os import subprocess +import sys import fcntl import resource @@ -445,3 +448,88 @@ def checkforkernelmodule(name): if line.startswith(name + " "): return line.rstrip() return None + + +def _valid_module(path, file_name): + """ + Check if file is a valid python module. + + :param str path: path to file + :param str file_name: file name to check + :return: True if a valid python module file, False otherwise + :rtype: bool + """ + file_path = os.path.join(path, file_name) + if not os.path.isfile(file_path): + return False + + if file_name.startswith("_"): + return False + + if not file_name.endswith(".py"): + return False + + return True + + +def _is_class(module, member, clazz): + """ + Validates if a module member is a class and an instance of a CoreService. + + :param module: module to validate for service + :param member: member to validate for service + :param clazz: clazz type to check for validation + :return: True if a valid service, False otherwise + :rtype: bool + """ + if not inspect.isclass(member): + return False + + if not issubclass(member, clazz): + return False + + if member.__module__ != module.__name__: + return False + + return True + + +def load_classes(path, clazz): + """ + Dynamically load classes for use within CORE. + + :param path: path to load classes from + :param clazz: class type expected to be inherited from for loading + :return: list of classes loaded + """ + # validate path exists + logger.info("attempting to load modules from path: %s", path) + if not os.path.isdir(path): + logger.warn("invalid custom module directory specified" ": %s" % path) + # check if path is in sys.path + parent_path = os.path.dirname(path) + if parent_path not in sys.path: + logger.info("adding parent path to allow imports: %s", parent_path) + sys.path.append(parent_path) + + # retrieve potential service modules, and filter out invalid modules + base_module = os.path.basename(path) + module_names = os.listdir(path) + module_names = filter(lambda x: _valid_module(path, x), module_names) + module_names = map(lambda x: x[:-3], module_names) + + # import and add all service modules in the path + classes = [] + for module_name in module_names: + import_statement = "%s.%s" % (base_module, module_name) + logger.info("importing custom module: %s", import_statement) + try: + module = importlib.import_module(import_statement) + members = inspect.getmembers(module, lambda x: _is_class(module, x, clazz)) + for member in members: + clazz = member[1] + classes.append(clazz) + except: + logger.exception("unexpected error during import, skipping: %s", import_statement) + + return classes