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 ## 2020-08-21 CORE 7.1.0
* Installation * Installation

View file

@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script. # Process this file with autoconf to produce a configure script.
# this defines the CORE version number, must be static for AC_INIT # 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 # autoconf and automake initialization
AC_CONFIG_SRCDIR([netns/version.h.in]) 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 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: def convert_iface(iface_data: InterfaceData) -> core_pb2.Interface:
return core_pb2.Interface( return core_pb2.Interface(
id=iface_data.id, id=iface_data.id,
@ -559,7 +530,8 @@ def get_emane_model_configs(session: Session) -> List[GetEmaneModelConfig]:
model = session.emane.models[model_name] model = session.emane.models[model_name]
current_config = session.emane.get_model_config(_id, model_name) current_config = session.emane.get_model_config(_id, model_name)
config = get_config_options(current_config, model) 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( model_config = GetEmaneModelConfig(
node_id=node_id, model=model_name, iface_id=iface_id, config=config 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, SetEmaneModelConfigResponse,
) )
from core.api.grpc.events import EventStreamer from core.api.grpc.events import EventStreamer
from core.api.grpc.grpcutils import ( from core.api.grpc.grpcutils import get_config_options, get_links, get_net_stats
get_config_options,
get_emane_model_id,
get_links,
get_net_stats,
)
from core.api.grpc.mobility_pb2 import ( from core.api.grpc.mobility_pb2 import (
GetMobilityConfigRequest, GetMobilityConfigRequest,
GetMobilityConfigResponse, GetMobilityConfigResponse,
@ -117,7 +112,7 @@ from core.emulator.session import NT, Session
from core.errors import CoreCommandError, CoreError from core.errors import CoreCommandError, CoreError
from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility
from core.nodes.base import CoreNode, NodeBase 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 from core.services.coreservices import ServiceManager
_ONE_DAY_IN_SECONDS: int = 60 * 60 * 24 _ONE_DAY_IN_SECONDS: int = 60 * 60 * 24
@ -249,7 +244,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
config = session.emane.get_configs() config = session.emane.get_configs()
config.update(request.emane_config) config.update(request.emane_config)
for config in request.emane_model_configs: 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) session.emane.set_model_config(_id, config.model, config.config)
# wlan configs # wlan configs
@ -340,7 +335,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
session = self.coreemu.create_session(request.session_id) session = self.coreemu.create_session(request.session_id)
session.set_state(EventTypes.DEFINITION_STATE) session.set_state(EventTypes.DEFINITION_STATE)
session.location.setrefgeo(47.57917, -122.13232, 2.0) session.location.setrefgeo(47.57917, -122.13232, 2.0)
session.location.refscale = 150000.0 session.location.refscale = 150.0
return core_pb2.CreateSessionResponse( return core_pb2.CreateSessionResponse(
session_id=session.id, state=session.state.value session_id=session.id, state=session.state.value
) )
@ -561,7 +556,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
nodes = [] nodes = []
for _id in session.nodes: for _id in session.nodes:
node = session.nodes[_id] node = session.nodes[_id]
if not isinstance(node, PtpNet): if not isinstance(node, (PtpNet, CtrlNet)):
node_proto = grpcutils.get_node_proto(session, node) node_proto = grpcutils.get_node_proto(session, node)
nodes.append(node_proto) nodes.append(node_proto)
node_links = get_links(node) node_links = get_links(node)
@ -1438,8 +1433,10 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
""" """
logging.debug("get emane model config: %s", request) logging.debug("get emane model config: %s", request)
session = self.get_session(request.session_id, context) session = self.get_session(request.session_id, context)
model = session.emane.models[request.model] model = session.emane.models.get(request.model)
_id = get_emane_model_id(request.node_id, request.iface_id) 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) current_config = session.emane.get_model_config(_id, request.model)
config = get_config_options(current_config, model) config = get_config_options(current_config, model)
return GetEmaneModelConfigResponse(config=config) return GetEmaneModelConfigResponse(config=config)
@ -1458,7 +1455,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
logging.debug("set emane model config: %s", request) logging.debug("set emane model config: %s", request)
session = self.get_session(request.session_id, context) session = self.get_session(request.session_id, context)
model_config = request.emane_model_config 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) session.emane.set_model_config(_id, model_config.model, model_config.config)
return SetEmaneModelConfigResponse(result=True) return SetEmaneModelConfigResponse(result=True)
@ -1483,7 +1480,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
self, request: core_pb2.SaveXmlRequest, context: ServicerContext self, request: core_pb2.SaveXmlRequest, context: ServicerContext
) -> core_pb2.SaveXmlResponse: ) -> 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 request: save xml request
:param context: context object :param context: context object

View file

