Merge pull request #504 from coreemu/bugfix/emane-mobility
Bugfix/emane mobility
This commit is contained in:
commit
f54ddc0912
7 changed files with 76 additions and 53 deletions
|
@ -22,9 +22,11 @@ from core.emane.nodes import EmaneNet
|
||||||
from core.emulator.data import InterfaceData, LinkData, LinkOptions, NodeOptions
|
from core.emulator.data import InterfaceData, LinkData, LinkOptions, NodeOptions
|
||||||
from core.emulator.enumerations import LinkTypes, NodeTypes
|
from core.emulator.enumerations import LinkTypes, NodeTypes
|
||||||
from core.emulator.session import Session
|
from core.emulator.session import Session
|
||||||
|
from core.errors import CoreError
|
||||||
from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility
|
from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility
|
||||||
from core.nodes.base import CoreNode, CoreNodeBase, NodeBase
|
from core.nodes.base import CoreNode, CoreNodeBase, NodeBase
|
||||||
from core.nodes.interface import CoreInterface
|
from core.nodes.interface import CoreInterface
|
||||||
|
from core.nodes.network import WlanNode
|
||||||
from core.services.coreservices import CoreService
|
from core.services.coreservices import CoreService
|
||||||
|
|
||||||
WORKERS = 10
|
WORKERS = 10
|
||||||
|
@ -661,3 +663,15 @@ def get_node_config_service_configs(session: Session) -> List[ConfigServiceConfi
|
||||||
def get_emane_config(session: Session) -> Dict[str, common_pb2.ConfigOption]:
|
def get_emane_config(session: Session) -> Dict[str, common_pb2.ConfigOption]:
|
||||||
current_config = session.emane.get_configs()
|
current_config = session.emane.get_configs()
|
||||||
return get_config_options(current_config, session.emane.emane_config)
|
return get_config_options(current_config, session.emane.emane_config)
|
||||||
|
|
||||||
|
|
||||||
|
def get_mobility_node(
|
||||||
|
session: Session, node_id: int, context: ServicerContext
|
||||||
|
) -> Union[WlanNode, EmaneNet]:
|
||||||
|
try:
|
||||||
|
return session.get_node(node_id, WlanNode)
|
||||||
|
except CoreError:
|
||||||
|
try:
|
||||||
|
return session.get_node(node_id, EmaneNet)
|
||||||
|
except CoreError:
|
||||||
|
context.abort(grpc.StatusCode.NOT_FOUND, "node id is not for wlan or emane")
|
||||||
|
|
|
@ -1125,7 +1125,11 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
"""
|
"""
|
||||||
logging.debug("mobility action: %s", request)
|
logging.debug("mobility action: %s", request)
|
||||||
session = self.get_session(request.session_id, context)
|
session = self.get_session(request.session_id, context)
|
||||||
node = self.get_node(session, request.node_id, context, WlanNode)
|
node = grpcutils.get_mobility_node(session, request.node_id, context)
|
||||||
|
if not node.mobility:
|
||||||
|
context.abort(
|
||||||
|
grpc.StatusCode.NOT_FOUND, f"node({node.name}) does not have mobility"
|
||||||
|
)
|
||||||
result = True
|
result = True
|
||||||
if request.action == MobilityAction.START:
|
if request.action == MobilityAction.START:
|
||||||
node.mobility.start()
|
node.mobility.start()
|
||||||
|
|
|
@ -130,8 +130,8 @@ class EmaneModel(WirelessModel):
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
wlan = self.session.get_node(self.id, EmaneNet)
|
emane_net = self.session.get_node(self.id, EmaneNet)
|
||||||
wlan.setnempositions(moved_ifaces)
|
emane_net.setnempositions(moved_ifaces)
|
||||||
except CoreError:
|
except CoreError:
|
||||||
logging.exception("error during update")
|
logging.exception("error during update")
|
||||||
|
|
||||||
|
|
|
@ -538,7 +538,7 @@ class CoreClient:
|
||||||
|
|
||||||
def show_mobility_players(self) -> None:
|
def show_mobility_players(self) -> None:
|
||||||
for node in self.session.nodes.values():
|
for node in self.session.nodes.values():
|
||||||
if node.type != NodeType.WIRELESS_LAN:
|
if not NodeUtils.is_mobility(node):
|
||||||
continue
|
continue
|
||||||
if node.mobility_config:
|
if node.mobility_config:
|
||||||
mobility_player = MobilityPlayer(self.app, node)
|
mobility_player = MobilityPlayer(self.app, node)
|
||||||
|
@ -927,7 +927,7 @@ class CoreClient:
|
||||||
def get_mobility_configs_proto(self) -> List[mobility_pb2.MobilityConfig]:
|
def get_mobility_configs_proto(self) -> List[mobility_pb2.MobilityConfig]:
|
||||||
configs = []
|
configs = []
|
||||||
for node in self.session.nodes.values():
|
for node in self.session.nodes.values():
|
||||||
if node.type != NodeType.WIRELESS_LAN:
|
if not NodeUtils.is_mobility(node):
|
||||||
continue
|
continue
|
||||||
if not node.mobility_config:
|
if not node.mobility_config:
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -206,6 +206,7 @@ class CanvasNode:
|
||||||
self.context.delete(0, tk.END)
|
self.context.delete(0, tk.END)
|
||||||
is_wlan = self.core_node.type == NodeType.WIRELESS_LAN
|
is_wlan = self.core_node.type == NodeType.WIRELESS_LAN
|
||||||
is_emane = self.core_node.type == NodeType.EMANE
|
is_emane = self.core_node.type == NodeType.EMANE
|
||||||
|
is_mobility = is_wlan or is_emane
|
||||||
if self.app.core.is_runtime():
|
if self.app.core.is_runtime():
|
||||||
self.context.add_command(label="Configure", command=self.show_config)
|
self.context.add_command(label="Configure", command=self.show_config)
|
||||||
if is_emane:
|
if is_emane:
|
||||||
|
@ -216,7 +217,7 @@ class CanvasNode:
|
||||||
self.context.add_command(
|
self.context.add_command(
|
||||||
label="WLAN Config", command=self.show_wlan_config
|
label="WLAN Config", command=self.show_wlan_config
|
||||||
)
|
)
|
||||||
if is_wlan and self.core_node.id in self.app.core.mobility_players:
|
if is_mobility and self.core_node.id in self.app.core.mobility_players:
|
||||||
self.context.add_command(
|
self.context.add_command(
|
||||||
label="Mobility Player", command=self.show_mobility_player
|
label="Mobility Player", command=self.show_mobility_player
|
||||||
)
|
)
|
||||||
|
@ -235,6 +236,7 @@ class CanvasNode:
|
||||||
self.context.add_command(
|
self.context.add_command(
|
||||||
label="WLAN Config", command=self.show_wlan_config
|
label="WLAN Config", command=self.show_wlan_config
|
||||||
)
|
)
|
||||||
|
if is_mobility:
|
||||||
self.context.add_command(
|
self.context.add_command(
|
||||||
label="Mobility Config", command=self.show_mobility_config
|
label="Mobility Config", command=self.show_mobility_config
|
||||||
)
|
)
|
||||||
|
|
|
@ -63,10 +63,15 @@ class NodeUtils:
|
||||||
WIRELESS_NODES: Set[NodeType] = {NodeType.WIRELESS_LAN, NodeType.EMANE}
|
WIRELESS_NODES: Set[NodeType] = {NodeType.WIRELESS_LAN, NodeType.EMANE}
|
||||||
RJ45_NODES: Set[NodeType] = {NodeType.RJ45}
|
RJ45_NODES: Set[NodeType] = {NodeType.RJ45}
|
||||||
IGNORE_NODES: Set[NodeType] = {NodeType.CONTROL_NET}
|
IGNORE_NODES: Set[NodeType] = {NodeType.CONTROL_NET}
|
||||||
|
MOBILITY_NODES: Set[NodeType] = {NodeType.WIRELESS_LAN, NodeType.EMANE}
|
||||||
NODE_MODELS: Set[str] = {"router", "host", "PC", "mdr", "prouter"}
|
NODE_MODELS: Set[str] = {"router", "host", "PC", "mdr", "prouter"}
|
||||||
ROUTER_NODES: Set[str] = {"router", "mdr"}
|
ROUTER_NODES: Set[str] = {"router", "mdr"}
|
||||||
ANTENNA_ICON: PhotoImage = None
|
ANTENNA_ICON: PhotoImage = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def is_mobility(cls, node: Node) -> bool:
|
||||||
|
return node.type in cls.MOBILITY_NODES
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_router_node(cls, node: Node) -> bool:
|
def is_router_node(cls, node: Node) -> bool:
|
||||||
return cls.is_model_node(node.type) and node.model in cls.ROUTER_NODES
|
return cls.is_model_node(node.type) and node.model in cls.ROUTER_NODES
|
||||||
|
|
|
@ -9,10 +9,11 @@ import threading
|
||||||
import time
|
import time
|
||||||
from functools import total_ordering
|
from functools import total_ordering
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Tuple
|
from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Tuple, Union
|
||||||
|
|
||||||
from core import utils
|
from core import utils
|
||||||
from core.config import ConfigGroup, ConfigurableOptions, Configuration, ModelManager
|
from core.config import ConfigGroup, ConfigurableOptions, Configuration, ModelManager
|
||||||
|
from core.emane.nodes import EmaneNet
|
||||||
from core.emulator.data import EventData, LinkData, LinkOptions
|
from core.emulator.data import EventData, LinkData, LinkOptions
|
||||||
from core.emulator.enumerations import (
|
from core.emulator.enumerations import (
|
||||||
ConfigDataTypes,
|
ConfigDataTypes,
|
||||||
|
@ -31,6 +32,13 @@ if TYPE_CHECKING:
|
||||||
from core.emulator.session import Session
|
from core.emulator.session import Session
|
||||||
|
|
||||||
|
|
||||||
|
def get_mobility_node(session: "Session", node_id: int) -> Union[WlanNode, EmaneNet]:
|
||||||
|
try:
|
||||||
|
return session.get_node(node_id, WlanNode)
|
||||||
|
except CoreError:
|
||||||
|
return session.get_node(node_id, EmaneNet)
|
||||||
|
|
||||||
|
|
||||||
class MobilityManager(ModelManager):
|
class MobilityManager(ModelManager):
|
||||||
"""
|
"""
|
||||||
Member of session class for handling configuration data for mobility and
|
Member of session class for handling configuration data for mobility and
|
||||||
|
@ -69,35 +77,30 @@ class MobilityManager(ModelManager):
|
||||||
"""
|
"""
|
||||||
if node_ids is None:
|
if node_ids is None:
|
||||||
node_ids = self.nodes()
|
node_ids = self.nodes()
|
||||||
|
|
||||||
for node_id in node_ids:
|
for node_id in node_ids:
|
||||||
logging.debug("checking mobility startup for node: %s", node_id)
|
|
||||||
logging.debug(
|
logging.debug(
|
||||||
"node mobility configurations: %s", self.get_all_configs(node_id)
|
"node(%s) mobility startup: %s", node_id, self.get_all_configs(node_id)
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
node = self.session.get_node(node_id, WlanNode)
|
node = get_mobility_node(self.session, node_id)
|
||||||
|
# TODO: may be an issue if there are multiple mobility models
|
||||||
|
for model in self.models.values():
|
||||||
|
config = self.get_configs(node_id, model.name)
|
||||||
|
if not config:
|
||||||
|
continue
|
||||||
|
self.set_model(node, model, config)
|
||||||
|
if node.mobility:
|
||||||
|
self.session.event_loop.add_event(0.0, node.mobility.startup)
|
||||||
except CoreError:
|
except CoreError:
|
||||||
|
logging.exception("mobility startup error")
|
||||||
logging.warning(
|
logging.warning(
|
||||||
"skipping mobility configuration for unknown node: %s", node_id
|
"skipping mobility configuration for unknown node: %s", node_id
|
||||||
)
|
)
|
||||||
continue
|
|
||||||
|
|
||||||
for model_name in self.models:
|
|
||||||
config = self.get_configs(node_id, model_name)
|
|
||||||
if not config:
|
|
||||||
continue
|
|
||||||
model_class = self.models[model_name]
|
|
||||||
self.set_model(node, model_class, config)
|
|
||||||
|
|
||||||
if node.mobility:
|
|
||||||
self.session.event_loop.add_event(0.0, node.mobility.startup)
|
|
||||||
|
|
||||||
def handleevent(self, event_data: EventData) -> None:
|
def handleevent(self, event_data: EventData) -> None:
|
||||||
"""
|
"""
|
||||||
Handle an Event Message used to start, stop, or pause
|
Handle an Event Message used to start, stop, or pause
|
||||||
mobility scripts for a given WlanNode.
|
mobility scripts for a given mobility network.
|
||||||
|
|
||||||
:param event_data: event data to handle
|
:param event_data: event data to handle
|
||||||
:return: nothing
|
:return: nothing
|
||||||
|
@ -106,40 +109,35 @@ class MobilityManager(ModelManager):
|
||||||
node_id = event_data.node
|
node_id = event_data.node
|
||||||
name = event_data.name
|
name = event_data.name
|
||||||
try:
|
try:
|
||||||
node = self.session.get_node(node_id, WlanNode)
|
node = get_mobility_node(self.session, node_id)
|
||||||
except CoreError:
|
except CoreError:
|
||||||
logging.exception(
|
logging.exception(
|
||||||
"Ignoring event for model '%s', unknown node '%s'", name, node_id
|
"ignoring event for model(%s), unknown node(%s)", name, node_id
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
# name is e.g. "mobility:ns2script"
|
# name is e.g. "mobility:ns2script"
|
||||||
models = name[9:].split(",")
|
models = name[9:].split(",")
|
||||||
for model in models:
|
for model in models:
|
||||||
try:
|
cls = self.models.get(model)
|
||||||
cls = self.models[model]
|
if not cls:
|
||||||
except KeyError:
|
logging.warning("ignoring event for unknown model '%s'", model)
|
||||||
logging.warning("Ignoring event for unknown model '%s'", model)
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if cls.config_type in [RegisterTlvs.WIRELESS, RegisterTlvs.MOBILITY]:
|
if cls.config_type in [RegisterTlvs.WIRELESS, RegisterTlvs.MOBILITY]:
|
||||||
model = node.mobility
|
model = node.mobility
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if model is None:
|
if model is None:
|
||||||
logging.warning("Ignoring event, %s has no model", node.name)
|
logging.warning("ignoring event, %s has no model", node.name)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if cls.name != model.name:
|
if cls.name != model.name:
|
||||||
logging.warning(
|
logging.warning(
|
||||||
"Ignoring event for %s wrong model %s,%s",
|
"ignoring event for %s wrong model %s,%s",
|
||||||
node.name,
|
node.name,
|
||||||
cls.name,
|
cls.name,
|
||||||
model.name,
|
model.name,
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if event_type in [EventTypes.STOP, EventTypes.RESTART]:
|
if event_type in [EventTypes.STOP, EventTypes.RESTART]:
|
||||||
model.stop(move_initial=True)
|
model.stop(move_initial=True)
|
||||||
if event_type in [EventTypes.START, EventTypes.RESTART]:
|
if event_type in [EventTypes.START, EventTypes.RESTART]:
|
||||||
|
@ -162,11 +160,9 @@ class MobilityManager(ModelManager):
|
||||||
event_type = EventTypes.START
|
event_type = EventTypes.START
|
||||||
elif model.state == model.STATE_PAUSED:
|
elif model.state == model.STATE_PAUSED:
|
||||||
event_type = EventTypes.PAUSE
|
event_type = EventTypes.PAUSE
|
||||||
|
|
||||||
start_time = int(model.lasttime - model.timezero)
|
start_time = int(model.lasttime - model.timezero)
|
||||||
end_time = int(model.endtime)
|
end_time = int(model.endtime)
|
||||||
data = f"start={start_time} end={end_time}"
|
data = f"start={start_time} end={end_time}"
|
||||||
|
|
||||||
event_data = EventData(
|
event_data = EventData(
|
||||||
node=model.id,
|
node=model.id,
|
||||||
event_type=event_type,
|
event_type=event_type,
|
||||||
|
@ -174,15 +170,14 @@ class MobilityManager(ModelManager):
|
||||||
data=data,
|
data=data,
|
||||||
time=str(time.monotonic()),
|
time=str(time.monotonic()),
|
||||||
)
|
)
|
||||||
|
|
||||||
self.session.broadcast_event(event_data)
|
self.session.broadcast_event(event_data)
|
||||||
|
|
||||||
def updatewlans(
|
def update_nets(
|
||||||
self, moved: List[CoreNode], moved_ifaces: List[CoreInterface]
|
self, moved: List[CoreNode], moved_ifaces: List[CoreInterface]
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
A mobility script has caused nodes in the 'moved' list to move.
|
A mobility script has caused nodes in the 'moved' list to move.
|
||||||
Update every WlanNode. This saves range calculations if the model
|
Update every mobility network. This saves range calculations if the model
|
||||||
were to recalculate for each individual node movement.
|
were to recalculate for each individual node movement.
|
||||||
|
|
||||||
:param moved: moved nodes
|
:param moved: moved nodes
|
||||||
|
@ -191,11 +186,11 @@ class MobilityManager(ModelManager):
|
||||||
"""
|
"""
|
||||||
for node_id in self.nodes():
|
for node_id in self.nodes():
|
||||||
try:
|
try:
|
||||||
node = self.session.get_node(node_id, WlanNode)
|
node = get_mobility_node(self.session, node_id)
|
||||||
except CoreError:
|
|
||||||
continue
|
|
||||||
if node.model:
|
if node.model:
|
||||||
node.model.update(moved, moved_ifaces)
|
node.model.update(moved, moved_ifaces)
|
||||||
|
except CoreError:
|
||||||
|
logging.exception("error updating mobility node")
|
||||||
|
|
||||||
|
|
||||||
class WirelessModel(ConfigurableOptions):
|
class WirelessModel(ConfigurableOptions):
|
||||||
|
@ -593,7 +588,7 @@ class WayPointMobility(WirelessModel):
|
||||||
self.lasttime: Optional[float] = None
|
self.lasttime: Optional[float] = None
|
||||||
self.endtime: Optional[int] = None
|
self.endtime: Optional[int] = None
|
||||||
self.timezero: float = 0.0
|
self.timezero: float = 0.0
|
||||||
self.wlan: WlanNode = session.get_node(_id, WlanNode)
|
self.net: Union[WlanNode, EmaneNet] = get_mobility_node(self.session, self.id)
|
||||||
# these are really set in child class via confmatrix
|
# these are really set in child class via confmatrix
|
||||||
self.loop: bool = False
|
self.loop: bool = False
|
||||||
self.refresh_ms: int = 50
|
self.refresh_ms: int = 50
|
||||||
|
@ -601,6 +596,9 @@ class WayPointMobility(WirelessModel):
|
||||||
# (ns-3 sets this to False as new waypoints may be added from trace)
|
# (ns-3 sets this to False as new waypoints may be added from trace)
|
||||||
self.empty_queue_stop: bool = True
|
self.empty_queue_stop: bool = True
|
||||||
|
|
||||||
|
def startup(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
def runround(self) -> None:
|
def runround(self) -> None:
|
||||||
"""
|
"""
|
||||||
Advance script time and move nodes.
|
Advance script time and move nodes.
|
||||||
|
@ -643,14 +641,14 @@ class WayPointMobility(WirelessModel):
|
||||||
# only move interfaces attached to self.wlan, or all nodenum in script?
|
# only move interfaces attached to self.wlan, or all nodenum in script?
|
||||||
moved = []
|
moved = []
|
||||||
moved_ifaces = []
|
moved_ifaces = []
|
||||||
for iface in self.wlan.get_ifaces():
|
for iface in self.net.get_ifaces():
|
||||||
node = iface.node
|
node = iface.node
|
||||||
if self.movenode(node, dt):
|
if self.movenode(node, dt):
|
||||||
moved.append(node)
|
moved.append(node)
|
||||||
moved_ifaces.append(iface)
|
moved_ifaces.append(iface)
|
||||||
|
|
||||||
# calculate all ranges after moving nodes; this saves calculations
|
# calculate all ranges after moving nodes; this saves calculations
|
||||||
self.session.mobility.updatewlans(moved, moved_ifaces)
|
self.session.mobility.update_nets(moved, moved_ifaces)
|
||||||
|
|
||||||
# TODO: check session state
|
# TODO: check session state
|
||||||
self.session.event_loop.add_event(0.001 * self.refresh_ms, self.runround)
|
self.session.event_loop.add_event(0.001 * self.refresh_ms, self.runround)
|
||||||
|
@ -723,7 +721,7 @@ class WayPointMobility(WirelessModel):
|
||||||
"""
|
"""
|
||||||
moved = []
|
moved = []
|
||||||
moved_ifaces = []
|
moved_ifaces = []
|
||||||
for iface in self.wlan.get_ifaces():
|
for iface in self.net.get_ifaces():
|
||||||
node = iface.node
|
node = iface.node
|
||||||
if node.id not in self.initial:
|
if node.id not in self.initial:
|
||||||
continue
|
continue
|
||||||
|
@ -731,7 +729,7 @@ class WayPointMobility(WirelessModel):
|
||||||
self.setnodeposition(node, x, y, z)
|
self.setnodeposition(node, x, y, z)
|
||||||
moved.append(node)
|
moved.append(node)
|
||||||
moved_ifaces.append(iface)
|
moved_ifaces.append(iface)
|
||||||
self.session.mobility.updatewlans(moved, moved_ifaces)
|
self.session.mobility.update_nets(moved, moved_ifaces)
|
||||||
|
|
||||||
def addwaypoint(
|
def addwaypoint(
|
||||||
self,
|
self,
|
||||||
|
@ -1094,7 +1092,7 @@ class Ns2ScriptedMobility(WayPointMobility):
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
if self.autostart == "":
|
if self.autostart == "":
|
||||||
logging.info("not auto-starting ns-2 script for %s", self.wlan.name)
|
logging.info("not auto-starting ns-2 script for %s", self.net.name)
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
t = float(self.autostart)
|
t = float(self.autostart)
|
||||||
|
@ -1102,11 +1100,11 @@ class Ns2ScriptedMobility(WayPointMobility):
|
||||||
logging.exception(
|
logging.exception(
|
||||||
"Invalid auto-start seconds specified '%s' for %s",
|
"Invalid auto-start seconds specified '%s' for %s",
|
||||||
self.autostart,
|
self.autostart,
|
||||||
self.wlan.name,
|
self.net.name,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
self.movenodesinitial()
|
self.movenodesinitial()
|
||||||
logging.info("scheduling ns-2 script for %s autostart at %s", self.wlan.name, t)
|
logging.info("scheduling ns-2 script for %s autostart at %s", self.net.name, t)
|
||||||
self.state = self.STATE_RUNNING
|
self.state = self.STATE_RUNNING
|
||||||
self.session.event_loop.add_event(t, self.run)
|
self.session.event_loop.add_event(t, self.run)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue