Merge pull request #510 from coreemu/develop

Develop
This commit is contained in:
bharnden 2020-09-15 13:22:27 -07:00 committed by GitHub
commit 16495c9008
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
64 changed files with 3645 additions and 882 deletions

View file

@ -1,3 +1,24 @@
## 2020-09-15 CORE 7.2.0
* Installation
* locked down version of ospf-mdr installed in automated install
* locked down version of emane to v1.2.5 in automated emane install
* added option to install locally using the -l option
* core-daemon
* improve error when retrieving services that do not exist, or failed to load
* fixed issue with writing/reading emane node interface configurations to xml
* fixed issue with not setting the emane model when creating a node
* added common utility method for getting a emane node interface config id in core.utils
* fixed issue running emane on more than one interface for a node
* fixed issue validating paths when creating emane transport xml for a node
* fixed issue avoiding multiple calls to shutdown, if already in shutdown state
* core-pygui
* fixed issue configuring emane for a node interface
* gRPC API
* added wrapper client that can provide type hinting and a simpler interface at core.api.grpc.clientw
* fixed issue creating sessions that default to having a very large reference scale
* fixed issue with GetSession returning control net nodes
## 2020-08-21 CORE 7.1.0
* Installation

View file

@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script.
# this defines the CORE version number, must be static for AC_INIT
AC_INIT(core, 7.1.0)
AC_INIT(core, 7.2.0)
# autoconf and automake initialization
AC_CONFIG_SRCDIR([netns/version.h.in])

File diff suppressed because it is too large Load diff

View file

@ -306,35 +306,6 @@ def get_links(node: NodeBase):
return links
def get_emane_model_id(node_id: int, iface_id: int) -> int:
"""
Get EMANE model id
:param node_id: node id
:param iface_id: interface id
:return: EMANE model id
"""
if iface_id >= 0:
return node_id * 1000 + iface_id
else:
return node_id
def parse_emane_model_id(_id: int) -> Tuple[int, int]:
"""
Parses EMANE model id to get true node id and interface id.
:param _id: id to parse
:return: node id and interface id
"""
iface_id = -1
node_id = _id
if _id >= 1000:
iface_id = _id % 1000
node_id = int(_id / 1000)
return node_id, iface_id
def convert_iface(iface_data: InterfaceData) -> core_pb2.Interface:
return core_pb2.Interface(
id=iface_data.id,
@ -559,7 +530,8 @@ def get_emane_model_configs(session: Session) -> List[GetEmaneModelConfig]:
model = session.emane.models[model_name]
current_config = session.emane.get_model_config(_id, model_name)
config = get_config_options(current_config, model)
node_id, iface_id = parse_emane_model_id(_id)
node_id, iface_id = utils.parse_iface_config_id(_id)
iface_id = iface_id if iface_id is not None else -1
model_config = GetEmaneModelConfig(
node_id=node_id, model=model_name, iface_id=iface_id, config=config
)

View file

@ -56,12 +56,7 @@ from core.api.grpc.emane_pb2 import (
SetEmaneModelConfigResponse,
)
from core.api.grpc.events import EventStreamer
from core.api.grpc.grpcutils import (
get_config_options,
get_emane_model_id,
get_links,
get_net_stats,
)
from core.api.grpc.grpcutils import get_config_options, get_links, get_net_stats
from core.api.grpc.mobility_pb2 import (
GetMobilityConfigRequest,
GetMobilityConfigResponse,
@ -117,7 +112,7 @@ from core.emulator.session import NT, Session
from core.errors import CoreCommandError, CoreError
from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility
from core.nodes.base import CoreNode, NodeBase
from core.nodes.network import PtpNet, WlanNode
from core.nodes.network import CtrlNet, PtpNet, WlanNode
from core.services.coreservices import ServiceManager
_ONE_DAY_IN_SECONDS: int = 60 * 60 * 24
@ -249,7 +244,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
config = session.emane.get_configs()
config.update(request.emane_config)
for config in request.emane_model_configs:
_id = get_emane_model_id(config.node_id, config.iface_id)
_id = utils.iface_config_id(config.node_id, config.iface_id)
session.emane.set_model_config(_id, config.model, config.config)
# wlan configs
@ -340,7 +335,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
session = self.coreemu.create_session(request.session_id)
session.set_state(EventTypes.DEFINITION_STATE)
session.location.setrefgeo(47.57917, -122.13232, 2.0)
session.location.refscale = 150000.0
session.location.refscale = 150.0
return core_pb2.CreateSessionResponse(
session_id=session.id, state=session.state.value
)
@ -561,7 +556,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
nodes = []
for _id in session.nodes:
node = session.nodes[_id]
if not isinstance(node, PtpNet):
if not isinstance(node, (PtpNet, CtrlNet)):
node_proto = grpcutils.get_node_proto(session, node)
nodes.append(node_proto)
node_links = get_links(node)
@ -1438,8 +1433,10 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
"""
logging.debug("get emane model config: %s", request)
session = self.get_session(request.session_id, context)
model = session.emane.models[request.model]
_id = get_emane_model_id(request.node_id, request.iface_id)
model = session.emane.models.get(request.model)
if not model:
raise CoreError(f"invalid emane model: {request.model}")
_id = utils.iface_config_id(request.node_id, request.iface_id)
current_config = session.emane.get_model_config(_id, request.model)
config = get_config_options(current_config, model)
return GetEmaneModelConfigResponse(config=config)
@ -1458,7 +1455,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
logging.debug("set emane model config: %s", request)
session = self.get_session(request.session_id, context)
model_config = request.emane_model_config
_id = get_emane_model_id(model_config.node_id, model_config.iface_id)
_id = utils.iface_config_id(model_config.node_id, model_config.iface_id)
session.emane.set_model_config(_id, model_config.model, model_config.config)
return SetEmaneModelConfigResponse(result=True)
@ -1483,7 +1480,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
self, request: core_pb2.SaveXmlRequest, context: ServicerContext
) -> core_pb2.SaveXmlResponse:
"""
Export the session nto the EmulationScript XML format
Export the session into the EmulationScript XML format
:param request: save xml request
:param context: context object

View file

@ -1,9 +1,15 @@
from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
from typing import Dict, List, Optional, Set, Tuple
from typing import Any, Dict, List, Optional, Set, Tuple
from core.api.grpc import common_pb2, configservices_pb2, core_pb2, services_pb2
from core.api.grpc import (
common_pb2,
configservices_pb2,
core_pb2,
emane_pb2,
services_pb2,
)
class ConfigServiceValidationMode(Enum):
@ -87,6 +93,22 @@ class MessageType(Enum):
TTY = 64
class ServiceAction(Enum):
START = 0
STOP = 1
RESTART = 2
VALIDATE = 3
class EventType:
SESSION = 0
NODE = 1
LINK = 2
CONFIG = 3
EXCEPTION = 4
FILE = 5
@dataclass
class ConfigService:
group: str
@ -120,12 +142,67 @@ class ConfigService:
)
@dataclass
class ConfigServiceConfig:
node_id: int
name: str
templates: Dict[str, str]
config: Dict[str, str]
@classmethod
def from_proto(
cls, proto: configservices_pb2.ConfigServiceConfig
) -> "ConfigServiceConfig":
return ConfigServiceConfig(
node_id=proto.node_id,
name=proto.name,
templates=dict(proto.templates),
config=dict(proto.config),
)
@dataclass
class ConfigServiceData:
templates: Dict[str, str] = field(default_factory=dict)
config: Dict[str, str] = field(default_factory=dict)
@dataclass
class ConfigServiceDefaults:
templates: Dict[str, str]
config: Dict[str, "ConfigOption"]
modes: List[str]
@classmethod
def from_proto(
cls, proto: configservices_pb2.GetConfigServicesResponse
) -> "ConfigServiceDefaults":
config = ConfigOption.from_dict(proto.config)
return ConfigServiceDefaults(
templates=dict(proto.templates), config=config, modes=list(proto.modes)
)
@dataclass
class Service:
group: str
name: str
@classmethod
def from_proto(cls, proto: services_pb2.Service) -> "Service":
return Service(group=proto.group, name=proto.name)
@dataclass
class ServiceDefault:
node_type: str
services: List[str]
@classmethod
def from_proto(cls, proto: services_pb2.ServiceDefaults) -> "ServiceDefault":
return ServiceDefault(node_type=proto.node_type, services=list(proto.services))
@dataclass
class NodeServiceData:
executables: List[str]
@ -155,6 +232,28 @@ class NodeServiceData:
)
@dataclass
class ServiceConfig:
node_id: int
service: str
files: List[str] = None
directories: List[str] = None
startup: List[str] = None
validate: List[str] = None
shutdown: List[str] = None
def to_proto(self) -> services_pb2.ServiceConfig:
return services_pb2.ServiceConfig(
node_id=self.node_id,
service=self.service,
files=self.files,
directories=self.directories,
startup=self.startup,
validate=self.validate,
shutdown=self.shutdown,
)
@dataclass
class BridgeThroughput:
node_id: int
@ -195,6 +294,15 @@ class ThroughputsEvent:
)
@dataclass
class CpuUsageEvent:
usage: float
@classmethod
def from_proto(cls, proto: core_pb2.CpuUsageEvent) -> "CpuUsageEvent":
return CpuUsageEvent(usage=proto.usage)
@dataclass
class SessionLocation:
x: float
@ -471,6 +579,30 @@ class Hook:
return core_pb2.Hook(state=self.state.value, file=self.file, data=self.data)
@dataclass
class EmaneModelConfig:
node_id: int
model: str
iface_id: int = -1
config: Dict[str, ConfigOption] = None
@classmethod
def from_proto(cls, proto: emane_pb2.GetEmaneModelConfig) -> "EmaneModelConfig":
iface_id = proto.iface_id if proto.iface_id != -1 else None
config = ConfigOption.from_dict(proto.config)
return EmaneModelConfig(
node_id=proto.node_id, iface_id=iface_id, model=proto.model, config=config
)
def to_proto(self) -> emane_pb2.EmaneModelConfig:
return emane_pb2.EmaneModelConfig(
node_id=self.node_id,
model=self.model,
iface_id=self.iface_id,
config=self.config,
)
@dataclass
class Position:
x: float
@ -660,3 +792,187 @@ class NodeEvent:
message_type=MessageType(proto.message_type),
node=Node.from_proto(proto.node),
)
@dataclass
class SessionEvent:
node_id: int
event: int
name: str
data: str
time: float
@classmethod
def from_proto(cls, proto: core_pb2.SessionEvent) -> "SessionEvent":
return SessionEvent(
node_id=proto.node_id,
event=proto.event,
name=proto.name,
data=proto.data,
time=proto.time,
)
@dataclass
class FileEvent:
message_type: MessageType
node_id: int
name: str
mode: str
number: int
type: str
source: str
data: str
compressed_data: str
@classmethod
def from_proto(cls, proto: core_pb2.FileEvent) -> "FileEvent":
return FileEvent(
message_type=MessageType(proto.message_type),
node_id=proto.node_id,
name=proto.name,
mode=proto.mode,
number=proto.number,
type=proto.type,
source=proto.source,
data=proto.data,
compressed_data=proto.compressed_data,
)
@dataclass
class ConfigEvent:
message_type: MessageType
node_id: int
object: str
type: int
data_types: List[int]
data_values: str
captions: str
bitmap: str
possible_values: str
groups: str
iface_id: int
network_id: int
opaque: str
@classmethod
def from_proto(cls, proto: core_pb2.ConfigEvent) -> "ConfigEvent":
return ConfigEvent(
message_type=MessageType(proto.message_type),
node_id=proto.node_id,
object=proto.object,
type=proto.type,
data_types=list(proto.data_types),
data_values=proto.data_values,
captions=proto.captions,
bitmap=proto.bitmap,
possible_values=proto.possible_values,
groups=proto.groups,
iface_id=proto.iface_id,
network_id=proto.network_id,
opaque=proto.opaque,
)
@dataclass
class Event:
session_id: int
source: str = None
session_event: SessionEvent = None
node_event: NodeEvent = None
link_event: LinkEvent = None
config_event: Any = None
exception_event: ExceptionEvent = None
file_event: FileEvent = None
@classmethod
def from_proto(cls, proto: core_pb2.Event) -> "Event":
source = proto.source if proto.source else None
node_event = None
link_event = None
exception_event = None
session_event = None
file_event = None
config_event = None
if proto.HasField("node_event"):
node_event = NodeEvent.from_proto(proto.node_event)
elif proto.HasField("link_event"):
link_event = LinkEvent.from_proto(proto.link_event)
elif proto.HasField("exception_event"):
exception_event = ExceptionEvent.from_proto(
proto.session_id, proto.exception_event
)
elif proto.HasField("session_event"):
session_event = SessionEvent.from_proto(proto.session_event)
elif proto.HasField("file_event"):
file_event = FileEvent.from_proto(proto.file_event)
elif proto.HasField("config_event"):
config_event = ConfigEvent.from_proto(proto.config_event)
return Event(
session_id=proto.session_id,
source=source,
node_event=node_event,
link_event=link_event,
exception_event=exception_event,
session_event=session_event,
file_event=file_event,
config_event=config_event,
)
@dataclass
class EmaneEventChannel:
group: str
port: int
device: str
@classmethod
def from_proto(
cls, proto: emane_pb2.GetEmaneEventChannelResponse
) -> "EmaneEventChannel":
return EmaneEventChannel(
group=proto.group, port=proto.port, device=proto.device
)
@dataclass
class EmanePathlossesRequest:
session_id: int
node1_id: int
rx1: float
iface1_id: int
node2_id: int
rx2: float
iface2_id: int
def to_proto(self) -> emane_pb2.EmanePathlossesRequest:
return emane_pb2.EmanePathlossesRequest(
session_id=self.session_id,
node1_id=self.node1_id,
rx1=self.rx1,
iface1_id=self.iface1_id,
node2_id=self.node2_id,
rx2=self.rx2,
iface2_id=self.iface2_id,
)
@dataclass
class MoveNodesRequest:
session_id: int
node_id: int
source: str = None
position: Position = None
geo: Geo = None
def to_proto(self) -> core_pb2.MoveNodesRequest:
position = self.position.to_proto() if self.position else None
geo = self.geo.to_proto() if self.geo else None
return core_pb2.MoveNodesRequest(
session_id=self.session_id,
node_id=self.node_id,
source=self.source,
position=position,
geo=geo,
)

View file

@ -1331,9 +1331,7 @@ class CoreHandler(socketserver.BaseRequestHandler):
iface_id = config_data.iface_id
values_str = config_data.data_values
if iface_id is not None:
node_id = node_id * 1000 + iface_id
node_id = utils.iface_config_id(node_id, iface_id)
logging.debug(
"received configure message for %s nodenum: %s", object_name, node_id
)
@ -1381,9 +1379,7 @@ class CoreHandler(socketserver.BaseRequestHandler):
iface_id = config_data.iface_id
values_str = config_data.data_values
if iface_id is not None:
node_id = node_id * 1000 + iface_id
node_id = utils.iface_config_id(node_id, iface_id)
logging.debug(
"received configure message for %s nodenum: %s", object_name, node_id
)
@ -1413,9 +1409,7 @@ class CoreHandler(socketserver.BaseRequestHandler):
iface_id = config_data.iface_id
values_str = config_data.data_values
if iface_id is not None:
node_id = node_id * 1000 + iface_id
node_id = utils.iface_config_id(node_id, iface_id)
logging.debug(
"received configure message for %s nodenum: %s", object_name, node_id
)

View file

@ -212,7 +212,7 @@ class ConfigurableManager:
def get_configs(
self, node_id: int = _default_node, config_type: str = _default_type
) -> Dict[str, str]:
) -> Optional[Dict[str, str]]:
"""
Retrieve configurations for a node and configuration type.

View file

@ -80,7 +80,7 @@ class EmaneCommEffectModel(emanemodel.EmaneModel):
nem_name = emanexml.nem_file_name(iface)
shim_name = emanexml.shim_file_name(iface)
etree.SubElement(nem_element, "shim", definition=shim_name)
emanexml.create_iface_file(iface, nem_element, "nem", nem_name)
emanexml.create_node_file(iface.node, nem_element, "nem", nem_name)
# create and write shim document
shim_element = etree.Element(
@ -99,7 +99,7 @@ class EmaneCommEffectModel(emanemodel.EmaneModel):
ff = config["filterfile"]
if ff.strip() != "":
emanexml.add_param(shim_element, "filterfile", ff)
emanexml.create_iface_file(iface, shim_element, "shim", shim_name)
emanexml.create_node_file(iface.node, shim_element, "shim", shim_name)
# create transport xml
emanexml.create_transport_xml(iface, config)

View file

@ -6,6 +6,7 @@ import logging
import os
import threading
from collections import OrderedDict
from dataclasses import dataclass, field
from enum import Enum
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type
@ -28,7 +29,7 @@ from core.emulator.enumerations import (
RegisterTlvs,
)
from core.errors import CoreCommandError, CoreError
from core.nodes.base import CoreNode, NodeBase
from core.nodes.base import CoreNetworkBase, CoreNode, CoreNodeBase, NodeBase
from core.nodes.interface import CoreInterface, TunTap
from core.xml import emanexml
@ -68,6 +69,13 @@ class EmaneState(Enum):
NOT_READY = 2
@dataclass
class StartData:
emane_net: EmaneNet
node: CoreNodeBase
ifaces: List[CoreInterface] = field(default_factory=list)
class EmaneManager(ModelManager):
"""
EMANE controller object. Lives in a Session instance and is used for
@ -126,33 +134,31 @@ class EmaneManager(ModelManager):
self, emane_net: EmaneNet, iface: CoreInterface
) -> Dict[str, str]:
"""
Retrieve configuration for a given interface.
Retrieve configuration for a given interface, first checking for interface
specific config, node specific config, network specific config, and finally
falling back to the default configuration settings.
:param emane_net: emane network the interface is connected to
:param iface: interface running emane
:return: net, node, or interface model configuration
"""
model_name = emane_net.model.name
# don"t use default values when interface config is the same as net
# note here that using iface.node.id as key allows for only one type
# of each model per node;
# TODO: use both node and interface as key
# Adamson change: first check for iface config keyed by "node:iface.name"
# (so that nodes w/ multiple interfaces of same conftype can have
# different configs for each separate interface)
key = 1000 * iface.node.id
config = None
# try to retrieve interface specific configuration
if iface.node_id is not None:
key += iface.node_id
# try retrieve interface specific configuration, avoid getting defaults
config = self.get_configs(node_id=key, config_type=model_name)
# otherwise retrieve the interfaces node configuration, avoid using defaults
key = utils.iface_config_id(iface.node.id, iface.node_id)
config = self.get_configs(node_id=key, config_type=model_name)
# attempt to retrieve node specific config, when iface config is not present
if not config:
config = self.get_configs(node_id=iface.node.id, config_type=model_name)
# get non interface config, when none found
# attempt to get emane net specific config, when node config is not present
if not config:
# 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=emane_net.id, config_type=model_name)
# return default config values, when a config is not present
if not config:
config = emane_net.model.default_values()
return config
def config_reset(self, node_id: int = None) -> None:
@ -342,36 +348,45 @@ class EmaneManager(ModelManager):
self.buildeventservicexml()
with self._emane_node_lock:
logging.info("emane building xmls...")
for node_id in sorted(self._emane_nets):
emane_net = self._emane_nets[node_id]
if not emane_net.model:
logging.error("emane net(%s) has no model", emane_net.name)
continue
for iface in emane_net.get_ifaces():
self.start_iface(emane_net, iface)
start_data = self.get_start_data()
for data in start_data:
self.start_node(data)
if self.links_enabled():
self.link_monitor.start()
return EmaneState.SUCCESS
def start_iface(self, emane_net: EmaneNet, iface: CoreInterface) -> None:
if not iface.node:
logging.error(
"emane net(%s) connected interface(%s) missing node",
emane_net.name,
iface.name,
)
return
def get_start_data(self) -> List[StartData]:
node_map = {}
for node_id in sorted(self._emane_nets):
emane_net = self._emane_nets[node_id]
if not emane_net.model:
logging.error("emane net(%s) has no model", emane_net.name)
continue
for iface in emane_net.get_ifaces():
if not iface.node:
logging.error(
"emane net(%s) connected interface(%s) missing node",
emane_net.name,
iface.name,
)
continue
start_node = node_map.setdefault(
iface.node, StartData(emane_net, iface.node)
)
start_node.ifaces.append(iface)
start_nodes = sorted(node_map.values(), key=lambda x: x.node.id)
for start_node in start_nodes:
start_node.ifaces = sorted(start_node.ifaces, key=lambda x: x.node_id)
return start_nodes
def start_node(self, data: StartData) -> None:
control_net = self.session.add_remove_control_net(
0, remove=False, conf_required=False
)
nem_id = self.next_nem_id()
self.set_nem(nem_id, iface)
self.write_nem(iface, nem_id)
emanexml.build_platform_xml(self, control_net, emane_net, iface, nem_id)
config = self.get_iface_config(emane_net, iface)
emane_net.model.build_xml_files(config, iface)
self.start_daemon(iface)
self.install_iface(emane_net, iface)
emanexml.build_platform_xml(self, control_net, data)
self.start_daemon(data.node)
for iface in data.ifaces:
self.install_iface(data.emane_net, iface)
def set_nem(self, nem_id: int, iface: CoreInterface) -> None:
if nem_id in self.nems_to_ifaces:
@ -432,8 +447,21 @@ class EmaneManager(ModelManager):
logging.info("stopping EMANE daemons")
if self.links_enabled():
self.link_monitor.stop()
self.deinstall_ifaces()
self.stopdaemons()
# shutdown interfaces and stop daemons
kill_emaned = "killall -q emane"
start_data = self.get_start_data()
for data in start_data:
node = data.node
if not node.up:
continue
for iface in data.ifaces:
if isinstance(node, CoreNode):
iface.shutdown()
iface.poshook = None
if isinstance(node, CoreNode):
node.cmd(kill_emaned, wait=False)
else:
node.host_cmd(kill_emaned, wait=False)
self.stopeventmonitor()
def check_node_models(self) -> None:
@ -521,7 +549,7 @@ class EmaneManager(ModelManager):
)
)
def start_daemon(self, iface: CoreInterface) -> None:
def start_daemon(self, node: CoreNodeBase) -> None:
"""
Start one EMANE daemon per node having a radio.
Add a control network even if the user has not configured one.
@ -536,8 +564,7 @@ class EmaneManager(ModelManager):
emanecmd = f"emane -d -l {loglevel}"
if realtime:
emanecmd += " -r"
node = iface.node
if iface.is_virtual():
if isinstance(node, CoreNode):
otagroup, _otaport = self.get_config("otamanagergroup").split(":")
otadev = self.get_config("otamanagerdevice")
otanetidx = self.session.get_control_net_index(otadev)
@ -566,35 +593,19 @@ class EmaneManager(ModelManager):
if eventservicenetidx >= 0 and eventgroup != otagroup:
node.node_net_client.create_route(eventgroup, eventdev)
# start emane
log_file = os.path.join(node.nodedir, f"{iface.name}-emane.log")
platform_xml = os.path.join(node.nodedir, f"{iface.name}-platform.xml")
log_file = os.path.join(node.nodedir, f"{node.name}-emane.log")
platform_xml = os.path.join(node.nodedir, f"{node.name}-platform.xml")
args = f"{emanecmd} -f {log_file} {platform_xml}"
node.cmd(args)
logging.info("node(%s) emane daemon running: %s", node.name, args)
else:
path = self.session.session_dir
log_file = os.path.join(path, f"{iface.name}-emane.log")
platform_xml = os.path.join(path, f"{iface.name}-platform.xml")
log_file = os.path.join(path, f"{node.name}-emane.log")
platform_xml = os.path.join(path, f"{node.name}-platform.xml")
emanecmd += f" -f {log_file} {platform_xml}"
node.host_cmd(emanecmd, cwd=path)
logging.info("node(%s) host emane daemon running: %s", node.name, emanecmd)
def stopdaemons(self) -> None:
"""
Kill the appropriate EMANE daemons.
"""
kill_emaned = "killall -q emane"
for node_id in sorted(self._emane_nets):
emane_net = self._emane_nets[node_id]
for iface in emane_net.get_ifaces():
node = iface.node
if not node.up:
continue
if iface.is_raw():
node.host_cmd(kill_emaned, wait=False)
else:
node.cmd(kill_emaned, wait=False)
def install_iface(self, emane_net: EmaneNet, iface: CoreInterface) -> None:
config = self.get_iface_config(emane_net, iface)
external = config.get("external", "0")
@ -606,17 +617,6 @@ class EmaneManager(ModelManager):
iface.poshook = emane_net.setnemposition
iface.setposition()
def deinstall_ifaces(self) -> None:
"""
Uninstall TUN/TAP virtual interfaces.
"""
for key in sorted(self._emane_nets):
emane_net = self._emane_nets[key]
for iface in emane_net.get_ifaces():
if iface.is_virtual():
iface.shutdown()
iface.poshook = None
def doeventmonitor(self) -> bool:
"""
Returns boolean whether or not EMANE events will be monitored.
@ -780,6 +780,9 @@ class EmaneManager(ModelManager):
self.session.broadcast_node(node)
return True
def is_emane_net(self, net: Optional[CoreNetworkBase]) -> bool:
return isinstance(net, EmaneNet)
def emanerunning(self, node: CoreNode) -> bool:
"""
Return True if an EMANE process associated with the given node is running,

View file

@ -545,7 +545,12 @@ class Session:
# ensure default emane configuration
if isinstance(node, EmaneNet) and options.emane:
self.emane.set_model_config(_id, options.emane)
model = self.emane.models.get(options.emane)
if not model:
raise CoreError(
f"node({node.name}) emane model({options.emane}) does not exist"
)
node.model = model(self, node.id)
if self.state == EventTypes.RUNTIME_STATE:
self.emane.add_node(node)
# set default wlan config if needed
@ -571,17 +576,10 @@ class Session:
:return: nothing
:raises core.CoreError: when node to update does not exist
"""
# get node to update
node = self.get_node(node_id, NodeBase)
# set node position and broadcast it
self.set_node_position(node, options)
# update attributes
node.canvas = options.canvas
node.icon = options.icon
# provide edits to sdt
self.set_node_position(node, options)
self.sdt.edit_node(node, options.lon, options.lat, options.alt)
def set_node_position(self, node: NodeBase, options: NodeOptions) -> None:
@ -758,7 +756,9 @@ class Session:
"""
Shutdown all session nodes and remove the session directory.
"""
logging.info("session(%s) shutting down", self.id)
if self.state == EventTypes.SHUTDOWN_STATE:
return
logging.info("session(%s) state(%s) shutting down", self.id, self.state)
self.set_state(EventTypes.DATACOLLECT_STATE, send_event=True)
self.set_state(EventTypes.SHUTDOWN_STATE, send_event=True)

View file

@ -30,3 +30,19 @@ class CoreXmlError(Exception):
"""
pass
class CoreServiceError(Exception):
"""
Used when there is an error related to accessing a service.
"""
pass
class CoreServiceBootError(Exception):
"""
Used when there is an error booting a service.
"""
pass

View file

@ -11,6 +11,7 @@ EBTABLES: str = "ebtables"
MOUNT: str = "mount"
UMOUNT: str = "umount"
OVS_VSCTL: str = "ovs-vsctl"
TEST: str = "test"
COMMON_REQUIREMENTS: List[str] = [
BASH,
@ -21,6 +22,7 @@ COMMON_REQUIREMENTS: List[str] = [
SYSCTL,
TC,
UMOUNT,
TEST,
]
VCMD_REQUIREMENTS: List[str] = [VNODED, VCMD]
OVS_REQUIREMENTS: List[str] = [OVS_VSCTL]

View file

@ -21,19 +21,7 @@ from core.api.grpc import (
services_pb2,
wlan_pb2,
)
from core.gui import appconfig
from core.gui.appconfig import BACKGROUNDS_PATH, XMLS_PATH, CoreServer, Observer
from core.gui.dialogs.emaneinstall import EmaneInstallDialog
from core.gui.dialogs.error import ErrorDialog
from core.gui.dialogs.mobilityplayer import MobilityPlayer
from core.gui.dialogs.sessions import SessionsDialog
from core.gui.graph.edges import CanvasEdge
from core.gui.graph.node import CanvasNode
from core.gui.graph.shape import AnnotationData, Shape
from core.gui.graph.shapeutils import ShapeType
from core.gui.interface import InterfaceManager
from core.gui.nodeutils import NodeDraw, NodeUtils
from core.gui.wrappers import (
from core.api.grpc.wrappers import (
ConfigOption,
ConfigService,
ExceptionEvent,
@ -52,6 +40,18 @@ from core.gui.wrappers import (
SessionState,
ThroughputsEvent,
)
from core.gui import appconfig
from core.gui.appconfig import BACKGROUNDS_PATH, XMLS_PATH, CoreServer, Observer
from core.gui.dialogs.emaneinstall import EmaneInstallDialog
from core.gui.dialogs.error import ErrorDialog
from core.gui.dialogs.mobilityplayer import MobilityPlayer
from core.gui.dialogs.sessions import SessionsDialog
from core.gui.graph.edges import CanvasEdge
from core.gui.graph.node import CanvasNode
from core.gui.graph.shape import AnnotationData, Shape
from core.gui.graph.shapeutils import ShapeType
from core.gui.interface import InterfaceManager
from core.gui.nodeutils import NodeDraw, NodeUtils
if TYPE_CHECKING:
from core.gui.app import Application
@ -937,8 +937,6 @@ class CoreClient:
def get_emane_model_configs_proto(self) -> List[emane_pb2.EmaneModelConfig]:
configs = []
for node in self.session.nodes.values():
if node.type != NodeType.EMANE:
continue
for key, config in node.emane_model_configs.items():
model, iface_id = key
config = ConfigOption.to_dict(config)

View file

@ -5,10 +5,10 @@ import tkinter as tk
from tkinter import ttk
from typing import TYPE_CHECKING, Dict, Optional
from core.api.grpc.wrappers import ExceptionEvent, ExceptionLevel
from core.gui.dialogs.dialog import Dialog
from core.gui.themes import PADX, PADY
from core.gui.widgets import CodeText
from core.gui.wrappers import ExceptionEvent, ExceptionLevel
if TYPE_CHECKING:
from core.gui.app import Application

View file

@ -8,15 +8,15 @@ from typing import TYPE_CHECKING, Dict, List, Optional, Set
import grpc
from core.gui.dialogs.dialog import Dialog
from core.gui.themes import FRAME_PAD, PADX, PADY
from core.gui.widgets import CodeText, ConfigFrame, ListboxScroll
from core.gui.wrappers import (
from core.api.grpc.wrappers import (
ConfigOption,
ConfigServiceData,
Node,
ServiceValidationMode,
)
from core.gui.dialogs.dialog import Dialog
from core.gui.themes import FRAME_PAD, PADX, PADY
from core.gui.widgets import CodeText, ConfigFrame, ListboxScroll
if TYPE_CHECKING:
from core.gui.app import Application

View file

@ -8,11 +8,11 @@ from typing import TYPE_CHECKING, Dict, List, Optional
import grpc
from core.api.grpc.wrappers import ConfigOption, Node
from core.gui.dialogs.dialog import Dialog
from core.gui.images import ImageEnum, Images
from core.gui.themes import PADX, PADY
from core.gui.widgets import ConfigFrame
from core.gui.wrappers import ConfigOption, Node
if TYPE_CHECKING:
from core.gui.app import Application

View file

@ -2,10 +2,10 @@ import tkinter as tk
from tkinter import ttk
from typing import TYPE_CHECKING, Optional
from core.api.grpc.wrappers import Hook, SessionState
from core.gui.dialogs.dialog import Dialog
from core.gui.themes import PADX, PADY
from core.gui.widgets import CodeText, ListboxScroll
from core.gui.wrappers import Hook, SessionState
if TYPE_CHECKING:
from core.gui.app import Application

View file

@ -5,11 +5,11 @@ import tkinter as tk
from tkinter import ttk
from typing import TYPE_CHECKING, Optional
from core.api.grpc.wrappers import Interface, Link, LinkOptions
from core.gui import validation
from core.gui.dialogs.colorpicker import ColorPickerDialog
from core.gui.dialogs.dialog import Dialog
from core.gui.themes import PADX, PADY
from core.gui.wrappers import Interface, Link, LinkOptions
if TYPE_CHECKING:
from core.gui.app import Application

View file

@ -7,10 +7,10 @@ from typing import TYPE_CHECKING, Dict, Optional
import grpc
from core.api.grpc.wrappers import ConfigOption, Node
from core.gui.dialogs.dialog import Dialog
from core.gui.themes import PADX, PADY
from core.gui.widgets import ConfigFrame
from core.gui.wrappers import ConfigOption, Node
if TYPE_CHECKING:
from core.gui.app import Application

View file

@ -4,10 +4,10 @@ from typing import TYPE_CHECKING, Optional
import grpc
from core.api.grpc.wrappers import MobilityAction, Node
from core.gui.dialogs.dialog import Dialog
from core.gui.images import ImageEnum
from core.gui.themes import PADX, PADY
from core.gui.wrappers import MobilityAction, Node
if TYPE_CHECKING:
from core.gui.app import Application

View file

@ -7,6 +7,7 @@ from typing import TYPE_CHECKING, Dict, Optional
import netaddr
from PIL.ImageTk import PhotoImage
from core.api.grpc.wrappers import Node
from core.gui import nodeutils, validation
from core.gui.appconfig import ICONS_PATH
from core.gui.dialogs.dialog import Dialog
@ -15,7 +16,6 @@ from core.gui.images import Images
from core.gui.nodeutils import NodeUtils
from core.gui.themes import FRAME_PAD, PADX, PADY
from core.gui.widgets import ListboxScroll, image_chooser
from core.gui.wrappers import Node
if TYPE_CHECKING:
from core.gui.app import Application
@ -282,9 +282,7 @@ class NodeConfigDialog(Dialog):
button.grid(row=0, column=1, sticky=tk.EW)
def click_emane_config(self, emane_model: str, iface_id: int) -> None:
dialog = EmaneModelDialog(
self, self.app, self.canvas_node, emane_model, iface_id
)
dialog = EmaneModelDialog(self, self.app, self.node, emane_model, iface_id)
dialog.show()
def click_icon(self) -> None:

View file

@ -6,11 +6,11 @@ import tkinter as tk
from tkinter import messagebox, ttk
from typing import TYPE_CHECKING, Optional, Set
from core.api.grpc.wrappers import Node
from core.gui.dialogs.configserviceconfig import ConfigServiceConfigDialog
from core.gui.dialogs.dialog import Dialog
from core.gui.themes import FRAME_PAD, PADX, PADY
from core.gui.widgets import CheckboxList, ListboxScroll
from core.gui.wrappers import Node
if TYPE_CHECKING:
from core.gui.app import Application

View file

@ -5,11 +5,11 @@ import tkinter as tk
from tkinter import messagebox, ttk
from typing import TYPE_CHECKING, Optional, Set
from core.api.grpc.wrappers import Node
from core.gui.dialogs.dialog import Dialog
from core.gui.dialogs.serviceconfig import ServiceConfigDialog
from core.gui.themes import FRAME_PAD, PADX, PADY
from core.gui.widgets import CheckboxList, ListboxScroll
from core.gui.wrappers import Node
if TYPE_CHECKING:
from core.gui.app import Application

View file

@ -7,12 +7,12 @@ from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple
import grpc
from PIL.ImageTk import PhotoImage
from core.api.grpc.wrappers import Node, NodeServiceData, ServiceValidationMode
from core.gui.dialogs.copyserviceconfig import CopyServiceConfigDialog
from core.gui.dialogs.dialog import Dialog
from core.gui.images import ImageEnum, Images
from core.gui.themes import FRAME_PAD, PADX, PADY
from core.gui.widgets import CodeText, ListboxScroll
from core.gui.wrappers import Node, NodeServiceData, ServiceValidationMode
if TYPE_CHECKING:
from core.gui.app import Application

View file

@ -5,10 +5,10 @@ from typing import TYPE_CHECKING, Dict, Optional
import grpc
from core.api.grpc.wrappers import ConfigOption
from core.gui.dialogs.dialog import Dialog
from core.gui.themes import PADX, PADY
from core.gui.widgets import ConfigFrame
from core.gui.wrappers import ConfigOption
if TYPE_CHECKING:
from core.gui.app import Application

View file

@ -5,11 +5,11 @@ from typing import TYPE_CHECKING, List, Optional
import grpc
from core.api.grpc.wrappers import SessionState, SessionSummary
from core.gui.dialogs.dialog import Dialog
from core.gui.images import ImageEnum, Images
from core.gui.task import ProgressTask
from core.gui.themes import PADX, PADY
from core.gui.wrappers import SessionState, SessionSummary
if TYPE_CHECKING:
from core.gui.app import Application

View file

@ -4,10 +4,10 @@ from typing import TYPE_CHECKING, Dict, Optional
import grpc
from core.api.grpc.wrappers import ConfigOption, Node
from core.gui.dialogs.dialog import Dialog
from core.gui.themes import PADX, PADY
from core.gui.widgets import ConfigFrame
from core.gui.wrappers import ConfigOption, Node
if TYPE_CHECKING:
from core.gui.app import Application

View file

@ -1,9 +1,9 @@
import tkinter as tk
from typing import TYPE_CHECKING, Optional
from core.api.grpc.wrappers import Interface
from core.gui.frames.base import DetailsFrame, InfoFrameBase
from core.gui.utils import bandwidth_text
from core.gui.wrappers import Interface
if TYPE_CHECKING:
from core.gui.app import Application

View file

@ -1,9 +1,9 @@
import tkinter as tk
from typing import TYPE_CHECKING
from core.api.grpc.wrappers import NodeType
from core.gui.frames.base import DetailsFrame, InfoFrameBase
from core.gui.nodeutils import NodeUtils
from core.gui.wrappers import NodeType
if TYPE_CHECKING:
from core.gui.app import Application

View file

@ -3,13 +3,13 @@ import math
import tkinter as tk
from typing import TYPE_CHECKING, Optional, Tuple
from core.api.grpc.wrappers import Interface, Link
from core.gui import themes
from core.gui.dialogs.linkconfig import LinkConfigurationDialog
from core.gui.frames.link import EdgeInfoFrame, WirelessEdgeInfoFrame
from core.gui.graph import tags
from core.gui.nodeutils import NodeUtils
from core.gui.utils import bandwidth_text, delay_jitter_text
from core.gui.wrappers import Interface, Link
if TYPE_CHECKING:
from core.gui.graph.graph import CanvasGraph

View file

@ -7,6 +7,14 @@ from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple
from PIL import Image
from PIL.ImageTk import PhotoImage
from core.api.grpc.wrappers import (
Interface,
Link,
LinkType,
Node,
Session,
ThroughputsEvent,
)
from core.gui.dialogs.shapemod import ShapeDialog
from core.gui.graph import tags
from core.gui.graph.edges import (
@ -22,7 +30,6 @@ from core.gui.graph.shape import Shape
from core.gui.graph.shapeutils import ShapeType, is_draw_shape, is_marker
from core.gui.images import ImageEnum, TypeToImage
from core.gui.nodeutils import NodeDraw, NodeUtils
from core.gui.wrappers import Interface, Link, LinkType, Node, Session, ThroughputsEvent
if TYPE_CHECKING:
from core.gui.app import Application

View file

@ -7,6 +7,7 @@ from typing import TYPE_CHECKING, Dict, List, Set
import grpc
from PIL.ImageTk import PhotoImage
from core.api.grpc.wrappers import Interface, Node, NodeType
from core.gui import nodeutils, themes
from core.gui.dialogs.emaneconfig import EmaneConfigDialog
from core.gui.dialogs.mobilityconfig import MobilityConfigDialog
@ -20,7 +21,6 @@ from core.gui.graph.edges import CanvasEdge, CanvasWirelessEdge
from core.gui.graph.tooltip import CanvasTooltip
from core.gui.images import ImageEnum, Images
from core.gui.nodeutils import ANTENNA_SIZE, NodeUtils
from core.gui.wrappers import Interface, Node, NodeType
if TYPE_CHECKING:
from core.gui.app import Application

View file

@ -5,8 +5,8 @@ from typing import Dict, Optional, Tuple
from PIL import Image
from PIL.ImageTk import PhotoImage
from core.api.grpc.wrappers import NodeType
from core.gui.appconfig import LOCAL_ICONS_PATH
from core.gui.wrappers import NodeType
class Images:

View file

@ -4,9 +4,9 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple
import netaddr
from netaddr import EUI, IPNetwork
from core.api.grpc.wrappers import Interface, Link, Node
from core.gui.graph.node import CanvasNode
from core.gui.nodeutils import NodeUtils
from core.gui.wrappers import Interface, Link, Node
if TYPE_CHECKING:
from core.gui.app import Application

View file

@ -3,9 +3,9 @@ from typing import List, Optional, Set
from PIL.ImageTk import PhotoImage
from core.api.grpc.wrappers import Node, NodeType
from core.gui.appconfig import CustomNode, GuiConfig
from core.gui.images import ImageEnum, Images, TypeToImage
from core.gui.wrappers import Node, NodeType
ICON_SIZE: int = 48
ANTENNA_SIZE: int = 32

View file

@ -5,9 +5,9 @@ import tkinter as tk
from tkinter import ttk
from typing import TYPE_CHECKING, List, Optional
from core.api.grpc.wrappers import ExceptionEvent, ExceptionLevel
from core.gui.dialogs.alerts import AlertsDialog
from core.gui.themes import Styles
from core.gui.wrappers import ExceptionEvent, ExceptionLevel
if TYPE_CHECKING:
from core.gui.app import Application

View file

@ -5,10 +5,10 @@ from pathlib import Path
from tkinter import filedialog, font, ttk
from typing import TYPE_CHECKING, Any, Callable, Dict, Set, Type
from core.api.grpc.wrappers import ConfigOption, ConfigOptionType
from core.gui import appconfig, themes, validation
from core.gui.dialogs.dialog import Dialog
from core.gui.themes import FRAME_PAD, PADX, PADY
from core.gui.wrappers import ConfigOption, ConfigOptionType
if TYPE_CHECKING:
from core.gui.app import Application

View file

@ -16,7 +16,7 @@ from core.configservice.dependencies import ConfigServiceDependencies
from core.emulator.data import InterfaceData, LinkData, LinkOptions
from core.emulator.enumerations import LinkTypes, MessageFlags, NodeTypes
from core.errors import CoreCommandError, CoreError
from core.executables import MOUNT, VNODED
from core.executables import MOUNT, TEST, VNODED
from core.nodes.client import VnodeClient
from core.nodes.interface import CoreInterface, TunTap, Veth
from core.nodes.netclient import LinuxNetClient, get_net_client
@ -294,6 +294,16 @@ class CoreNodeBase(NodeBase):
"""
raise NotImplementedError
@abc.abstractmethod
def path_exists(self, path: str) -> bool:
"""
Determines if a file or directory path exists.
:param path: path to file or directory
:return: True if path exists, False otherwise
"""
raise NotImplementedError
def add_config_service(self, service_class: "ConfigServiceType") -> None:
"""
Adds a configuration service to the node.
@ -602,6 +612,19 @@ class CoreNode(CoreNodeBase):
args = self.client.create_cmd(args, shell)
return self.server.remote_cmd(args, wait=wait)
def path_exists(self, path: str) -> bool:
"""
Determines if a file or directory path exists.
:param path: path to file or directory
:return: True if path exists, False otherwise
"""
try:
self.cmd(f"{TEST} -e {path}")
return True
except CoreCommandError:
return False
def termcmdstring(self, sh: str = "/bin/sh") -> str:
"""
Create a terminal command string.

View file

@ -528,8 +528,9 @@ class TunTap(CoreInterface):
# check if this is an EMANE interface; if so, continue
# waiting if EMANE is still running
should_retry = count < 5
is_emane_running = self.node.session.emane.emanerunning(self.node)
if all([should_retry, self.net.is_emane, is_emane_running]):
is_emane = self.session.emane.is_emane_net(self.net)
is_emane_running = self.session.emane.emanerunning(self.node)
if all([should_retry, is_emane, is_emane_running]):
count += 1
else:
raise RuntimeError("node device failed to exist")

View file

@ -11,7 +11,7 @@ from core.emulator.data import InterfaceData, LinkOptions
from core.emulator.distributed import DistributedServer
from core.emulator.enumerations import NodeTypes, TransportType
from core.errors import CoreCommandError, CoreError
from core.executables import MOUNT, UMOUNT
from core.executables import MOUNT, TEST, UMOUNT
from core.nodes.base import CoreNetworkBase, CoreNodeBase
from core.nodes.interface import CoreInterface
from core.nodes.network import CoreNetwork, GreTap
@ -55,6 +55,19 @@ class PhysicalNode(CoreNodeBase):
self.rmnodedir()
def path_exists(self, path: str) -> bool:
"""
Determines if a file or directory path exists.
:param path: path to file or directory
:return: True if path exists, False otherwise
"""
try:
self.host_cmd(f"{TEST} -e {path}")
return True
except CoreCommandError:
return False
def termcmdstring(self, sh: str = "/bin/sh") -> str:
"""
Create a terminal command string.
@ -291,6 +304,19 @@ class Rj45Node(CoreNodeBase):
self.up = False
self.restorestate()
def path_exists(self, path: str) -> bool:
"""
Determines if a file or directory path exists.
:param path: path to file or directory
:return: True if path exists, False otherwise
"""
try:
self.host_cmd(f"{TEST} -e {path}")
return True
except CoreCommandError:
return False
def new_iface(
self, net: CoreNetworkBase, iface_data: InterfaceData
) -> CoreInterface:

View file

@ -25,7 +25,12 @@ from typing import (
from core import utils
from core.emulator.data import FileData
from core.emulator.enumerations import ExceptionLevels, MessageFlags, RegisterTlvs
from core.errors import CoreCommandError, CoreError
from core.errors import (
CoreCommandError,
CoreError,
CoreServiceBootError,
CoreServiceError,
)
from core.nodes.base import CoreNode
if TYPE_CHECKING:
@ -34,10 +39,6 @@ if TYPE_CHECKING:
CoreServiceType = Union["CoreService", Type["CoreService"]]
class ServiceBootError(Exception):
pass
class ServiceMode(enum.Enum):
BLOCKING = 0
NON_BLOCKING = 1
@ -257,7 +258,10 @@ class ServiceManager:
:param name: name of the service to retrieve
:return: service if it exists, None otherwise
"""
return cls.services.get(name)
service = cls.services.get(name)
if service is None:
raise CoreServiceError(f"service({name}) does not exist")
return service
@classmethod
def add_services(cls, path: str) -> List[str]:
@ -450,7 +454,7 @@ class CoreServices:
funcs.append((self._boot_service_path, args, {}))
result, exceptions = utils.threadpool(funcs)
if exceptions:
raise ServiceBootError(*exceptions)
raise CoreServiceBootError(*exceptions)
def _boot_service_path(self, node: CoreNode, boot_path: List["CoreServiceType"]):
logging.info(
@ -464,7 +468,7 @@ class CoreServices:
self.boot_service(node, service)
except Exception as e:
logging.exception("exception booting service: %s", service.name)
raise ServiceBootError(e)
raise CoreServiceBootError(e)
def boot_service(self, node: CoreNode, service: "CoreServiceType") -> None:
"""
@ -501,7 +505,7 @@ class CoreServices:
wait = service.validation_mode == ServiceMode.BLOCKING
status = self.startup_service(node, service, wait)
if status:
raise ServiceBootError(
raise CoreServiceBootError(
"node(%s) service(%s) error during startup" % (node.name, service.name)
)
@ -526,7 +530,7 @@ class CoreServices:
time.sleep(service.validation_period)
if status:
raise ServiceBootError(
raise CoreServiceBootError(
"node(%s) service(%s) failed validation" % (node.name, service.name)
)

View file

@ -41,6 +41,7 @@ if TYPE_CHECKING:
T = TypeVar("T")
DEVNULL = open(os.devnull, "wb")
IFACE_CONFIG_FACTOR: int = 1000
def execute_file(
@ -430,3 +431,34 @@ def random_mac() -> str:
value |= 0x00163E << 24
mac = netaddr.EUI(value, dialect=netaddr.mac_unix_expanded)
return str(mac)
def iface_config_id(node_id: int, iface_id: int = None) -> int:
"""
Common utility to generate a configuration id, in case an interface is being
targeted.
:param node_id: node for config id
:param iface_id: interface for config id
:return: generated config id when interface is present, node id otherwise
"""
if iface_id is not None and iface_id >= 0:
return node_id * IFACE_CONFIG_FACTOR + iface_id
else:
return node_id
def parse_iface_config_id(config_id: int) -> Tuple[int, Optional[int]]:
"""
Parses configuration id, that may be potentially derived from an interface for a
node.
:param config_id: configuration id to parse
:return:
"""
iface_id = None
node_id = config_id
if config_id >= IFACE_CONFIG_FACTOR:
iface_id = config_id % IFACE_CONFIG_FACTOR
node_id = config_id // IFACE_CONFIG_FACTOR
return node_id, iface_id

View file

@ -5,6 +5,7 @@ from lxml import etree
import core.nodes.base
import core.nodes.physical
from core import utils
from core.emane.nodes import EmaneNet
from core.emulator.data import InterfaceData, LinkData, LinkOptions, NodeOptions
from core.emulator.enumerations import EventTypes, NodeTypes
@ -91,10 +92,14 @@ def create_emane_config(session: "Session") -> etree.Element:
def create_emane_model_config(
node_id: int, model: "EmaneModelType", config: Dict[str, str]
node_id: int,
model: "EmaneModelType",
config: Dict[str, str],
iface_id: Optional[int],
) -> etree.Element:
emane_element = etree.Element("emane_configuration")
add_attribute(emane_element, "node", node_id)
add_attribute(emane_element, "iface", iface_id)
add_attribute(emane_element, "model", model.name)
mac_element = etree.SubElement(emane_element, "mac")
@ -378,13 +383,16 @@ class CoreXmlWriter:
all_configs = self.session.emane.get_all_configs(node_id)
if not all_configs:
continue
node_id, iface_id = utils.parse_iface_config_id(node_id)
for model_name in all_configs:
config = all_configs[model_name]
logging.debug(
"writing emane config node(%s) model(%s)", node_id, model_name
)
model = self.session.emane.models[model_name]
emane_configuration = create_emane_model_config(node_id, model, config)
emane_configuration = create_emane_model_config(
node_id, model, config, iface_id
)
emane_configurations.append(emane_configuration)
if emane_configurations.getchildren():
self.scenario.append(emane_configurations)
@ -588,9 +596,9 @@ class CoreXmlReader:
self.read_mobility_configs()
self.read_emane_global_config()
self.read_nodes()
self.read_links()
self.read_emane_configs()
self.read_configservice_configs()
self.read_links()
def read_default_services(self) -> None:
default_services = self.scenario.find("default_services")
@ -748,6 +756,7 @@ class CoreXmlReader:
for emane_configuration in emane_configurations.iterchildren():
node_id = get_int(emane_configuration, "node")
iface_id = get_int(emane_configuration, "iface")
model_name = emane_configuration.get("model")
configs = {}
@ -755,12 +764,13 @@ class CoreXmlReader:
node = self.session.nodes.get(node_id)
if not node:
raise CoreXmlError(f"node for emane config doesn't exist: {node_id}")
if not isinstance(node, EmaneNet):
raise CoreXmlError(f"invalid node for emane config: {node.name}")
model = self.session.emane.models.get(model_name)
if not model:
raise CoreXmlError(f"invalid emane model: {model_name}")
node.setmodel(model, {})
if iface_id is not None and iface_id not in node.ifaces:
raise CoreXmlError(
f"invalid interface id({iface_id}) for node({node.name})"
)
# read and set emane model configuration
mac_configuration = emane_configuration.find("mac")
@ -784,6 +794,7 @@ class CoreXmlReader:
logging.info(
"reading emane configuration node(%s) model(%s)", node_id, model_name
)
node_id = utils.iface_config_id(node_id, iface_id)
self.session.emane.set_model_config(node_id, model_name, configs)
def read_mobility_configs(self) -> None:
@ -869,6 +880,9 @@ class CoreXmlReader:
icon = network_element.get("icon")
server = network_element.get("server")
options = NodeOptions(name=name, icon=icon, server=server)
if node_type == NodeTypes.EMANE:
model = network_element.get("model")
options.emane = model
position_element = network_element.find("position")
if position_element is not None:

View file

@ -7,15 +7,15 @@ from lxml import etree
from core import utils
from core.config import Configuration
from core.emane.nodes import EmaneNet
from core.emulator.distributed import DistributedServer
from core.errors import CoreError
from core.nodes.base import CoreNode, CoreNodeBase
from core.nodes.interface import CoreInterface
from core.nodes.network import CtrlNet
from core.xml import corexml
if TYPE_CHECKING:
from core.emane.emanemanager import EmaneManager
from core.emane.emanemanager import EmaneManager, StartData
from core.emane.emanemodel import EmaneModel
_MAC_PREFIX = "02:02"
@ -78,23 +78,22 @@ def create_file(
corexml.write_xml_file(xml_element, file_path, doctype=doctype)
def create_iface_file(
iface: CoreInterface, xml_element: etree.Element, doc_name: str, file_name: str
def create_node_file(
node: CoreNodeBase, xml_element: etree.Element, doc_name: str, file_name: str
) -> None:
"""
Create emane xml for an interface.
:param iface: interface running emane
:param node: node running emane
:param xml_element: root element to write to file
:param doc_name: name to use in the emane doctype
:param file_name: name of xml file
:return:
"""
node = iface.node
if iface.is_raw():
file_path = os.path.join(node.session.session_dir, file_name)
else:
if isinstance(node, CoreNode):
file_path = os.path.join(node.nodedir, file_name)
else:
file_path = os.path.join(node.session.session_dir, file_name)
create_file(xml_element, doc_name, file_path, node.server)
@ -143,11 +142,7 @@ def add_configurations(
def build_platform_xml(
emane_manager: "EmaneManager",
control_net: CtrlNet,
emane_net: EmaneNet,
iface: CoreInterface,
nem_id: int,
emane_manager: "EmaneManager", control_net: CtrlNet, data: "StartData"
) -> None:
"""
Create platform xml for a specific node.
@ -156,50 +151,62 @@ def build_platform_xml(
configurations
:param control_net: control net node for this emane
network
:param emane_net: emane network associated with interface
:param iface: interface running emane
:param nem_id: nem id to use for this interface
:param data: start data for a node connected to emane and associated interfaces
:return: the next nem id that can be used for creating platform xml files
"""
# build nem xml
nem_definition = nem_file_name(iface)
nem_element = etree.Element(
"nem", id=str(nem_id), name=iface.localname, definition=nem_definition
)
# check if this is an external transport, get default config if an interface
# specific one does not exist
config = emane_manager.get_iface_config(emane_net, iface)
if is_external(config):
nem_element.set("transport", "external")
platform_endpoint = "platformendpoint"
add_param(nem_element, platform_endpoint, config[platform_endpoint])
transport_endpoint = "transportendpoint"
add_param(nem_element, transport_endpoint, config[transport_endpoint])
else:
transport_name = transport_file_name(iface)
transport_element = etree.SubElement(
nem_element, "transport", definition=transport_name
)
add_param(transport_element, "device", iface.name)
# create top level platform element
transport_configs = {"otamanagerdevice", "eventservicedevice"}
platform_element = etree.Element("platform")
for configuration in emane_manager.emane_config.emulator_config:
name = configuration.id
if iface.is_raw() and name in transport_configs:
if not isinstance(data.node, CoreNode) and name in transport_configs:
value = control_net.brname
else:
value = emane_manager.get_config(name)
add_param(platform_element, name, value)
platform_element.append(nem_element)
mac = _MAC_PREFIX + ":00:00:"
mac += f"{(nem_id >> 8) & 0xFF:02X}:{nem_id & 0xFF:02X}"
iface.set_mac(mac)
# create nem xml entries for all interfaces
emane_net = data.emane_net
for iface in data.ifaces:
nem_id = emane_manager.next_nem_id()
emane_manager.set_nem(nem_id, iface)
emane_manager.write_nem(iface, nem_id)
config = emane_manager.get_iface_config(emane_net, iface)
emane_net.model.build_xml_files(config, iface)
# build nem xml
nem_definition = nem_file_name(iface)
nem_element = etree.Element(
"nem", id=str(nem_id), name=iface.localname, definition=nem_definition
)
# check if this is an external transport, get default config if an interface
# specific one does not exist
config = emane_manager.get_iface_config(emane_net, iface)
if is_external(config):
nem_element.set("transport", "external")
platform_endpoint = "platformendpoint"
add_param(nem_element, platform_endpoint, config[platform_endpoint])
transport_endpoint = "transportendpoint"
add_param(nem_element, transport_endpoint, config[transport_endpoint])
else:
transport_name = transport_file_name(iface)
transport_element = etree.SubElement(
nem_element, "transport", definition=transport_name
)
add_param(transport_element, "device", iface.name)
# add nem element to platform element
platform_element.append(nem_element)
# generate and assign interface mac address based on nem id
mac = _MAC_PREFIX + ":00:00:"
mac += f"{(nem_id >> 8) & 0xFF:02X}:{nem_id & 0xFF:02X}"
iface.set_mac(mac)
doc_name = "platform"
file_name = f"{iface.name}-platform.xml"
create_iface_file(iface, platform_element, doc_name, file_name)
file_name = f"{data.node.name}-platform.xml"
create_node_file(data.node, platform_element, doc_name, file_name)
def create_transport_xml(iface: CoreInterface, config: Dict[str, str]) -> None:
@ -220,16 +227,16 @@ def create_transport_xml(iface: CoreInterface, config: Dict[str, str]) -> None:
# get emane model cnfiguration
flowcontrol = config.get("flowcontrolenable", "0") == "1"
if iface.is_virtual():
if isinstance(iface.node, CoreNode):
device_path = "/dev/net/tun_flowctl"
if not os.path.exists(device_path):
if not iface.node.path_exists(device_path):
device_path = "/dev/net/tun"
add_param(transport_element, "devicepath", device_path)
if flowcontrol:
add_param(transport_element, "flowcontrolenable", "on")
doc_name = "transport"
transport_name = transport_file_name(iface)
create_iface_file(iface, transport_element, doc_name, transport_name)
create_node_file(iface.node, transport_element, doc_name, transport_name)
def create_phy_xml(
@ -250,7 +257,7 @@ def create_phy_xml(
phy_element, emane_model.phy_config, config, emane_model.config_ignore
)
file_name = phy_file_name(iface)
create_iface_file(iface, phy_element, "phy", file_name)
create_node_file(iface.node, phy_element, "phy", file_name)
def create_mac_xml(
@ -273,7 +280,7 @@ def create_mac_xml(
mac_element, emane_model.mac_config, config, emane_model.config_ignore
)
file_name = mac_file_name(iface)
create_iface_file(iface, mac_element, "mac", file_name)
create_node_file(iface.node, mac_element, "mac", file_name)
def create_nem_xml(
@ -298,7 +305,7 @@ def create_nem_xml(
phy_name = phy_file_name(iface)
etree.SubElement(nem_element, "phy", definition=phy_name)
nem_name = nem_file_name(iface)
create_iface_file(iface, nem_element, "nem", nem_name)
create_node_file(iface.node, nem_element, "nem", nem_name)
def create_event_service_xml(
@ -351,7 +358,7 @@ def nem_file_name(iface: CoreInterface) -> str:
:param iface: interface running emane
:return: nem xm file name
"""
append = "-raw" if iface.is_raw() else ""
append = "-raw" if not isinstance(iface.node, CoreNode) else ""
return f"{iface.name}-nem{append}.xml"

View file

@ -1,74 +1,51 @@
"""
Example using gRPC API to create a simple EMANE 80211 network.
"""
import logging
# required imports
from core.api.grpc import client
from core.api.grpc.core_pb2 import Node, NodeType, Position, SessionState
from core.emane.ieee80211abg import EmaneIeee80211abgModel
# interface helper
iface_helper = client.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64")
def log_event(event):
logging.info("event: %s", event)
# create grpc client and connect
core = client.CoreGrpcClient()
core.connect()
# create session and get id
response = core.create_session()
session_id = response.session_id
def main():
# helper to create interface addresses
interface_helper = client.InterfaceHelper(ip4_prefix="10.83.0.0/24")
# change session state to configuration so that nodes get started when added
core.set_session_state(session_id, SessionState.CONFIGURATION)
# create grpc client and start connection context, which auto closes connection
core = client.CoreGrpcClient()
with core.context_connect():
# create session
response = core.create_session()
logging.info("created session: %s", response)
# create emane node
position = Position(x=200, y=200)
emane = Node(type=NodeType.EMANE, position=position, emane=EmaneIeee80211abgModel.name)
response = core.add_node(session_id, emane)
emane_id = response.node_id
# handle events session may broadcast
session_id = response.session_id
core.events(session_id, log_event)
# create node one
position = Position(x=100, y=100)
n1 = Node(type=NodeType.DEFAULT, position=position, model="mdr")
response = core.add_node(session_id, n1)
n1_id = response.node_id
# change session state to configuration so that nodes get started when added
response = core.set_session_state(session_id, SessionState.CONFIGURATION)
logging.info("set session state: %s", response)
# create node two
position = Position(x=300, y=100)
n2 = Node(type=NodeType.DEFAULT, position=position, model="mdr")
response = core.add_node(session_id, n2)
n2_id = response.node_id
# create emane node
position = Position(x=200, y=200)
emane = Node(type=NodeType.EMANE, position=position)
response = core.add_node(session_id, emane)
logging.info("created emane: %s", response)
emane_id = response.node_id
# configure general emane settings
core.set_emane_config(session_id, {"eventservicettl": "2"})
# an emane model must be configured for use, by the emane node
core.set_emane_model_config(session_id, emane_id, EmaneIeee80211abgModel.name)
# configure emane model settings
# using a dict mapping currently support values as strings
core.set_emane_model_config(
session_id, emane_id, EmaneIeee80211abgModel.name, {"unicastrate": "3"}
)
# create node one
position = Position(x=100, y=100)
node1 = Node(type=NodeType.DEFAULT, position=position)
response = core.add_node(session_id, node1)
logging.info("created node: %s", response)
node1_id = response.node_id
# create node two
position = Position(x=300, y=100)
node2 = Node(type=NodeType.DEFAULT, position=position)
response = core.add_node(session_id, node2)
logging.info("created node: %s", response)
node2_id = response.node_id
# links nodes to switch
interface1 = interface_helper.create_iface(node1_id, 0)
response = core.add_link(session_id, node1_id, emane_id, interface1)
logging.info("created link: %s", response)
interface1 = interface_helper.create_iface(node2_id, 0)
response = core.add_link(session_id, node2_id, emane_id, interface1)
logging.info("created link: %s", response)
# change session state
response = core.set_session_state(session_id, SessionState.INSTANTIATION)
logging.info("set session state: %s", response)
if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG)
main()
# links nodes to emane
iface1 = iface_helper.create_iface(n1_id, 0)
core.add_link(session_id, n1_id, emane_id, iface1)
iface1 = iface_helper.create_iface(n2_id, 0)
core.add_link(session_id, n2_id, emane_id, iface1)

View file

@ -0,0 +1,36 @@
from core.api.grpc import client
from core.api.grpc.core_pb2 import Node, NodeType, Position, SessionState
# interface helper
iface_helper = client.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64")
# create grpc client and connect
core = client.CoreGrpcClient()
core.connect()
# create session and get id
response = core.create_session()
session_id = response.session_id
# change session state to configuration so that nodes get started when added
core.set_session_state(session_id, SessionState.CONFIGURATION)
# create node one
position = Position(x=100, y=100)
n1 = Node(type=NodeType.DEFAULT, position=position, model="PC")
response = core.add_node(session_id, n1)
n1_id = response.node_id
# create node two
position = Position(x=300, y=100)
n2 = Node(type=NodeType.DEFAULT, position=position, model="PC")
response = core.add_node(session_id, n2)
n2_id = response.node_id
# links nodes together
iface1 = iface_helper.create_iface(n1_id, 0)
iface2 = iface_helper.create_iface(n2_id, 0)
core.add_link(session_id, n1_id, n2_id, iface1, iface2)
# change session state
core.set_session_state(session_id, SessionState.INSTANTIATION)

View file

@ -1,70 +1,44 @@
"""
Example using gRPC API to create a simple switch network.
"""
import logging
# required imports
from core.api.grpc import client
from core.api.grpc.core_pb2 import Node, NodeType, Position, SessionState
# interface helper
iface_helper = client.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64")
def log_event(event):
logging.info("event: %s", event)
# create grpc client and connect
core = client.CoreGrpcClient()
core.connect()
# create session and get id
response = core.create_session()
session_id = response.session_id
def main():
# helper to create interface addresses
interface_helper = client.InterfaceHelper(ip4_prefix="10.83.0.0/24")
# change session state to configuration so that nodes get started when added
core.set_session_state(session_id, SessionState.CONFIGURATION)
# create grpc client and start connection context, which auto closes connection
core = client.CoreGrpcClient()
with core.context_connect():
# create session
response = core.create_session()
logging.info("created session: %s", response)
# create switch node
position = Position(x=200, y=200)
switch = Node(type=NodeType.SWITCH, position=position)
response = core.add_node(session_id, switch)
switch_id = response.node_id
# handle events session may broadcast
session_id = response.session_id
core.events(session_id, log_event)
# create node one
position = Position(x=100, y=100)
n1 = Node(type=NodeType.DEFAULT, position=position, model="PC")
response = core.add_node(session_id, n1)
n1_id = response.node_id
# change session state to configuration so that nodes get started when added
response = core.set_session_state(session_id, SessionState.CONFIGURATION)
logging.info("set session state: %s", response)
# create node two
position = Position(x=300, y=100)
n2 = Node(type=NodeType.DEFAULT, position=position, model="PC")
response = core.add_node(session_id, n2)
n2_id = response.node_id
# create switch node
position = Position(x=200, y=200)
switch = Node(type=NodeType.SWITCH, position=position)
response = core.add_node(session_id, switch)
logging.info("created switch: %s", response)
switch_id = response.node_id
# links nodes to switch
iface1 = iface_helper.create_iface(n1_id, 0)
core.add_link(session_id, n1_id, switch_id, iface1)
iface1 = iface_helper.create_iface(n2_id, 0)
core.add_link(session_id, n2_id, switch_id, iface1)
# create node one
position = Position(x=100, y=100)
node1 = Node(type=NodeType.DEFAULT, position=position, model="PC")
response = core.add_node(session_id, node1)
logging.info("created node: %s", response)
node1_id = response.node_id
# create node two
position = Position(x=300, y=100)
node2 = Node(type=NodeType.DEFAULT, position=position, model="PC")
response = core.add_node(session_id, node2)
logging.info("created node: %s", response)
node2_id = response.node_id
# links nodes to switch
interface1 = interface_helper.create_iface(node1_id, 0)
response = core.add_link(session_id, node1_id, switch_id, interface1)
logging.info("created link: %s", response)
interface1 = interface_helper.create_iface(node2_id, 0)
response = core.add_link(session_id, node2_id, switch_id, interface1)
logging.info("created link: %s", response)
# change session state
response = core.set_session_state(session_id, SessionState.INSTANTIATION)
logging.info("set session state: %s", response)
if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG)
main()
# change session state
core.set_session_state(session_id, SessionState.INSTANTIATION)