@ -1,9 +1,15 @@
from dataclasses import dataclass, field from dataclasses import dataclass, field
from enum import Enum from enum import Enum
from pathlib import Path 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): class ConfigServiceValidationMode(Enum):
@ -87,6 +93,22 @@ class MessageType(Enum):
TTY = 64 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 @dataclass
class ConfigService: class ConfigService:
group: str 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 @dataclass
class ConfigServiceData: class ConfigServiceData:
templates: Dict[str, str] = field(default_factory=dict) templates: Dict[str, str] = field(default_factory=dict)
config: 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 @dataclass
class NodeServiceData: class NodeServiceData:
executables: List[str] 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 @dataclass
class BridgeThroughput: class BridgeThroughput:
node_id: int 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 @dataclass
class SessionLocation: class SessionLocation:
x: float x: float
@ -471,6 +579,30 @@ class Hook:
return core_pb2.Hook(state=self.state.value, file=self.file, data=self.data) 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 @dataclass
class Position: class Position:
x: float x: float
@ -660,3 +792,187 @@ class NodeEvent:
message_type=MessageType(proto.message_type), message_type=MessageType(proto.message_type),
node=Node.from_proto(proto.node), 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 iface_id = config_data.iface_id
values_str = config_data.data_values values_str = config_data.data_values
if iface_id is not None: node_id = utils.iface_config_id(node_id, iface_id)
node_id = node_id * 1000 + iface_id
logging.debug( logging.debug(
"received configure message for %s nodenum: %s", object_name, node_id "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 iface_id = config_data.iface_id
values_str = config_data.data_values values_str = config_data.data_values
if iface_id is not None: node_id = utils.iface_config_id(node_id, iface_id)
node_id = node_id * 1000 + iface_id
logging.debug( logging.debug(
"received configure message for %s nodenum: %s", object_name, node_id "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 iface_id = config_data.iface_id
values_str = config_data.data_values values_str = config_data.data_values
if iface_id is not None: node_id = utils.iface_config_id(node_id, iface_id)
node_id = node_id * 1000 + iface_id
logging.debug( logging.debug(
"received configure message for %s nodenum: %s", object_name, node_id "received configure message for %s nodenum: %s", object_name, node_id
) )

View file

@ -212,7 +212,7 @@ class ConfigurableManager:
def get_configs( def get_configs(
self, node_id: int = _default_node, config_type: str = _default_type 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. 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) nem_name = emanexml.nem_file_name(iface)
shim_name = emanexml.shim_file_name(iface) shim_name = emanexml.shim_file_name(iface)
etree.SubElement(nem_element, "shim", definition=shim_name) 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 # create and write shim document
shim_element = etree.Element( shim_element = etree.Element(
@ -99,7 +99,7 @@ class EmaneCommEffectModel(emanemodel.EmaneModel):
ff = config["filterfile"] ff = config["filterfile"]
if ff.strip() != "": if ff.strip() != "":
emanexml.add_param(shim_element, "filterfile", ff) 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 # create transport xml
emanexml.create_transport_xml(iface, config) emanexml.create_transport_xml(iface, config)

View file

@ -6,6 +6,7 @@ import logging
import os import os
import threading import threading
from collections import OrderedDict from collections import OrderedDict
from dataclasses import dataclass, field
from enum import Enum from enum import Enum
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type
@ -28,7 +29,7 @@ from core.emulator.enumerations import (
RegisterTlvs, RegisterTlvs,
) )
from core.errors import CoreCommandError, CoreError 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.nodes.interface import CoreInterface, TunTap
from core.xml import emanexml from core.xml import emanexml
@ -68,6 +69,13 @@ class EmaneState(Enum):
NOT_READY = 2 NOT_READY = 2
@dataclass
class StartData:
emane_net: EmaneNet
node: CoreNodeBase
ifaces: List[CoreInterface] = field(default_factory=list)
class EmaneManager(ModelManager): class EmaneManager(ModelManager):
""" """
EMANE controller object. Lives in a Session instance and is used for 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 self, emane_net: EmaneNet, iface: CoreInterface
) -> Dict[str, str]: ) -> 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 emane_net: emane network the interface is connected to
:param iface: interface running emane :param iface: interface running emane
:return: net, node, or interface model configuration :return: net, node, or interface model configuration
""" """
model_name = emane_net.model.name model_name = emane_net.model.name
# don"t use default values when interface config is the same as net config = None
# note here that using iface.node.id as key allows for only one type # try to retrieve interface specific configuration
# 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
if iface.node_id is not None: if iface.node_id is not None:
key += iface.node_id key = utils.iface_config_id(iface.node.id, iface.node_id)
# try retrieve interface specific configuration, avoid getting defaults config = self.get_configs(node_id=key, config_type=model_name)
config = self.get_configs(node_id=key, config_type=model_name) # attempt to retrieve node specific config, when iface config is not present
# otherwise retrieve the interfaces node configuration, avoid using defaults
if not config: if not config:
config = self.get_configs(node_id=iface.node.id, config_type=model_name) 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: if not config:
# with EMANE 0.9.2+, we need an extra NEM XML from # with EMANE 0.9.2+, we need an extra NEM XML from
# model.buildnemxmlfiles(), so defaults are returned here # model.buildnemxmlfiles(), so defaults are returned here
config = self.get_configs(node_id=emane_net.id, config_type=model_name) 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 return config
def config_reset(self, node_id: int = None) -> None: def config_reset(self, node_id: int = None) -> None:
@ -342,36 +348,45 @@ class EmaneManager(ModelManager):
self.buildeventservicexml() self.buildeventservicexml()
with self._emane_node_lock: with self._emane_node_lock:
logging.info("emane building xmls...") logging.info("emane building xmls...")
for node_id in sorted(self._emane_nets): start_data = self.get_start_data()
emane_net = self._emane_nets[node_id] for data in start_data:
if not emane_net.model: self.start_node(data)
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)
if self.links_enabled(): if self.links_enabled():
self.link_monitor.start() self.link_monitor.start()
return EmaneState.SUCCESS return EmaneState.SUCCESS
def start_iface(self, emane_net: EmaneNet, iface: CoreInterface) -> None: def get_start_data(self) -> List[StartData]:
if not iface.node: node_map = {}
logging.error( for node_id in sorted(self._emane_nets):
"emane net(%s) connected interface(%s) missing node", emane_net = self._emane_nets[node_id]
emane_net.name, if not emane_net.model:
iface.name, logging.error("emane net(%s) has no model", emane_net.name)
) continue
return 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( control_net = self.session.add_remove_control_net(
0, remove=False, conf_required=False 0, remove=False, conf_required=False
) )
nem_id = self.next_nem_id() emanexml.build_platform_xml(self, control_net, data)
self.set_nem(nem_id, iface) self.start_daemon(data.node)
self.write_nem(iface, nem_id) for iface in data.ifaces:
emanexml.build_platform_xml(self, control_net, emane_net, iface, nem_id) self.install_iface(data.emane_net, iface)
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)
def set_nem(self, nem_id: int, iface: CoreInterface) -> None: def set_nem(self, nem_id: int, iface: CoreInterface) -> None:
if nem_id in self.nems_to_ifaces: if nem_id in self.nems_to_ifaces:
@ -432,8 +447,21 @@ class EmaneManager(ModelManager):
logging.info("stopping EMANE daemons") logging.info("stopping EMANE daemons")
if self.links_enabled(): if self.links_enabled():
self.link_monitor.stop() self.link_monitor.stop()
self.deinstall_ifaces() # shutdown interfaces and stop daemons
self.stopdaemons() 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() self.stopeventmonitor()
def check_node_models(self) -> None: 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. Start one EMANE daemon per node having a radio.
Add a control network even if the user has not configured one. 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}" emanecmd = f"emane -d -l {loglevel}"
if realtime: if realtime:
emanecmd += " -r" emanecmd += " -r"
node = iface.node if isinstance(node, CoreNode):
if iface.is_virtual():
otagroup, _otaport = self.get_config("otamanagergroup").split(":") otagroup, _otaport = self.get_config("otamanagergroup").split(":")
otadev = self.get_config("otamanagerdevice") otadev = self.get_config("otamanagerdevice")
otanetidx = self.session.get_control_net_index(otadev) otanetidx = self.session.get_control_net_index(otadev)
@ -566,35 +593,19 @@ class EmaneManager(ModelManager):
if eventservicenetidx >= 0 and eventgroup != otagroup: if eventservicenetidx >= 0 and eventgroup != otagroup:
node.node_net_client.create_route(eventgroup, eventdev) node.node_net_client.create_route(eventgroup, eventdev)
# start emane # start emane
log_file = os.path.join(node.nodedir, f"{iface.name}-emane.log") log_file = os.path.join(node.nodedir, f"{node.name}-emane.log")
platform_xml = os.path.join(node.nodedir, f"{iface.name}-platform.xml") platform_xml = os.path.join(node.nodedir, f"{node.name}-platform.xml")
args = f"{emanecmd} -f {log_file} {platform_xml}" args = f"{emanecmd} -f {log_file} {platform_xml}"
node.cmd(args) node.cmd(args)
logging.info("node(%s) emane daemon running: %s", node.name, args) logging.info("node(%s) emane daemon running: %s", node.name, args)
else: else:
path = self.session.session_dir path = self.session.session_dir
log_file = os.path.join(path, f"{iface.name}-emane.log") log_file = os.path.join(path, f"{node.name}-emane.log")
platform_xml = os.path.join(path, f"{iface.name}-platform.xml") platform_xml = os.path.join(path, f"{node.name}-platform.xml")
emanecmd += f" -f {log_file} {platform_xml}" emanecmd += f" -f {log_file} {platform_xml}"
node.host_cmd(emanecmd, cwd=path) node.host_cmd(emanecmd, cwd=path)
logging.info("node(%s) host emane daemon running: %s", node.name, emanecmd) 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: def install_iface(self, emane_net: EmaneNet, iface: CoreInterface) -> None:
config = self.get_iface_config(emane_net, iface) config = self.get_iface_config(emane_net, iface)
external = config.get("external", "0") external = config.get("external", "0")
@ -606,17 +617,6 @@ class EmaneManager(ModelManager):
iface.poshook = emane_net.setnemposition iface.poshook = emane_net.setnemposition
iface.setposition() 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: def doeventmonitor(self) -> bool:
""" """
Returns boolean whether or not EMANE events will be monitored. Returns boolean whether or not EMANE events will be monitored.
@ -780,6 +780,9 @@ class EmaneManager(ModelManager):
self.session.broadcast_node(node) self.session.broadcast_node(node)
return True return True
def is_emane_net(self, net: Optional[CoreNetworkBase]) -> bool:
return isinstance(net, EmaneNet)
def emanerunning(self, node: CoreNode) -> bool: def emanerunning(self, node: CoreNode) -> bool:
""" """
Return True if an EMANE process associated with the given node is running, 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 # ensure default emane configuration
if isinstance(node, EmaneNet) and options.emane: 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: if self.state == EventTypes.RUNTIME_STATE:
self.emane.add_node(node) self.emane.add_node(node)
# set default wlan config if needed # set default wlan config if needed
@ -571,17 +576,10 @@ class Session:
:return: nothing :return: nothing
:raises core.CoreError: when node to update does not exist :raises core.CoreError: when node to update does not exist
""" """
# get node to update
node = self.get_node(node_id, NodeBase) 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.canvas = options.canvas
node.icon = options.icon node.icon = options.icon
self.set_node_position(node, options)
# provide edits to sdt
self.sdt.edit_node(node, options.lon, options.lat, options.alt) self.sdt.edit_node(node, options.lon, options.lat, options.alt)
def set_node_position(self, node: NodeBase, options: NodeOptions) -> None: 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. 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.DATACOLLECT_STATE, send_event=True)
self.set_state(EventTypes.SHUTDOWN_STATE, send_event=True) self.set_state(EventTypes.SHUTDOWN_STATE, send_event=True)

