2017-04-25 16:45:34 +01:00
|
|
|
"""
|
|
|
|
emane.py: definition of an Emane class for implementing configuration control of an EMANE emulation.
|
|
|
|
"""
|
|
|
|
|
2019-02-16 17:50:19 +00:00
|
|
|
import logging
|
2017-04-25 16:45:34 +01:00
|
|
|
import os
|
|
|
|
import threading
|
2019-12-20 21:30:55 +00:00
|
|
|
from collections import OrderedDict
|
2020-01-15 00:27:08 +00:00
|
|
|
from typing import TYPE_CHECKING, Dict, List, Set, Tuple, Type
|
2018-07-04 02:49:36 +01:00
|
|
|
|
2019-10-01 20:14:37 +01:00
|
|
|
from core import utils
|
2019-10-15 22:13:42 +01:00
|
|
|
from core.config import ConfigGroup, Configuration, ModelManager
|
2018-04-02 22:00:28 +01:00
|
|
|
from core.emane import emanemanifest
|
2017-04-25 16:45:34 +01:00
|
|
|
from core.emane.bypass import EmaneBypassModel
|
|
|
|
from core.emane.commeffect import EmaneCommEffectModel
|
|
|
|
from core.emane.emanemodel import EmaneModel
|
|
|
|
from core.emane.ieee80211abg import EmaneIeee80211abgModel
|
2019-09-28 06:31:56 +01:00
|
|
|
from core.emane.nodes import EmaneNet
|
2017-04-25 16:45:34 +01:00
|
|
|
from core.emane.rfpipe import EmaneRfPipeModel
|
2017-07-27 22:59:40 +01:00
|
|
|
from core.emane.tdma import EmaneTdmaModel
|
2019-10-15 22:13:42 +01:00
|
|
|
from core.emulator.enumerations import ConfigDataTypes, RegisterTlvs
|
2019-09-28 07:29:15 +01:00
|
|
|
from core.errors import CoreCommandError, CoreError
|
2020-01-15 00:27:08 +00:00
|
|
|
from core.nodes.base import CoreNode
|
|
|
|
from core.nodes.interface import CoreInterface
|
|
|
|
from core.nodes.network import CtrlNet
|
2018-07-04 02:49:36 +01:00
|
|
|
from core.xml import emanexml
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2020-01-15 00:27:08 +00:00
|
|
|
if TYPE_CHECKING:
|
|
|
|
from core.emulator.session import Session
|
|
|
|
|
|
|
|
|
2017-04-25 16:45:34 +01:00
|
|
|
try:
|
2018-03-16 16:33:43 +00:00
|
|
|
from emane.events import EventService
|
|
|
|
from emane.events import LocationEvent
|
2018-03-19 23:33:36 +00:00
|
|
|
from emane.events.eventserviceexception import EventServiceException
|
2017-04-25 16:45:34 +01:00
|
|
|
except ImportError:
|
2018-04-21 05:22:08 +01:00
|
|
|
try:
|
|
|
|
from emanesh.events import EventService
|
|
|
|
from emanesh.events import LocationEvent
|
|
|
|
from emanesh.events.eventserviceexception import EventServiceException
|
|
|
|
except ImportError:
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.debug("compatible emane python bindings not installed")
|
2017-04-25 16:45:34 +01:00
|
|
|
|
|
|
|
EMANE_MODELS = [
|
|
|
|
EmaneRfPipeModel,
|
|
|
|
EmaneIeee80211abgModel,
|
|
|
|
EmaneCommEffectModel,
|
2017-07-27 22:59:40 +01:00
|
|
|
EmaneBypassModel,
|
2019-09-10 23:10:24 +01:00
|
|
|
EmaneTdmaModel,
|
2017-04-25 16:45:34 +01:00
|
|
|
]
|
2019-04-08 17:49:37 +01:00
|
|
|
DEFAULT_EMANE_PREFIX = "/usr"
|
2019-12-20 21:30:55 +00:00
|
|
|
DEFAULT_DEV = "ctrl0"
|
2017-04-25 16:45:34 +01:00
|
|
|
|
|
|
|
|
2018-06-14 00:17:47 +01:00
|
|
|
class EmaneManager(ModelManager):
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
|
|
|
EMANE controller object. Lives in a Session instance and is used for
|
2019-09-28 06:31:56 +01:00
|
|
|
building EMANE config files for all EMANE networks in this emulation, and for
|
|
|
|
controlling the EMANE daemons.
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
2019-09-10 23:10:24 +01:00
|
|
|
|
2017-04-25 16:45:34 +01:00
|
|
|
name = "emane"
|
|
|
|
config_type = RegisterTlvs.EMULATION_SERVER.value
|
2018-03-19 23:33:36 +00:00
|
|
|
SUCCESS, NOT_NEEDED, NOT_READY = (0, 1, 2)
|
2017-08-04 22:34:44 +01:00
|
|
|
EVENTCFGVAR = "LIBEMANEEVENTSERVICECONFIG"
|
2017-04-25 16:45:34 +01:00
|
|
|
DEFAULT_LOG_LEVEL = 3
|
|
|
|
|
2020-01-15 00:27:08 +00:00
|
|
|
def __init__(self, session: "Session") -> None:
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
|
|
|
Creates a Emane instance.
|
|
|
|
|
2020-01-16 19:00:57 +00:00
|
|
|
:param session: session this manager is tied to
|
2017-04-25 16:45:34 +01:00
|
|
|
:return: nothing
|
|
|
|
"""
|
2019-10-23 17:51:52 +01:00
|
|
|
super().__init__()
|
2017-04-25 16:45:34 +01:00
|
|
|
self.session = session
|
2019-09-28 06:31:56 +01:00
|
|
|
self._emane_nets = {}
|
2018-03-19 23:33:36 +00:00
|
|
|
self._emane_node_lock = threading.Lock()
|
2018-06-08 22:21:41 +01:00
|
|
|
# port numbers are allocated from these counters
|
2019-09-10 23:10:24 +01:00
|
|
|
self.platformport = self.session.options.get_config_int(
|
|
|
|
"emane_platform_port", 8100
|
|
|
|
)
|
|
|
|
self.transformport = self.session.options.get_config_int(
|
|
|
|
"emane_transform_port", 8200
|
|
|
|
)
|
2017-04-25 16:45:34 +01:00
|
|
|
self.doeventloop = False
|
|
|
|
self.eventmonthread = None
|
2018-04-21 05:22:08 +01:00
|
|
|
|
2017-04-25 16:45:34 +01:00
|
|
|
# model for global EMANE configuration options
|
2018-06-11 20:26:12 +01:00
|
|
|
self.emane_config = EmaneGlobalModel(session)
|
2018-06-13 19:59:50 +01:00
|
|
|
self.set_configs(self.emane_config.default_values())
|
2018-06-11 20:26:12 +01:00
|
|
|
|
2017-04-25 16:45:34 +01:00
|
|
|
self.service = None
|
2020-02-15 00:22:28 +00:00
|
|
|
self.eventchannel = None
|
2018-03-26 18:27:39 +01:00
|
|
|
self.event_device = None
|
2018-04-21 05:22:08 +01:00
|
|
|
self.emane_check()
|
2018-03-19 23:33:36 +00:00
|
|
|
|
2020-01-15 00:27:08 +00:00
|
|
|
def getifcconfig(
|
|
|
|
self, node_id: int, interface: CoreInterface, model_name: str
|
|
|
|
) -> Dict[str, str]:
|
2018-06-13 19:59:50 +01:00
|
|
|
"""
|
|
|
|
Retrieve interface configuration or node configuration if not provided.
|
|
|
|
|
2020-01-16 19:00:57 +00:00
|
|
|
:param node_id: node id
|
2018-06-13 19:59:50 +01:00
|
|
|
:param interface: node interface
|
2020-01-16 19:00:57 +00:00
|
|
|
:param model_name: model to get configuration for
|
2018-06-13 19:59:50 +01:00
|
|
|
:return: node/interface model configuration
|
2020-01-17 00:12:01 +00:00
|
|
|
"""
|
2018-06-13 19:59:50 +01:00
|
|
|
# use the network-wide config values or interface(NEM)-specific values?
|
|
|
|
if interface is None:
|
|
|
|
return self.get_configs(node_id=node_id, config_type=model_name)
|
|
|
|
else:
|
|
|
|
# don"t use default values when interface config is the same as net
|
2019-04-27 06:07:51 +01:00
|
|
|
# note here that using ifc.node.id as key allows for only one type
|
2018-06-13 19:59:50 +01:00
|
|
|
# of each model per node;
|
|
|
|
# TODO: use both node and interface as key
|
|
|
|
|
|
|
|
# Adamson change: first check for iface config keyed by "node:ifc.name"
|
|
|
|
# (so that nodes w/ multiple interfaces of same conftype can have
|
|
|
|
# different configs for each separate interface)
|
2019-04-27 06:07:51 +01:00
|
|
|
key = 1000 * interface.node.id
|
2018-06-13 19:59:50 +01:00
|
|
|
if interface.netindex is not None:
|
|
|
|
key += interface.netindex
|
|
|
|
|
|
|
|
# try retrieve interface specific configuration, avoid getting defaults
|
2018-07-12 05:34:21 +01:00
|
|
|
config = self.get_configs(node_id=key, config_type=model_name)
|
2018-06-13 19:59:50 +01:00
|
|
|
|
|
|
|
# otherwise retrieve the interfaces node configuration, avoid using defaults
|
2018-07-12 05:34:21 +01:00
|
|
|
if not config:
|
2019-09-10 23:10:24 +01:00
|
|
|
config = self.get_configs(
|
|
|
|
node_id=interface.node.id, config_type=model_name
|
|
|
|
)
|
2018-06-13 19:59:50 +01:00
|
|
|
|
2018-07-11 17:19:06 +01:00
|
|
|
# get non interface config, when none found
|
2018-07-12 05:34:21 +01:00
|
|
|
if not config:
|
2018-06-13 19:59:50 +01:00
|
|
|
# with EMANE 0.9.2+, we need an extra NEM XML from
|
|
|
|
# model.buildnemxmlfiles(), so defaults are returned here
|
|
|
|
config = self.get_configs(node_id=node_id, config_type=model_name)
|
|
|
|
|
|
|
|
return config
|
|
|
|
|
2020-01-15 00:27:08 +00:00
|
|
|
def config_reset(self, node_id: int = None) -> None:
|
2019-10-23 17:51:52 +01:00
|
|
|
super().config_reset(node_id)
|
2018-06-13 19:59:50 +01:00
|
|
|
self.set_configs(self.emane_config.default_values())
|
2018-06-08 22:21:41 +01:00
|
|
|
|
2020-01-15 00:27:08 +00:00
|
|
|
def emane_check(self) -> None:
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
2018-04-21 05:22:08 +01:00
|
|
|
Check if emane is installed and load models.
|
|
|
|
|
|
|
|
:return: nothing
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
2018-03-26 18:27:39 +01:00
|
|
|
try:
|
2018-04-21 05:22:08 +01:00
|
|
|
# check for emane
|
2019-10-12 00:36:57 +01:00
|
|
|
args = "emane --version"
|
2019-10-21 18:32:42 +01:00
|
|
|
emane_version = utils.cmd(args)
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.info("using EMANE: %s", emane_version)
|
2019-10-17 19:10:59 +01:00
|
|
|
self.session.distributed.execute(lambda x: x.remote_cmd(args))
|
2018-04-21 05:22:08 +01:00
|
|
|
|
|
|
|
# load default emane models
|
|
|
|
self.load_models(EMANE_MODELS)
|
|
|
|
|
|
|
|
# load custom models
|
2018-06-12 16:37:39 +01:00
|
|
|
custom_models_path = self.session.options.get_config("emane_models_dir")
|
2018-04-21 05:22:08 +01:00
|
|
|
if custom_models_path:
|
|
|
|
emane_models = utils.load_classes(custom_models_path, EmaneModel)
|
|
|
|
self.load_models(emane_models)
|
2018-03-26 18:27:39 +01:00
|
|
|
except CoreCommandError:
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.info("emane is not installed")
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2020-01-15 00:27:08 +00:00
|
|
|
def deleteeventservice(self) -> None:
|
2018-03-19 23:33:36 +00:00
|
|
|
if self.service:
|
|
|
|
for fd in self.service._readFd, self.service._writeFd:
|
|
|
|
if fd >= 0:
|
|
|
|
os.close(fd)
|
|
|
|
for f in self.service._socket, self.service._socketOTA:
|
|
|
|
if f:
|
|
|
|
f.close()
|
|
|
|
self.service = None
|
2018-03-26 18:27:39 +01:00
|
|
|
self.event_device = None
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2020-01-15 00:27:08 +00:00
|
|
|
def initeventservice(self, filename: str = None, shutdown: bool = False) -> None:
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
2017-08-18 19:34:25 +01:00
|
|
|
Re-initialize the EMANE Event service.
|
2017-04-25 16:45:34 +01:00
|
|
|
The multicast group and/or port may be configured.
|
|
|
|
"""
|
|
|
|
self.deleteeventservice()
|
|
|
|
|
2018-03-19 23:33:36 +00:00
|
|
|
if shutdown:
|
|
|
|
return
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2018-03-19 23:33:36 +00:00
|
|
|
# Get the control network to be used for events
|
2018-06-13 19:59:50 +01:00
|
|
|
group, port = self.get_config("eventservicegroup").split(":")
|
|
|
|
self.event_device = self.get_config("eventservicedevice")
|
2018-03-26 18:27:39 +01:00
|
|
|
eventnetidx = self.session.get_control_net_index(self.event_device)
|
2018-03-19 23:33:36 +00:00
|
|
|
if eventnetidx < 0:
|
2019-09-10 23:10:24 +01:00
|
|
|
logging.error(
|
|
|
|
"invalid emane event service device provided: %s", self.event_device
|
|
|
|
)
|
2020-01-15 00:27:08 +00:00
|
|
|
return
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2018-03-19 23:33:36 +00:00
|
|
|
# make sure the event control network is in place
|
2019-09-10 23:10:24 +01:00
|
|
|
eventnet = self.session.add_remove_control_net(
|
|
|
|
net_index=eventnetidx, remove=False, conf_required=False
|
|
|
|
)
|
2018-03-19 23:33:36 +00:00
|
|
|
if eventnet is not None:
|
|
|
|
# direct EMANE events towards control net bridge
|
2018-03-26 18:27:39 +01:00
|
|
|
self.event_device = eventnet.brname
|
2020-02-15 00:22:28 +00:00
|
|
|
self.eventchannel = (group, int(port), self.event_device)
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2018-03-19 23:33:36 +00:00
|
|
|
# disabled otachannel for event service
|
|
|
|
# only needed for e.g. antennaprofile events xmit by models
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.info("using %s for event service traffic", self.event_device)
|
2017-04-25 16:45:34 +01:00
|
|
|
try:
|
2020-02-15 00:22:28 +00:00
|
|
|
self.service = EventService(eventchannel=self.eventchannel, otachannel=None)
|
2018-03-19 23:33:36 +00:00
|
|
|
except EventServiceException:
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.exception("error instantiating emane EventService")
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2020-01-15 00:27:08 +00:00
|
|
|
def load_models(self, emane_models: List[Type[EmaneModel]]) -> None:
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
2018-07-06 19:56:09 +01:00
|
|
|
Load EMANE models and make them available.
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
2018-02-05 17:01:49 +00:00
|
|
|
for emane_model in emane_models:
|
2019-09-11 23:05:05 +01:00
|
|
|
logging.debug("loading emane model: %s", emane_model.__name__)
|
2019-09-10 23:10:24 +01:00
|
|
|
emane_prefix = self.session.options.get_config(
|
|
|
|
"emane_prefix", default=DEFAULT_EMANE_PREFIX
|
|
|
|
)
|
2019-04-08 17:49:37 +01:00
|
|
|
emane_model.load(emane_prefix)
|
2018-06-14 00:17:47 +01:00
|
|
|
self.models[emane_model.name] = emane_model
|
2018-02-05 17:01:49 +00:00
|
|
|
|
2020-01-15 00:27:08 +00:00
|
|
|
def add_node(self, emane_net: EmaneNet) -> None:
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
2019-09-28 06:31:56 +01:00
|
|
|
Add EMANE network object to this manager.
|
2018-03-19 23:33:36 +00:00
|
|
|
|
2020-01-16 19:00:57 +00:00
|
|
|
:param emane_net: emane node to add
|
2018-03-19 23:33:36 +00:00
|
|
|
:return: nothing
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
2018-03-19 23:33:36 +00:00
|
|
|
with self._emane_node_lock:
|
2019-09-28 06:31:56 +01:00
|
|
|
if emane_net.id in self._emane_nets:
|
2019-09-10 23:10:24 +01:00
|
|
|
raise KeyError(
|
2019-10-18 18:33:31 +01:00
|
|
|
f"non-unique EMANE object id {emane_net.id} for {emane_net}"
|
2019-09-10 23:10:24 +01:00
|
|
|
)
|
2019-09-28 06:31:56 +01:00
|
|
|
self._emane_nets[emane_net.id] = emane_net
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2020-01-15 00:27:08 +00:00
|
|
|
def getnodes(self) -> Set[CoreNode]:
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
2019-09-28 06:31:56 +01:00
|
|
|
Return a set of CoreNodes that are linked to an EMANE network,
|
2017-04-25 16:45:34 +01:00
|
|
|
e.g. containers having one or more radio interfaces.
|
|
|
|
"""
|
|
|
|
# assumes self._objslock already held
|
2018-03-19 23:33:36 +00:00
|
|
|
nodes = set()
|
2019-09-28 06:31:56 +01:00
|
|
|
for emane_net in self._emane_nets.values():
|
|
|
|
for netif in emane_net.netifs():
|
2018-03-19 23:33:36 +00:00
|
|
|
nodes.add(netif.node)
|
|
|
|
return nodes
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2020-01-15 00:27:08 +00:00
|
|
|
def setup(self) -> int:
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
2019-09-28 06:31:56 +01:00
|
|
|
Setup duties for EMANE manager.
|
|
|
|
|
|
|
|
:return: SUCCESS, NOT_NEEDED, NOT_READY in order to delay session
|
|
|
|
instantiation
|
2020-01-17 00:12:01 +00:00
|
|
|
"""
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.debug("emane setup")
|
2018-03-19 23:33:36 +00:00
|
|
|
|
|
|
|
# TODO: drive this from the session object
|
2019-04-30 07:31:47 +01:00
|
|
|
with self.session._nodes_lock:
|
2019-05-06 05:23:43 +01:00
|
|
|
for node_id in self.session.nodes:
|
|
|
|
node = self.session.nodes[node_id]
|
2019-09-28 06:31:56 +01:00
|
|
|
if isinstance(node, EmaneNet):
|
2019-09-10 23:10:24 +01:00
|
|
|
logging.debug(
|
|
|
|
"adding emane node: id(%s) name(%s)", node.id, node.name
|
|
|
|
)
|
2018-03-19 23:33:36 +00:00
|
|
|
self.add_node(node)
|
2018-03-21 16:57:08 +00:00
|
|
|
|
2019-09-28 06:31:56 +01:00
|
|
|
if not self._emane_nets:
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.debug("no emane nodes in session")
|
2017-04-25 16:45:34 +01:00
|
|
|
return EmaneManager.NOT_NEEDED
|
2018-03-19 23:33:36 +00:00
|
|
|
|
2017-04-25 16:45:34 +01:00
|
|
|
# control network bridge required for EMANE 0.9.2
|
|
|
|
# - needs to exist when eventservice binds to it (initeventservice)
|
2019-10-26 06:06:30 +01:00
|
|
|
otadev = self.get_config("otamanagerdevice")
|
|
|
|
netidx = self.session.get_control_net_index(otadev)
|
|
|
|
logging.debug("emane ota manager device: index(%s) otadev(%s)", netidx, otadev)
|
|
|
|
if netidx < 0:
|
|
|
|
logging.error(
|
|
|
|
"EMANE cannot start, check core config. invalid OTA device provided: %s",
|
|
|
|
otadev,
|
2019-09-10 23:10:24 +01:00
|
|
|
)
|
2019-10-26 06:06:30 +01:00
|
|
|
return EmaneManager.NOT_READY
|
|
|
|
|
|
|
|
self.session.add_remove_control_net(
|
|
|
|
net_index=netidx, remove=False, conf_required=False
|
|
|
|
)
|
|
|
|
eventdev = self.get_config("eventservicedevice")
|
|
|
|
logging.debug("emane event service device: eventdev(%s)", eventdev)
|
|
|
|
if eventdev != otadev:
|
|
|
|
netidx = self.session.get_control_net_index(eventdev)
|
|
|
|
logging.debug("emane event service device index: %s", netidx)
|
2017-04-25 16:45:34 +01:00
|
|
|
if netidx < 0:
|
2019-09-10 23:10:24 +01:00
|
|
|
logging.error(
|
2019-10-26 06:06:30 +01:00
|
|
|
"EMANE cannot start, check core config. invalid event service device: %s",
|
|
|
|
eventdev,
|
2019-09-10 23:10:24 +01:00
|
|
|
)
|
2017-04-25 16:45:34 +01:00
|
|
|
return EmaneManager.NOT_READY
|
|
|
|
|
2019-10-15 22:13:42 +01:00
|
|
|
self.session.add_remove_control_net(
|
2019-09-10 23:10:24 +01:00
|
|
|
net_index=netidx, remove=False, conf_required=False
|
|
|
|
)
|
2018-03-19 23:33:36 +00:00
|
|
|
|
2018-06-11 20:26:12 +01:00
|
|
|
self.check_node_models()
|
2017-04-25 16:45:34 +01:00
|
|
|
return EmaneManager.SUCCESS
|
|
|
|
|
2020-01-15 00:27:08 +00:00
|
|
|
def startup(self) -> int:
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
2019-09-28 06:31:56 +01:00
|
|
|
After all the EMANE networks have been added, build XML files
|
|
|
|
and start the daemons.
|
|
|
|
|
|
|
|
:return: SUCCESS, NOT_NEEDED, NOT_READY in order to delay session
|
|
|
|
instantiation
|
2020-01-17 00:12:01 +00:00
|
|
|
"""
|
2017-04-25 16:45:34 +01:00
|
|
|
self.reset()
|
|
|
|
r = self.setup()
|
2018-03-19 23:33:36 +00:00
|
|
|
|
|
|
|
# NOT_NEEDED or NOT_READY
|
2017-04-25 16:45:34 +01:00
|
|
|
if r != EmaneManager.SUCCESS:
|
2018-03-19 23:33:36 +00:00
|
|
|
return r
|
|
|
|
|
2017-04-25 16:45:34 +01:00
|
|
|
nems = []
|
2018-03-19 23:33:36 +00:00
|
|
|
with self._emane_node_lock:
|
|
|
|
self.buildxml()
|
|
|
|
self.starteventmonitor()
|
|
|
|
|
|
|
|
if self.numnems() > 0:
|
|
|
|
self.startdaemons()
|
2018-07-11 17:24:44 +01:00
|
|
|
self.installnetifs()
|
2018-03-19 23:33:36 +00:00
|
|
|
|
2019-09-28 06:31:56 +01:00
|
|
|
for node_id in self._emane_nets:
|
|
|
|
emane_node = self._emane_nets[node_id]
|
2018-03-19 23:33:36 +00:00
|
|
|
for netif in emane_node.netifs():
|
2019-09-10 23:10:24 +01:00
|
|
|
nems.append(
|
|
|
|
(netif.node.name, netif.name, emane_node.getnemid(netif))
|
|
|
|
)
|
2018-03-19 23:33:36 +00:00
|
|
|
|
2017-04-25 16:45:34 +01:00
|
|
|
if nems:
|
2018-03-19 23:33:36 +00:00
|
|
|
emane_nems_filename = os.path.join(self.session.session_dir, "emane_nems")
|
2017-04-25 16:45:34 +01:00
|
|
|
try:
|
2017-08-04 22:34:44 +01:00
|
|
|
with open(emane_nems_filename, "w") as f:
|
2017-04-25 16:45:34 +01:00
|
|
|
for nodename, ifname, nemid in nems:
|
2019-10-18 18:33:31 +01:00
|
|
|
f.write(f"{nodename} {ifname} {nemid}\n")
|
2017-04-25 16:45:34 +01:00
|
|
|
except IOError:
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.exception("Error writing EMANE NEMs file: %s")
|
2017-04-25 16:45:34 +01:00
|
|
|
|
|
|
|
return EmaneManager.SUCCESS
|
|
|
|
|
2020-01-15 00:27:08 +00:00
|
|
|
def poststartup(self) -> None:
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
|
|
|
Retransmit location events now that all NEMs are active.
|
|
|
|
"""
|
|
|
|
if not self.genlocationevents():
|
|
|
|
return
|
2018-03-19 23:33:36 +00:00
|
|
|
|
|
|
|
with self._emane_node_lock:
|
2019-09-28 06:31:56 +01:00
|
|
|
for key in sorted(self._emane_nets.keys()):
|
|
|
|
emane_node = self._emane_nets[key]
|
2019-09-10 23:10:24 +01:00
|
|
|
logging.debug(
|
|
|
|
"post startup for emane node: %s - %s",
|
|
|
|
emane_node.id,
|
|
|
|
emane_node.name,
|
|
|
|
)
|
2018-06-11 20:26:51 +01:00
|
|
|
emane_node.model.post_startup()
|
2018-03-19 23:33:36 +00:00
|
|
|
for netif in emane_node.netifs():
|
|
|
|
x, y, z = netif.node.position.get()
|
|
|
|
emane_node.setnemposition(netif, x, y, z)
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2020-01-15 00:27:08 +00:00
|
|
|
def reset(self) -> None:
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
2019-09-28 06:31:56 +01:00
|
|
|
Remove all EMANE networks from the dictionary, reset port numbers and
|
|
|
|
nem id counters
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
2018-03-19 23:33:36 +00:00
|
|
|
with self._emane_node_lock:
|
2019-09-28 06:31:56 +01:00
|
|
|
self._emane_nets.clear()
|
2018-03-19 23:33:36 +00:00
|
|
|
|
2019-09-10 23:10:24 +01:00
|
|
|
self.platformport = self.session.options.get_config_int(
|
|
|
|
"emane_platform_port", 8100
|
|
|
|
)
|
|
|
|
self.transformport = self.session.options.get_config_int(
|
|
|
|
"emane_transform_port", 8200
|
|
|
|
)
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2020-01-15 00:27:08 +00:00
|
|
|
def shutdown(self) -> None:
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
|
|
|
stop all EMANE daemons
|
|
|
|
"""
|
2018-03-19 23:33:36 +00:00
|
|
|
with self._emane_node_lock:
|
2019-09-28 06:31:56 +01:00
|
|
|
if not self._emane_nets:
|
2018-03-19 23:33:36 +00:00
|
|
|
return
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.info("stopping EMANE daemons.")
|
2018-03-19 23:33:36 +00:00
|
|
|
self.deinstallnetifs()
|
|
|
|
self.stopdaemons()
|
|
|
|
self.stopeventmonitor()
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2020-01-15 00:27:08 +00:00
|
|
|
def buildxml(self) -> None:
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
|
|
|
Build XML files required to run EMANE on each node.
|
|
|
|
NEMs run inside containers using the control network for passing
|
|
|
|
events and data.
|
|
|
|
"""
|
|
|
|
# assume self._objslock is already held here
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.info("emane building xml...")
|
2017-04-25 16:45:34 +01:00
|
|
|
# on master, control network bridge added earlier in startup()
|
2019-09-10 23:10:24 +01:00
|
|
|
ctrlnet = self.session.add_remove_control_net(
|
|
|
|
net_index=0, remove=False, conf_required=False
|
|
|
|
)
|
2018-03-19 23:33:36 +00:00
|
|
|
self.buildplatformxml(ctrlnet)
|
2017-04-25 16:45:34 +01:00
|
|
|
self.buildnemxml()
|
|
|
|
self.buildeventservicexml()
|
|
|
|
|
2020-01-15 00:27:08 +00:00
|
|
|
def check_node_models(self) -> None:
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
2019-09-28 06:31:56 +01:00
|
|
|
Associate EMANE model classes with EMANE network nodes.
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
2019-09-28 06:31:56 +01:00
|
|
|
for node_id in self._emane_nets:
|
|
|
|
emane_node = self._emane_nets[node_id]
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.debug("checking emane model for node: %s", node_id)
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2018-06-11 20:26:12 +01:00
|
|
|
# skip nodes that already have a model set
|
|
|
|
if emane_node.model:
|
2019-09-10 23:10:24 +01:00
|
|
|
logging.debug(
|
|
|
|
"node(%s) already has model(%s)",
|
|
|
|
emane_node.id,
|
|
|
|
emane_node.model.name,
|
|
|
|
)
|
2018-06-11 20:26:12 +01:00
|
|
|
continue
|
2018-03-19 23:33:36 +00:00
|
|
|
|
2018-06-11 20:26:12 +01:00
|
|
|
# set model configured for node, due to legacy messaging configuration before nodes exist
|
|
|
|
model_name = self.node_models.get(node_id)
|
|
|
|
if not model_name:
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.error("emane node(%s) has no node model", node_id)
|
2018-06-11 20:26:12 +01:00
|
|
|
raise ValueError("emane node has no model set")
|
|
|
|
|
2018-06-13 19:59:50 +01:00
|
|
|
config = self.get_model_config(node_id=node_id, model_name=model_name)
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.debug("setting emane model(%s) config(%s)", model_name, config)
|
2018-06-14 00:17:47 +01:00
|
|
|
model_class = self.models[model_name]
|
2018-06-06 22:51:45 +01:00
|
|
|
emane_node.setmodel(model_class, config)
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2020-01-15 00:27:08 +00:00
|
|
|
def nemlookup(self, nemid) -> Tuple[EmaneNet, CoreInterface]:
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
|
|
|
Look for the given numerical NEM ID and return the first matching
|
2019-09-28 06:31:56 +01:00
|
|
|
EMANE network and NEM interface.
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
2018-03-19 23:33:36 +00:00
|
|
|
emane_node = None
|
2017-04-25 16:45:34 +01:00
|
|
|
netif = None
|
|
|
|
|
2019-09-28 06:31:56 +01:00
|
|
|
for node_id in self._emane_nets:
|
|
|
|
emane_node = self._emane_nets[node_id]
|
2018-03-19 23:33:36 +00:00
|
|
|
netif = emane_node.getnemnetif(nemid)
|
2017-04-25 16:45:34 +01:00
|
|
|
if netif is not None:
|
|
|
|
break
|
|
|
|
else:
|
2018-03-19 23:33:36 +00:00
|
|
|
emane_node = None
|
|
|
|
|
|
|
|
return emane_node, netif
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2020-01-15 00:27:08 +00:00
|
|
|
def numnems(self) -> int:
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
|
|
|
Return the number of NEMs emulated locally.
|
|
|
|
"""
|
|
|
|
count = 0
|
2019-09-28 06:31:56 +01:00
|
|
|
for node_id in self._emane_nets:
|
|
|
|
emane_node = self._emane_nets[node_id]
|
2018-03-19 23:33:36 +00:00
|
|
|
count += len(emane_node.netifs())
|
2017-04-25 16:45:34 +01:00
|
|
|
return count
|
|
|
|
|
2020-01-15 00:27:08 +00:00
|
|
|
def buildplatformxml(self, ctrlnet: CtrlNet) -> None:
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
|
|
|
Build a platform.xml file now that all nodes are configured.
|
|
|
|
"""
|
2018-06-13 19:59:50 +01:00
|
|
|
nemid = int(self.get_config("nem_id_start"))
|
2018-07-11 17:19:06 +01:00
|
|
|
platform_xmls = {}
|
2017-04-25 16:45:34 +01:00
|
|
|
|
|
|
|
# assume self._objslock is already held here
|
2019-09-28 06:31:56 +01:00
|
|
|
for key in sorted(self._emane_nets.keys()):
|
|
|
|
emane_node = self._emane_nets[key]
|
2019-09-10 23:10:24 +01:00
|
|
|
nemid = emanexml.build_node_platform_xml(
|
|
|
|
self, ctrlnet, emane_node, nemid, platform_xmls
|
|
|
|
)
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2020-01-15 00:27:08 +00:00
|
|
|
def buildnemxml(self) -> None:
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
2019-09-28 06:31:56 +01:00
|
|
|
Builds the nem, mac, and phy xml files for each EMANE network.
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
2019-09-28 06:31:56 +01:00
|
|
|
for key in sorted(self._emane_nets):
|
|
|
|
emane_net = self._emane_nets[key]
|
|
|
|
emanexml.build_xml_files(self, emane_net)
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2020-01-15 00:27:08 +00:00
|
|
|
def buildeventservicexml(self) -> None:
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
|
|
|
Build the libemaneeventservice.xml file if event service options
|
|
|
|
were changed in the global config.
|
|
|
|
"""
|
|
|
|
need_xml = False
|
2018-06-06 22:51:45 +01:00
|
|
|
default_values = self.emane_config.default_values()
|
|
|
|
for name in ["eventservicegroup", "eventservicedevice"]:
|
|
|
|
a = default_values[name]
|
2018-06-13 19:59:50 +01:00
|
|
|
b = self.get_config(name)
|
2017-04-25 16:45:34 +01:00
|
|
|
if a != b:
|
|
|
|
need_xml = True
|
|
|
|
|
|
|
|
if not need_xml:
|
|
|
|
# reset to using default config
|
|
|
|
self.initeventservice()
|
|
|
|
return
|
|
|
|
|
|
|
|
try:
|
2018-06-13 19:59:50 +01:00
|
|
|
group, port = self.get_config("eventservicegroup").split(":")
|
2017-04-25 16:45:34 +01:00
|
|
|
except ValueError:
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.exception("invalid eventservicegroup in EMANE config")
|
2017-04-25 16:45:34 +01:00
|
|
|
return
|
|
|
|
|
2018-06-13 19:59:50 +01:00
|
|
|
dev = self.get_config("eventservicedevice")
|
2018-07-06 19:41:32 +01:00
|
|
|
emanexml.create_event_service_xml(group, port, dev, self.session.session_dir)
|
2019-10-17 19:10:59 +01:00
|
|
|
self.session.distributed.execute(
|
|
|
|
lambda x: emanexml.create_event_service_xml(
|
|
|
|
group, port, dev, self.session.session_dir, x
|
2019-10-10 19:53:52 +01:00
|
|
|
)
|
2019-10-17 19:10:59 +01:00
|
|
|
)
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2020-01-15 00:27:08 +00:00
|
|
|
def startdaemons(self) -> None:
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
|
|
|
Start one EMANE daemon per node having a radio.
|
|
|
|
Add a control network even if the user has not configured one.
|
|
|
|
"""
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.info("starting emane daemons...")
|
2017-04-25 16:45:34 +01:00
|
|
|
loglevel = str(EmaneManager.DEFAULT_LOG_LEVEL)
|
2018-06-12 16:37:39 +01:00
|
|
|
cfgloglevel = self.session.options.get_config_int("emane_log_level")
|
|
|
|
realtime = self.session.options.get_config_bool("emane_realtime", default=True)
|
2017-04-25 16:45:34 +01:00
|
|
|
if cfgloglevel:
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.info("setting user-defined EMANE log level: %d", cfgloglevel)
|
2017-04-25 16:45:34 +01:00
|
|
|
loglevel = str(cfgloglevel)
|
2018-03-19 23:33:36 +00:00
|
|
|
|
2019-10-18 18:33:31 +01:00
|
|
|
emanecmd = f"emane -d -l {loglevel}"
|
2017-04-25 16:45:34 +01:00
|
|
|
if realtime:
|
2019-10-12 00:36:57 +01:00
|
|
|
emanecmd += " -r"
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2018-10-12 01:26:34 +01:00
|
|
|
otagroup, _otaport = self.get_config("otamanagergroup").split(":")
|
2018-06-13 19:59:50 +01:00
|
|
|
otadev = self.get_config("otamanagerdevice")
|
2017-04-25 16:45:34 +01:00
|
|
|
otanetidx = self.session.get_control_net_index(otadev)
|
|
|
|
|
2018-10-12 01:26:34 +01:00
|
|
|
eventgroup, _eventport = self.get_config("eventservicegroup").split(":")
|
2018-06-13 19:59:50 +01:00
|
|
|
eventdev = self.get_config("eventservicedevice")
|
2017-04-25 16:45:34 +01:00
|
|
|
eventservicenetidx = self.session.get_control_net_index(eventdev)
|
|
|
|
|
|
|
|
run_emane_on_host = False
|
|
|
|
for node in self.getnodes():
|
2017-08-04 22:34:44 +01:00
|
|
|
if hasattr(node, "transport_type") and node.transport_type == "raw":
|
2017-04-25 16:45:34 +01:00
|
|
|
run_emane_on_host = True
|
|
|
|
continue
|
|
|
|
path = self.session.session_dir
|
2019-04-27 06:07:51 +01:00
|
|
|
n = node.id
|
2017-04-25 16:45:34 +01:00
|
|
|
|
|
|
|
# control network not yet started here
|
2019-09-10 23:10:24 +01:00
|
|
|
self.session.add_remove_control_interface(
|
|
|
|
node, 0, remove=False, conf_required=False
|
|
|
|
)
|
2017-04-25 16:45:34 +01:00
|
|
|
|
|
|
|
if otanetidx > 0:
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.info("adding ota device ctrl%d", otanetidx)
|
2019-09-10 23:10:24 +01:00
|
|
|
self.session.add_remove_control_interface(
|
|
|
|
node, otanetidx, remove=False, conf_required=False
|
|
|
|
)
|
2017-04-25 16:45:34 +01:00
|
|
|
|
|
|
|
if eventservicenetidx >= 0:
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.info("adding event service device ctrl%d", eventservicenetidx)
|
2019-09-10 23:10:24 +01:00
|
|
|
self.session.add_remove_control_interface(
|
|
|
|
node, eventservicenetidx, remove=False, conf_required=False
|
|
|
|
)
|
2017-04-25 16:45:34 +01:00
|
|
|
|
|
|
|
# multicast route is needed for OTA data
|
2019-10-01 23:38:23 +01:00
|
|
|
node.node_net_client.create_route(otagroup, otadev)
|
2018-03-01 21:21:25 +00:00
|
|
|
|
2017-04-25 16:45:34 +01:00
|
|
|
# multicast route is also needed for event data if on control network
|
|
|
|
if eventservicenetidx >= 0 and eventgroup != otagroup:
|
2019-10-01 23:38:23 +01:00
|
|
|
node.node_net_client.create_route(eventgroup, eventdev)
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2018-03-02 23:54:27 +00:00
|
|
|
# start emane
|
2019-10-18 18:33:31 +01:00
|
|
|
log_file = os.path.join(path, f"emane{n}.log")
|
|
|
|
platform_xml = os.path.join(path, f"platform{n}.xml")
|
|
|
|
args = f"{emanecmd} -f {log_file} {platform_xml}"
|
2019-10-19 07:28:09 +01:00
|
|
|
output = node.cmd(args)
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.info("node(%s) emane daemon running: %s", node.name, args)
|
2019-10-22 23:13:28 +01:00
|
|
|
logging.debug("node(%s) emane daemon output: %s", node.name, output)
|
2017-04-25 16:45:34 +01:00
|
|
|
|
|
|
|
if not run_emane_on_host:
|
|
|
|
return
|
|
|
|
|
|
|
|
path = self.session.session_dir
|
2019-10-18 18:33:31 +01:00
|
|
|
log_file = os.path.join(path, "emane.log")
|
|
|
|
platform_xml = os.path.join(path, "platform.xml")
|
|
|
|
emanecmd += f" -f {log_file} {platform_xml}"
|
2019-10-21 18:32:42 +01:00
|
|
|
utils.cmd(emanecmd, cwd=path)
|
2019-10-17 19:10:59 +01:00
|
|
|
self.session.distributed.execute(lambda x: x.remote_cmd(emanecmd, cwd=path))
|
2019-10-12 00:36:57 +01:00
|
|
|
logging.info("host emane daemon running: %s", emanecmd)
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2020-01-15 00:27:08 +00:00
|
|
|
def stopdaemons(self) -> None:
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
|
|
|
Kill the appropriate EMANE daemons.
|
|
|
|
"""
|
2019-10-12 00:36:57 +01:00
|
|
|
# TODO: we may want to improve this if we had the PIDs from the specific EMANE
|
|
|
|
# daemons that we"ve started
|
|
|
|
kill_emaned = "killall -q emane"
|
|
|
|
kill_transortd = "killall -q emanetransportd"
|
2017-04-25 16:45:34 +01:00
|
|
|
stop_emane_on_host = False
|
2018-03-19 23:33:36 +00:00
|
|
|
for node in self.getnodes():
|
|
|
|
if hasattr(node, "transport_type") and node.transport_type == "raw":
|
|
|
|
stop_emane_on_host = True
|
|
|
|
continue
|
|
|
|
|
|
|
|
if node.up:
|
2019-10-19 07:28:09 +01:00
|
|
|
node.cmd(kill_emaned, wait=False)
|
2018-03-19 23:33:36 +00:00
|
|
|
# TODO: RJ45 node
|
2018-03-02 00:23:58 +00:00
|
|
|
|
2017-04-25 16:45:34 +01:00
|
|
|
if stop_emane_on_host:
|
2018-03-02 00:23:58 +00:00
|
|
|
try:
|
2019-10-21 18:32:42 +01:00
|
|
|
utils.cmd(kill_emaned)
|
|
|
|
utils.cmd(kill_transortd)
|
2019-10-17 19:10:59 +01:00
|
|
|
self.session.distributed.execute(lambda x: x.remote_cmd(kill_emaned))
|
|
|
|
self.session.distributed.execute(lambda x: x.remote_cmd(kill_transortd))
|
2018-03-03 00:22:20 +00:00
|
|
|
except CoreCommandError:
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.exception("error shutting down emane daemons")
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2020-01-15 00:27:08 +00:00
|
|
|
def installnetifs(self) -> None:
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
|
|
|
Install TUN/TAP virtual interfaces into their proper namespaces
|
|
|
|
now that the EMANE daemons are running.
|
|
|
|
"""
|
2019-09-28 06:31:56 +01:00
|
|
|
for key in sorted(self._emane_nets.keys()):
|
|
|
|
emane_node = self._emane_nets[key]
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.info("emane install netifs for node: %d", key)
|
2018-07-11 17:24:44 +01:00
|
|
|
emane_node.installnetifs()
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2020-01-15 00:27:08 +00:00
|
|
|
def deinstallnetifs(self) -> None:
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
|
|
|
Uninstall TUN/TAP virtual interfaces.
|
|
|
|
"""
|
2019-09-28 06:31:56 +01:00
|
|
|
for key in sorted(self._emane_nets.keys()):
|
|
|
|
emane_node = self._emane_nets[key]
|
2018-03-19 23:33:36 +00:00
|
|
|
emane_node.deinstallnetifs()
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2020-01-15 00:27:08 +00:00
|
|
|
def doeventmonitor(self) -> bool:
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
|
|
|
Returns boolean whether or not EMANE events will be monitored.
|
|
|
|
"""
|
|
|
|
# this support must be explicitly turned on; by default, CORE will
|
|
|
|
# generate the EMANE events when nodes are moved
|
2018-06-12 16:37:39 +01:00
|
|
|
return self.session.options.get_config_bool("emane_event_monitor")
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2020-01-15 00:27:08 +00:00
|
|
|
def genlocationevents(self) -> bool:
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
|
|
|
Returns boolean whether or not EMANE events will be generated.
|
|
|
|
"""
|
|
|
|
# By default, CORE generates EMANE location events when nodes
|
|
|
|
# are moved; this can be explicitly disabled in core.conf
|
2018-06-12 16:37:39 +01:00
|
|
|
tmp = self.session.options.get_config_bool("emane_event_generate")
|
2017-04-25 16:45:34 +01:00
|
|
|
if tmp is None:
|
|
|
|
tmp = not self.doeventmonitor()
|
|
|
|
return tmp
|
|
|
|
|
2020-01-15 00:27:08 +00:00
|
|
|
def starteventmonitor(self) -> None:
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
|
|
|
Start monitoring EMANE location events if configured to do so.
|
|
|
|
"""
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.info("emane start event monitor")
|
2017-04-25 16:45:34 +01:00
|
|
|
if not self.doeventmonitor():
|
|
|
|
return
|
2018-03-19 23:33:36 +00:00
|
|
|
|
2017-04-25 16:45:34 +01:00
|
|
|
if self.service is None:
|
2019-09-10 23:10:24 +01:00
|
|
|
logging.error(
|
|
|
|
"Warning: EMANE events will not be generated "
|
|
|
|
"because the emaneeventservice\n binding was "
|
|
|
|
"unable to load "
|
|
|
|
"(install the python-emaneeventservice bindings)"
|
|
|
|
)
|
2017-04-25 16:45:34 +01:00
|
|
|
return
|
|
|
|
self.doeventloop = True
|
2020-02-26 23:29:19 +00:00
|
|
|
self.eventmonthread = threading.Thread(
|
|
|
|
target=self.eventmonitorloop, daemon=True
|
|
|
|
)
|
2017-04-25 16:45:34 +01:00
|
|
|
self.eventmonthread.start()
|
|
|
|
|
2020-01-15 00:27:08 +00:00
|
|
|
def stopeventmonitor(self) -> None:
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
|
|
|
Stop monitoring EMANE location events.
|
|
|
|
"""
|
|
|
|
self.doeventloop = False
|
|
|
|
if self.service is not None:
|
|
|
|
self.service.breakloop()
|
2017-08-04 22:34:44 +01:00
|
|
|
# reset the service, otherwise nextEvent won"t work
|
2017-04-25 16:45:34 +01:00
|
|
|
self.initeventservice(shutdown=True)
|
2018-03-19 23:33:36 +00:00
|
|
|
|
2017-04-25 16:45:34 +01:00
|
|
|
if self.eventmonthread is not None:
|
|
|
|
self.eventmonthread.join()
|
|
|
|
self.eventmonthread = None
|
|
|
|
|
2020-01-15 00:27:08 +00:00
|
|
|
def eventmonitorloop(self) -> None:
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
|
|
|
Thread target that monitors EMANE location events.
|
|
|
|
"""
|
|
|
|
if self.service is None:
|
|
|
|
return
|
2019-09-10 23:10:24 +01:00
|
|
|
logging.info(
|
|
|
|
"subscribing to EMANE location events. (%s)",
|
|
|
|
threading.currentThread().getName(),
|
|
|
|
)
|
2017-04-25 16:45:34 +01:00
|
|
|
while self.doeventloop is True:
|
2018-10-12 01:26:34 +01:00
|
|
|
_uuid, _seq, events = self.service.nextEvent()
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2018-03-19 23:33:36 +00:00
|
|
|
# this occurs with 0.9.1 event service
|
|
|
|
if not self.doeventloop:
|
|
|
|
break
|
|
|
|
|
|
|
|
for event in events:
|
|
|
|
nem, eid, data = event
|
|
|
|
if eid == LocationEvent.IDENTIFIER:
|
|
|
|
self.handlelocationevent(nem, eid, data)
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2019-09-10 23:10:24 +01:00
|
|
|
logging.info(
|
|
|
|
"unsubscribing from EMANE location events. (%s)",
|
|
|
|
threading.currentThread().getName(),
|
|
|
|
)
|
2018-03-19 23:33:36 +00:00
|
|
|
|
2020-01-15 00:27:08 +00:00
|
|
|
def handlelocationevent(self, rxnemid: int, eid: int, data: str) -> None:
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
2018-03-19 23:33:36 +00:00
|
|
|
Handle an EMANE location event.
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
|
|
|
events = LocationEvent()
|
|
|
|
events.restore(data)
|
|
|
|
for event in events:
|
2018-03-19 23:33:36 +00:00
|
|
|
txnemid, attrs = event
|
2019-09-10 23:10:24 +01:00
|
|
|
if (
|
|
|
|
"latitude" not in attrs
|
|
|
|
or "longitude" not in attrs
|
|
|
|
or "altitude" not in attrs
|
|
|
|
):
|
2019-06-03 22:36:21 +01:00
|
|
|
logging.warning("dropped invalid location event")
|
2017-04-25 16:45:34 +01:00
|
|
|
continue
|
2018-03-19 23:33:36 +00:00
|
|
|
|
2017-04-25 16:45:34 +01:00
|
|
|
# yaw,pitch,roll,azimuth,elevation,velocity are unhandled
|
2017-08-04 22:34:44 +01:00
|
|
|
lat = attrs["latitude"]
|
2018-08-07 18:51:33 +01:00
|
|
|
lon = attrs["longitude"]
|
2017-08-04 22:34:44 +01:00
|
|
|
alt = attrs["altitude"]
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.debug("emane location event: %s,%s,%s", lat, lon, alt)
|
2018-08-07 18:51:33 +01:00
|
|
|
self.handlelocationeventtoxyz(txnemid, lat, lon, alt)
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2020-01-15 00:27:08 +00:00
|
|
|
def handlelocationeventtoxyz(
|
|
|
|
self, nemid: int, lat: float, lon: float, alt: float
|
|
|
|
) -> bool:
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
|
|
|
Convert the (NEM ID, lat, long, alt) from a received location event
|
|
|
|
into a node and x,y,z coordinate values, sending a Node Message.
|
|
|
|
Returns True if successfully parsed and a Node Message was sent.
|
|
|
|
"""
|
|
|
|
# convert nemid to node number
|
2018-10-12 01:26:34 +01:00
|
|
|
_emanenode, netif = self.nemlookup(nemid)
|
2017-04-25 16:45:34 +01:00
|
|
|
if netif is None:
|
2019-02-16 17:50:19 +00:00
|
|
|
logging.info("location event for unknown NEM %s", nemid)
|
2017-04-25 16:45:34 +01:00
|
|
|
return False
|
2018-03-19 23:33:36 +00:00
|
|
|
|
2019-04-27 06:07:51 +01:00
|
|
|
n = netif.node.id
|
2017-04-25 16:45:34 +01:00
|
|
|
# convert from lat/long/alt to x,y,z coordinates
|
2017-08-07 23:37:41 +01:00
|
|
|
x, y, z = self.session.location.getxyz(lat, lon, alt)
|
2017-04-25 16:45:34 +01:00
|
|
|
x = int(x)
|
|
|
|
y = int(y)
|
|
|
|
z = int(z)
|
2020-02-22 00:42:23 +00:00
|
|
|
logging.debug(
|
2019-09-10 23:10:24 +01:00
|
|
|
"location event NEM %s (%s, %s, %s) -> (%s, %s, %s)",
|
|
|
|
nemid,
|
|
|
|
lat,
|
|
|
|
lon,
|
|
|
|
alt,
|
|
|
|
x,
|
|
|
|
y,
|
|
|
|
z,
|
|
|
|
)
|
2018-03-19 23:33:36 +00:00
|
|
|
xbit_check = x.bit_length() > 16 or x < 0
|
|
|
|
ybit_check = y.bit_length() > 16 or y < 0
|
|
|
|
zbit_check = z.bit_length() > 16 or z < 0
|
|
|
|
if any([xbit_check, ybit_check, zbit_check]):
|
2019-09-10 23:10:24 +01:00
|
|
|
logging.error(
|
|
|
|
"Unable to build node location message, received lat/long/alt exceeds coordinate "
|
|
|
|
"space: NEM %s (%d, %d, %d)",
|
|
|
|
nemid,
|
|
|
|
x,
|
|
|
|
y,
|
|
|
|
z,
|
|
|
|
)
|
2018-03-19 23:33:36 +00:00
|
|
|
return False
|
2017-04-25 16:45:34 +01:00
|
|
|
|
|
|
|
# generate a node message for this location update
|
|
|
|
try:
|
2019-04-30 07:31:47 +01:00
|
|
|
node = self.session.get_node(n)
|
2019-09-12 23:48:09 +01:00
|
|
|
except CoreError:
|
2019-09-10 23:10:24 +01:00
|
|
|
logging.exception(
|
2019-10-18 18:33:31 +01:00
|
|
|
"location event NEM %s has no corresponding node %s", nemid, n
|
2019-09-10 23:10:24 +01:00
|
|
|
)
|
2017-04-25 16:45:34 +01:00
|
|
|
return False
|
2018-03-19 23:33:36 +00:00
|
|
|
|
2017-08-04 22:34:44 +01:00
|
|
|
# don"t use node.setposition(x,y,z) which generates an event
|
2017-04-25 16:45:34 +01:00
|
|
|
node.position.set(x, y, z)
|
2020-01-15 00:27:08 +00:00
|
|
|
node_data = node.data(message_type=0, lat=lat, lon=lon, alt=alt)
|
2017-04-25 16:45:34 +01:00
|
|
|
self.session.broadcast_node(node_data)
|
|
|
|
return True
|
|
|
|
|
2020-01-15 00:27:08 +00:00
|
|
|
def emanerunning(self, node: CoreNode) -> bool:
|
2017-04-27 21:34:23 +01:00
|
|
|
"""
|
2019-10-12 06:37:33 +01:00
|
|
|
Return True if an EMANE process associated with the given node is running,
|
|
|
|
False otherwise.
|
2017-04-27 21:34:23 +01:00
|
|
|
"""
|
2019-10-12 06:37:33 +01:00
|
|
|
args = "pkill -0 -x emane"
|
2019-10-10 23:25:12 +01:00
|
|
|
try:
|
2019-10-19 07:28:09 +01:00
|
|
|
node.cmd(args)
|
2019-10-10 23:25:12 +01:00
|
|
|
result = True
|
|
|
|
except CoreCommandError:
|
|
|
|
result = False
|
|
|
|
|
|
|
|
return result
|
2017-04-27 21:34:23 +01:00
|
|
|
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2019-12-20 21:30:55 +00:00
|
|
|
class EmaneGlobalModel:
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
|
|
|
Global EMANE configuration options.
|
|
|
|
"""
|
2018-06-06 22:51:45 +01:00
|
|
|
|
2017-04-25 16:45:34 +01:00
|
|
|
name = "emane"
|
2019-12-20 21:30:55 +00:00
|
|
|
bitmap = None
|
2018-03-19 23:33:36 +00:00
|
|
|
|
2020-01-15 00:27:08 +00:00
|
|
|
def __init__(self, session: "Session") -> None:
|
2019-12-20 21:30:55 +00:00
|
|
|
self.session = session
|
|
|
|
self.nem_config = [
|
|
|
|
Configuration(
|
|
|
|
_id="nem_id_start",
|
|
|
|
_type=ConfigDataTypes.INT32,
|
|
|
|
default="1",
|
|
|
|
label="Starting NEM ID (core)",
|
|
|
|
)
|
|
|
|
]
|
|
|
|
self.emulator_config = None
|
|
|
|
self.parse_config()
|
|
|
|
|
2020-01-15 00:27:08 +00:00
|
|
|
def parse_config(self) -> None:
|
2019-12-20 21:30:55 +00:00
|
|
|
emane_prefix = self.session.options.get_config(
|
|
|
|
"emane_prefix", default=DEFAULT_EMANE_PREFIX
|
|
|
|
)
|
|
|
|
emulator_xml = os.path.join(emane_prefix, "share/emane/manifest/nemmanager.xml")
|
|
|
|
emulator_defaults = {
|
|
|
|
"eventservicedevice": DEFAULT_DEV,
|
|
|
|
"eventservicegroup": "224.1.2.8:45703",
|
|
|
|
"otamanagerdevice": DEFAULT_DEV,
|
|
|
|
"otamanagergroup": "224.1.2.8:45702",
|
|
|
|
}
|
|
|
|
self.emulator_config = emanemanifest.parse(emulator_xml, emulator_defaults)
|
|
|
|
self.emulator_config.insert(
|
|
|
|
0,
|
|
|
|
Configuration(
|
|
|
|
_id="platform_id_start",
|
|
|
|
_type=ConfigDataTypes.INT32,
|
|
|
|
default="1",
|
|
|
|
label="Starting Platform ID (core)",
|
|
|
|
),
|
2019-09-10 23:10:24 +01:00
|
|
|
)
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2020-01-15 00:27:08 +00:00
|
|
|
def configurations(self) -> List[Configuration]:
|
2019-12-20 21:30:55 +00:00
|
|
|
return self.emulator_config + self.nem_config
|
2018-06-06 22:51:45 +01:00
|
|
|
|
2020-01-15 00:27:08 +00:00
|
|
|
def config_groups(self) -> List[ConfigGroup]:
|
2019-12-20 21:30:55 +00:00
|
|
|
emulator_len = len(self.emulator_config)
|
|
|
|
config_len = len(self.configurations())
|
2018-06-29 00:30:55 +01:00
|
|
|
return [
|
|
|
|
ConfigGroup("Platform Attributes", 1, emulator_len),
|
2019-09-10 23:10:24 +01:00
|
|
|
ConfigGroup("NEM Parameters", emulator_len + 1, config_len),
|
2018-06-29 00:30:55 +01:00
|
|
|
]
|
2018-03-19 23:33:36 +00:00
|
|
|
|
2020-01-15 00:27:08 +00:00
|
|
|
def default_values(self) -> Dict[str, str]:
|
2019-12-20 21:30:55 +00:00
|
|
|
return OrderedDict(
|
|
|
|
[(config.id, config.default) for config in self.configurations()]
|
|
|
|
)
|