View file

@ -1,82 +1,58 @@
"""
Example using gRPC API to create a simple wlan network.
"""
import logging
# required imports
from core.api.grpc import client
from core.api.grpc.core_pb2 import Node, NodeType, Position, SessionState
# interface helper
iface_helper = client.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64")
def log_event(event):
logging.info("event: %s", event)
# create grpc client and connect
core = client.CoreGrpcClient()
core.connect()
# create session and get id
response = core.create_session()
session_id = response.session_id
def main():
# helper to create interface addresses
interface_helper = client.InterfaceHelper(ip4_prefix="10.83.0.0/24")
# change session state to configuration so that nodes get started when added
core.set_session_state(session_id, SessionState.CONFIGURATION)
# create grpc client and start connection context, which auto closes connection
core = client.CoreGrpcClient()
with core.context_connect():
# create session
response = core.create_session()
logging.info("created session: %s", response)
# create wlan node
position = Position(x=200, y=200)
wlan = Node(type=NodeType.WIRELESS_LAN, position=position)
response = core.add_node(session_id, wlan)
wlan_id = response.node_id
# handle events session may broadcast
session_id = response.session_id
core.events(session_id, log_event)
# create node one
position = Position(x=100, y=100)
n1 = Node(type=NodeType.DEFAULT, position=position, model="mdr")
response = core.add_node(session_id, n1)
n1_id = response.node_id
# change session state to configuration so that nodes get started when added
response = core.set_session_state(session_id, SessionState.CONFIGURATION)
logging.info("set session state: %s", response)
# create node two
position = Position(x=300, y=100)
n2 = Node(type=NodeType.DEFAULT, position=position, model="mdr")
response = core.add_node(session_id, n2)
n2_id = response.node_id
# create wlan node
position = Position(x=200, y=200)
wlan = Node(type=NodeType.WIRELESS_LAN, position=position)
response = core.add_node(session_id, wlan)
logging.info("created wlan: %s", response)
wlan_id = response.node_id
# configure wlan using a dict mapping currently
# support values as strings
core.set_wlan_config(
session_id,
wlan_id,
{
"range": "280",
"bandwidth": "55000000",
"delay": "6000",
"jitter": "5",
"error": "5",
},
)
# change/configure wlan if desired
# NOTE: error = loss, and named this way for legacy purposes for now
config = {
"bandwidth": "54000000",
"range": "500",
"jitter": "0",
"delay": "5000",
"error": "0",
}
response = core.set_wlan_config(session_id, wlan_id, config)
logging.info("set wlan config: %s", response)
# links nodes to wlan
iface1 = iface_helper.create_iface(n1_id, 0)
core.add_link(session_id, n1_id, wlan_id, iface1)
iface1 = iface_helper.create_iface(n2_id, 0)
core.add_link(session_id, n2_id, wlan_id, iface1)
# create node one
position = Position(x=100, y=100)
node1 = Node(type=NodeType.DEFAULT, position=position)
response = core.add_node(session_id, node1)
logging.info("created node: %s", response)
node1_id = response.node_id
# create node two
position = Position(x=300, y=100)
node2 = Node(type=NodeType.DEFAULT, position=position)
response = core.add_node(session_id, node2)
logging.info("created node: %s", response)
node2_id = response.node_id
# links nodes to switch
interface1 = interface_helper.create_iface(node1_id, 0)
response = core.add_link(session_id, node1_id, wlan_id, interface1)
logging.info("created link: %s", response)
interface1 = interface_helper.create_iface(node2_id, 0)
response = core.add_link(session_id, node2_id, wlan_id, interface1)
logging.info("created link: %s", response)
# change session state
response = core.set_session_state(session_id, SessionState.INSTANTIATION)
logging.info("set session state: %s", response)
if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG)
main()
# change session state
core.set_session_state(session_id, SessionState.INSTANTIATION)

View file

@ -1,12 +1,4 @@
"""
This is a standalone script to run a small EMANE scenario and will not interact
with the GUI. You also must have installed OSPF MDR as noted in the documentation
installation page.
"""
import logging
import time
# required imports
from core.emane.ieee80211abg import EmaneIeee80211abgModel
from core.emane.nodes import EmaneNet
from core.emulator.coreemu import CoreEmu
@ -14,56 +6,51 @@ from core.emulator.data import IpPrefixes, NodeOptions
from core.emulator.enumerations import EventTypes
from core.nodes.base import CoreNode
NODES = 2
EMANE_DELAY = 10
# ip nerator for example
ip_prefixes = IpPrefixes(ip4_prefix="10.0.0.0/24")
# create emulator instance for creating sessions and utility methods
coreemu = CoreEmu()
session = coreemu.create_session()
def main():
# ip generator for example
prefixes = IpPrefixes(ip4_prefix="10.83.0.0/16")
# location information is required to be set for emane
session.location.setrefgeo(47.57917, -122.13232, 2.0)
session.location.refscale = 150.0
# create emulator instance for creating sessions and utility methods
coreemu = CoreEmu()
session = coreemu.create_session()
# must be in configuration state for nodes to start, when using "node_add" below
session.set_state(EventTypes.CONFIGURATION_STATE)
# must be in configuration state for nodes to start, when using "node_add" below
session.set_state(EventTypes.CONFIGURATION_STATE)
# create emane
options = NodeOptions(x=200, y=200, emane=EmaneIeee80211abgModel.name)
emane = session.add_node(EmaneNet, options=options)
# create emane network node, emane determines connectivity based on
# location, so the session and nodes must be configured to provide one
session.set_location(47.57917, -122.13232, 2.00000, 1.0)
options = NodeOptions()
options.set_position(80, 50)
emane_network = session.add_node(EmaneNet, options=options, _id=100)
session.emane.set_model(emane_network, EmaneIeee80211abgModel)
# create nodes
options = NodeOptions(model="mdr", x=100, y=100)
n1 = session.add_node(CoreNode, options=options)
options = NodeOptions(model="mdr", x=300, y=100)
n2 = session.add_node(CoreNode, options=options)
# create nodes
options = NodeOptions(model="mdr")
for i in range(NODES):
node = session.add_node(CoreNode, options=options)
node.setposition(x=150 * (i + 1), y=150)
interface = prefixes.create_iface(node)
session.add_link(node.id, emane_network.id, iface1_data=interface)
# configure general emane settings
config = session.emane.get_configs()
config.update({"eventservicettl": "2"})
# instantiate session
session.instantiate()
# configure emane model settings
# using a dict mapping currently support values as strings
session.emane.set_model_config(
emane.id, EmaneIeee80211abgModel.name, {"unicastrate": "3"}
)
# OSPF MDR requires some time for routes to be created
logging.info("waiting %s seconds for OSPF MDR to create routes", EMANE_DELAY)
time.sleep(EMANE_DELAY)
# link nodes to emane
iface1 = ip_prefixes.create_iface(n1)
session.add_link(n1.id, emane.id, iface1)
iface1 = ip_prefixes.create_iface(n2)
session.add_link(n2.id, emane.id, iface1)
# get nodes to run example
first_node = session.get_node(1, CoreNode)
last_node = session.get_node(NODES, CoreNode)
address = prefixes.ip4_address(first_node.id)
logging.info("node %s pinging %s", last_node.name, address)
output = last_node.cmd(f"ping -c 3 {address}")
logging.info(output)
# start session
session.instantiate()
# shutdown session
coreemu.shutdown()
# do whatever you like here
input("press enter to shutdown")
if __name__ == "__main__" or __name__ == "__builtin__":
logging.basicConfig(level=logging.INFO)
main()
# stop session
session.shutdown()

View file

@ -0,0 +1,35 @@
# required imports
from core.emulator.coreemu import CoreEmu
from core.emulator.data import IpPrefixes, NodeOptions
from core.emulator.enumerations import EventTypes
from core.nodes.base import CoreNode
# ip nerator for example
ip_prefixes = IpPrefixes(ip4_prefix="10.0.0.0/24")
# create emulator instance for creating sessions and utility methods
coreemu = CoreEmu()
session = coreemu.create_session()
# must be in configuration state for nodes to start, when using "node_add" below
session.set_state(EventTypes.CONFIGURATION_STATE)
# create nodes
options = NodeOptions(x=100, y=100)
n1 = session.add_node(CoreNode, options=options)
options = NodeOptions(x=300, y=100)
n2 = session.add_node(CoreNode, options=options)
# link nodes together
iface1 = ip_prefixes.create_iface(n1)
iface2 = ip_prefixes.create_iface(n2)
session.add_link(n1.id, n2.id, iface1, iface2)
# start session
session.instantiate()
# do whatever you like here
input("press enter to shutdown")
# stop session
session.shutdown()

View file

@ -1,54 +1,41 @@
"""
This is a standalone script to run a small switch based scenario and will not
interact with the GUI.
"""
import logging
# required imports
from core.emulator.coreemu import CoreEmu
from core.emulator.data import IpPrefixes
from core.emulator.data import IpPrefixes, NodeOptions
from core.emulator.enumerations import EventTypes
from core.nodes.base import CoreNode
from core.nodes.network import SwitchNode
NODES = 2
# ip nerator for example
ip_prefixes = IpPrefixes(ip4_prefix="10.0.0.0/24")
# create emulator instance for creating sessions and utility methods
coreemu = CoreEmu()
session = coreemu.create_session()
def main():
# ip generator for example
prefixes = IpPrefixes(ip4_prefix="10.83.0.0/16")
# must be in configuration state for nodes to start, when using "node_add" below
session.set_state(EventTypes.CONFIGURATION_STATE)
# create emulator instance for creating sessions and utility methods
coreemu = CoreEmu()
session = coreemu.create_session()
# create switch
options = NodeOptions(x=200, y=200)
switch = session.add_node(SwitchNode, options=options)
# must be in configuration state for nodes to start, when using "node_add" below
session.set_state(EventTypes.CONFIGURATION_STATE)
# create nodes
options = NodeOptions(x=100, y=100)
n1 = session.add_node(CoreNode, options=options)
options = NodeOptions(x=300, y=100)
n2 = session.add_node(CoreNode, options=options)
# create switch network node
switch = session.add_node(SwitchNode, _id=100)
# link nodes to switch
iface1 = ip_prefixes.create_iface(n1)
session.add_link(n1.id, switch.id, iface1)
iface1 = ip_prefixes.create_iface(n2)
session.add_link(n2.id, switch.id, iface1)
# create nodes
for _ in range(NODES):
node = session.add_node(CoreNode)
interface = prefixes.create_iface(node)
session.add_link(node.id, switch.id, iface1_data=interface)
# start session
session.instantiate()
# instantiate session
session.instantiate()
# do whatever you like here
input("press enter to shutdown")
# get nodes to run example
first_node = session.get_node(1, CoreNode)
last_node = session.get_node(NODES, CoreNode)
address = prefixes.ip4_address(first_node.id)
logging.info("node %s pinging %s", last_node.name, address)
output = last_node.cmd(f"ping -c 3 {address}")
logging.info(output)
# shutdown session
coreemu.shutdown()
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
main()
# stop session
session.shutdown()

View file

@ -1,45 +0,0 @@
"""
This is a script to run a small switch based scenario and depends on
the user running this script through the "Execute Python Script" option
in the GUI. The usage of globals() below allows this script to leverage the
same CoreEmu instance the GUI is using.
"""
import logging
from core.emulator.coreemu import CoreEmu
from core.emulator.data import IpPrefixes
from core.emulator.enumerations import EventTypes
from core.nodes.base import CoreNode
from core.nodes.network import SwitchNode
NODES = 2
def main():
# ip generator for example
prefixes = IpPrefixes("10.83.0.0/16")
# create emulator instance for creating sessions and utility methods
coreemu: CoreEmu = globals()["coreemu"]
session = coreemu.create_session()
# must be in configuration state for nodes to start, when using "node_add" below
session.set_state(EventTypes.CONFIGURATION_STATE)
# create switch network node
switch = session.add_node(SwitchNode)
# create nodes
for _ in range(NODES):
node = session.add_node(CoreNode)
interface = prefixes.create_iface(node)
session.add_link(node.id, switch.id, iface1_data=interface)
# instantiate session
session.instantiate()
if __name__ in {"__main__", "__builtin__"}:
logging.basicConfig(level=logging.INFO)
main()

View file

@ -1,10 +1,4 @@
"""
This is a standalone script to run a small WLAN based scenario and will not
interact with the GUI.
"""
import logging
# required imports
from core.emulator.coreemu import CoreEmu
from core.emulator.data import IpPrefixes, NodeOptions
from core.emulator.enumerations import EventTypes
@ -12,47 +6,50 @@ from core.location.mobility import BasicRangeModel
from core.nodes.base import CoreNode
from core.nodes.network import WlanNode
NODES = 2
# ip nerator for example
ip_prefixes = IpPrefixes(ip4_prefix="10.0.0.0/24")
# create emulator instance for creating sessions and utility methods
coreemu = CoreEmu()
session = coreemu.create_session()
def main():
# ip generator for example
prefixes = IpPrefixes("10.83.0.0/16")
# must be in configuration state for nodes to start, when using "node_add" below
session.set_state(EventTypes.CONFIGURATION_STATE)
# create emulator instance for creating sessions and utility methods
coreemu = CoreEmu()
session = coreemu.create_session()
# create wlan
options = NodeOptions(x=200, y=200)
wlan = session.add_node(WlanNode, options=options)
# must be in configuration state for nodes to start, when using "node_add" below
session.set_state(EventTypes.CONFIGURATION_STATE)
# create nodes
options = NodeOptions(model="mdr", x=100, y=100)
n1 = session.add_node(CoreNode, options=options)
options = NodeOptions(model="mdr", x=300, y=100)
n2 = session.add_node(CoreNode, options=options)
# create wlan network node
wlan = session.add_node(WlanNode, _id=100)
session.mobility.set_model(wlan, BasicRangeModel)
# configuring wlan
session.mobility.set_model_config(
wlan.id,
BasicRangeModel.name,
{
"range": "280",
"bandwidth": "55000000",
"delay": "6000",
"jitter": "5",
"error": "5",
},
)
# create nodes, must set a position for wlan basic range model
options = NodeOptions(model="mdr")
options.set_position(0, 0)
for _ in range(NODES):
node = session.add_node(CoreNode, options=options)
interface = prefixes.create_iface(node)
session.add_link(node.id, wlan.id, iface1_data=interface)
# link nodes to wlan
iface1 = ip_prefixes.create_iface(n1)
session.add_link(n1.id, wlan.id, iface1)
iface1 = ip_prefixes.create_iface(n2)
session.add_link(n2.id, wlan.id, iface1)
# instantiate session
session.instantiate()
# start session
session.instantiate()
# get nodes for example run
first_node = session.get_node(1, CoreNode)
last_node = session.get_node(NODES, CoreNode)
address = prefixes.ip4_address(first_node.id)
logging.info("node %s pinging %s", last_node.name, address)
output = last_node.cmd(f"ping -c 3 {address}")
logging.info(output)
# do whatever you like here
input("press enter to shutdown")
# shutdown session
coreemu.shutdown()
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
main()
# stop session
session.shutdown()

View file

@ -1,12 +1,19 @@
[tool.poetry]
name = "core"
version = "7.1.0"
version = "7.2.0"
description = "CORE Common Open Research Emulator"
authors = ["Boeing Research and Technology"]
license = "BSD-2-Clause"
repository = "https://github.com/coreemu/core"
documentation = "https://coreemu.github.io/core/"
include = ["core/gui/data/**/*", "core/configservices/*/templates"]
include = [
"core/api/grpc/*",
"core/configservices/*/templates",
"core/constants.py",
"core/gui/data/**/*",
]
exclude = ["core/constants.py.in"]
[tool.poetry.dependencies]
python = "^3.6"

View file

@ -117,6 +117,8 @@ def module_coretlv(patcher, global_coreemu, global_session):
@pytest.fixture
def grpc_server(module_grpc):
yield module_grpc
for session in module_grpc.coreemu.sessions.values():
session.set_state(EventTypes.CONFIGURATION_STATE)
module_grpc.coreemu.shutdown()
@ -130,6 +132,7 @@ def session(global_session):
@pytest.fixture
def coretlv(module_coretlv):
session = module_coretlv.session
session.set_state(EventTypes.CONFIGURATION_STATE)
coreemu = module_coretlv.coreemu
coreemu.sessions[session.id] = session
yield module_coretlv

View file