View file

@ -30,3 +30,19 @@ class CoreXmlError(Exception):
""" """
pass 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" MOUNT: str = "mount"
UMOUNT: str = "umount" UMOUNT: str = "umount"
OVS_VSCTL: str = "ovs-vsctl" OVS_VSCTL: str = "ovs-vsctl"
TEST: str = "test"
COMMON_REQUIREMENTS: List[str] = [ COMMON_REQUIREMENTS: List[str] = [
BASH, BASH,
@ -21,6 +22,7 @@ COMMON_REQUIREMENTS: List[str] = [
SYSCTL, SYSCTL,
TC, TC,
UMOUNT, UMOUNT,
TEST,
] ]
VCMD_REQUIREMENTS: List[str] = [VNODED, VCMD] VCMD_REQUIREMENTS: List[str] = [VNODED, VCMD]
OVS_REQUIREMENTS: List[str] = [OVS_VSCTL] OVS_REQUIREMENTS: List[str] = [OVS_VSCTL]

View file

@ -21,19 +21,7 @@ from core.api.grpc import (
services_pb2, services_pb2,
wlan_pb2, wlan_pb2,
) )
from core.gui import appconfig from core.api.grpc.wrappers import (
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 (
ConfigOption, ConfigOption,
ConfigService, ConfigService,
ExceptionEvent, ExceptionEvent,
@ -52,6 +40,18 @@ from core.gui.wrappers import (
SessionState, SessionState,
ThroughputsEvent, 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: if TYPE_CHECKING:
from core.gui.app import Application from core.gui.app import Application
@ -937,8 +937,6 @@ class CoreClient:
def get_emane_model_configs_proto(self) -> List[emane_pb2.EmaneModelConfig]: def get_emane_model_configs_proto(self) -> List[emane_pb2.EmaneModelConfig]:
configs = [] configs = []
for node in self.session.nodes.values(): for node in self.session.nodes.values():
if node.type != NodeType.EMANE:
continue
for key, config in node.emane_model_configs.items(): for key, config in node.emane_model_configs.items():
model, iface_id = key model, iface_id = key
config = ConfigOption.to_dict(config) config = ConfigOption.to_dict(config)

View file

@ -5,10 +5,10 @@ import tkinter as tk
from tkinter import ttk from tkinter import ttk
from typing import TYPE_CHECKING, Dict, Optional 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.dialogs.dialog import Dialog
from core.gui.themes import PADX, PADY from core.gui.themes import PADX, PADY
from core.gui.widgets import CodeText from core.gui.widgets import CodeText
from core.gui.wrappers import ExceptionEvent, ExceptionLevel
if TYPE_CHECKING: if TYPE_CHECKING:
from core.gui.app import Application from core.gui.app import Application

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -3,13 +3,13 @@ import math
import tkinter as tk import tkinter as tk
from typing import TYPE_CHECKING, Optional, Tuple from typing import TYPE_CHECKING, Optional, Tuple
from core.api.grpc.wrappers import Interface, Link
from core.gui import themes from core.gui import themes
from core.gui.dialogs.linkconfig import LinkConfigurationDialog from core.gui.dialogs.linkconfig import LinkConfigurationDialog
from core.gui.frames.link import EdgeInfoFrame, WirelessEdgeInfoFrame from core.gui.frames.link import EdgeInfoFrame, WirelessEdgeInfoFrame
from core.gui.graph import tags from core.gui.graph import tags
from core.gui.nodeutils import NodeUtils from core.gui.nodeutils import NodeUtils
from core.gui.utils import bandwidth_text, delay_jitter_text from core.gui.utils import bandwidth_text, delay_jitter_text
from core.gui.wrappers import Interface, Link
if TYPE_CHECKING: if TYPE_CHECKING:
from core.gui.graph.graph import CanvasGraph 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 import Image
from PIL.ImageTk import PhotoImage 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.dialogs.shapemod import ShapeDialog
from core.gui.graph import tags from core.gui.graph import tags
from core.gui.graph.edges import ( 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.graph.shapeutils import ShapeType, is_draw_shape, is_marker
from core.gui.images import ImageEnum, TypeToImage from core.gui.images import ImageEnum, TypeToImage
from core.gui.nodeutils import NodeDraw, NodeUtils from core.gui.nodeutils import NodeDraw, NodeUtils
from core.gui.wrappers import Interface, Link, LinkType, Node, Session, ThroughputsEvent
if TYPE_CHECKING: if TYPE_CHECKING:
from core.gui.app import Application from core.gui.app import Application

View file

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

View file

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

View file

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

View file

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

View file

@ -5,9 +5,9 @@ import tkinter as tk
from tkinter import ttk from tkinter import ttk
from typing import TYPE_CHECKING, List, Optional 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.dialogs.alerts import AlertsDialog
from core.gui.themes import Styles from core.gui.themes import Styles
from core.gui.wrappers import ExceptionEvent, ExceptionLevel
if TYPE_CHECKING: if TYPE_CHECKING:
from core.gui.app import Application from core.gui.app import Application

View file

@ -5,10 +5,10 @@ from pathlib import Path
from tkinter import filedialog, font, ttk from tkinter import filedialog, font, ttk
from typing import TYPE_CHECKING, Any, Callable, Dict, Set, Type 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 import appconfig, themes, validation
from core.gui.dialogs.dialog import Dialog from core.gui.dialogs.dialog import Dialog
from core.gui.themes import FRAME_PAD, PADX, PADY from core.gui.themes import FRAME_PAD, PADX, PADY
from core.gui.wrappers import ConfigOption, ConfigOptionType
if TYPE_CHECKING: if TYPE_CHECKING:
from core.gui.app import Application 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.data import InterfaceData, LinkData, LinkOptions
from core.emulator.enumerations import LinkTypes, MessageFlags, NodeTypes from core.emulator.enumerations import LinkTypes, MessageFlags, NodeTypes
from core.errors import CoreCommandError, CoreError 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.client import VnodeClient
from core.nodes.interface import CoreInterface, TunTap, Veth from core.nodes.interface import CoreInterface, TunTap, Veth
from core.nodes.netclient import LinuxNetClient, get_net_client from core.nodes.netclient import LinuxNetClient, get_net_client
@ -294,6 +294,16 @@ class CoreNodeBase(NodeBase):
""" """
raise NotImplementedError 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: def add_config_service(self, service_class: "ConfigServiceType") -> None:
""" """
Adds a configuration service to the node. Adds a configuration service to the node.
@ -602,6 +612,19 @@ class CoreNode(CoreNodeBase):
args = self.client.create_cmd(args, shell) args = self.client.create_cmd(args, shell)
return self.server.remote_cmd(args, wait=wait) 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: def termcmdstring(self, sh: str = "/bin/sh") -> str:
""" """
Create a terminal command string. Create a terminal command string.

View file

@ -528,8 +528,9 @@ class TunTap(CoreInterface):
# check if this is an EMANE interface; if so, continue # check if this is an EMANE interface; if so, continue
# waiting if EMANE is still running # waiting if EMANE is still running
should_retry = count < 5 should_retry = count < 5
is_emane_running = self.node.session.emane.emanerunning(self.node) is_emane = self.session.emane.is_emane_net(self.net)
if all([should_retry, self.net.is_emane, is_emane_running]): is_emane_running = self.session.emane.emanerunning(self.node)
if all([should_retry, is_emane, is_emane_running]):
count += 1 count += 1
else: else:
raise RuntimeError("node device failed to exist") 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.distributed import DistributedServer
from core.emulator.enumerations import NodeTypes, TransportType from core.emulator.enumerations import NodeTypes, TransportType
from core.errors import CoreCommandError, CoreError 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.base import CoreNetworkBase, CoreNodeBase
from core.nodes.interface import CoreInterface from core.nodes.interface import CoreInterface
from core.nodes.network import CoreNetwork, GreTap from core.nodes.network import CoreNetwork, GreTap
@ -55,6 +55,19 @@ class PhysicalNode(CoreNodeBase):
self.rmnodedir() 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: def termcmdstring(self, sh: str = "/bin/sh") -> str:
""" """
Create a terminal command string. Create a terminal command string.
@ -291,6 +304,19 @@ class Rj45Node(CoreNodeBase):
self.up = False self.up = False
self.restorestate() 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( def new_iface(
self, net: CoreNetworkBase, iface_data: InterfaceData self, net: CoreNetworkBase, iface_data: InterfaceData
) -> CoreInterface: ) -> CoreInterface:

View file

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

View file

@ -41,6 +41,7 @@ if TYPE_CHECKING:
T = TypeVar("T") T = TypeVar("T")
DEVNULL = open(os.devnull, "wb") DEVNULL = open(os.devnull, "wb")
IFACE_CONFIG_FACTOR: int = 1000
def execute_file( def execute_file(
@ -430,3 +431,34 @@ def random_mac() -> str:
value |= 0x00163E << 24 value |= 0x00163E << 24
mac = netaddr.EUI(value, dialect=netaddr.mac_unix_expanded) mac = netaddr.EUI(value, dialect=netaddr.mac_unix_expanded)
return str(mac) 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.base
import core.nodes.physical import core.nodes.physical
from core import utils
from core.emane.nodes import EmaneNet from core.emane.nodes import EmaneNet
from core.emulator.data import InterfaceData, LinkData, LinkOptions, NodeOptions from core.emulator.data import InterfaceData, LinkData, LinkOptions, NodeOptions
from core.emulator.enumerations import EventTypes, NodeTypes from core.emulator.enumerations import EventTypes, NodeTypes
@ -91,10 +92,14 @@ def create_emane_config(session: "Session") -> etree.Element:
def create_emane_model_config( 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: ) -> etree.Element:
emane_element = etree.Element("emane_configuration") emane_element = etree.Element("emane_configuration")
add_attribute(emane_element, "node", node_id) add_attribute(emane_element, "node", node_id)
add_attribute(emane_element, "iface", iface_id)
add_attribute(emane_element, "model", model.name) add_attribute(emane_element, "model", model.name)
mac_element = etree.SubElement(emane_element, "mac") mac_element = etree.SubElement(emane_element, "mac")
@ -378,13 +383,16 @@ class CoreXmlWriter:
all_configs = self.session.emane.get_all_configs(node_id) all_configs = self.session.emane.get_all_configs(node_id)
if not all_configs: if not all_configs:
continue continue
node_id, iface_id = utils.parse_iface_config_id(node_id)
for model_name in all_configs: for model_name in all_configs:
config = all_configs[model_name] config = all_configs[model_name]
logging.debug( logging.debug(
"writing emane config node(%s) model(%s)", node_id, model_name "writing emane config node(%s) model(%s)", node_id, model_name
) )
model = self.session.emane.models[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) emane_configurations.append(emane_configuration)
if emane_configurations.getchildren(): if emane_configurations.getchildren():
self.scenario.append(emane_configurations) self.scenario.append(emane_configurations)
@ -588,9 +596,9 @@ class CoreXmlReader:
self.read_mobility_configs() self.read_mobility_configs()
self.read_emane_global_config() self.read_emane_global_config()
self.read_nodes() self.read_nodes()
self.read_links()
self.read_emane_configs() self.read_emane_configs()
self.read_configservice_configs() self.read_configservice_configs()
self.read_links()
def read_default_services(self) -> None: def read_default_services(self) -> None:
default_services = self.scenario.find("default_services") default_services = self.scenario.find("default_services")
@ -748,6 +756,7 @@ class CoreXmlReader:
for emane_configuration in emane_configurations.iterchildren(): for emane_configuration in emane_configurations.iterchildren():
node_id = get_int(emane_configuration, "node") node_id = get_int(emane_configuration, "node")
iface_id = get_int(emane_configuration, "iface")
model_name = emane_configuration.get("model") model_name = emane_configuration.get("model")
configs = {} configs = {}
@ -755,12 +764,13 @@ class CoreXmlReader:
node = self.session.nodes.get(node_id) node = self.session.nodes.get(node_id)
if not node: if not node:
raise CoreXmlError(f"node for emane config doesn't exist: {node_id}") 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) model = self.session.emane.models.get(model_name)
if not model: if not model:
raise CoreXmlError(f"invalid emane model: {model_name}") 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 # read and set emane model configuration
mac_configuration = emane_configuration.find("mac") mac_configuration = emane_configuration.find("mac")
@ -784,6 +794,7 @@ class CoreXmlReader:
logging.info( logging.info(
"reading emane configuration node(%s) model(%s)", node_id, model_name "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) self.session.emane.set_model_config(node_id, model_name, configs)
def read_mobility_configs(self) -> None: def read_mobility_configs(self) -> None:
@ -869,6 +880,9 @@ class CoreXmlReader:
icon = network_element.get("icon") icon = network_element.get("icon")
server = network_element.get("server") server = network_element.get("server")
options = NodeOptions(name=name, icon=icon, server=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") position_element = network_element.find("position")
if position_element is not None: if position_element is not None:

View file

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

View file

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

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 @@
""" # required imports
Example using gRPC API to create a simple switch network.
"""
import logging
from core.api.grpc import client from core.api.grpc import client
from core.api.grpc.core_pb2 import Node, NodeType, Position, SessionState 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): # create grpc client and connect
logging.info("event: %s", event) core = client.CoreGrpcClient()
core.connect()
# create session and get id
response = core.create_session()
session_id = response.session_id
def main(): # change session state to configuration so that nodes get started when added
# helper to create interface addresses core.set_session_state(session_id, SessionState.CONFIGURATION)
interface_helper = client.InterfaceHelper(ip4_prefix="10.83.0.0/24")
# create grpc client and start connection context, which auto closes connection # create switch node
core = client.CoreGrpcClient() position = Position(x=200, y=200)
with core.context_connect(): switch = Node(type=NodeType.SWITCH, position=position)
# create session response = core.add_node(session_id, switch)
response = core.create_session() switch_id = response.node_id
logging.info("created session: %s", response)
# handle events session may broadcast # create node one
session_id = response.session_id position = Position(x=100, y=100)
core.events(session_id, log_event) 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 # create node two
response = core.set_session_state(session_id, SessionState.CONFIGURATION) position = Position(x=300, y=100)
logging.info("set session state: %s", response) n2 = Node(type=NodeType.DEFAULT, position=position, model="PC")
response = core.add_node(session_id, n2)
n2_id = response.node_id
# create switch node # links nodes to switch
position = Position(x=200, y=200) iface1 = iface_helper.create_iface(n1_id, 0)
switch = Node(type=NodeType.SWITCH, position=position) core.add_link(session_id, n1_id, switch_id, iface1)
response = core.add_node(session_id, switch) iface1 = iface_helper.create_iface(n2_id, 0)
logging.info("created switch: %s", response) core.add_link(session_id, n2_id, switch_id, iface1)
switch_id = response.node_id
# create node one # change session state
position = Position(x=100, y=100) core.set_session_state(session_id, SessionState.INSTANTIATION)
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()

View file

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

View file

@ -1,12 +1,4 @@
""" # required imports
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
from core.emane.ieee80211abg import EmaneIeee80211abgModel from core.emane.ieee80211abg import EmaneIeee80211abgModel
from core.emane.nodes import EmaneNet from core.emane.nodes import EmaneNet
from core.emulator.coreemu import CoreEmu 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.emulator.enumerations import EventTypes
from core.nodes.base import CoreNode from core.nodes.base import CoreNode
NODES = 2 # ip nerator for example
EMANE_DELAY = 10 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(): # location information is required to be set for emane
# ip generator for example session.location.setrefgeo(47.57917, -122.13232, 2.0)
prefixes = IpPrefixes(ip4_prefix="10.83.0.0/16") session.location.refscale = 150.0
# create emulator instance for creating sessions and utility methods # must be in configuration state for nodes to start, when using "node_add" below
coreemu = CoreEmu() session.set_state(EventTypes.CONFIGURATION_STATE)
session = coreemu.create_session()
# must be in configuration state for nodes to start, when using "node_add" below # create emane
session.set_state(EventTypes.CONFIGURATION_STATE) 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 # create nodes
# location, so the session and nodes must be configured to provide one options = NodeOptions(model="mdr", x=100, y=100)
session.set_location(47.57917, -122.13232, 2.00000, 1.0) n1 = session.add_node(CoreNode, options=options)
options = NodeOptions() options = NodeOptions(model="mdr", x=300, y=100)
options.set_position(80, 50) n2 = session.add_node(CoreNode, options=options)
emane_network = session.add_node(EmaneNet, options=options, _id=100)
session.emane.set_model(emane_network, EmaneIeee80211abgModel)
# create nodes # configure general emane settings
options = NodeOptions(model="mdr") config = session.emane.get_configs()
for i in range(NODES): config.update({"eventservicettl": "2"})
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)
# instantiate session # configure emane model settings
session.instantiate() # 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 # link nodes to emane
logging.info("waiting %s seconds for OSPF MDR to create routes", EMANE_DELAY) iface1 = ip_prefixes.create_iface(n1)
time.sleep(EMANE_DELAY) 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 # start session
first_node = session.get_node(1, CoreNode) session.instantiate()
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 # do whatever you like here
coreemu.shutdown() input("press enter to shutdown")
# stop session
if __name__ == "__main__" or __name__ == "__builtin__": session.shutdown()
logging.basicConfig(level=logging.INFO)
main()

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 @@
""" # required imports
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.coreemu import CoreEmu
from core.emulator.data import IpPrefixes from core.emulator.data import IpPrefixes, NodeOptions
from core.emulator.enumerations import EventTypes from core.emulator.enumerations import EventTypes
from core.nodes.base import CoreNode from core.nodes.base import CoreNode
from core.nodes.network import SwitchNode 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(): # must be in configuration state for nodes to start, when using "node_add" below
# ip generator for example session.set_state(EventTypes.CONFIGURATION_STATE)
prefixes = IpPrefixes(ip4_prefix="10.83.0.0/16")
# create emulator instance for creating sessions and utility methods # create switch
coreemu = CoreEmu() options = NodeOptions(x=200, y=200)
session = coreemu.create_session() switch = session.add_node(SwitchNode, options=options)
# must be in configuration state for nodes to start, when using "node_add" below # create nodes
session.set_state(EventTypes.CONFIGURATION_STATE) 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 # link nodes to switch
switch = session.add_node(SwitchNode, _id=100) 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 # start session
for _ in range(NODES): session.instantiate()
node = session.add_node(CoreNode)
interface = prefixes.create_iface(node)
session.add_link(node.id, switch.id, iface1_data=interface)
# instantiate session # do whatever you like here
session.instantiate() input("press enter to shutdown")
# get nodes to run example # stop session
first_node = session.get_node(1, CoreNode) session.shutdown()
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()

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 @@
""" # required imports
This is a standalone script to run a small WLAN based scenario and will not
interact with the GUI.
"""
import logging
from core.emulator.coreemu import CoreEmu from core.emulator.coreemu import CoreEmu
from core.emulator.data import IpPrefixes, NodeOptions from core.emulator.data import IpPrefixes, NodeOptions
from core.emulator.enumerations import EventTypes 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.base import CoreNode
from core.nodes.network import WlanNode 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(): # must be in configuration state for nodes to start, when using "node_add" below
# ip generator for example session.set_state(EventTypes.CONFIGURATION_STATE)
prefixes = IpPrefixes("10.83.0.0/16")
# create emulator instance for creating sessions and utility methods # create wlan
coreemu = CoreEmu() options = NodeOptions(x=200, y=200)
session = coreemu.create_session() wlan = session.add_node(WlanNode, options=options)
# must be in configuration state for nodes to start, when using "node_add" below # create nodes
session.set_state(EventTypes.CONFIGURATION_STATE) 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 # configuring wlan
wlan = session.add_node(WlanNode, _id=100) session.mobility.set_model_config(
session.mobility.set_model(wlan, BasicRangeModel) 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 # link nodes to wlan
options = NodeOptions(model="mdr") iface1 = ip_prefixes.create_iface(n1)
options.set_position(0, 0) session.add_link(n1.id, wlan.id, iface1)
for _ in range(NODES): iface1 = ip_prefixes.create_iface(n2)
node = session.add_node(CoreNode, options=options) session.add_link(n2.id, wlan.id, iface1)
interface = prefixes.create_iface(node)
session.add_link(node.id, wlan.id, iface1_data=interface)
# instantiate session # start session
session.instantiate() session.instantiate()
# get nodes for example run # do whatever you like here
first_node = session.get_node(1, CoreNode) input("press enter to shutdown")
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 # stop session
coreemu.shutdown() session.shutdown()
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
main()

View file

@ -1,12 +1,19 @@
[tool.poetry] [tool.poetry]
name = "core" name = "core"
version = "7.1.0" version = "7.2.0"
description = "CORE Common Open Research Emulator" description = "CORE Common Open Research Emulator"
authors = ["Boeing Research and Technology"] authors = ["Boeing Research and Technology"]
license = "BSD-2-Clause" license = "BSD-2-Clause"
repository = "https://github.com/coreemu/core" repository = "https://github.com/coreemu/core"
documentation = "https://coreemu.github.io/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] [tool.poetry.dependencies]
python = "^3.6" python = "^3.6"

View file

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

View file

@ -8,6 +8,7 @@ from xml.etree import ElementTree
import pytest import pytest
from core import utils
from core.emane.bypass import EmaneBypassModel from core.emane.bypass import EmaneBypassModel
from core.emane.commeffect import EmaneCommEffectModel from core.emane.commeffect import EmaneCommEffectModel
from core.emane.emanemodel import EmaneModel from core.emane.emanemodel import EmaneModel
@ -43,6 +44,47 @@ def ping(
class TestEmane: 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) @pytest.mark.parametrize("model", _EMANE_MODELS)
def test_models( def test_models(
self, session: Session, model: Type[EmaneModel], ip_prefixes: IpPrefixes 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(node2_id, CoreNode)
assert session.get_node(emane_id, EmaneNet) assert session.get_node(emane_id, EmaneNet)
assert value == config_value 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 * Table of Contents
the python GUI for driving all functionality. {:toc}
Currently we are providing a python client that wraps the generated files for [gRPC](https://grpc.io/) is a client/server API for interfacing with CORE
leveraging the API, but proto files noted below can also be leveraged to generate and used by the python GUI for driving all functionality. It is dependent
bindings for other languages as well. on having a running `core-daemon` instance to be leveraged.
## HTTP Proxy 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
Since gRPC is HTTP2 based, proxy configurations can cause issue. You can either some of the functionality to try and help make things easier.
properly account for this issue or clear out your proxy when running if needed.
## Python Client ## 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) [CoreGrpcClient](https://github.com/coreemu/core/blob/master/daemon/core/api/grpc/client.py)
to help provide some conveniences when using the API. 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
Proto files are used to define the API and protobuf messages that are used for 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 ## 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). [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. 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| |[Installation](install.md)|How to install CORE and its requirements|
|[GUI](gui.md)|How to use the GUI| |[GUI](gui.md)|How to use the GUI|
|[(BETA) Python GUI](pygui.md)|How to use the BETA python based 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| |[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| |[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| |[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| |[Services](services.md)|Overview of provided services and creating custom ones|

View file

@ -74,11 +74,16 @@ sudo apt remove core
## Automated Installation ## Automated Installation
The automated install will install the various tools needed to help automate > **NOTE:** installing globally can have issues with dependency conflicts etc
the CORE installation (python3, pip, pipx, invoke, poetry). The script will
also automatically clone, build, and install the latest version of OSPF MDR. The automated install will install do the following:
Finally it will install CORE scripts and a systemd service, which have * install base tools needed for installation
been modified to use the installed poetry created virtual environment. * 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 After installation has completed you should be able to run the various
CORE scripts for running core. CORE scripts for running core.
@ -92,10 +97,11 @@ git clone https://github.com/coreemu/core.git
cd core cd core
# run install script # run install script
# script usage: install.sh [-d] [-v] # script usage: install.sh [-v] [-d] [-l] [-p <prefix>]
# #
# -v enable verbose install # -v enable verbose install
# -d enable developer install # -d enable developer install
# -l enable local install, not compatible with developer install
# -p install prefix, defaults to /usr/local # -p install prefix, defaults to /usr/local
./install.sh ./install.sh
``` ```
@ -117,16 +123,17 @@ After the installation complete it will have installed the following scripts.
| Name | Description | | 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-daemon | runs the backed core server providing TLV and gRPC APIs |
| core-gui | runs the legacy tcl/tk based GUI | | 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-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-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 | | 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 | | 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 ## Running User Scripts
@ -142,28 +149,57 @@ environment interpreter or to run a script within it.
core-python <script> 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 ```shell
[EMANE GitHub](https://github.com/adjacentlink/emane) for full details. python3 <script>
```
There is an invoke task to help with installing EMANE, but has issues, ## Installing EMANE
which attempts to build EMANE from source, but has issue on systems with
older protobuf-compilers. > **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 ```shell
cd <CORE_REPO> cd <CORE_REPO>
# install to virtual environment
inv install-emane 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) [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 The following would install the EMANE python bindings after being
successfully built. successfully built.
```shell ```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 cd <CORE_REPO>/daemon
poetry run pip install <EMANE_REPO>/src/python 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="" dev=""
verbose="" verbose=""
prefix="" prefix=""
while getopts "dvp:" opt; do local=""
while getopts "dvlp:" opt; do
case ${opt} in case ${opt} in
d) d)
dev="-d" dev="-d"
@ -22,14 +23,18 @@ while getopts "dvp:" opt; do
v) v)
verbose="-v" verbose="-v"
;; ;;
l)
local="-l"
;;
p) p)
prefix="-p ${OPTARG}" 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 "" >&2
echo "-v enable verbose install" >&2 echo "-v enable verbose install" >&2
echo "-d enable developer 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 echo "-p install prefix, defaults to /usr/local" >&2
exit 1 exit 1
;; ;;
@ -54,4 +59,4 @@ python3 -m pip install --user pipx
python3 -m pipx ensurepath python3 -m pipx ensurepath
export PATH=$PATH:~/.local/bin export PATH=$PATH:~/.local/bin
pipx install invoke 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" DAEMON_DIR: str = "daemon"
DEFAULT_PREFIX: str = "/usr/local" DEFAULT_PREFIX: str = "/usr/local"
EMANE_CHECKOUT: str = "v1.2.5"
OSPFMDR_CHECKOUT: str = "26fe5a4401a26760c553fcadfde5311199e89450"
class Progress: class Progress:
@ -169,13 +171,18 @@ def install_core(c: Context, hide: bool) -> None:
c.run("sudo make install", hide=hide) 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) c.run("pipx install poetry", hide=hide)
args = "" if dev else "--no-dev" if local:
with c.cd(DAEMON_DIR): with c.cd(DAEMON_DIR):
c.run(f"poetry install {args}", hide=hide) c.run("poetry build -f wheel", hide=hide)
if dev: c.run("sudo python3 -m pip install dist/*")
c.run("poetry run pre-commit install", hide=hide) 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: 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) c.run("sudo apt install -y libtool gawk libreadline-dev git", hide=hide)
elif os_info.like == OsLike.REDHAT: elif os_info.like == OsLike.REDHAT:
c.run("sudo yum install -y libtool gawk readline-devel git", hide=hide) c.run("sudo yum install -y libtool gawk readline-devel git", hide=hide)
clone_dir = "/tmp/ospf-mdr" ospf_dir = "../ospf-mdr"
c.run( ospf_url = "https://github.com/USNavalResearchLaboratory/ospf-mdr.git"
f"git clone https://github.com/USNavalResearchLaboratory/ospf-mdr {clone_dir}", c.run(f"git clone {ospf_url} {ospf_dir}", hide=hide)
hide=hide with c.cd(ospf_dir):
) c.run(f"git checkout {OSPFMDR_CHECKOUT}", hide=hide)
with c.cd(clone_dir):
c.run("./bootstrap.sh", hide=hide) c.run("./bootstrap.sh", hide=hide)
c.run( c.run(
"./configure --disable-doc --enable-user=root --enable-group=root " "./configure --disable-doc --enable-user=root --enable-group=root "
@ -242,10 +248,11 @@ def install_service(c, verbose=False, prefix=DEFAULT_PREFIX):
@task( @task(
help={ help={
"verbose": "enable verbose", "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 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() lines = f.readlines()
first = lines[0].strip() first = lines[0].strip()
# modify python scripts to point to virtual environment # 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" lines[0] = f"#!{python}\n"
temp = NamedTemporaryFile("w", delete=False) temp = NamedTemporaryFile("w", delete=False)
for line in lines: 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) c.run(f"sudo cp {script} {dest}", hide=hide)
# setup core python helper # setup core python helper
core_python = bin_dir.joinpath("core-python") if not local:
temp = NamedTemporaryFile("w", delete=False) core_python = bin_dir.joinpath("core-python")
temp.writelines([ temp = NamedTemporaryFile("w", delete=False)
"#!/bin/bash\n", temp.writelines([
f'exec "{python}" "$@"\n', "#!/bin/bash\n",
]) f'exec "{python}" "$@"\n',
temp.close() ])
c.run(f"sudo cp {temp.name} {core_python}", hide=hide) temp.close()
c.run(f"sudo chmod 755 {core_python}", hide=hide) c.run(f"sudo cp {temp.name} {core_python}", hide=hide)
os.unlink(temp.name) c.run(f"sudo chmod 755 {core_python}", hide=hide)
os.unlink(temp.name)
# install core configuration file # install core configuration file
config_dir = "/etc/core" config_dir = "/etc/core"
@ -294,13 +302,15 @@ def install_scripts(c, verbose=False, prefix=DEFAULT_PREFIX):
help={ help={
"dev": "install development mode", "dev": "install development mode",
"verbose": "enable verbose", "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 install core, poetry, scripts, service, and ospf mdr
""" """
print(f"installing core locally: {local}")
print(f"installing core with prefix: {prefix}") print(f"installing core with prefix: {prefix}")
c.run("sudo -v", hide=True) c.run("sudo -v", hide=True)
p = Progress(verbose) p = Progress(verbose)
@ -316,10 +326,11 @@ def install(c, dev=False, verbose=False, prefix=DEFAULT_PREFIX):
build_core(c, hide, prefix) build_core(c, hide, prefix)
with p.start("installing vcmd/gui"): with p.start("installing vcmd/gui"):
install_core(c, hide) install_core(c, hide)
with p.start("installing poetry virtual environment"): install_type = "core" if local else "core virtual environment"
install_poetry(c, dev, hide) with p.start(f"installing {install_type}"):
install_poetry(c, dev, local, hide)
with p.start("installing scripts and /etc/core"): 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"): with p.start("installing systemd service"):
install_service(c, hide, prefix) install_service(c, hide, prefix)
with p.start("installing ospf mdr"): with p.start("installing ospf mdr"):
@ -330,9 +341,10 @@ def install(c, dev=False, verbose=False, prefix=DEFAULT_PREFIX):
@task( @task(
help={ help={
"verbose": "enable verbose", "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 install emane and the python bindings
""" """
@ -340,7 +352,6 @@ def install_emane(c, verbose=False):
p = Progress(verbose) p = Progress(verbose)
hide = not verbose hide = not verbose
os_info = get_os() os_info = get_os()
emane_dir = "/tmp/emane"
with p.start("installing system dependencies"): with p.start("installing system dependencies"):
if os_info.like == OsLike.DEBIAN: if os_info.like == OsLike.DEBIAN:
c.run( c.run(
@ -358,13 +369,14 @@ def install_emane(c, verbose=False):
"protobuf-devel python3-setuptools", "protobuf-devel python3-setuptools",
hide=hide, 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"): with p.start("cloning emane"):
c.run( c.run(f"git clone {emane_url} {emane_dir}", hide=hide)
f"git clone https://github.com/adjacentlink/emane.git {emane_dir}",
hide=hide
)
with p.start("building emane"): with p.start("building emane"):
with c.cd(emane_dir): with c.cd(emane_dir):
c.run(f"git checkout {EMANE_CHECKOUT}", hide=hide)
c.run("./autogen.sh", hide=hide) c.run("./autogen.sh", hide=hide)
c.run("PYTHON=python3 ./configure --prefix=/usr", hide=hide) c.run("PYTHON=python3 ./configure --prefix=/usr", hide=hide)
c.run("make -j$(nproc)", hide=hide) c.run("make -j$(nproc)", hide=hide)
@ -372,18 +384,25 @@ def install_emane(c, verbose=False):
with c.cd(emane_dir): with c.cd(emane_dir):
c.run("sudo make install", hide=hide) c.run("sudo make install", hide=hide)
with p.start("installing python binding for core"): with p.start("installing python binding for core"):
with c.cd(DAEMON_DIR): if local:
c.run(f"poetry run pip install {emane_dir}/src/python", hide=hide) 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( @task(
help={ help={
"dev": "uninstall development mode", "dev": "uninstall development mode",
"verbose": "enable verbose", "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}" "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 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("make clean", hide=hide)
c.run("./bootstrap.sh clean", hide=hide) c.run("./bootstrap.sh clean", hide=hide)
python = get_python(c, warn=True) if local:
if python: with p.start("uninstalling core"):
with c.cd(DAEMON_DIR): c.run("sudo python3 -m pip uninstall -y core", hide=hide)
if dev: else:
with p.start("uninstalling pre-commit"): python = get_python(c, warn=True)
c.run("poetry run pre-commit uninstall", hide=hide) if python:
with p.start("uninstalling poetry virtual environment"): with c.cd(DAEMON_DIR):
c.run(f"poetry env remove {python}", hide=hide) 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 # remove installed files
bin_dir = Path(prefix).joinpath("bin") 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) c.run(f"sudo rm -f {dest}", hide=hide)
# remove core-python symlink # remove core-python symlink
core_python = bin_dir.joinpath("core-python") if not local:
c.run(f"sudo rm -f {core_python}", hide=hide) 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/") systemd_dir = Path("/lib/systemd/system/")
service_name = "core-daemon.service" service_name = "core-daemon.service"
service_file = systemd_dir.joinpath(service_name) service_file = systemd_dir.joinpath(service_name)
@ -432,15 +456,18 @@ def uninstall(c, dev=False, verbose=False, prefix=DEFAULT_PREFIX):
help={ help={
"dev": "reinstall development mode", "dev": "reinstall development mode",
"verbose": "enable verbose", "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}", "prefix": f"prefix where scripts are installed, default is {DEFAULT_PREFIX}",
"branch": "branch to install latest code from, default is current branch" "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 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 hide = not verbose
p = Progress(verbose) p = Progress(verbose)
with p.start("pulling latest code"): 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) c.run("git pull", hide=hide)
if not Path("tasks.py").exists(): if not Path("tasks.py").exists():
raise FileNotFoundError(f"missing tasks.py on branch: {branch}") raise FileNotFoundError(f"missing tasks.py on branch: {branch}")
install(c, dev, verbose, prefix) install(c, dev, verbose, local, prefix)
@task @task
@ -495,4 +522,4 @@ def test_emane(c):
""" """
pytest = get_pytest(c) pytest = get_pytest(c)
with c.cd(DAEMON_DIR): 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)