From 25cfb21586755dc675c61a4766e68999bce8ec4b Mon Sep 17 00:00:00 2001 From: "Blake J. Harnden" Date: Wed, 13 Jun 2018 16:17:47 -0700 Subject: [PATCH] added modelmanager for both mobility and emane to leverage and reduce duplicate logic --- daemon/core/conf.py | 84 ++++++++++++++++++++++++ daemon/core/corehandlers.py | 16 ++--- daemon/core/emane/emanemanager.py | 92 ++------------------------- daemon/core/mobility.py | 102 +++--------------------------- daemon/tests/test_conf.py | 69 +++++++++++++++++++- 5 files changed, 171 insertions(+), 192 deletions(-) diff --git a/daemon/core/conf.py b/daemon/core/conf.py index 631d2e5d..99c9a738 100644 --- a/daemon/core/conf.py +++ b/daemon/core/conf.py @@ -265,3 +265,87 @@ class ConfigurableOptions(object): :rtype: OrderedDict """ return OrderedDict([(config.id, config.default) for config in cls.configurations()]) + + +class ModelManager(ConfigurableManager): + def __init__(self): + super(ModelManager, self).__init__() + self.models = {} + self.node_models = {} + + def set_model_config(self, node_id, model_name, config=None): + """ + Set configuration data for a model. + + :param int node_id: node id to set model configuration for + :param str model_name: model to set configuration for + :param dict config: configuration data to set for model + :return: nothing + """ + # get model class to configure + model_class = self.models.get(model_name) + if not model_class: + raise ValueError("%s is an invalid model" % model_name) + + # retrieve default values + node_config = self.get_model_config(node_id, model_name) + if not config: + config = {} + for key, value in config.iteritems(): + node_config[key] = value + + # set as node model for startup + self.node_models[node_id] = model_name + + # set configuration + self.set_configs(node_config, node_id=node_id, config_type=model_name) + + def get_model_config(self, node_id, model_name): + """ + Set configuration data for a model. + + :param int node_id: node id to set model configuration for + :param str model_name: model to set configuration for + :return: current model configuration for node + :rtype: dict + """ + # get model class to configure + model_class = self.models.get(model_name) + if not model_class: + raise ValueError("%s is an invalid model" % model_name) + + config = self.get_configs(node_id=node_id, config_type=model_name) + if not config: + # set default values, when not already set + config = model_class.default_values() + self.set_configs(config, node_id=node_id, config_type=model_name) + + return config + + def set_model(self, node, model_class, config=None): + logger.info("setting mobility model(%s) for node(%s): %s", model_class.name, node.objid, config) + self.set_model_config(node.objid, model_class.name, config) + config = self.get_model_config(node.objid, model_class.name) + node.setmodel(model_class, config) + + def getmodels(self, node): + """ + Return a list of model classes and values for a net if one has been + configured. This is invoked when exporting a session to XML. + + :param node: network node to get models for + :return: list of model and values tuples for the network node + :rtype: list + """ + models = [] + all_configs = {} + if self.has_configs(node_id=node.objid): + all_configs = self.get_all_configs(node_id=node.objid) + + for model_name in all_configs.iterkeys(): + model_class = self.models[model_name] + config = self.get_configs(node_id=node.objid, config_type=model_name) + models.append((model_class, config)) + + logger.debug("mobility models for node(%s): %s", node.objid, models) + return models diff --git a/daemon/core/corehandlers.py b/daemon/core/corehandlers.py index 23eb1a94..bb7967fb 100644 --- a/daemon/core/corehandlers.py +++ b/daemon/core/corehandlers.py @@ -42,8 +42,6 @@ from core.enumerations import SessionTlvs from core.misc import nodeutils from core.misc import structutils from core.misc import utils -from core.mobility import BasicRangeModel -from core.mobility import Ns2ScriptedMobility from core.service import CoreService from core.service import ServiceManager @@ -380,13 +378,11 @@ class CoreHandler(SocketServer.BaseRequestHandler): tlv_data += coreapi.CoreRegisterTlv.pack(self.session.broker.config_type, self.session.broker.name) tlv_data += coreapi.CoreRegisterTlv.pack(self.session.location.config_type, self.session.location.name) tlv_data += coreapi.CoreRegisterTlv.pack(self.session.mobility.config_type, self.session.mobility.name) - for model_name in self.session.mobility.mobility_models(): - model_class = self.session.mobility.get_model_class(model_name) + for model_class in self.session.mobility.models.itervalues(): tlv_data += coreapi.CoreRegisterTlv.pack(model_class.config_type, model_class.name) tlv_data += coreapi.CoreRegisterTlv.pack(self.session.services.config_type, self.session.services.name) tlv_data += coreapi.CoreRegisterTlv.pack(self.session.emane.config_type, self.session.emane.name) - for model_name in self.session.emane.emane_models(): - model_class = self.session.emane.get_model_class(model_name) + for model_class in self.session.emane.models.itervalues(): tlv_data += coreapi.CoreRegisterTlv.pack(model_class.config_type, model_class.name) tlv_data += coreapi.CoreRegisterTlv.pack(self.session.options.config_type, self.session.options.name) tlv_data += coreapi.CoreRegisterTlv.pack(self.session.metadata.config_type, self.session.metadata.name) @@ -937,11 +933,11 @@ class CoreHandler(SocketServer.BaseRequestHandler): replies = self.handle_config_services(message_type, config_data) elif config_data.object == self.session.mobility.name: self.handle_config_mobility(message_type, config_data) - elif config_data.object in [BasicRangeModel.name, Ns2ScriptedMobility.name]: + elif config_data.object in self.session.mobility.models: replies = self.handle_config_mobility_models(message_type, config_data) elif config_data.object == self.session.emane.name: replies = self.handle_config_emane(message_type, config_data) - elif config_data.object in self.session.emane.emane_models(): + elif config_data.object in self.session.emane.models: replies = self.handle_config_emane_models(message_type, config_data) else: raise Exception("no handler for configuration: %s", config_data.object) @@ -1181,7 +1177,7 @@ class CoreHandler(SocketServer.BaseRequestHandler): logger.info("replying to configure request for model: %s", object_name) typeflags = ConfigFlags.NONE.value - model_class = self.session.mobility.get_model_class(object_name) + model_class = self.session.mobility.models.get(object_name) if not model_class: logger.warn("model class does not exist: %s", object_name) return [] @@ -1251,7 +1247,7 @@ class CoreHandler(SocketServer.BaseRequestHandler): logger.info("replying to configure request for model: %s", object_name) typeflags = ConfigFlags.NONE.value - model_class = self.session.emane.get_model_class(object_name) + model_class = self.session.emane.models.get(object_name) if not model_class: logger.warn("model class does not exist: %s", object_name) return [] diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 78b770b4..ae58d1b5 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -11,8 +11,9 @@ from core import constants from core import logger from core.api import coreapi from core.api import dataconversion -from core.conf import ConfigShim, ConfigurableManager +from core.conf import ConfigShim from core.conf import Configuration +from core.conf import ModelManager from core.emane import emanemanifest from core.emane.bypass import EmaneBypassModel from core.emane.commeffect import EmaneCommEffectModel @@ -53,7 +54,7 @@ EMANE_MODELS = [ ] -class EmaneManager(ConfigurableManager): +class EmaneManager(ModelManager): """ EMANE controller object. Lives in a Session instance and is used for building EMANE config files from all of the EmaneNode objects in this @@ -89,64 +90,11 @@ class EmaneManager(ConfigurableManager): self.emane_config = EmaneGlobalModel(session) self.set_configs(self.emane_config.default_values()) - # store the last configured model for a node, used during startup - self.node_models = {} - session.broker.handlers.add(self.handledistributed) self.service = None self.event_device = None - self._modelclsmap = {} - - self.service = None self.emane_check() - def set_model_config(self, node_id, model_name, config): - """ - Set configuration data for a model. - - :param int node_id: node id to set model configuration for - :param str model_name: model to set configuration for - :param dict config: configuration data to set for model - :return: nothing - """ - # get model class to configure - model_class = self._modelclsmap.get(model_name) - if not model_class: - raise ValueError("%s is an invalid model" % model_name) - - # retrieve default values - node_config = self.get_model_config(node_id, model_name) - for key, value in config.iteritems(): - node_config[key] = value - - # set as node model for startup - self.node_models[node_id] = model_name - - # set configuration - self.set_configs(node_config, node_id=node_id, config_type=model_name) - - def get_model_config(self, node_id, model_name): - """ - Set configuration data for a model. - - :param int node_id: node id to set model configuration for - :param str model_name: model to set configuration for - :return: current model configuration for node - :rtype: dict - """ - # get model class to configure - model_class = self._modelclsmap.get(model_name) - if not model_class: - raise ValueError("%s is an invalid model" % model_name) - - config = self.get_configs(node_id=node_id, config_type=model_name) - if not config: - # set default values, when not already set - config = model_class.default_values() - self.set_configs(config, node_id=node_id, config_type=model_name) - - return config - def getifcconfig(self, node_id, interface, model_name): """ Retrieve interface configuration or node configuration if not provided. @@ -189,24 +137,10 @@ class EmaneManager(ConfigurableManager): return config - def set_model(self, node, model_class, config=None): - logger.info("setting emane model(%s) for node(%s): %s", model_class.name, node.objid, config) - if not config: - config = {} - self.set_model_config(node.objid, model_class.name, config) - config = self.get_model_config(node.objid, model_class.name) - node.setmodel(model_class, config) - def config_reset(self, node_id=None): super(EmaneManager, self).config_reset(node_id) self.set_configs(self.emane_config.default_values()) - def emane_models(self): - return self._modelclsmap.keys() - - def get_model_class(self, model_name): - return self._modelclsmap[model_name] - def emane_check(self): """ Check if emane is installed and load models. @@ -281,7 +215,7 @@ class EmaneManager(ConfigurableManager): """ for emane_model in emane_models: logger.info("loading emane model: %s", emane_model.__name__) - self._modelclsmap[emane_model.name] = emane_model + self.models[emane_model.name] = emane_model def add_node(self, emane_node): """ @@ -307,22 +241,6 @@ class EmaneManager(ConfigurableManager): nodes.add(netif.node) return nodes - def getmodels(self, node): - """ - Used with XML export. - """ - models = [] - all_configs = {} - if self.has_configs(node_id=node.objid): - all_configs = self.get_all_configs(node_id=node.objid) - - for model_name in all_configs.iterkeys(): - model_class = self._modelclsmap[model_name] - config = self.get_configs(node_id=node.objid, config_type=model_name) - models.append((model_class, config)) - logger.debug("emane models for node(%s): %s", node.objid, models) - return models - def setup(self): """ Populate self._objs with EmaneNodes; perform distributed setup; @@ -650,7 +568,7 @@ class EmaneManager(ConfigurableManager): config = self.get_model_config(node_id=node_id, model_name=model_name) logger.debug("setting emane model(%s) config(%s)", model_name, config) - model_class = self._modelclsmap[model_name] + model_class = self.models[model_name] emane_node.setmodel(model_class, config) def nemlookup(self, nemid): diff --git a/daemon/core/mobility.py b/daemon/core/mobility.py index 8c0e69b4..70efb539 100644 --- a/daemon/core/mobility.py +++ b/daemon/core/mobility.py @@ -9,8 +9,9 @@ import threading import time from core import logger -from core.conf import ConfigurableOptions, ConfigurableManager +from core.conf import ConfigurableOptions from core.conf import Configuration +from core.conf import ModelManager from core.coreobj import PyCoreNode from core.data import EventData from core.data import LinkData @@ -25,7 +26,7 @@ from core.misc import utils from core.misc.ipaddress import IpAddress -class MobilityManager(ConfigurableManager): +class MobilityManager(ModelManager): """ Member of session class for handling configuration data for mobility and range models. @@ -41,90 +42,14 @@ class MobilityManager(ConfigurableManager): """ super(MobilityManager, self).__init__() self.session = session - # configurations for basic range, indexed by WLAN node number, are stored in configurations - # mapping from model names to their classes - self._modelclsmap = { - BasicRangeModel.name: BasicRangeModel, - Ns2ScriptedMobility.name: Ns2ScriptedMobility - } + self.models[BasicRangeModel.name] = BasicRangeModel + self.models[Ns2ScriptedMobility.name] = Ns2ScriptedMobility # dummy node objects for tracking position of nodes on other servers self.phys = {} self.physnets = {} self.session.broker.handlers.add(self.physnodehandlelink) - def set_model_config(self, node_id, model_name, config): - """ - Set configuration data for a model. - - :param int node_id: node id to set model configuration for - :param str model_name: model to set configuration for - :param dict config: configuration data to set for model - :return: nothing - """ - # get model class to configure - model_class = self._modelclsmap.get(model_name) - if not model_class: - raise ValueError("%s is an invalid model" % model_name) - - # retrieve default values - node_config = self.get_model_config(node_id, model_name) - for key, value in config.iteritems(): - node_config[key] = value - - # set configuration - self.set_configs(node_config, node_id=node_id, config_type=model_name) - - def get_model_config(self, node_id, model_name): - """ - Set configuration data for a model. - - :param int node_id: node id to set model configuration for - :param str model_name: model to set configuration for - :return: current model configuration for node - :rtype: dict - """ - # get model class to configure - model_class = self._modelclsmap.get(model_name) - if not model_class: - raise ValueError("%s is an invalid model" % model_name) - - config = self.get_configs(node_id=node_id, config_type=model_name) - if not config: - # set default values, when not already set - config = model_class.default_values() - self.set_configs(config, node_id=node_id, config_type=model_name) - - return config - - def mobility_models(self): - return self._modelclsmap.keys() - - def get_model_class(self, model_name): - return self._modelclsmap[model_name] - - def getmodels(self, node): - """ - Return a list of model classes and values for a net if one has been - configured. This is invoked when exporting a session to XML. - - :param node: network node to get models for - :return: list of model and values tuples for the network node - :rtype: list - """ - models = [] - all_configs = {} - if self.has_configs(node_id=node.objid): - all_configs = self.get_all_configs(node_id=node.objid) - - for model_name in all_configs.iterkeys(): - model_class = self._modelclsmap[model_name] - config = self.get_configs(node_id=node.objid, config_type=model_name) - models.append((model_class, config)) - - logger.debug("mobility models for node(%s): %s", node.objid, models) - return models - def startup(self, node_ids=None): """ Session is transitioning from instantiation to runtime state. @@ -146,11 +71,11 @@ class MobilityManager(ConfigurableManager): logger.warn("skipping mobility configuration for unknown node: %s", node_id) continue - for model_name in self._modelclsmap.iterkeys(): + for model_name in self.models.iterkeys(): config = self.get_configs(node_id=node_id, config_type=model_name) if not config: continue - model_class = self._modelclsmap[model_name] + model_class = self.models[model_name] self.set_model(node, model_class, config) if self.session.master: @@ -159,14 +84,6 @@ class MobilityManager(ConfigurableManager): if node.mobility: self.session.event_loop.add_event(0.0, node.mobility.startup) - def set_model(self, node, model_class, config=None): - logger.info("setting mobility model(%s) for node(%s): %s", model_class.name, node.objid, config) - if not config: - config = {} - self.set_model_config(node.objid, model_class.name, config) - config = self.get_model_config(node.objid, model_class.name) - node.setmodel(model_class, config) - def handleevent(self, event_data): """ Handle an Event Message used to start, stop, or pause @@ -189,7 +106,7 @@ class MobilityManager(ConfigurableManager): models = name[9:].split(',') for model in models: try: - cls = self._modelclsmap[model] + cls = self.models[model] except KeyError: logger.warn("Ignoring event for unknown model '%s'", model) continue @@ -298,8 +215,7 @@ class MobilityManager(ConfigurableManager): dummy = PyCoreNode(session=self.session, objid=nn[1], name="n%d" % nn[1], start=False) self.addphys(nn[0], dummy) - # TODO: remove need to handling old style messages - + # TODO: remove need to handling old style messages def physnodeupdateposition(self, message): """ Snoop node messages belonging to physical nodes. The dummy object diff --git a/daemon/tests/test_conf.py b/daemon/tests/test_conf.py index 8c215e03..f19fc168 100644 --- a/daemon/tests/test_conf.py +++ b/daemon/tests/test_conf.py @@ -1,6 +1,10 @@ -from core.conf import ConfigurableOptions, ConfigurableManager +import pytest + +from core.conf import ConfigurableOptions, ConfigurableManager, ModelManager from core.conf import Configuration -from core.enumerations import ConfigDataTypes +from core.emane.ieee80211abg import EmaneIeee80211abgModel +from core.enumerations import ConfigDataTypes, NodeTypes +from core.mobility import BasicRangeModel class TestConfigurableOptions(ConfigurableOptions): @@ -117,3 +121,64 @@ class TestConf: # then assert defaults_value == value assert node_value == value + + def test_model_setget_config(self): + # given + manager = ModelManager() + manager.models[BasicRangeModel.name] = BasicRangeModel + + # when + manager.set_model_config(1, BasicRangeModel.name) + + # then + assert manager.get_model_config(1, BasicRangeModel.name) + + def test_model_set_config_error(self): + # given + manager = ModelManager() + manager.models[BasicRangeModel.name] = BasicRangeModel + bad_name = "bad-model" + + # when/then + with pytest.raises(ValueError): + manager.set_model_config(1, bad_name) + + def test_model_get_config_error(self): + # given + manager = ModelManager() + manager.models[BasicRangeModel.name] = BasicRangeModel + bad_name = "bad-model" + + # when/then + with pytest.raises(ValueError): + manager.get_model_config(1, bad_name) + + def test_model_set(self, session): + # given + wlan_node = session.add_node(_type=NodeTypes.WIRELESS_LAN) + + # when + session.mobility.set_model(wlan_node, BasicRangeModel) + + # then + assert session.mobility.get_model_config(wlan_node.objid, BasicRangeModel.name) + + def test_model_set_error(self, session): + # given + wlan_node = session.add_node(_type=NodeTypes.WIRELESS_LAN) + + # when / then + with pytest.raises(ValueError): + session.mobility.set_model(wlan_node, EmaneIeee80211abgModel) + + def test_get_models(self, session): + # given + wlan_node = session.add_node(_type=NodeTypes.WIRELESS_LAN) + session.mobility.set_model(wlan_node, BasicRangeModel) + + # when + models = session.mobility.getmodels(wlan_node) + + # then + assert models + assert len(models) == 1