@ -8,6 +8,7 @@ from xml.etree import ElementTree
import pytest
from core import utils
from core.emane.bypass import EmaneBypassModel
from core.emane.commeffect import EmaneCommEffectModel
from core.emane.emanemodel import EmaneModel
@ -43,6 +44,47 @@ def ping(
class TestEmane:
def test_two_emane_interfaces(self, session: Session):
"""
Test nodes running multiple emane interfaces.
:param core.emulator.coreemu.EmuSession session: session for test
"""
# create emane node for networking the core nodes
session.set_location(47.57917, -122.13232, 2.00000, 1.0)
options = NodeOptions()
options.set_position(80, 50)
options.emane = EmaneIeee80211abgModel.name
emane_net1 = session.add_node(EmaneNet, options=options)
options.emane = EmaneRfPipeModel.name
emane_net2 = session.add_node(EmaneNet, options=options)
# create nodes
options = NodeOptions(model="mdr")
options.set_position(150, 150)
node1 = session.add_node(CoreNode, options=options)
options.set_position(300, 150)
node2 = session.add_node(CoreNode, options=options)
# create interfaces
ip_prefix1 = IpPrefixes("10.0.0.0/24")
ip_prefix2 = IpPrefixes("10.0.1.0/24")
for i, node in enumerate([node1, node2]):
node.setposition(x=150 * (i + 1), y=150)
iface_data = ip_prefix1.create_iface(node)
session.add_link(node.id, emane_net1.id, iface1_data=iface_data)
iface_data = ip_prefix2.create_iface(node)
session.add_link(node.id, emane_net2.id, iface1_data=iface_data)
# instantiate session
session.instantiate()
# ping node2 from node1 on both interfaces and check success
status = ping(node1, node2, ip_prefix1, count=5)
assert not status
status = ping(node1, node2, ip_prefix2, count=5)
assert not status
@pytest.mark.parametrize("model", _EMANE_MODELS)
def test_models(
self, session: Session, model: Type[EmaneModel], ip_prefixes: IpPrefixes
@ -161,3 +203,128 @@ class TestEmane:
assert session.get_node(node2_id, CoreNode)
assert session.get_node(emane_id, EmaneNet)
assert value == config_value
def test_xml_emane_node_config(
self, session: Session, tmpdir: TemporaryFile, ip_prefixes: IpPrefixes
):
# create nodes
options = NodeOptions(model="mdr", x=50, y=50)
node1 = session.add_node(CoreNode, options=options)
iface1_data = ip_prefixes.create_iface(node1)
node2 = session.add_node(CoreNode, options=options)
iface2_data = ip_prefixes.create_iface(node2)
# create emane node
options = NodeOptions(model=None, emane=EmaneRfPipeModel.name)
emane_node = session.add_node(EmaneNet, options=options)
# create links
session.add_link(node1.id, emane_node.id, iface1_data)
session.add_link(node2.id, emane_node.id, iface2_data)
# set node specific conifg
datarate = "101"
session.emane.set_model_config(
node1.id, EmaneRfPipeModel.name, {"datarate": datarate}
)
# instantiate session
session.instantiate()
# save xml
xml_file = tmpdir.join("session.xml")
file_path = xml_file.strpath
session.save_xml(file_path)
# verify xml file was created and can be parsed
assert xml_file.isfile()
assert ElementTree.parse(file_path)
# stop current session, clearing data
session.shutdown()
# verify nodes have been removed from session
with pytest.raises(CoreError):
assert not session.get_node(node1.id, CoreNode)
with pytest.raises(CoreError):
assert not session.get_node(node2.id, CoreNode)
with pytest.raises(CoreError):
assert not session.get_node(emane_node.id, EmaneNet)
# load saved xml
session.open_xml(file_path, start=True)
# verify nodes have been recreated
assert session.get_node(node1.id, CoreNode)
assert session.get_node(node2.id, CoreNode)
assert session.get_node(emane_node.id, EmaneNet)
links = []
for node_id in session.nodes:
node = session.nodes[node_id]
links += node.links()
assert len(links) == 2
config = session.emane.get_model_config(node1.id, EmaneRfPipeModel.name)
assert config["datarate"] == datarate
def test_xml_emane_interface_config(
self, session: Session, tmpdir: TemporaryFile, ip_prefixes: IpPrefixes
):
# create nodes
options = NodeOptions(model="mdr", x=50, y=50)
node1 = session.add_node(CoreNode, options=options)
iface1_data = ip_prefixes.create_iface(node1)
node2 = session.add_node(CoreNode, options=options)
iface2_data = ip_prefixes.create_iface(node2)
# create emane node
options = NodeOptions(model=None, emane=EmaneRfPipeModel.name)
emane_node = session.add_node(EmaneNet, options=options)
# create links
session.add_link(node1.id, emane_node.id, iface1_data)
session.add_link(node2.id, emane_node.id, iface2_data)
# set node specific conifg
datarate = "101"
config_id = utils.iface_config_id(node1.id, iface1_data.id)
session.emane.set_model_config(
config_id, EmaneRfPipeModel.name, {"datarate": datarate}
)
# instantiate session
session.instantiate()
# save xml
xml_file = tmpdir.join("session.xml")
file_path = xml_file.strpath
session.save_xml(file_path)
# verify xml file was created and can be parsed
assert xml_file.isfile()
assert ElementTree.parse(file_path)
# stop current session, clearing data
session.shutdown()
# verify nodes have been removed from session
with pytest.raises(CoreError):
assert not session.get_node(node1.id, CoreNode)
with pytest.raises(CoreError):
assert not session.get_node(node2.id, CoreNode)
with pytest.raises(CoreError):
assert not session.get_node(emane_node.id, EmaneNet)
# load saved xml
session.open_xml(file_path, start=True)
# verify nodes have been recreated
assert session.get_node(node1.id, CoreNode)
assert session.get_node(node2.id, CoreNode)
assert session.get_node(emane_node.id, EmaneNet)
links = []
for node_id in session.nodes:
node = session.nodes[node_id]
links += node.links()
assert len(links) == 2
config = session.emane.get_model_config(config_id, EmaneRfPipeModel.name)
assert config["datarate"] == datarate

View file

@ -1,16 +1,15 @@
# Using the gRPC API
# gRPC API
[gRPC](https://grpc.io/) is the main API for interfacing with CORE and used by
the python GUI for driving all functionality.
* Table of Contents
{:toc}
Currently we are providing a python client that wraps the generated files for
leveraging the API, but proto files noted below can also be leveraged to generate
bindings for other languages as well.
[gRPC](https://grpc.io/) is a client/server API for interfacing with CORE
and used by the python GUI for driving all functionality. It is dependent
on having a running `core-daemon` instance to be leveraged.
## HTTP Proxy
Since gRPC is HTTP2 based, proxy configurations can cause issue. You can either
properly account for this issue or clear out your proxy when running if needed.
A python client can be created from the raw generated grpc files included
with CORE or one can leverage a provided gRPC client that helps encapsulate
some of the functionality to try and help make things easier.
## Python Client
@ -18,6 +17,12 @@ A python client wrapper is provided at
[CoreGrpcClient](https://github.com/coreemu/core/blob/master/daemon/core/api/grpc/client.py)
to help provide some conveniences when using the API.
### Client HTTP Proxy
Since gRPC is HTTP2 based, proxy configurations can cause issues. By default
the client disables proxy support to avoid issues when a proxy is present.
You can enable and properly account for this issue when needed.
## Proto Files
Proto files are used to define the API and protobuf messages that are used for
@ -30,7 +35,400 @@ what is going on and response message values that would be returned.
## Examples
Example usage of this API can be found
### Node Models
When creating nodes of type `NodeType.DEFAULT` these are the default models
and the services they map to.
* mdr
* zebra, OSPFv3MDR, IPForward
* PC
* DefaultRoute
* router
* zebra, OSPFv2, OSPFv3, IPForward
* host
* DefaultRoute, SSH
### Interface Helper
There is an interface helper class that can be leveraged for convenience
when creating interface data for nodes. Alternatively one can manually create
a `core.api.grpc.core_pb2.Interface` class instead with appropriate information.
Manually creating gRPC interface data:
```python
from core.api.grpc import core_pb2
# id is optional and will set to the next available id
# name is optional and will default to eth<id>
# mac is optional and will result in a randomly generated mac
iface_data = core_pb2.Interface(
id=0,
name="eth0",
ip4="10.0.0.1",
ip4_mask=24,
ip6="2001::",
ip6_mask=64,
)
```
Leveraging the interface helper class:
```python
from core.api.grpc import client
iface_helper = client.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64")
# node_id is used to get an ip4/ip6 address indexed from within the above prefixes
# iface_id is required and used exactly for that
# name is optional and would default to eth<id>
# mac is optional and will result in a randomly generated mac
iface_data = iface_helper.create_iface(
node_id=1, iface_id=0, name="eth0", mac="00:00:00:00:aa:00"
)
```
### Listening to Events
Various events that can occur within a session can be listened to.
Event types:
* session - events for changes in session state and mobility start/stop/pause
* node - events for node movements and icon changes
* link - events for link configuration changes and wireless link add/delete
* config - configuration events when legacy gui joins a session
* exception - alert/error events
* file - file events when the legacy gui joins a session
```python
from core.api.grpc import core_pb2
def event_listener(event):
print(event)
# provide no events to listen to all events
core.events(session_id, event_listener)
# provide events to listen to specific events
core.events(session_id, event_listener, [core_pb2.EventType.NODE])
```
### Configuring Links
Links can be configured at the time of creation or during runtime.
Currently supported configuration options:
* bandwidth (bps)
* delay (us)
* duplicate (%)
* jitter (us)
* loss (%)
```python
from core.api.grpc import core_pb2
# configuring when creating a link
options = core_pb2.LinkOptions(
bandwidth=54_000_000,
delay=5000,
dup=5,
loss=5.5,
jitter=0,
)
core.add_link(session_id, n1_id, n2_id, iface1_data, iface2_data, options)
# configuring during runtime
core.edit_link(session_id, n1_id, n2_id, iface1_id, iface2_id, options)
```
### Peer to Peer Example
```python
# required imports
from core.api.grpc import client
from core.api.grpc.core_pb2 import Node, NodeType, Position, SessionState
# interface helper
iface_helper = client.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64")
# create grpc client and connect
core = client.CoreGrpcClient()
core.connect()
# create session and get id
response = core.create_session()
session_id = response.session_id
# change session state to configuration so that nodes get started when added
core.set_session_state(session_id, SessionState.CONFIGURATION)
# create node one
position = Position(x=100, y=100)
n1 = Node(type=NodeType.DEFAULT, position=position, model="PC")
response = core.add_node(session_id, n1)
n1_id = response.node_id
# create node two
position = Position(x=300, y=100)
n2 = Node(type=NodeType.DEFAULT, position=position, model="PC")
response = core.add_node(session_id, n2)
n2_id = response.node_id
# links nodes together
iface1 = iface_helper.create_iface(n1_id, 0)
iface2 = iface_helper.create_iface(n2_id, 0)
core.add_link(session_id, n1_id, n2_id, iface1, iface2)
# change session state
core.set_session_state(session_id, SessionState.INSTANTIATION)
```
### Switch/Hub Example
```python
# required imports
from core.api.grpc import client
from core.api.grpc.core_pb2 import Node, NodeType, Position, SessionState
# interface helper
iface_helper = client.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64")
# create grpc client and connect
core = client.CoreGrpcClient()
core.connect()
# create session and get id
response = core.create_session()
session_id = response.session_id
# change session state to configuration so that nodes get started when added
core.set_session_state(session_id, SessionState.CONFIGURATION)
# create switch node
position = Position(x=200, y=200)
switch = Node(type=NodeType.SWITCH, position=position)
response = core.add_node(session_id, switch)
switch_id = response.node_id
# create node one
position = Position(x=100, y=100)
n1 = Node(type=NodeType.DEFAULT, position=position, model="PC")
response = core.add_node(session_id, n1)
n1_id = response.node_id
# create node two
position = Position(x=300, y=100)
n2 = Node(type=NodeType.DEFAULT, position=position, model="PC")
response = core.add_node(session_id, n2)
n2_id = response.node_id
# links nodes to switch
iface1 = iface_helper.create_iface(n1_id, 0)
core.add_link(session_id, n1_id, switch_id, iface1)
iface1 = iface_helper.create_iface(n2_id, 0)
core.add_link(session_id, n2_id, switch_id, iface1)
# change session state
core.set_session_state(session_id, SessionState.INSTANTIATION)
```
### WLAN Example
```python
# required imports
from core.api.grpc import client
from core.api.grpc.core_pb2 import Node, NodeType, Position, SessionState
# interface helper
iface_helper = client.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64")
# create grpc client and connect
core = client.CoreGrpcClient()
core.connect()
# create session and get id
response = core.create_session()
session_id = response.session_id
# change session state to configuration so that nodes get started when added
core.set_session_state(session_id, SessionState.CONFIGURATION)
# create wlan node
position = Position(x=200, y=200)
wlan = Node(type=NodeType.WIRELESS_LAN, position=position)
response = core.add_node(session_id, wlan)
wlan_id = response.node_id
# create node one
position = Position(x=100, y=100)
n1 = Node(type=NodeType.DEFAULT, position=position, model="mdr")
response = core.add_node(session_id, n1)
n1_id = response.node_id
# create node two
position = Position(x=300, y=100)
n2 = Node(type=NodeType.DEFAULT, position=position, model="mdr")
response = core.add_node(session_id, n2)
n2_id = response.node_id
# configure wlan using a dict mapping currently
# support values as strings
core.set_wlan_config(session_id, wlan_id, {
"range": "280",
"bandwidth": "55000000",
"delay": "6000",
"jitter": "5",
"error": "5",
})
# links nodes to wlan
iface1 = iface_helper.create_iface(n1_id, 0)
core.add_link(session_id, n1_id, wlan_id, iface1)
iface1 = iface_helper.create_iface(n2_id, 0)
core.add_link(session_id, n2_id, wlan_id, iface1)
# change session state
core.set_session_state(session_id, SessionState.INSTANTIATION)
```
### EMANE Example
For EMANE you can import and use one of the existing models and
use its name for configuration.
Current models:
* core.emane.ieee80211abg.EmaneIeee80211abgModel
* core.emane.rfpipe.EmaneRfPipeModel
* core.emane.tdma.EmaneTdmaModel
* core.emane.bypass.EmaneBypassModel
Their configurations options are driven dynamically from parsed EMANE manifest files
from the installed version of EMANE.
Options and their purpose can be found at the [EMANE Wiki](https://github.com/adjacentlink/emane/wiki).
If configuring EMANE global settings or model mac/phy specific settings, any value not provided
will use the defaults. When no configuration is used, the defaults are used.
```python
# required imports
from core.api.grpc import client
from core.api.grpc.core_pb2 import Node, NodeType, Position, SessionState
from core.emane.ieee80211abg import EmaneIeee80211abgModel
# interface helper
iface_helper = client.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64")
# create grpc client and connect
core = client.CoreGrpcClient()
core.connect()
# create session and get id
response = core.create_session()
session_id = response.session_id
# change session state to configuration so that nodes get started when added
core.set_session_state(session_id, SessionState.CONFIGURATION)
# create emane node
position = Position(x=200, y=200)
emane = Node(type=NodeType.EMANE, position=position, emane=EmaneIeee80211abgModel.name)
response = core.add_node(session_id, emane)
emane_id = response.node_id
# create node one
position = Position(x=100, y=100)
n1 = Node(type=NodeType.DEFAULT, position=position, model="mdr")
response = core.add_node(session_id, n1)
n1_id = response.node_id
# create node two
position = Position(x=300, y=100)
n2 = Node(type=NodeType.DEFAULT, position=position, model="mdr")
response = core.add_node(session_id, n2)
n2_id = response.node_id
# configure general emane settings
core.set_emane_config(session_id, {
"eventservicettl": "2"
})
# configure emane model settings
# using a dict mapping currently support values as strings
core.set_emane_model_config(session_id, emane_id, EmaneIeee80211abgModel.name, {
"unicastrate": "3",
})
# links nodes to emane
iface1 = iface_helper.create_iface(n1_id, 0)
core.add_link(session_id, n1_id, emane_id, iface1)
iface1 = iface_helper.create_iface(n2_id, 0)
core.add_link(session_id, n2_id, emane_id, iface1)
# change session state
core.set_session_state(session_id, SessionState.INSTANTIATION)
```
EMANE Model Configuration:
```python
# emane network specific config
core.set_emane_model_config(session_id, emane_id, EmaneIeee80211abgModel.name, {
"unicastrate": "3",
})
# node specific config
core.set_emane_model_config(session_id, node_id, EmaneIeee80211abgModel.name, {
"unicastrate": "3",
})
# node interface specific config
core.set_emane_model_config(session_id, node_id, EmaneIeee80211abgModel.name, {
"unicastrate": "3",
}, iface_id)
```
## Configuring a Service
Services help generate and run bash scripts on nodes for a given purpose.
Configuring the files of a service results in a specific hard coded script being
generated, instead of the default scripts, that may leverage dynamic generation.
The following features can be configured for a service:
* files - files that will be generated
* directories - directories that will be mounted unique to the node
* startup - commands to run start a service
* validate - commands to run to validate a service
* shutdown - commands to run to stop a service
Editing service properties:
```python
# configure a service, for a node, for a given session
core.set_node_service(
session_id,
node_id,
service_name,
files=["file1.sh", "file2.sh"],
directories=["/etc/node"],
startup=["bash file1.sh"],
validate=[],
shutdown=[],
)
```
When editing a service file, it must be the name of `config`
file that the service will generate.
Editing a service file:
```python
# to edit the contents of a generated file you can specify
# the service, the file name, and its contents
core.set_node_service_file(
session_id,
node_id,
service_name,
file_name,
"echo hello",
)
```
## File Examples
File versions of the network examples can be found
[here](https://github.com/coreemu/core/tree/master/daemon/examples/grpc).
These examples will create a session using the gRPC API when the core-daemon is running.

View file

@ -24,9 +24,9 @@ networking scenarios, security studies, and increasing the size of physical test
|[Installation](install.md)|How to install CORE and its requirements|
|[GUI](gui.md)|How to use the GUI|
|[(BETA) Python GUI](pygui.md)|How to use the BETA python based GUI|
|[Python API](python.md)|Covers how to control core directly using python|
|[gRPC API](grpc.md)|Covers how control core using gRPC|
|[Distributed](distributed.md)|Details for running CORE across multiple servers|
|[Python Scripting](scripting.md)|How to write python scripts for creating a CORE session|
|[gRPC API](grpc.md)|How to enable and use the gRPC API|
|[Node Types](nodetypes.md)|Overview of node types supported within CORE|
|[CTRLNET](ctrlnet.md)|How to use control networks to communicate with nodes from host|
|[Services](services.md)|Overview of provided services and creating custom ones|

View file

@ -74,11 +74,16 @@ sudo apt remove core
## Automated Installation
The automated install will install the various tools needed to help automate
the CORE installation (python3, pip, pipx, invoke, poetry). The script will
also automatically clone, build, and install the latest version of OSPF MDR.
Finally it will install CORE scripts and a systemd service, which have
been modified to use the installed poetry created virtual environment.
> **NOTE:** installing globally can have issues with dependency conflicts etc
The automated install will install do the following:
* install base tools needed for installation
* python3, pip, pipx, invoke, poetry
* installs system dependencies for building core
* installs latest version of [OPSF MDR](https://github.com/USNavalResearchLaboratory/ospf-mdr)
* installs core into poetry managed virtual environment or locally, if flag is passed
* installs scripts pointing to python interpreter being used
* installs systemd service, disabled by default
After installation has completed you should be able to run the various
CORE scripts for running core.
@ -92,10 +97,11 @@ git clone https://github.com/coreemu/core.git
cd core
# run install script
# script usage: install.sh [-d] [-v]
# script usage: install.sh [-v] [-d] [-l] [-p <prefix>]
#
# -v enable verbose install
# -d enable developer install
# -l enable local install, not compatible with developer install
# -p install prefix, defaults to /usr/local
./install.sh
```
@ -117,16 +123,17 @@ After the installation complete it will have installed the following scripts.
| Name | Description |
|---|---|
| core-cleanup | tool to help removed lingering core created containers, bridges, directories |
| core-cli | tool to query, open xml files, and send commands using gRPC |
| core-daemon | runs the backed core server providing TLV and gRPC APIs |
| core-gui | runs the legacy tcl/tk based GUI |
| core-pygui | runs the new python/tk based GUI |
| core-cleanup | tool to help removed lingering core created containers, bridges, directories |
| core-imn-to-xml | tool to help automate converting a .imn file to .xml format |
| core-manage | tool to add, remove, or check for services, models, and node types |
| core-pygui | runs the new python/tk based GUI |
| core-python | provides a convenience for running the core python virtual environment |
| core-route-monitor | tool to help monitor traffic across nodes and feed that to SDT |
| core-service-update | tool to update automate modifying a legacy service to match current naming |
| coresendmsg | tool to send TLV API commands from command line |
| core-cli | tool to query, open xml files, and send commands using gRPC |
| core-manage | tool to add, remove, or check for services, models, and node types |
## Running User Scripts
@ -142,28 +149,57 @@ environment interpreter or to run a script within it.
core-python <script>
```
## Manually Install EMANE
If CORE was installed locally, then you can run scripts using the default python3
interpreter.
EMANE can be installed from deb or RPM packages or from source. See the
[EMANE GitHub](https://github.com/adjacentlink/emane) for full details.
```shell
python3 <script>
```
There is an invoke task to help with installing EMANE, but has issues,
which attempts to build EMANE from source, but has issue on systems with
older protobuf-compilers.
## Installing EMANE
> **NOTE:** installng emane for the virtual environment is known to work for 1.21+
> **NOTE:** automated install currently targets 1.25
There is an invoke task to help with installing EMANE, which attempts to
build EMANE from source, but has issue on systems with older protobuf-compilers.
```shell
cd <CORE_REPO>
# install to virtual environment
inv install-emane
# install locally to system python3
inv install-emane -l
```
Alternatively, you can
Alternatively EMANE can be installed from deb or RPM packages or from source. See the
[EMANE GitHub](https://github.com/adjacentlink/emane) for full details.
With the caveat that the python bindings need to be installed into CORE's
virtualenv, unless installed locally.
### Installing EMANE Python Bindings for Virtual Environment
If you need to just install the EMANE python bindings to the CORE virtual
environment, since you are installing EMANE itself from pre-built packages.
You can run the following
Leveraging the following wiki:
[build EMANE](https://github.com/adjacentlink/emane/wiki/Build)
from source and install the python
bindings into the core virtual environment.
The following would install the EMANE python bindings after being
successfully built.
```shell
# clone and build emane python bindings
git clone https://github.com/adjacentlink/emane.git
cd emane
./autogen.sh
PYTHON=python3 ./configure --prefix=/usr
cd src/python
make
# install to core virtual environment
cd <CORE_REPO>/daemon
poetry run pip install <EMANE_REPO>/src/python
```

426
docs/python.md Normal file
View file

@ -0,0 +1,426 @@
# Python API
* Table of Contents
{:toc}
## Overview
Writing your own Python scripts offers a rich programming environment with
complete control over all aspects of the emulation.
The scripts need to be ran with root privileges because they create new network
namespaces. In general, a CORE Python script does not connect to the CORE
daemon, in fact the *core-daemon* is just another Python script that uses
the CORE Python modules and exchanges messages with the GUI.
## Examples
### Node Models
When creating nodes of type `core.nodes.base.CoreNode` these are the default models
and the services they map to.
* mdr
* zebra, OSPFv3MDR, IPForward
* PC
* DefaultRoute
* router
* zebra, OSPFv2, OSPFv3, IPForward
* host
* DefaultRoute, SSH
### Interface Helper
There is an interface helper class that can be leveraged for convenience
when creating interface data for nodes. Alternatively one can manually create
a `core.emulator.data.InterfaceData` class instead with appropriate information.
Manually creating interface data:
```python
from core.emulator.data import InterfaceData
# id is optional and will set to the next available id
# name is optional and will default to eth<id>
# mac is optional and will result in a randomly generated mac
iface_data = InterfaceData(
id=0,
name="eth0",
ip4="10.0.0.1",
ip4_mask=24,
ip6="2001::",
ip6_mask=64,
)
```
Leveraging the interface prefixes helper class:
```python
from core.emulator.data import IpPrefixes
ip_prefixes = IpPrefixes(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64")
# node is used to get an ip4/ip6 address indexed from within the above prefixes
# name is optional and would default to eth<id>
# mac is optional and will result in a randomly generated mac
iface_data = ip_prefixes.create_iface(
node=node, name="eth0", mac="00:00:00:00:aa:00"
)
```
### Listening to Events
Various events that can occur within a session can be listened to.
Event types:
* session - events for changes in session state and mobility start/stop/pause
* node - events for node movements and icon changes
* link - events for link configuration changes and wireless link add/delete
* config - configuration events when legacy gui joins a session
* exception - alert/error events
* file - file events when the legacy gui joins a session
```python
def event_listener(event):
print(event)
# add an event listener to event type you want to listen to
# each handler will receive an object unique to that type
session.event_handlers.append(event_listener)
session.exception_handlers.append(event_listener)
session.node_handlers.append(event_listener)
session.link_handlers.append(event_listener)
session.file_handlers.append(event_listener)
session.config_handlers.append(event_listener)
```
### Configuring Links
Links can be configured at the time of creation or during runtime.
Currently supported configuration options:
* bandwidth (bps)
* delay (us)
* dup (%)
* jitter (us)
* loss (%)
```python
from core.emulator.data import LinkOptions
# configuring when creating a link
options = LinkOptions(
bandwidth=54_000_000,
delay=5000,
dup=5,
loss=5.5,
jitter=0,
)
session.add_link(n1_id, n2_id, iface1_data, iface2_data, options)
# configuring during runtime
session.update_link(n1_id, n2_id, iface1_id, iface2_id, options)
```
### Peer to Peer Example
```python
# required imports
from core.emulator.coreemu import CoreEmu
from core.emulator.data import IpPrefixes, NodeOptions
from core.emulator.enumerations import EventTypes
from core.nodes.base import CoreNode
# ip nerator for example
ip_prefixes = IpPrefixes(ip4_prefix="10.0.0.0/24")
# create emulator instance for creating sessions and utility methods
coreemu = CoreEmu()
session = coreemu.create_session()
# must be in configuration state for nodes to start, when using "node_add" below
session.set_state(EventTypes.CONFIGURATION_STATE)
# create nodes
options = NodeOptions(x=100, y=100)
n1 = session.add_node(CoreNode, options=options)
options = NodeOptions(x=300, y=100)
n2 = session.add_node(CoreNode, options=options)
# link nodes together
iface1 = ip_prefixes.create_iface(n1)
iface2 = ip_prefixes.create_iface(n2)
session.add_link(n1.id, n2.id, iface1, iface2)
# start session
session.instantiate()
# do whatever you like here
input("press enter to shutdown")
# stop session
session.shutdown()
```
### Switch/Hub Example
```python
# required imports
from core.emulator.coreemu import CoreEmu
from core.emulator.data import IpPrefixes, NodeOptions
from core.emulator.enumerations import EventTypes
from core.nodes.base import CoreNode
from core.nodes.network import SwitchNode
# ip nerator for example
ip_prefixes = IpPrefixes(ip4_prefix="10.0.0.0/24")
# create emulator instance for creating sessions and utility methods
coreemu = CoreEmu()
session = coreemu.create_session()
# must be in configuration state for nodes to start, when using "node_add" below
session.set_state(EventTypes.CONFIGURATION_STATE)
# create switch
options = NodeOptions(x=200, y=200)
switch = session.add_node(SwitchNode, options=options)
# create nodes
options = NodeOptions(x=100, y=100)
n1 = session.add_node(CoreNode, options=options)
options = NodeOptions(x=300, y=100)
n2 = session.add_node(CoreNode, options=options)
# link nodes to switch
iface1 = ip_prefixes.create_iface(n1)
session.add_link(n1.id, switch.id, iface1)
iface1 = ip_prefixes.create_iface(n2)
session.add_link(n2.id, switch.id, iface1)
# start session
session.instantiate()
# do whatever you like here
input("press enter to shutdown")
# stop session
session.shutdown()
```
### WLAN Example
```python
# required imports
from core.emulator.coreemu import CoreEmu
from core.emulator.data import IpPrefixes, NodeOptions
from core.emulator.enumerations import EventTypes
from core.location.mobility import BasicRangeModel
from core.nodes.base import CoreNode
from core.nodes.network import WlanNode
# ip nerator for example
ip_prefixes = IpPrefixes(ip4_prefix="10.0.0.0/24")
# create emulator instance for creating sessions and utility methods
coreemu = CoreEmu()
session = coreemu.create_session()
# must be in configuration state for nodes to start, when using "node_add" below
session.set_state(EventTypes.CONFIGURATION_STATE)
# create wlan
options = NodeOptions(x=200, y=200)
wlan = session.add_node(WlanNode, options=options)
# create nodes
options = NodeOptions(model="mdr", x=100, y=100)
n1 = session.add_node(CoreNode, options=options)
options = NodeOptions(model="mdr", x=300, y=100)
n2 = session.add_node(CoreNode, options=options)
# configuring wlan
session.mobility.set_model_config(wlan.id, BasicRangeModel.name, {
"range": "280",
"bandwidth": "55000000",
"delay": "6000",
"jitter": "5",
"error": "5",
})
# link nodes to wlan
iface1 = ip_prefixes.create_iface(n1)
session.add_link(n1.id, wlan.id, iface1)
iface1 = ip_prefixes.create_iface(n2)
session.add_link(n2.id, wlan.id, iface1)
# start session
session.instantiate()
# do whatever you like here
input("press enter to shutdown")
# stop session
session.shutdown()
```
### EMANE Example
For EMANE you can import and use one of the existing models and
use its name for configuration.
Current models:
* core.emane.ieee80211abg.EmaneIeee80211abgModel
* core.emane.rfpipe.EmaneRfPipeModel
* core.emane.tdma.EmaneTdmaModel
* core.emane.bypass.EmaneBypassModel
Their configurations options are driven dynamically from parsed EMANE manifest files
from the installed version of EMANE.
Options and their purpose can be found at the [EMANE Wiki](https://github.com/adjacentlink/emane/wiki).
If configuring EMANE global settings or model mac/phy specific settings, any value not provided
will use the defaults. When no configuration is used, the defaults are used.
```python
# required imports
from core.emane.ieee80211abg import EmaneIeee80211abgModel
from core.emane.nodes import EmaneNet
from core.emulator.coreemu import CoreEmu
from core.emulator.data import IpPrefixes, NodeOptions
from core.emulator.enumerations import EventTypes
from core.nodes.base import CoreNode
# ip nerator for example
ip_prefixes = IpPrefixes(ip4_prefix="10.0.0.0/24")
# create emulator instance for creating sessions and utility methods
coreemu = CoreEmu()
session = coreemu.create_session()
# location information is required to be set for emane
session.location.setrefgeo(47.57917, -122.13232, 2.0)
session.location.refscale = 150.0
# must be in configuration state for nodes to start, when using "node_add" below
session.set_state(EventTypes.CONFIGURATION_STATE)
# create emane
options = NodeOptions(x=200, y=200, emane=EmaneIeee80211abgModel.name)
emane = session.add_node(EmaneNet, options=options)
# create nodes
options = NodeOptions(model="mdr", x=100, y=100)
n1 = session.add_node(CoreNode, options=options)
options = NodeOptions(model="mdr", x=300, y=100)
n2 = session.add_node(CoreNode, options=options)
# configure general emane settings
config = session.emane.get_configs()
config.update({
"eventservicettl": "2"
})
# configure emane model settings
# using a dict mapping currently support values as strings
session.emane.set_model_config(emane.id, EmaneIeee80211abgModel.name, {
"unicastrate": "3",
})
# link nodes to emane
iface1 = ip_prefixes.create_iface(n1)
session.add_link(n1.id, emane.id, iface1)
iface1 = ip_prefixes.create_iface(n2)
session.add_link(n2.id, emane.id, iface1)
# start session
session.instantiate()
# do whatever you like here
input("press enter to shutdown")
# stop session
session.shutdown()
```
EMANE Model Configuration:
```python
from core import utils
# emane network specific config
session.emane.set_model_config(emane.id, EmaneIeee80211abgModel.name, {
"unicastrate": "3",
})
# node specific config
session.emane.set_model_config(node.id, EmaneIeee80211abgModel.name, {
"unicastrate": "3",
})
# node interface specific config
config_id = utils.iface_config_id(node.id, iface_id)
session.emane.set_model_config(config_id, EmaneIeee80211abgModel.name, {
"unicastrate": "3",
})
```
## Configuring a Service
Services help generate and run bash scripts on nodes for a given purpose.
Configuring the files of a service results in a specific hard coded script being
generated, instead of the default scripts, that may leverage dynamic generation.
The following features can be configured for a service:
* configs - files that will be generated
* dirs - directories that will be mounted unique to the node
* startup - commands to run start a service
* validate - commands to run to validate a service
* shutdown - commands to run to stop a service
Editing service properties:
```python
# configure a service, for a node, for a given session
session.services.set_service(node_id, service_name)
service = session.services.get_service(node_id, service_name)
service.configs = ("file1.sh", "file2.sh")
service.dirs = ("/etc/node",)
service.startup = ("bash file1.sh",)
service.validate = ()
service.shutdown = ()
```
When editing a service file, it must be the name of `config`
file that the service will generate.
Editing a service file:
```python
# to edit the contents of a generated file you can specify
# the service, the file name, and its contents
session.services.set_service_file(
node_id,
service_name,
file_name,
"echo hello",
)
```
## File Examples
File versions of the network examples can be found
[here](https://github.com/coreemu/core/tree/master/daemon/examples/python).
## Executing Scripts from GUI
To execute a python script from a GUI you need have the following.
The builtin name check here to know it is being executed from the GUI, this can
be avoided if your script does not use a name check.
```python
if __name__ in ["__main__", "__builtin__"]:
main()
```
A script can add sessions to the core-daemon. A global *coreemu* variable is
exposed to the script pointing to the *CoreEmu* object.
The example below has a fallback to a new CoreEmu object, in the case you would
like to run the script standalone, outside of the core-daemon.
```python
coreemu = globals().get("coreemu", CoreEmu())
session = coreemu.create_session()
```

View file

@ -1,161 +0,0 @@
# CORE Python Scripting
* Table of Contents
{:toc}
## Overview
Writing your own Python scripts offers a rich programming environment with
complete control over all aspects of the emulation. This chapter provides a
brief introduction to scripting. Most of the documentation is available from
sample scripts, or online via interactive Python.
The best starting point is the sample scripts that are included with CORE.
If you have a CORE source tree, the example script files can be found under
*core/daemon/examples/python/*. When CORE is installed from packages, the example
script files will be in */usr/share/core/examples/python/* (or */usr/local/*
prefix when installed from source.) For the most part, the example scripts are
self-documenting; see the comments contained within the Python code.
The scripts should be run with root privileges because they create new network
namespaces. In general, a CORE Python script does not connect to the CORE
daemon, in fact the *core-daemon* is just another Python script that uses
the CORE Python modules and exchanges messages with the GUI. To connect the
GUI to your scripts, see the included sample scripts that allow for GUI
connections.
Here are the basic elements of a CORE Python script:
```python
"""
This is a standalone script to run a small switch based scenario and will not
interact with the GUI.
"""
import logging
from core.emulator.coreemu import CoreEmu
from core.emulator.data import IpPrefixes
from core.emulator.enumerations import EventTypes
from core.nodes.base import CoreNode
from core.nodes.network import SwitchNode
NODES = 2
def main():
# ip generator for example
prefixes = IpPrefixes(ip4_prefix="10.83.0.0/16")
# create emulator instance for creating sessions and utility methods
coreemu = CoreEmu()
session = coreemu.create_session()
# must be in configuration state for nodes to start, when using "node_add" below
session.set_state(EventTypes.CONFIGURATION_STATE)
# create switch network node
switch = session.add_node(SwitchNode, _id=100)
# create nodes
for _ in range(NODES):
node = session.add_node(CoreNode)
iface_data = prefixes.create_iface(node)
session.add_link(node.id, switch.id, iface1_data=iface_data)
# instantiate session
session.instantiate()
# run any desired logic here
# shutdown session
coreemu.shutdown()
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
main()
```
The above script creates a CORE session having two nodes connected with a
switch, Then immediately shutsdown.
The CORE Python modules are documented with comments in the code. From an
interactive Python shell, you can retrieve online help about the various
classes and methods; for example *help(CoreNode)* or *help(Session)*.
> **NOTE:** The CORE daemon *core-daemon* manages a list of sessions and allows
the GUI to connect and control sessions. Your Python script uses the same CORE
modules but runs independently of the daemon. The daemon does not need to be
running for your script to work.
The session created by a Python script may be viewed in the GUI if certain
steps are followed. The GUI has a *File Menu*, *Execute Python script...*
option for running a script and automatically connecting to it. Once connected,
normal GUI interaction is possible, such as moving and double-clicking nodes,
activating Widgets, etc.
The script should have a line such as the following for running it from the GUI.
```python
if __name__ in ["__main__", "__builtin__"]:
main()
```
A script can add sessions to the core-daemon. A global *coreemu* variable is
exposed to the script pointing to the *CoreEmu* object.
The example below has a fallback to a new CoreEmu object, in the case you would
like to run the script standalone, outside of the core-daemon.
```python
coreemu = globals().get("coreemu", CoreEmu())
session = coreemu.create_session()
```
Finally, nodes and networks need to have their coordinates set to something,
otherwise they will be grouped at the coordinates *<0, 0>*. First sketching
the topology in the GUI and then using the *Export Python script* option may
help here.
```python
switch.setposition(x=80,y=50)
```
A fully-worked example script that you can launch from the GUI is available
in the examples directory.
## Configuring Services
Examples setting or configuring custom services for a node.
```python
# create session and node
coreemu = CoreEmu()
session = coreemu.create_session()
# create node with custom services
options = NodeOptions(services=["ServiceName"])
node = session.add_node(CoreNode, options=options)
# set custom file data
session.services.set_service_file(node.id, "ServiceName", "FileName", "custom file data")
```
# Configuring EMANE Models
Examples for configuring custom emane model settings.
```python
# create session and emane network
coreemu = CoreEmu()
session = coreemu.create_session()
session.set_location(47.57917, -122.13232, 2.00000, 1.0)
options = NodeOptions()
options.set_position(80, 50)
emane_network = session.add_node(EmaneNet, options=options)
# set custom emane model config defaults
session.emane.set_model(emane_network, EmaneIeee80211abgModel)
```

View file

@ -14,7 +14,8 @@ fi
dev=""
verbose=""
prefix=""
while getopts "dvp:" opt; do
local=""
while getopts "dvlp:" opt; do
case ${opt} in
d)
dev="-d"
@ -22,14 +23,18 @@ while getopts "dvp:" opt; do
v)
verbose="-v"
;;
l)
local="-l"
;;
p)
prefix="-p ${OPTARG}"
;;
\?)
echo "script usage: $(basename $0) [-d] [-v]" >&2
echo "script usage: $(basename $0) [-v] [-d] [-l] [-p <prefix>]" >&2
echo "" >&2
echo "-v enable verbose install" >&2
echo "-d enable developer install" >&2
echo "-l enable local install, not compatible with developer install" >&2
echo "-p install prefix, defaults to /usr/local" >&2
exit 1
;;
@ -54,4 +59,4 @@ python3 -m pip install --user pipx
python3 -m pipx ensurepath
export PATH=$PATH:~/.local/bin
pipx install invoke
inv install ${dev} ${verbose} ${prefix}
inv install ${dev} ${verbose} ${local} ${prefix}

135
tasks.py
View file

@ -14,6 +14,8 @@ from invoke import task, Context
DAEMON_DIR: str = "daemon"
DEFAULT_PREFIX: str = "/usr/local"
EMANE_CHECKOUT: str = "v1.2.5"
OSPFMDR_CHECKOUT: str = "26fe5a4401a26760c553fcadfde5311199e89450"
class Progress:
@ -169,13 +171,18 @@ def install_core(c: Context, hide: bool) -> None:
c.run("sudo make install", hide=hide)
def install_poetry(c: Context, dev: bool, hide: bool) -> None:
def install_poetry(c: Context, dev: bool, local: bool, hide: bool) -> None:
c.run("pipx install poetry", hide=hide)
args = "" if dev else "--no-dev"
with c.cd(DAEMON_DIR):
c.run(f"poetry install {args}", hide=hide)
if dev:
c.run("poetry run pre-commit install", hide=hide)
if local:
with c.cd(DAEMON_DIR):
c.run("poetry build -f wheel", hide=hide)
c.run("sudo python3 -m pip install dist/*")
else:
args = "" if dev else "--no-dev"
with c.cd(DAEMON_DIR):
c.run(f"poetry install {args}", hide=hide)
if dev:
c.run("poetry run pre-commit install", hide=hide)
def install_ospf_mdr(c: Context, os_info: OsInfo, hide: bool) -> None:
@ -186,12 +193,11 @@ def install_ospf_mdr(c: Context, os_info: OsInfo, hide: bool) -> None:
c.run("sudo apt install -y libtool gawk libreadline-dev git", hide=hide)
elif os_info.like == OsLike.REDHAT:
c.run("sudo yum install -y libtool gawk readline-devel git", hide=hide)
clone_dir = "/tmp/ospf-mdr"
c.run(
f"git clone https://github.com/USNavalResearchLaboratory/ospf-mdr {clone_dir}",
hide=hide
)
with c.cd(clone_dir):
ospf_dir = "../ospf-mdr"
ospf_url = "https://github.com/USNavalResearchLaboratory/ospf-mdr.git"
c.run(f"git clone {ospf_url} {ospf_dir}", hide=hide)
with c.cd(ospf_dir):
c.run(f"git checkout {OSPFMDR_CHECKOUT}", hide=hide)
c.run("./bootstrap.sh", hide=hide)
c.run(
"./configure --disable-doc --enable-user=root --enable-group=root "
@ -242,10 +248,11 @@ def install_service(c, verbose=False, prefix=DEFAULT_PREFIX):
@task(
help={
"verbose": "enable verbose",
"prefix": f"prefix where scripts are installed, default is {DEFAULT_PREFIX}"
"prefix": f"prefix where scripts are installed, default is {DEFAULT_PREFIX}",
"local": "determines if core will install to local system, default is False",
},
)
def install_scripts(c, verbose=False, prefix=DEFAULT_PREFIX):
def install_scripts(c, local=False, verbose=False, prefix=DEFAULT_PREFIX):
"""
install core script files, modified to leverage virtual environment
"""
@ -258,7 +265,7 @@ def install_scripts(c, verbose=False, prefix=DEFAULT_PREFIX):
lines = f.readlines()
first = lines[0].strip()
# modify python scripts to point to virtual environment
if first == "#!/usr/bin/env python3":
if not local and first == "#!/usr/bin/env python3":
lines[0] = f"#!{python}\n"
temp = NamedTemporaryFile("w", delete=False)
for line in lines:
@ -272,16 +279,17 @@ def install_scripts(c, verbose=False, prefix=DEFAULT_PREFIX):
c.run(f"sudo cp {script} {dest}", hide=hide)
# setup core python helper
core_python = bin_dir.joinpath("core-python")
temp = NamedTemporaryFile("w", delete=False)
temp.writelines([
"#!/bin/bash\n",
f'exec "{python}" "$@"\n',
])
temp.close()
c.run(f"sudo cp {temp.name} {core_python}", hide=hide)
c.run(f"sudo chmod 755 {core_python}", hide=hide)
os.unlink(temp.name)
if not local:
core_python = bin_dir.joinpath("core-python")
temp = NamedTemporaryFile("w", delete=False)
temp.writelines([
"#!/bin/bash\n",
f'exec "{python}" "$@"\n',
])
temp.close()
c.run(f"sudo cp {temp.name} {core_python}", hide=hide)
c.run(f"sudo chmod 755 {core_python}", hide=hide)
os.unlink(temp.name)
# install core configuration file
config_dir = "/etc/core"
@ -294,13 +302,15 @@ def install_scripts(c, verbose=False, prefix=DEFAULT_PREFIX):
help={
"dev": "install development mode",
"verbose": "enable verbose",
"prefix": f"prefix where scripts are installed, default is {DEFAULT_PREFIX}"
"local": "determines if core will install to local system, default is False",
"prefix": f"prefix where scripts are installed, default is {DEFAULT_PREFIX}",
},
)
def install(c, dev=False, verbose=False, prefix=DEFAULT_PREFIX):
def install(c, dev=False, verbose=False, local=False, prefix=DEFAULT_PREFIX):
"""
install core, poetry, scripts, service, and ospf mdr
"""
print(f"installing core locally: {local}")
print(f"installing core with prefix: {prefix}")
c.run("sudo -v", hide=True)
p = Progress(verbose)
@ -316,10 +326,11 @@ def install(c, dev=False, verbose=False, prefix=DEFAULT_PREFIX):
build_core(c, hide, prefix)
with p.start("installing vcmd/gui"):
install_core(c, hide)
with p.start("installing poetry virtual environment"):
install_poetry(c, dev, hide)
install_type = "core" if local else "core virtual environment"
with p.start(f"installing {install_type}"):
install_poetry(c, dev, local, hide)
with p.start("installing scripts and /etc/core"):
install_scripts(c, hide, prefix)
install_scripts(c, local, hide, prefix)
with p.start("installing systemd service"):
install_service(c, hide, prefix)
with p.start("installing ospf mdr"):
@ -330,9 +341,10 @@ def install(c, dev=False, verbose=False, prefix=DEFAULT_PREFIX):
@task(
help={
"verbose": "enable verbose",
"local": "used determine if core is installed locally, default is False",
},
)
def install_emane(c, verbose=False):
def install_emane(c, verbose=False, local=False):
"""
install emane and the python bindings
"""
@ -340,7 +352,6 @@ def install_emane(c, verbose=False):
p = Progress(verbose)
hide = not verbose
os_info = get_os()
emane_dir = "/tmp/emane"
with p.start("installing system dependencies"):
if os_info.like == OsLike.DEBIAN:
c.run(
@ -358,13 +369,14 @@ def install_emane(c, verbose=False):
"protobuf-devel python3-setuptools",
hide=hide,
)
emane_dir = "../emane"
emane_python_dir = Path(emane_dir).joinpath("src/python")
emane_url = "https://github.com/adjacentlink/emane.git"
with p.start("cloning emane"):
c.run(
f"git clone https://github.com/adjacentlink/emane.git {emane_dir}",
hide=hide
)
c.run(f"git clone {emane_url} {emane_dir}", hide=hide)
with p.start("building emane"):
with c.cd(emane_dir):
c.run(f"git checkout {EMANE_CHECKOUT}", hide=hide)
c.run("./autogen.sh", hide=hide)
c.run("PYTHON=python3 ./configure --prefix=/usr", hide=hide)
c.run("make -j$(nproc)", hide=hide)
@ -372,18 +384,25 @@ def install_emane(c, verbose=False):
with c.cd(emane_dir):
c.run("sudo make install", hide=hide)
with p.start("installing python binding for core"):
with c.cd(DAEMON_DIR):
c.run(f"poetry run pip install {emane_dir}/src/python", hide=hide)
if local:
with c.cd(str(emane_python_dir)):
c.run("sudo python3 -m pip install .", hide=hide)
else:
with c.cd(DAEMON_DIR):
c.run(
f"poetry run pip install {emane_python_dir.absolute()}", hide=hide
)
@task(
help={
"dev": "uninstall development mode",
"verbose": "enable verbose",
"local": "determines if core was installed local to system, default is False",
"prefix": f"prefix where scripts are installed, default is {DEFAULT_PREFIX}"
},
)
def uninstall(c, dev=False, verbose=False, prefix=DEFAULT_PREFIX):
def uninstall(c, dev=False, verbose=False, local=False, prefix=DEFAULT_PREFIX):
"""
uninstall core, scripts, service, virtual environment, and clean build directory
"""
@ -398,14 +417,18 @@ def uninstall(c, dev=False, verbose=False, prefix=DEFAULT_PREFIX):
c.run("make clean", hide=hide)
c.run("./bootstrap.sh clean", hide=hide)
python = get_python(c, warn=True)
if python:
with c.cd(DAEMON_DIR):
if dev:
with p.start("uninstalling pre-commit"):
c.run("poetry run pre-commit uninstall", hide=hide)
with p.start("uninstalling poetry virtual environment"):
c.run(f"poetry env remove {python}", hide=hide)
if local:
with p.start("uninstalling core"):
c.run("sudo python3 -m pip uninstall -y core", hide=hide)
else:
python = get_python(c, warn=True)
if python:
with c.cd(DAEMON_DIR):
if dev:
with p.start("uninstalling pre-commit"):
c.run("poetry run pre-commit uninstall", hide=hide)
with p.start("uninstalling poetry virtual environment"):
c.run(f"poetry env remove {python}", hide=hide)
# remove installed files
bin_dir = Path(prefix).joinpath("bin")
@ -415,10 +438,11 @@ def uninstall(c, dev=False, verbose=False, prefix=DEFAULT_PREFIX):
c.run(f"sudo rm -f {dest}", hide=hide)
# remove core-python symlink
core_python = bin_dir.joinpath("core-python")
c.run(f"sudo rm -f {core_python}", hide=hide)
if not local:
core_python = bin_dir.joinpath("core-python")
c.run(f"sudo rm -f {core_python}", hide=hide)
# install service
# remove service
systemd_dir = Path("/lib/systemd/system/")
service_name = "core-daemon.service"
service_file = systemd_dir.joinpath(service_name)
@ -432,15 +456,18 @@ def uninstall(c, dev=False, verbose=False, prefix=DEFAULT_PREFIX):
help={
"dev": "reinstall development mode",
"verbose": "enable verbose",
"local": "determines if core will install to local system, default is False",
"prefix": f"prefix where scripts are installed, default is {DEFAULT_PREFIX}",
"branch": "branch to install latest code from, default is current branch"
},
)
def reinstall(c, dev=False, verbose=False, prefix=DEFAULT_PREFIX, branch=None):
def reinstall(
c, dev=False, verbose=False, local=False, prefix=DEFAULT_PREFIX, branch=None
):
"""
run the uninstall task, get latest from specified branch, and run install task
"""
uninstall(c, dev, verbose, prefix)
uninstall(c, dev, verbose, local, prefix)
hide = not verbose
p = Progress(verbose)
with p.start("pulling latest code"):
@ -452,7 +479,7 @@ def reinstall(c, dev=False, verbose=False, prefix=DEFAULT_PREFIX, branch=None):
c.run("git pull", hide=hide)
if not Path("tasks.py").exists():
raise FileNotFoundError(f"missing tasks.py on branch: {branch}")
install(c, dev, verbose, prefix)
install(c, dev, verbose, local, prefix)
@task
@ -495,4 +522,4 @@ def test_emane(c):
"""
pytest = get_pytest(c)
with c.cd(DAEMON_DIR):
c.run(f"{pytest} -v --lf -x tests/emane", pty=True)
c.run(f"sudo {pytest} -v --lf -x tests/emane", pty=True)