commit
16495c9008
64 changed files with 3645 additions and 882 deletions
21
CHANGELOG.md
21
CHANGELOG.md
|
@ -1,3 +1,24 @@
|
|||
## 2020-09-15 CORE 7.2.0
|
||||
|
||||
* Installation
|
||||
* locked down version of ospf-mdr installed in automated install
|
||||
* locked down version of emane to v1.2.5 in automated emane install
|
||||
* added option to install locally using the -l option
|
||||
* core-daemon
|
||||
* improve error when retrieving services that do not exist, or failed to load
|
||||
* fixed issue with writing/reading emane node interface configurations to xml
|
||||
* fixed issue with not setting the emane model when creating a node
|
||||
* added common utility method for getting a emane node interface config id in core.utils
|
||||
* fixed issue running emane on more than one interface for a node
|
||||
* fixed issue validating paths when creating emane transport xml for a node
|
||||
* fixed issue avoiding multiple calls to shutdown, if already in shutdown state
|
||||
* core-pygui
|
||||
* fixed issue configuring emane for a node interface
|
||||
* gRPC API
|
||||
* added wrapper client that can provide type hinting and a simpler interface at core.api.grpc.clientw
|
||||
* fixed issue creating sessions that default to having a very large reference scale
|
||||
* fixed issue with GetSession returning control net nodes
|
||||
|
||||
## 2020-08-21 CORE 7.1.0
|
||||
|
||||
* Installation
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
# Process this file with autoconf to produce a configure script.
|
||||
|
||||
# this defines the CORE version number, must be static for AC_INIT
|
||||
AC_INIT(core, 7.1.0)
|
||||
AC_INIT(core, 7.2.0)
|
||||
|
||||
# autoconf and automake initialization
|
||||
AC_CONFIG_SRCDIR([netns/version.h.in])
|
||||
|
|
1500
daemon/core/api/grpc/clientw.py
Normal file
1500
daemon/core/api/grpc/clientw.py
Normal file
File diff suppressed because it is too large
Load diff
|
@ -306,35 +306,6 @@ def get_links(node: NodeBase):
|
|||
return links
|
||||
|
||||
|
||||
def get_emane_model_id(node_id: int, iface_id: int) -> int:
|
||||
"""
|
||||
Get EMANE model id
|
||||
|
||||
:param node_id: node id
|
||||
:param iface_id: interface id
|
||||
:return: EMANE model id
|
||||
"""
|
||||
if iface_id >= 0:
|
||||
return node_id * 1000 + iface_id
|
||||
else:
|
||||
return node_id
|
||||
|
||||
|
||||
def parse_emane_model_id(_id: int) -> Tuple[int, int]:
|
||||
"""
|
||||
Parses EMANE model id to get true node id and interface id.
|
||||
|
||||
:param _id: id to parse
|
||||
:return: node id and interface id
|
||||
"""
|
||||
iface_id = -1
|
||||
node_id = _id
|
||||
if _id >= 1000:
|
||||
iface_id = _id % 1000
|
||||
node_id = int(_id / 1000)
|
||||
return node_id, iface_id
|
||||
|
||||
|
||||
def convert_iface(iface_data: InterfaceData) -> core_pb2.Interface:
|
||||
return core_pb2.Interface(
|
||||
id=iface_data.id,
|
||||
|
@ -559,7 +530,8 @@ def get_emane_model_configs(session: Session) -> List[GetEmaneModelConfig]:
|
|||
model = session.emane.models[model_name]
|
||||
current_config = session.emane.get_model_config(_id, model_name)
|
||||
config = get_config_options(current_config, model)
|
||||
node_id, iface_id = parse_emane_model_id(_id)
|
||||
node_id, iface_id = utils.parse_iface_config_id(_id)
|
||||
iface_id = iface_id if iface_id is not None else -1
|
||||
model_config = GetEmaneModelConfig(
|
||||
node_id=node_id, model=model_name, iface_id=iface_id, config=config
|
||||
)
|
||||
|
|
|
@ -56,12 +56,7 @@ from core.api.grpc.emane_pb2 import (
|
|||
SetEmaneModelConfigResponse,
|
||||
)
|
||||
from core.api.grpc.events import EventStreamer
|
||||
from core.api.grpc.grpcutils import (
|
||||
get_config_options,
|
||||
get_emane_model_id,
|
||||
get_links,
|
||||
get_net_stats,
|
||||
)
|
||||
from core.api.grpc.grpcutils import get_config_options, get_links, get_net_stats
|
||||
from core.api.grpc.mobility_pb2 import (
|
||||
GetMobilityConfigRequest,
|
||||
GetMobilityConfigResponse,
|
||||
|
@ -117,7 +112,7 @@ from core.emulator.session import NT, Session
|
|||
from core.errors import CoreCommandError, CoreError
|
||||
from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility
|
||||
from core.nodes.base import CoreNode, NodeBase
|
||||
from core.nodes.network import PtpNet, WlanNode
|
||||
from core.nodes.network import CtrlNet, PtpNet, WlanNode
|
||||
from core.services.coreservices import ServiceManager
|
||||
|
||||
_ONE_DAY_IN_SECONDS: int = 60 * 60 * 24
|
||||
|
@ -249,7 +244,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
|||
config = session.emane.get_configs()
|
||||
config.update(request.emane_config)
|
||||
for config in request.emane_model_configs:
|
||||
_id = get_emane_model_id(config.node_id, config.iface_id)
|
||||
_id = utils.iface_config_id(config.node_id, config.iface_id)
|
||||
session.emane.set_model_config(_id, config.model, config.config)
|
||||
|
||||
# wlan configs
|
||||
|
@ -340,7 +335,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
|||
session = self.coreemu.create_session(request.session_id)
|
||||
session.set_state(EventTypes.DEFINITION_STATE)
|
||||
session.location.setrefgeo(47.57917, -122.13232, 2.0)
|
||||
session.location.refscale = 150000.0
|
||||
session.location.refscale = 150.0
|
||||
return core_pb2.CreateSessionResponse(
|
||||
session_id=session.id, state=session.state.value
|
||||
)
|
||||
|
@ -561,7 +556,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
|||
nodes = []
|
||||
for _id in session.nodes:
|
||||
node = session.nodes[_id]
|
||||
if not isinstance(node, PtpNet):
|
||||
if not isinstance(node, (PtpNet, CtrlNet)):
|
||||
node_proto = grpcutils.get_node_proto(session, node)
|
||||
nodes.append(node_proto)
|
||||
node_links = get_links(node)
|
||||
|
@ -1438,8 +1433,10 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
|||
"""
|
||||
logging.debug("get emane model config: %s", request)
|
||||
session = self.get_session(request.session_id, context)
|
||||
model = session.emane.models[request.model]
|
||||
_id = get_emane_model_id(request.node_id, request.iface_id)
|
||||
model = session.emane.models.get(request.model)
|
||||
if not model:
|
||||
raise CoreError(f"invalid emane model: {request.model}")
|
||||
_id = utils.iface_config_id(request.node_id, request.iface_id)
|
||||
current_config = session.emane.get_model_config(_id, request.model)
|
||||
config = get_config_options(current_config, model)
|
||||
return GetEmaneModelConfigResponse(config=config)
|
||||
|
@ -1458,7 +1455,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
|||
logging.debug("set emane model config: %s", request)
|
||||
session = self.get_session(request.session_id, context)
|
||||
model_config = request.emane_model_config
|
||||
_id = get_emane_model_id(model_config.node_id, model_config.iface_id)
|
||||
_id = utils.iface_config_id(model_config.node_id, model_config.iface_id)
|
||||
session.emane.set_model_config(_id, model_config.model, model_config.config)
|
||||
return SetEmaneModelConfigResponse(result=True)
|
||||
|
||||
|
@ -1483,7 +1480,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
|||
self, request: core_pb2.SaveXmlRequest, context: ServicerContext
|
||||
) -> core_pb2.SaveXmlResponse:
|
||||
"""
|
||||
Export the session nto the EmulationScript XML format
|
||||
Export the session into the EmulationScript XML format
|
||||
|
||||
:param request: save xml request
|
||||
:param context: context object
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional, Set, Tuple
|
||||
from typing import Any, Dict, List, Optional, Set, Tuple
|
||||
|
||||
from core.api.grpc import common_pb2, configservices_pb2, core_pb2, services_pb2
|
||||
from core.api.grpc import (
|
||||
common_pb2,
|
||||
configservices_pb2,
|
||||
core_pb2,
|
||||
emane_pb2,
|
||||
services_pb2,
|
||||
)
|
||||
|
||||
|
||||
class ConfigServiceValidationMode(Enum):
|
||||
|
@ -87,6 +93,22 @@ class MessageType(Enum):
|
|||
TTY = 64
|
||||
|
||||
|
||||
class ServiceAction(Enum):
|
||||
START = 0
|
||||
STOP = 1
|
||||
RESTART = 2
|
||||
VALIDATE = 3
|
||||
|
||||
|
||||
class EventType:
|
||||
SESSION = 0
|
||||
NODE = 1
|
||||
LINK = 2
|
||||
CONFIG = 3
|
||||
EXCEPTION = 4
|
||||
FILE = 5
|
||||
|
||||
|
||||
@dataclass
|
||||
class ConfigService:
|
||||
group: str
|
||||
|
@ -120,12 +142,67 @@ class ConfigService:
|
|||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ConfigServiceConfig:
|
||||
node_id: int
|
||||
name: str
|
||||
templates: Dict[str, str]
|
||||
config: Dict[str, str]
|
||||
|
||||
@classmethod
|
||||
def from_proto(
|
||||
cls, proto: configservices_pb2.ConfigServiceConfig
|
||||
) -> "ConfigServiceConfig":
|
||||
return ConfigServiceConfig(
|
||||
node_id=proto.node_id,
|
||||
name=proto.name,
|
||||
templates=dict(proto.templates),
|
||||
config=dict(proto.config),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ConfigServiceData:
|
||||
templates: Dict[str, str] = field(default_factory=dict)
|
||||
config: Dict[str, str] = field(default_factory=dict)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ConfigServiceDefaults:
|
||||
templates: Dict[str, str]
|
||||
config: Dict[str, "ConfigOption"]
|
||||
modes: List[str]
|
||||
|
||||
@classmethod
|
||||
def from_proto(
|
||||
cls, proto: configservices_pb2.GetConfigServicesResponse
|
||||
) -> "ConfigServiceDefaults":
|
||||
config = ConfigOption.from_dict(proto.config)
|
||||
return ConfigServiceDefaults(
|
||||
templates=dict(proto.templates), config=config, modes=list(proto.modes)
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Service:
|
||||
group: str
|
||||
name: str
|
||||
|
||||
@classmethod
|
||||
def from_proto(cls, proto: services_pb2.Service) -> "Service":
|
||||
return Service(group=proto.group, name=proto.name)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ServiceDefault:
|
||||
node_type: str
|
||||
services: List[str]
|
||||
|
||||
@classmethod
|
||||
def from_proto(cls, proto: services_pb2.ServiceDefaults) -> "ServiceDefault":
|
||||
return ServiceDefault(node_type=proto.node_type, services=list(proto.services))
|
||||
|
||||
|
||||
@dataclass
|
||||
class NodeServiceData:
|
||||
executables: List[str]
|
||||
|
@ -155,6 +232,28 @@ class NodeServiceData:
|
|||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ServiceConfig:
|
||||
node_id: int
|
||||
service: str
|
||||
files: List[str] = None
|
||||
directories: List[str] = None
|
||||
startup: List[str] = None
|
||||
validate: List[str] = None
|
||||
shutdown: List[str] = None
|
||||
|
||||
def to_proto(self) -> services_pb2.ServiceConfig:
|
||||
return services_pb2.ServiceConfig(
|
||||
node_id=self.node_id,
|
||||
service=self.service,
|
||||
files=self.files,
|
||||
directories=self.directories,
|
||||
startup=self.startup,
|
||||
validate=self.validate,
|
||||
shutdown=self.shutdown,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class BridgeThroughput:
|
||||
node_id: int
|
||||
|
@ -195,6 +294,15 @@ class ThroughputsEvent:
|
|||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class CpuUsageEvent:
|
||||
usage: float
|
||||
|
||||
@classmethod
|
||||
def from_proto(cls, proto: core_pb2.CpuUsageEvent) -> "CpuUsageEvent":
|
||||
return CpuUsageEvent(usage=proto.usage)
|
||||
|
||||
|
||||
@dataclass
|
||||
class SessionLocation:
|
||||
x: float
|
||||
|
@ -471,6 +579,30 @@ class Hook:
|
|||
return core_pb2.Hook(state=self.state.value, file=self.file, data=self.data)
|
||||
|
||||
|
||||
@dataclass
|
||||
class EmaneModelConfig:
|
||||
node_id: int
|
||||
model: str
|
||||
iface_id: int = -1
|
||||
config: Dict[str, ConfigOption] = None
|
||||
|
||||
@classmethod
|
||||
def from_proto(cls, proto: emane_pb2.GetEmaneModelConfig) -> "EmaneModelConfig":
|
||||
iface_id = proto.iface_id if proto.iface_id != -1 else None
|
||||
config = ConfigOption.from_dict(proto.config)
|
||||
return EmaneModelConfig(
|
||||
node_id=proto.node_id, iface_id=iface_id, model=proto.model, config=config
|
||||
)
|
||||
|
||||
def to_proto(self) -> emane_pb2.EmaneModelConfig:
|
||||
return emane_pb2.EmaneModelConfig(
|
||||
node_id=self.node_id,
|
||||
model=self.model,
|
||||
iface_id=self.iface_id,
|
||||
config=self.config,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Position:
|
||||
x: float
|
||||
|
@ -660,3 +792,187 @@ class NodeEvent:
|
|||
message_type=MessageType(proto.message_type),
|
||||
node=Node.from_proto(proto.node),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class SessionEvent:
|
||||
node_id: int
|
||||
event: int
|
||||
name: str
|
||||
data: str
|
||||
time: float
|
||||
|
||||
@classmethod
|
||||
def from_proto(cls, proto: core_pb2.SessionEvent) -> "SessionEvent":
|
||||
return SessionEvent(
|
||||
node_id=proto.node_id,
|
||||
event=proto.event,
|
||||
name=proto.name,
|
||||
data=proto.data,
|
||||
time=proto.time,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class FileEvent:
|
||||
message_type: MessageType
|
||||
node_id: int
|
||||
name: str
|
||||
mode: str
|
||||
number: int
|
||||
type: str
|
||||
source: str
|
||||
data: str
|
||||
compressed_data: str
|
||||
|
||||
@classmethod
|
||||
def from_proto(cls, proto: core_pb2.FileEvent) -> "FileEvent":
|
||||
return FileEvent(
|
||||
message_type=MessageType(proto.message_type),
|
||||
node_id=proto.node_id,
|
||||
name=proto.name,
|
||||
mode=proto.mode,
|
||||
number=proto.number,
|
||||
type=proto.type,
|
||||
source=proto.source,
|
||||
data=proto.data,
|
||||
compressed_data=proto.compressed_data,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ConfigEvent:
|
||||
message_type: MessageType
|
||||
node_id: int
|
||||
object: str
|
||||
type: int
|
||||
data_types: List[int]
|
||||
data_values: str
|
||||
captions: str
|
||||
bitmap: str
|
||||
possible_values: str
|
||||
groups: str
|
||||
iface_id: int
|
||||
network_id: int
|
||||
opaque: str
|
||||
|
||||
@classmethod
|
||||
def from_proto(cls, proto: core_pb2.ConfigEvent) -> "ConfigEvent":
|
||||
return ConfigEvent(
|
||||
message_type=MessageType(proto.message_type),
|
||||
node_id=proto.node_id,
|
||||
object=proto.object,
|
||||
type=proto.type,
|
||||
data_types=list(proto.data_types),
|
||||
data_values=proto.data_values,
|
||||
captions=proto.captions,
|
||||
bitmap=proto.bitmap,
|
||||
possible_values=proto.possible_values,
|
||||
groups=proto.groups,
|
||||
iface_id=proto.iface_id,
|
||||
network_id=proto.network_id,
|
||||
opaque=proto.opaque,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Event:
|
||||
session_id: int
|
||||
source: str = None
|
||||
session_event: SessionEvent = None
|
||||
node_event: NodeEvent = None
|
||||
link_event: LinkEvent = None
|
||||
config_event: Any = None
|
||||
exception_event: ExceptionEvent = None
|
||||
file_event: FileEvent = None
|
||||
|
||||
@classmethod
|
||||
def from_proto(cls, proto: core_pb2.Event) -> "Event":
|
||||
source = proto.source if proto.source else None
|
||||
node_event = None
|
||||
link_event = None
|
||||
exception_event = None
|
||||
session_event = None
|
||||
file_event = None
|
||||
config_event = None
|
||||
if proto.HasField("node_event"):
|
||||
node_event = NodeEvent.from_proto(proto.node_event)
|
||||
elif proto.HasField("link_event"):
|
||||
link_event = LinkEvent.from_proto(proto.link_event)
|
||||
elif proto.HasField("exception_event"):
|
||||
exception_event = ExceptionEvent.from_proto(
|
||||
proto.session_id, proto.exception_event
|
||||
)
|
||||
elif proto.HasField("session_event"):
|
||||
session_event = SessionEvent.from_proto(proto.session_event)
|
||||
elif proto.HasField("file_event"):
|
||||
file_event = FileEvent.from_proto(proto.file_event)
|
||||
elif proto.HasField("config_event"):
|
||||
config_event = ConfigEvent.from_proto(proto.config_event)
|
||||
return Event(
|
||||
session_id=proto.session_id,
|
||||
source=source,
|
||||
node_event=node_event,
|
||||
link_event=link_event,
|
||||
exception_event=exception_event,
|
||||
session_event=session_event,
|
||||
file_event=file_event,
|
||||
config_event=config_event,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class EmaneEventChannel:
|
||||
group: str
|
||||
port: int
|
||||
device: str
|
||||
|
||||
@classmethod
|
||||
def from_proto(
|
||||
cls, proto: emane_pb2.GetEmaneEventChannelResponse
|
||||
) -> "EmaneEventChannel":
|
||||
return EmaneEventChannel(
|
||||
group=proto.group, port=proto.port, device=proto.device
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class EmanePathlossesRequest:
|
||||
session_id: int
|
||||
node1_id: int
|
||||
rx1: float
|
||||
iface1_id: int
|
||||
node2_id: int
|
||||
rx2: float
|
||||
iface2_id: int
|
||||
|
||||
def to_proto(self) -> emane_pb2.EmanePathlossesRequest:
|
||||
return emane_pb2.EmanePathlossesRequest(
|
||||
session_id=self.session_id,
|
||||
node1_id=self.node1_id,
|
||||
rx1=self.rx1,
|
||||
iface1_id=self.iface1_id,
|
||||
node2_id=self.node2_id,
|
||||
rx2=self.rx2,
|
||||
iface2_id=self.iface2_id,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class MoveNodesRequest:
|
||||
session_id: int
|
||||
node_id: int
|
||||
source: str = None
|
||||
position: Position = None
|
||||
geo: Geo = None
|
||||
|
||||
def to_proto(self) -> core_pb2.MoveNodesRequest:
|
||||
position = self.position.to_proto() if self.position else None
|
||||
geo = self.geo.to_proto() if self.geo else None
|
||||
return core_pb2.MoveNodesRequest(
|
||||
session_id=self.session_id,
|
||||
node_id=self.node_id,
|
||||
source=self.source,
|
||||
position=position,
|
||||
geo=geo,
|
||||
)
|
|
@ -1331,9 +1331,7 @@ class CoreHandler(socketserver.BaseRequestHandler):
|
|||
iface_id = config_data.iface_id
|
||||
values_str = config_data.data_values
|
||||
|
||||
if iface_id is not None:
|
||||
node_id = node_id * 1000 + iface_id
|
||||
|
||||
node_id = utils.iface_config_id(node_id, iface_id)
|
||||
logging.debug(
|
||||
"received configure message for %s nodenum: %s", object_name, node_id
|
||||
)
|
||||
|
@ -1381,9 +1379,7 @@ class CoreHandler(socketserver.BaseRequestHandler):
|
|||
iface_id = config_data.iface_id
|
||||
values_str = config_data.data_values
|
||||
|
||||
if iface_id is not None:
|
||||
node_id = node_id * 1000 + iface_id
|
||||
|
||||
node_id = utils.iface_config_id(node_id, iface_id)
|
||||
logging.debug(
|
||||
"received configure message for %s nodenum: %s", object_name, node_id
|
||||
)
|
||||
|
@ -1413,9 +1409,7 @@ class CoreHandler(socketserver.BaseRequestHandler):
|
|||
iface_id = config_data.iface_id
|
||||
values_str = config_data.data_values
|
||||
|
||||
if iface_id is not None:
|
||||
node_id = node_id * 1000 + iface_id
|
||||
|
||||
node_id = utils.iface_config_id(node_id, iface_id)
|
||||
logging.debug(
|
||||
"received configure message for %s nodenum: %s", object_name, node_id
|
||||
)
|
||||
|
|
|
@ -212,7 +212,7 @@ class ConfigurableManager:
|
|||
|
||||
def get_configs(
|
||||
self, node_id: int = _default_node, config_type: str = _default_type
|
||||
) -> Dict[str, str]:
|
||||
) -> Optional[Dict[str, str]]:
|
||||
"""
|
||||
Retrieve configurations for a node and configuration type.
|
||||
|
||||
|
|
|
@ -80,7 +80,7 @@ class EmaneCommEffectModel(emanemodel.EmaneModel):
|
|||
nem_name = emanexml.nem_file_name(iface)
|
||||
shim_name = emanexml.shim_file_name(iface)
|
||||
etree.SubElement(nem_element, "shim", definition=shim_name)
|
||||
emanexml.create_iface_file(iface, nem_element, "nem", nem_name)
|
||||
emanexml.create_node_file(iface.node, nem_element, "nem", nem_name)
|
||||
|
||||
# create and write shim document
|
||||
shim_element = etree.Element(
|
||||
|
@ -99,7 +99,7 @@ class EmaneCommEffectModel(emanemodel.EmaneModel):
|
|||
ff = config["filterfile"]
|
||||
if ff.strip() != "":
|
||||
emanexml.add_param(shim_element, "filterfile", ff)
|
||||
emanexml.create_iface_file(iface, shim_element, "shim", shim_name)
|
||||
emanexml.create_node_file(iface.node, shim_element, "shim", shim_name)
|
||||
|
||||
# create transport xml
|
||||
emanexml.create_transport_xml(iface, config)
|
||||
|
|
|
@ -6,6 +6,7 @@ import logging
|
|||
import os
|
||||
import threading
|
||||
from collections import OrderedDict
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type
|
||||
|
||||
|
@ -28,7 +29,7 @@ from core.emulator.enumerations import (
|
|||
RegisterTlvs,
|
||||
)
|
||||
from core.errors import CoreCommandError, CoreError
|
||||
from core.nodes.base import CoreNode, NodeBase
|
||||
from core.nodes.base import CoreNetworkBase, CoreNode, CoreNodeBase, NodeBase
|
||||
from core.nodes.interface import CoreInterface, TunTap
|
||||
from core.xml import emanexml
|
||||
|
||||
|
@ -68,6 +69,13 @@ class EmaneState(Enum):
|
|||
NOT_READY = 2
|
||||
|
||||
|
||||
@dataclass
|
||||
class StartData:
|
||||
emane_net: EmaneNet
|
||||
node: CoreNodeBase
|
||||
ifaces: List[CoreInterface] = field(default_factory=list)
|
||||
|
||||
|
||||
class EmaneManager(ModelManager):
|
||||
"""
|
||||
EMANE controller object. Lives in a Session instance and is used for
|
||||
|
@ -126,33 +134,31 @@ class EmaneManager(ModelManager):
|
|||
self, emane_net: EmaneNet, iface: CoreInterface
|
||||
) -> Dict[str, str]:
|
||||
"""
|
||||
Retrieve configuration for a given interface.
|
||||
Retrieve configuration for a given interface, first checking for interface
|
||||
specific config, node specific config, network specific config, and finally
|
||||
falling back to the default configuration settings.
|
||||
|
||||
:param emane_net: emane network the interface is connected to
|
||||
:param iface: interface running emane
|
||||
:return: net, node, or interface model configuration
|
||||
"""
|
||||
model_name = emane_net.model.name
|
||||
# don"t use default values when interface config is the same as net
|
||||
# note here that using iface.node.id as key allows for only one type
|
||||
# of each model per node;
|
||||
# TODO: use both node and interface as key
|
||||
# Adamson change: first check for iface config keyed by "node:iface.name"
|
||||
# (so that nodes w/ multiple interfaces of same conftype can have
|
||||
# different configs for each separate interface)
|
||||
key = 1000 * iface.node.id
|
||||
config = None
|
||||
# try to retrieve interface specific configuration
|
||||
if iface.node_id is not None:
|
||||
key += iface.node_id
|
||||
# try retrieve interface specific configuration, avoid getting defaults
|
||||
config = self.get_configs(node_id=key, config_type=model_name)
|
||||
# otherwise retrieve the interfaces node configuration, avoid using defaults
|
||||
key = utils.iface_config_id(iface.node.id, iface.node_id)
|
||||
config = self.get_configs(node_id=key, config_type=model_name)
|
||||
# attempt to retrieve node specific config, when iface config is not present
|
||||
if not config:
|
||||
config = self.get_configs(node_id=iface.node.id, config_type=model_name)
|
||||
# get non interface config, when none found
|
||||
# attempt to get emane net specific config, when node config is not present
|
||||
if not config:
|
||||
# with EMANE 0.9.2+, we need an extra NEM XML from
|
||||
# model.buildnemxmlfiles(), so defaults are returned here
|
||||
config = self.get_configs(node_id=emane_net.id, config_type=model_name)
|
||||
# return default config values, when a config is not present
|
||||
if not config:
|
||||
config = emane_net.model.default_values()
|
||||
return config
|
||||
|
||||
def config_reset(self, node_id: int = None) -> None:
|
||||
|
@ -342,36 +348,45 @@ class EmaneManager(ModelManager):
|
|||
self.buildeventservicexml()
|
||||
with self._emane_node_lock:
|
||||
logging.info("emane building xmls...")
|
||||
for node_id in sorted(self._emane_nets):
|
||||
emane_net = self._emane_nets[node_id]
|
||||
if not emane_net.model:
|
||||
logging.error("emane net(%s) has no model", emane_net.name)
|
||||
continue
|
||||
for iface in emane_net.get_ifaces():
|
||||
self.start_iface(emane_net, iface)
|
||||
start_data = self.get_start_data()
|
||||
for data in start_data:
|
||||
self.start_node(data)
|
||||
if self.links_enabled():
|
||||
self.link_monitor.start()
|
||||
return EmaneState.SUCCESS
|
||||
|
||||
def start_iface(self, emane_net: EmaneNet, iface: CoreInterface) -> None:
|
||||
if not iface.node:
|
||||
logging.error(
|
||||
"emane net(%s) connected interface(%s) missing node",
|
||||
emane_net.name,
|
||||
iface.name,
|
||||
)
|
||||
return
|
||||
def get_start_data(self) -> List[StartData]:
|
||||
node_map = {}
|
||||
for node_id in sorted(self._emane_nets):
|
||||
emane_net = self._emane_nets[node_id]
|
||||
if not emane_net.model:
|
||||
logging.error("emane net(%s) has no model", emane_net.name)
|
||||
continue
|
||||
for iface in emane_net.get_ifaces():
|
||||
if not iface.node:
|
||||
logging.error(
|
||||
"emane net(%s) connected interface(%s) missing node",
|
||||
emane_net.name,
|
||||
iface.name,
|
||||
)
|
||||
continue
|
||||
start_node = node_map.setdefault(
|
||||
iface.node, StartData(emane_net, iface.node)
|
||||
)
|
||||
start_node.ifaces.append(iface)
|
||||
start_nodes = sorted(node_map.values(), key=lambda x: x.node.id)
|
||||
for start_node in start_nodes:
|
||||
start_node.ifaces = sorted(start_node.ifaces, key=lambda x: x.node_id)
|
||||
return start_nodes
|
||||
|
||||
def start_node(self, data: StartData) -> None:
|
||||
control_net = self.session.add_remove_control_net(
|
||||
0, remove=False, conf_required=False
|
||||
)
|
||||
nem_id = self.next_nem_id()
|
||||
self.set_nem(nem_id, iface)
|
||||
self.write_nem(iface, nem_id)
|
||||
emanexml.build_platform_xml(self, control_net, emane_net, iface, nem_id)
|
||||
config = self.get_iface_config(emane_net, iface)
|
||||
emane_net.model.build_xml_files(config, iface)
|
||||
self.start_daemon(iface)
|
||||
self.install_iface(emane_net, iface)
|
||||
emanexml.build_platform_xml(self, control_net, data)
|
||||
self.start_daemon(data.node)
|
||||
for iface in data.ifaces:
|
||||
self.install_iface(data.emane_net, iface)
|
||||
|
||||
def set_nem(self, nem_id: int, iface: CoreInterface) -> None:
|
||||
if nem_id in self.nems_to_ifaces:
|
||||
|
@ -432,8 +447,21 @@ class EmaneManager(ModelManager):
|
|||
logging.info("stopping EMANE daemons")
|
||||
if self.links_enabled():
|
||||
self.link_monitor.stop()
|
||||
self.deinstall_ifaces()
|
||||
self.stopdaemons()
|
||||
# shutdown interfaces and stop daemons
|
||||
kill_emaned = "killall -q emane"
|
||||
start_data = self.get_start_data()
|
||||
for data in start_data:
|
||||
node = data.node
|
||||
if not node.up:
|
||||
continue
|
||||
for iface in data.ifaces:
|
||||
if isinstance(node, CoreNode):
|
||||
iface.shutdown()
|
||||
iface.poshook = None
|
||||
if isinstance(node, CoreNode):
|
||||
node.cmd(kill_emaned, wait=False)
|
||||
else:
|
||||
node.host_cmd(kill_emaned, wait=False)
|
||||
self.stopeventmonitor()
|
||||
|
||||
def check_node_models(self) -> None:
|
||||
|
@ -521,7 +549,7 @@ class EmaneManager(ModelManager):
|
|||
)
|
||||
)
|
||||
|
||||
def start_daemon(self, iface: CoreInterface) -> None:
|
||||
def start_daemon(self, node: CoreNodeBase) -> None:
|
||||
"""
|
||||
Start one EMANE daemon per node having a radio.
|
||||
Add a control network even if the user has not configured one.
|
||||
|
@ -536,8 +564,7 @@ class EmaneManager(ModelManager):
|
|||
emanecmd = f"emane -d -l {loglevel}"
|
||||
if realtime:
|
||||
emanecmd += " -r"
|
||||
node = iface.node
|
||||
if iface.is_virtual():
|
||||
if isinstance(node, CoreNode):
|
||||
otagroup, _otaport = self.get_config("otamanagergroup").split(":")
|
||||
otadev = self.get_config("otamanagerdevice")
|
||||
otanetidx = self.session.get_control_net_index(otadev)
|
||||
|
@ -566,35 +593,19 @@ class EmaneManager(ModelManager):
|
|||
if eventservicenetidx >= 0 and eventgroup != otagroup:
|
||||
node.node_net_client.create_route(eventgroup, eventdev)
|
||||
# start emane
|
||||
log_file = os.path.join(node.nodedir, f"{iface.name}-emane.log")
|
||||
platform_xml = os.path.join(node.nodedir, f"{iface.name}-platform.xml")
|
||||
log_file = os.path.join(node.nodedir, f"{node.name}-emane.log")
|
||||
platform_xml = os.path.join(node.nodedir, f"{node.name}-platform.xml")
|
||||
args = f"{emanecmd} -f {log_file} {platform_xml}"
|
||||
node.cmd(args)
|
||||
logging.info("node(%s) emane daemon running: %s", node.name, args)
|
||||
else:
|
||||
path = self.session.session_dir
|
||||
log_file = os.path.join(path, f"{iface.name}-emane.log")
|
||||
platform_xml = os.path.join(path, f"{iface.name}-platform.xml")
|
||||
log_file = os.path.join(path, f"{node.name}-emane.log")
|
||||
platform_xml = os.path.join(path, f"{node.name}-platform.xml")
|
||||
emanecmd += f" -f {log_file} {platform_xml}"
|
||||
node.host_cmd(emanecmd, cwd=path)
|
||||
logging.info("node(%s) host emane daemon running: %s", node.name, emanecmd)
|
||||
|
||||
def stopdaemons(self) -> None:
|
||||
"""
|
||||
Kill the appropriate EMANE daemons.
|
||||
"""
|
||||
kill_emaned = "killall -q emane"
|
||||
for node_id in sorted(self._emane_nets):
|
||||
emane_net = self._emane_nets[node_id]
|
||||
for iface in emane_net.get_ifaces():
|
||||
node = iface.node
|
||||
if not node.up:
|
||||
continue
|
||||
if iface.is_raw():
|
||||
node.host_cmd(kill_emaned, wait=False)
|
||||
else:
|
||||
node.cmd(kill_emaned, wait=False)
|
||||
|
||||
def install_iface(self, emane_net: EmaneNet, iface: CoreInterface) -> None:
|
||||
config = self.get_iface_config(emane_net, iface)
|
||||
external = config.get("external", "0")
|
||||
|
@ -606,17 +617,6 @@ class EmaneManager(ModelManager):
|
|||
iface.poshook = emane_net.setnemposition
|
||||
iface.setposition()
|
||||
|
||||
def deinstall_ifaces(self) -> None:
|
||||
"""
|
||||
Uninstall TUN/TAP virtual interfaces.
|
||||
"""
|
||||
for key in sorted(self._emane_nets):
|
||||
emane_net = self._emane_nets[key]
|
||||
for iface in emane_net.get_ifaces():
|
||||
if iface.is_virtual():
|
||||
iface.shutdown()
|
||||
iface.poshook = None
|
||||
|
||||
def doeventmonitor(self) -> bool:
|
||||
"""
|
||||
Returns boolean whether or not EMANE events will be monitored.
|
||||
|
@ -780,6 +780,9 @@ class EmaneManager(ModelManager):
|
|||
self.session.broadcast_node(node)
|
||||
return True
|
||||
|
||||
def is_emane_net(self, net: Optional[CoreNetworkBase]) -> bool:
|
||||
return isinstance(net, EmaneNet)
|
||||
|
||||
def emanerunning(self, node: CoreNode) -> bool:
|
||||
"""
|
||||
Return True if an EMANE process associated with the given node is running,
|
||||
|
|
|
@ -545,7 +545,12 @@ class Session:
|
|||
|
||||
# ensure default emane configuration
|
||||
if isinstance(node, EmaneNet) and options.emane:
|
||||
self.emane.set_model_config(_id, options.emane)
|
||||
model = self.emane.models.get(options.emane)
|
||||
if not model:
|
||||
raise CoreError(
|
||||
f"node({node.name}) emane model({options.emane}) does not exist"
|
||||
)
|
||||
node.model = model(self, node.id)
|
||||
if self.state == EventTypes.RUNTIME_STATE:
|
||||
self.emane.add_node(node)
|
||||
# set default wlan config if needed
|
||||
|
@ -571,17 +576,10 @@ class Session:
|
|||
:return: nothing
|
||||
:raises core.CoreError: when node to update does not exist
|
||||
"""
|
||||
# get node to update
|
||||
node = self.get_node(node_id, NodeBase)
|
||||
|
||||
# set node position and broadcast it
|
||||
self.set_node_position(node, options)
|
||||
|
||||
# update attributes
|
||||
node.canvas = options.canvas
|
||||
node.icon = options.icon
|
||||
|
||||
# provide edits to sdt
|
||||
self.set_node_position(node, options)
|
||||
self.sdt.edit_node(node, options.lon, options.lat, options.alt)
|
||||
|
||||
def set_node_position(self, node: NodeBase, options: NodeOptions) -> None:
|
||||
|
@ -758,7 +756,9 @@ class Session:
|
|||
"""
|
||||
Shutdown all session nodes and remove the session directory.
|
||||
"""
|
||||
logging.info("session(%s) shutting down", self.id)
|
||||
if self.state == EventTypes.SHUTDOWN_STATE:
|
||||
return
|
||||
logging.info("session(%s) state(%s) shutting down", self.id, self.state)
|
||||
self.set_state(EventTypes.DATACOLLECT_STATE, send_event=True)
|
||||
self.set_state(EventTypes.SHUTDOWN_STATE, send_event=True)
|
||||
|
||||
|
|
|
@ -30,3 +30,19 @@ class CoreXmlError(Exception):
|
|||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class CoreServiceError(Exception):
|
||||
"""
|
||||
Used when there is an error related to accessing a service.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class CoreServiceBootError(Exception):
|
||||
"""
|
||||
Used when there is an error booting a service.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
|
|
@ -11,6 +11,7 @@ EBTABLES: str = "ebtables"
|
|||
MOUNT: str = "mount"
|
||||
UMOUNT: str = "umount"
|
||||
OVS_VSCTL: str = "ovs-vsctl"
|
||||
TEST: str = "test"
|
||||
|
||||
COMMON_REQUIREMENTS: List[str] = [
|
||||
BASH,
|
||||
|
@ -21,6 +22,7 @@ COMMON_REQUIREMENTS: List[str] = [
|
|||
SYSCTL,
|
||||
TC,
|
||||
UMOUNT,
|
||||
TEST,
|
||||
]
|
||||
VCMD_REQUIREMENTS: List[str] = [VNODED, VCMD]
|
||||
OVS_REQUIREMENTS: List[str] = [OVS_VSCTL]
|
||||
|
|
|
@ -21,19 +21,7 @@ from core.api.grpc import (
|
|||
services_pb2,
|
||||
wlan_pb2,
|
||||
)
|
||||
from core.gui import appconfig
|
||||
from core.gui.appconfig import BACKGROUNDS_PATH, XMLS_PATH, CoreServer, Observer
|
||||
from core.gui.dialogs.emaneinstall import EmaneInstallDialog
|
||||
from core.gui.dialogs.error import ErrorDialog
|
||||
from core.gui.dialogs.mobilityplayer import MobilityPlayer
|
||||
from core.gui.dialogs.sessions import SessionsDialog
|
||||
from core.gui.graph.edges import CanvasEdge
|
||||
from core.gui.graph.node import CanvasNode
|
||||
from core.gui.graph.shape import AnnotationData, Shape
|
||||
from core.gui.graph.shapeutils import ShapeType
|
||||
from core.gui.interface import InterfaceManager
|
||||
from core.gui.nodeutils import NodeDraw, NodeUtils
|
||||
from core.gui.wrappers import (
|
||||
from core.api.grpc.wrappers import (
|
||||
ConfigOption,
|
||||
ConfigService,
|
||||
ExceptionEvent,
|
||||
|
@ -52,6 +40,18 @@ from core.gui.wrappers import (
|
|||
SessionState,
|
||||
ThroughputsEvent,
|
||||
)
|
||||
from core.gui import appconfig
|
||||
from core.gui.appconfig import BACKGROUNDS_PATH, XMLS_PATH, CoreServer, Observer
|
||||
from core.gui.dialogs.emaneinstall import EmaneInstallDialog
|
||||
from core.gui.dialogs.error import ErrorDialog
|
||||
from core.gui.dialogs.mobilityplayer import MobilityPlayer
|
||||
from core.gui.dialogs.sessions import SessionsDialog
|
||||
from core.gui.graph.edges import CanvasEdge
|
||||
from core.gui.graph.node import CanvasNode
|
||||
from core.gui.graph.shape import AnnotationData, Shape
|
||||
from core.gui.graph.shapeutils import ShapeType
|
||||
from core.gui.interface import InterfaceManager
|
||||
from core.gui.nodeutils import NodeDraw, NodeUtils
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
@ -937,8 +937,6 @@ class CoreClient:
|
|||
def get_emane_model_configs_proto(self) -> List[emane_pb2.EmaneModelConfig]:
|
||||
configs = []
|
||||
for node in self.session.nodes.values():
|
||||
if node.type != NodeType.EMANE:
|
||||
continue
|
||||
for key, config in node.emane_model_configs.items():
|
||||
model, iface_id = key
|
||||
config = ConfigOption.to_dict(config)
|
||||
|
|
|
@ -5,10 +5,10 @@ import tkinter as tk
|
|||
from tkinter import ttk
|
||||
from typing import TYPE_CHECKING, Dict, Optional
|
||||
|
||||
from core.api.grpc.wrappers import ExceptionEvent, ExceptionLevel
|
||||
from core.gui.dialogs.dialog import Dialog
|
||||
from core.gui.themes import PADX, PADY
|
||||
from core.gui.widgets import CodeText
|
||||
from core.gui.wrappers import ExceptionEvent, ExceptionLevel
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
|
|
@ -8,15 +8,15 @@ from typing import TYPE_CHECKING, Dict, List, Optional, Set
|
|||
|
||||
import grpc
|
||||
|
||||
from core.gui.dialogs.dialog import Dialog
|
||||
from core.gui.themes import FRAME_PAD, PADX, PADY
|
||||
from core.gui.widgets import CodeText, ConfigFrame, ListboxScroll
|
||||
from core.gui.wrappers import (
|
||||
from core.api.grpc.wrappers import (
|
||||
ConfigOption,
|
||||
ConfigServiceData,
|
||||
Node,
|
||||
ServiceValidationMode,
|
||||
)
|
||||
from core.gui.dialogs.dialog import Dialog
|
||||
from core.gui.themes import FRAME_PAD, PADX, PADY
|
||||
from core.gui.widgets import CodeText, ConfigFrame, ListboxScroll
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
|
|
@ -8,11 +8,11 @@ from typing import TYPE_CHECKING, Dict, List, Optional
|
|||
|
||||
import grpc
|
||||
|
||||
from core.api.grpc.wrappers import ConfigOption, Node
|
||||
from core.gui.dialogs.dialog import Dialog
|
||||
from core.gui.images import ImageEnum, Images
|
||||
from core.gui.themes import PADX, PADY
|
||||
from core.gui.widgets import ConfigFrame
|
||||
from core.gui.wrappers import ConfigOption, Node
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
|
|
@ -2,10 +2,10 @@ import tkinter as tk
|
|||
from tkinter import ttk
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from core.api.grpc.wrappers import Hook, SessionState
|
||||
from core.gui.dialogs.dialog import Dialog
|
||||
from core.gui.themes import PADX, PADY
|
||||
from core.gui.widgets import CodeText, ListboxScroll
|
||||
from core.gui.wrappers import Hook, SessionState
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
|
|
@ -5,11 +5,11 @@ import tkinter as tk
|
|||
from tkinter import ttk
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from core.api.grpc.wrappers import Interface, Link, LinkOptions
|
||||
from core.gui import validation
|
||||
from core.gui.dialogs.colorpicker import ColorPickerDialog
|
||||
from core.gui.dialogs.dialog import Dialog
|
||||
from core.gui.themes import PADX, PADY
|
||||
from core.gui.wrappers import Interface, Link, LinkOptions
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
|
|
@ -7,10 +7,10 @@ from typing import TYPE_CHECKING, Dict, Optional
|
|||
|
||||
import grpc
|
||||
|
||||
from core.api.grpc.wrappers import ConfigOption, Node
|
||||
from core.gui.dialogs.dialog import Dialog
|
||||
from core.gui.themes import PADX, PADY
|
||||
from core.gui.widgets import ConfigFrame
|
||||
from core.gui.wrappers import ConfigOption, Node
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
|
|
@ -4,10 +4,10 @@ from typing import TYPE_CHECKING, Optional
|
|||
|
||||
import grpc
|
||||
|
||||
from core.api.grpc.wrappers import MobilityAction, Node
|
||||
from core.gui.dialogs.dialog import Dialog
|
||||
from core.gui.images import ImageEnum
|
||||
from core.gui.themes import PADX, PADY
|
||||
from core.gui.wrappers import MobilityAction, Node
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
|
|
@ -7,6 +7,7 @@ from typing import TYPE_CHECKING, Dict, Optional
|
|||
import netaddr
|
||||
from PIL.ImageTk import PhotoImage
|
||||
|
||||
from core.api.grpc.wrappers import Node
|
||||
from core.gui import nodeutils, validation
|
||||
from core.gui.appconfig import ICONS_PATH
|
||||
from core.gui.dialogs.dialog import Dialog
|
||||
|
@ -15,7 +16,6 @@ from core.gui.images import Images
|
|||
from core.gui.nodeutils import NodeUtils
|
||||
from core.gui.themes import FRAME_PAD, PADX, PADY
|
||||
from core.gui.widgets import ListboxScroll, image_chooser
|
||||
from core.gui.wrappers import Node
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
@ -282,9 +282,7 @@ class NodeConfigDialog(Dialog):
|
|||
button.grid(row=0, column=1, sticky=tk.EW)
|
||||
|
||||
def click_emane_config(self, emane_model: str, iface_id: int) -> None:
|
||||
dialog = EmaneModelDialog(
|
||||
self, self.app, self.canvas_node, emane_model, iface_id
|
||||
)
|
||||
dialog = EmaneModelDialog(self, self.app, self.node, emane_model, iface_id)
|
||||
dialog.show()
|
||||
|
||||
def click_icon(self) -> None:
|
||||
|
|
|
@ -6,11 +6,11 @@ import tkinter as tk
|
|||
from tkinter import messagebox, ttk
|
||||
from typing import TYPE_CHECKING, Optional, Set
|
||||
|
||||
from core.api.grpc.wrappers import Node
|
||||
from core.gui.dialogs.configserviceconfig import ConfigServiceConfigDialog
|
||||
from core.gui.dialogs.dialog import Dialog
|
||||
from core.gui.themes import FRAME_PAD, PADX, PADY
|
||||
from core.gui.widgets import CheckboxList, ListboxScroll
|
||||
from core.gui.wrappers import Node
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
|
|
@ -5,11 +5,11 @@ import tkinter as tk
|
|||
from tkinter import messagebox, ttk
|
||||
from typing import TYPE_CHECKING, Optional, Set
|
||||
|
||||
from core.api.grpc.wrappers import Node
|
||||
from core.gui.dialogs.dialog import Dialog
|
||||
from core.gui.dialogs.serviceconfig import ServiceConfigDialog
|
||||
from core.gui.themes import FRAME_PAD, PADX, PADY
|
||||
from core.gui.widgets import CheckboxList, ListboxScroll
|
||||
from core.gui.wrappers import Node
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
|
|
@ -7,12 +7,12 @@ from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple
|
|||
import grpc
|
||||
from PIL.ImageTk import PhotoImage
|
||||
|
||||
from core.api.grpc.wrappers import Node, NodeServiceData, ServiceValidationMode
|
||||
from core.gui.dialogs.copyserviceconfig import CopyServiceConfigDialog
|
||||
from core.gui.dialogs.dialog import Dialog
|
||||
from core.gui.images import ImageEnum, Images
|
||||
from core.gui.themes import FRAME_PAD, PADX, PADY
|
||||
from core.gui.widgets import CodeText, ListboxScroll
|
||||
from core.gui.wrappers import Node, NodeServiceData, ServiceValidationMode
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
|
|
@ -5,10 +5,10 @@ from typing import TYPE_CHECKING, Dict, Optional
|
|||
|
||||
import grpc
|
||||
|
||||
from core.api.grpc.wrappers import ConfigOption
|
||||
from core.gui.dialogs.dialog import Dialog
|
||||
from core.gui.themes import PADX, PADY
|
||||
from core.gui.widgets import ConfigFrame
|
||||
from core.gui.wrappers import ConfigOption
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
|
|
@ -5,11 +5,11 @@ from typing import TYPE_CHECKING, List, Optional
|
|||
|
||||
import grpc
|
||||
|
||||
from core.api.grpc.wrappers import SessionState, SessionSummary
|
||||
from core.gui.dialogs.dialog import Dialog
|
||||
from core.gui.images import ImageEnum, Images
|
||||
from core.gui.task import ProgressTask
|
||||
from core.gui.themes import PADX, PADY
|
||||
from core.gui.wrappers import SessionState, SessionSummary
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
|
|
@ -4,10 +4,10 @@ from typing import TYPE_CHECKING, Dict, Optional
|
|||
|
||||
import grpc
|
||||
|
||||
from core.api.grpc.wrappers import ConfigOption, Node
|
||||
from core.gui.dialogs.dialog import Dialog
|
||||
from core.gui.themes import PADX, PADY
|
||||
from core.gui.widgets import ConfigFrame
|
||||
from core.gui.wrappers import ConfigOption, Node
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import tkinter as tk
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from core.api.grpc.wrappers import Interface
|
||||
from core.gui.frames.base import DetailsFrame, InfoFrameBase
|
||||
from core.gui.utils import bandwidth_text
|
||||
from core.gui.wrappers import Interface
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import tkinter as tk
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from core.api.grpc.wrappers import NodeType
|
||||
from core.gui.frames.base import DetailsFrame, InfoFrameBase
|
||||
from core.gui.nodeutils import NodeUtils
|
||||
from core.gui.wrappers import NodeType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
|
|
@ -3,13 +3,13 @@ import math
|
|||
import tkinter as tk
|
||||
from typing import TYPE_CHECKING, Optional, Tuple
|
||||
|
||||
from core.api.grpc.wrappers import Interface, Link
|
||||
from core.gui import themes
|
||||
from core.gui.dialogs.linkconfig import LinkConfigurationDialog
|
||||
from core.gui.frames.link import EdgeInfoFrame, WirelessEdgeInfoFrame
|
||||
from core.gui.graph import tags
|
||||
from core.gui.nodeutils import NodeUtils
|
||||
from core.gui.utils import bandwidth_text, delay_jitter_text
|
||||
from core.gui.wrappers import Interface, Link
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.graph.graph import CanvasGraph
|
||||
|
|
|
@ -7,6 +7,14 @@ from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple
|
|||
from PIL import Image
|
||||
from PIL.ImageTk import PhotoImage
|
||||
|
||||
from core.api.grpc.wrappers import (
|
||||
Interface,
|
||||
Link,
|
||||
LinkType,
|
||||
Node,
|
||||
Session,
|
||||
ThroughputsEvent,
|
||||
)
|
||||
from core.gui.dialogs.shapemod import ShapeDialog
|
||||
from core.gui.graph import tags
|
||||
from core.gui.graph.edges import (
|
||||
|
@ -22,7 +30,6 @@ from core.gui.graph.shape import Shape
|
|||
from core.gui.graph.shapeutils import ShapeType, is_draw_shape, is_marker
|
||||
from core.gui.images import ImageEnum, TypeToImage
|
||||
from core.gui.nodeutils import NodeDraw, NodeUtils
|
||||
from core.gui.wrappers import Interface, Link, LinkType, Node, Session, ThroughputsEvent
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
|
|
@ -7,6 +7,7 @@ from typing import TYPE_CHECKING, Dict, List, Set
|
|||
import grpc
|
||||
from PIL.ImageTk import PhotoImage
|
||||
|
||||
from core.api.grpc.wrappers import Interface, Node, NodeType
|
||||
from core.gui import nodeutils, themes
|
||||
from core.gui.dialogs.emaneconfig import EmaneConfigDialog
|
||||
from core.gui.dialogs.mobilityconfig import MobilityConfigDialog
|
||||
|
@ -20,7 +21,6 @@ from core.gui.graph.edges import CanvasEdge, CanvasWirelessEdge
|
|||
from core.gui.graph.tooltip import CanvasTooltip
|
||||
from core.gui.images import ImageEnum, Images
|
||||
from core.gui.nodeutils import ANTENNA_SIZE, NodeUtils
|
||||
from core.gui.wrappers import Interface, Node, NodeType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
|
|
@ -5,8 +5,8 @@ from typing import Dict, Optional, Tuple
|
|||
from PIL import Image
|
||||
from PIL.ImageTk import PhotoImage
|
||||
|
||||
from core.api.grpc.wrappers import NodeType
|
||||
from core.gui.appconfig import LOCAL_ICONS_PATH
|
||||
from core.gui.wrappers import NodeType
|
||||
|
||||
|
||||
class Images:
|
||||
|
|
|
@ -4,9 +4,9 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple
|
|||
import netaddr
|
||||
from netaddr import EUI, IPNetwork
|
||||
|
||||
from core.api.grpc.wrappers import Interface, Link, Node
|
||||
from core.gui.graph.node import CanvasNode
|
||||
from core.gui.nodeutils import NodeUtils
|
||||
from core.gui.wrappers import Interface, Link, Node
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
|
|
@ -3,9 +3,9 @@ from typing import List, Optional, Set
|
|||
|
||||
from PIL.ImageTk import PhotoImage
|
||||
|
||||
from core.api.grpc.wrappers import Node, NodeType
|
||||
from core.gui.appconfig import CustomNode, GuiConfig
|
||||
from core.gui.images import ImageEnum, Images, TypeToImage
|
||||
from core.gui.wrappers import Node, NodeType
|
||||
|
||||
ICON_SIZE: int = 48
|
||||
ANTENNA_SIZE: int = 32
|
||||
|
|
|
@ -5,9 +5,9 @@ import tkinter as tk
|
|||
from tkinter import ttk
|
||||
from typing import TYPE_CHECKING, List, Optional
|
||||
|
||||
from core.api.grpc.wrappers import ExceptionEvent, ExceptionLevel
|
||||
from core.gui.dialogs.alerts import AlertsDialog
|
||||
from core.gui.themes import Styles
|
||||
from core.gui.wrappers import ExceptionEvent, ExceptionLevel
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
|
|
@ -5,10 +5,10 @@ from pathlib import Path
|
|||
from tkinter import filedialog, font, ttk
|
||||
from typing import TYPE_CHECKING, Any, Callable, Dict, Set, Type
|
||||
|
||||
from core.api.grpc.wrappers import ConfigOption, ConfigOptionType
|
||||
from core.gui import appconfig, themes, validation
|
||||
from core.gui.dialogs.dialog import Dialog
|
||||
from core.gui.themes import FRAME_PAD, PADX, PADY
|
||||
from core.gui.wrappers import ConfigOption, ConfigOptionType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
|
|
@ -16,7 +16,7 @@ from core.configservice.dependencies import ConfigServiceDependencies
|
|||
from core.emulator.data import InterfaceData, LinkData, LinkOptions
|
||||
from core.emulator.enumerations import LinkTypes, MessageFlags, NodeTypes
|
||||
from core.errors import CoreCommandError, CoreError
|
||||
from core.executables import MOUNT, VNODED
|
||||
from core.executables import MOUNT, TEST, VNODED
|
||||
from core.nodes.client import VnodeClient
|
||||
from core.nodes.interface import CoreInterface, TunTap, Veth
|
||||
from core.nodes.netclient import LinuxNetClient, get_net_client
|
||||
|
@ -294,6 +294,16 @@ class CoreNodeBase(NodeBase):
|
|||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def path_exists(self, path: str) -> bool:
|
||||
"""
|
||||
Determines if a file or directory path exists.
|
||||
|
||||
:param path: path to file or directory
|
||||
:return: True if path exists, False otherwise
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def add_config_service(self, service_class: "ConfigServiceType") -> None:
|
||||
"""
|
||||
Adds a configuration service to the node.
|
||||
|
@ -602,6 +612,19 @@ class CoreNode(CoreNodeBase):
|
|||
args = self.client.create_cmd(args, shell)
|
||||
return self.server.remote_cmd(args, wait=wait)
|
||||
|
||||
def path_exists(self, path: str) -> bool:
|
||||
"""
|
||||
Determines if a file or directory path exists.
|
||||
|
||||
:param path: path to file or directory
|
||||
:return: True if path exists, False otherwise
|
||||
"""
|
||||
try:
|
||||
self.cmd(f"{TEST} -e {path}")
|
||||
return True
|
||||
except CoreCommandError:
|
||||
return False
|
||||
|
||||
def termcmdstring(self, sh: str = "/bin/sh") -> str:
|
||||
"""
|
||||
Create a terminal command string.
|
||||
|
|
|
@ -528,8 +528,9 @@ class TunTap(CoreInterface):
|
|||
# check if this is an EMANE interface; if so, continue
|
||||
# waiting if EMANE is still running
|
||||
should_retry = count < 5
|
||||
is_emane_running = self.node.session.emane.emanerunning(self.node)
|
||||
if all([should_retry, self.net.is_emane, is_emane_running]):
|
||||
is_emane = self.session.emane.is_emane_net(self.net)
|
||||
is_emane_running = self.session.emane.emanerunning(self.node)
|
||||
if all([should_retry, is_emane, is_emane_running]):
|
||||
count += 1
|
||||
else:
|
||||
raise RuntimeError("node device failed to exist")
|
||||
|
|
|
@ -11,7 +11,7 @@ from core.emulator.data import InterfaceData, LinkOptions
|
|||
from core.emulator.distributed import DistributedServer
|
||||
from core.emulator.enumerations import NodeTypes, TransportType
|
||||
from core.errors import CoreCommandError, CoreError
|
||||
from core.executables import MOUNT, UMOUNT
|
||||
from core.executables import MOUNT, TEST, UMOUNT
|
||||
from core.nodes.base import CoreNetworkBase, CoreNodeBase
|
||||
from core.nodes.interface import CoreInterface
|
||||
from core.nodes.network import CoreNetwork, GreTap
|
||||
|
@ -55,6 +55,19 @@ class PhysicalNode(CoreNodeBase):
|
|||
|
||||
self.rmnodedir()
|
||||
|
||||
def path_exists(self, path: str) -> bool:
|
||||
"""
|
||||
Determines if a file or directory path exists.
|
||||
|
||||
:param path: path to file or directory
|
||||
:return: True if path exists, False otherwise
|
||||
"""
|
||||
try:
|
||||
self.host_cmd(f"{TEST} -e {path}")
|
||||
return True
|
||||
except CoreCommandError:
|
||||
return False
|
||||
|
||||
def termcmdstring(self, sh: str = "/bin/sh") -> str:
|
||||
"""
|
||||
Create a terminal command string.
|
||||
|
@ -291,6 +304,19 @@ class Rj45Node(CoreNodeBase):
|
|||
self.up = False
|
||||
self.restorestate()
|
||||
|
||||
def path_exists(self, path: str) -> bool:
|
||||
"""
|
||||
Determines if a file or directory path exists.
|
||||
|
||||
:param path: path to file or directory
|
||||
:return: True if path exists, False otherwise
|
||||
"""
|
||||
try:
|
||||
self.host_cmd(f"{TEST} -e {path}")
|
||||
return True
|
||||
except CoreCommandError:
|
||||
return False
|
||||
|
||||
def new_iface(
|
||||
self, net: CoreNetworkBase, iface_data: InterfaceData
|
||||
) -> CoreInterface:
|
||||
|
|
|
@ -25,7 +25,12 @@ from typing import (
|
|||
from core import utils
|
||||
from core.emulator.data import FileData
|
||||
from core.emulator.enumerations import ExceptionLevels, MessageFlags, RegisterTlvs
|
||||
from core.errors import CoreCommandError, CoreError
|
||||
from core.errors import (
|
||||
CoreCommandError,
|
||||
CoreError,
|
||||
CoreServiceBootError,
|
||||
CoreServiceError,
|
||||
)
|
||||
from core.nodes.base import CoreNode
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
@ -34,10 +39,6 @@ if TYPE_CHECKING:
|
|||
CoreServiceType = Union["CoreService", Type["CoreService"]]
|
||||
|
||||
|
||||
class ServiceBootError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ServiceMode(enum.Enum):
|
||||
BLOCKING = 0
|
||||
NON_BLOCKING = 1
|
||||
|
@ -257,7 +258,10 @@ class ServiceManager:
|
|||
:param name: name of the service to retrieve
|
||||
:return: service if it exists, None otherwise
|
||||
"""
|
||||
return cls.services.get(name)
|
||||
service = cls.services.get(name)
|
||||
if service is None:
|
||||
raise CoreServiceError(f"service({name}) does not exist")
|
||||
return service
|
||||
|
||||
@classmethod
|
||||
def add_services(cls, path: str) -> List[str]:
|
||||
|
@ -450,7 +454,7 @@ class CoreServices:
|
|||
funcs.append((self._boot_service_path, args, {}))
|
||||
result, exceptions = utils.threadpool(funcs)
|
||||
if exceptions:
|
||||
raise ServiceBootError(*exceptions)
|
||||
raise CoreServiceBootError(*exceptions)
|
||||
|
||||
def _boot_service_path(self, node: CoreNode, boot_path: List["CoreServiceType"]):
|
||||
logging.info(
|
||||
|
@ -464,7 +468,7 @@ class CoreServices:
|
|||
self.boot_service(node, service)
|
||||
except Exception as e:
|
||||
logging.exception("exception booting service: %s", service.name)
|
||||
raise ServiceBootError(e)
|
||||
raise CoreServiceBootError(e)
|
||||
|
||||
def boot_service(self, node: CoreNode, service: "CoreServiceType") -> None:
|
||||
"""
|
||||
|
@ -501,7 +505,7 @@ class CoreServices:
|
|||
wait = service.validation_mode == ServiceMode.BLOCKING
|
||||
status = self.startup_service(node, service, wait)
|
||||
if status:
|
||||
raise ServiceBootError(
|
||||
raise CoreServiceBootError(
|
||||
"node(%s) service(%s) error during startup" % (node.name, service.name)
|
||||
)
|
||||
|
||||
|
@ -526,7 +530,7 @@ class CoreServices:
|
|||
time.sleep(service.validation_period)
|
||||
|
||||
if status:
|
||||
raise ServiceBootError(
|
||||
raise CoreServiceBootError(
|
||||
"node(%s) service(%s) failed validation" % (node.name, service.name)
|
||||
)
|
||||
|
||||
|
|
|
@ -41,6 +41,7 @@ if TYPE_CHECKING:
|
|||
T = TypeVar("T")
|
||||
|
||||
DEVNULL = open(os.devnull, "wb")
|
||||
IFACE_CONFIG_FACTOR: int = 1000
|
||||
|
||||
|
||||
def execute_file(
|
||||
|
@ -430,3 +431,34 @@ def random_mac() -> str:
|
|||
value |= 0x00163E << 24
|
||||
mac = netaddr.EUI(value, dialect=netaddr.mac_unix_expanded)
|
||||
return str(mac)
|
||||
|
||||
|
||||
def iface_config_id(node_id: int, iface_id: int = None) -> int:
|
||||
"""
|
||||
Common utility to generate a configuration id, in case an interface is being
|
||||
targeted.
|
||||
|
||||
:param node_id: node for config id
|
||||
:param iface_id: interface for config id
|
||||
:return: generated config id when interface is present, node id otherwise
|
||||
"""
|
||||
if iface_id is not None and iface_id >= 0:
|
||||
return node_id * IFACE_CONFIG_FACTOR + iface_id
|
||||
else:
|
||||
return node_id
|
||||
|
||||
|
||||
def parse_iface_config_id(config_id: int) -> Tuple[int, Optional[int]]:
|
||||
"""
|
||||
Parses configuration id, that may be potentially derived from an interface for a
|
||||
node.
|
||||
|
||||
:param config_id: configuration id to parse
|
||||
:return:
|
||||
"""
|
||||
iface_id = None
|
||||
node_id = config_id
|
||||
if config_id >= IFACE_CONFIG_FACTOR:
|
||||
iface_id = config_id % IFACE_CONFIG_FACTOR
|
||||
node_id = config_id // IFACE_CONFIG_FACTOR
|
||||
return node_id, iface_id
|
||||
|
|
|
@ -5,6 +5,7 @@ from lxml import etree
|
|||
|
||||
import core.nodes.base
|
||||
import core.nodes.physical
|
||||
from core import utils
|
||||
from core.emane.nodes import EmaneNet
|
||||
from core.emulator.data import InterfaceData, LinkData, LinkOptions, NodeOptions
|
||||
from core.emulator.enumerations import EventTypes, NodeTypes
|
||||
|
@ -91,10 +92,14 @@ def create_emane_config(session: "Session") -> etree.Element:
|
|||
|
||||
|
||||
def create_emane_model_config(
|
||||
node_id: int, model: "EmaneModelType", config: Dict[str, str]
|
||||
node_id: int,
|
||||
model: "EmaneModelType",
|
||||
config: Dict[str, str],
|
||||
iface_id: Optional[int],
|
||||
) -> etree.Element:
|
||||
emane_element = etree.Element("emane_configuration")
|
||||
add_attribute(emane_element, "node", node_id)
|
||||
add_attribute(emane_element, "iface", iface_id)
|
||||
add_attribute(emane_element, "model", model.name)
|
||||
|
||||
mac_element = etree.SubElement(emane_element, "mac")
|
||||
|
@ -378,13 +383,16 @@ class CoreXmlWriter:
|
|||
all_configs = self.session.emane.get_all_configs(node_id)
|
||||
if not all_configs:
|
||||
continue
|
||||
node_id, iface_id = utils.parse_iface_config_id(node_id)
|
||||
for model_name in all_configs:
|
||||
config = all_configs[model_name]
|
||||
logging.debug(
|
||||
"writing emane config node(%s) model(%s)", node_id, model_name
|
||||
)
|
||||
model = self.session.emane.models[model_name]
|
||||
emane_configuration = create_emane_model_config(node_id, model, config)
|
||||
emane_configuration = create_emane_model_config(
|
||||
node_id, model, config, iface_id
|
||||
)
|
||||
emane_configurations.append(emane_configuration)
|
||||
if emane_configurations.getchildren():
|
||||
self.scenario.append(emane_configurations)
|
||||
|
@ -588,9 +596,9 @@ class CoreXmlReader:
|
|||
self.read_mobility_configs()
|
||||
self.read_emane_global_config()
|
||||
self.read_nodes()
|
||||
self.read_links()
|
||||
self.read_emane_configs()
|
||||
self.read_configservice_configs()
|
||||
self.read_links()
|
||||
|
||||
def read_default_services(self) -> None:
|
||||
default_services = self.scenario.find("default_services")
|
||||
|
@ -748,6 +756,7 @@ class CoreXmlReader:
|
|||
|
||||
for emane_configuration in emane_configurations.iterchildren():
|
||||
node_id = get_int(emane_configuration, "node")
|
||||
iface_id = get_int(emane_configuration, "iface")
|
||||
model_name = emane_configuration.get("model")
|
||||
configs = {}
|
||||
|
||||
|
@ -755,12 +764,13 @@ class CoreXmlReader:
|
|||
node = self.session.nodes.get(node_id)
|
||||
if not node:
|
||||
raise CoreXmlError(f"node for emane config doesn't exist: {node_id}")
|
||||
if not isinstance(node, EmaneNet):
|
||||
raise CoreXmlError(f"invalid node for emane config: {node.name}")
|
||||
model = self.session.emane.models.get(model_name)
|
||||
if not model:
|
||||
raise CoreXmlError(f"invalid emane model: {model_name}")
|
||||
node.setmodel(model, {})
|
||||
if iface_id is not None and iface_id not in node.ifaces:
|
||||
raise CoreXmlError(
|
||||
f"invalid interface id({iface_id}) for node({node.name})"
|
||||
)
|
||||
|
||||
# read and set emane model configuration
|
||||
mac_configuration = emane_configuration.find("mac")
|
||||
|
@ -784,6 +794,7 @@ class CoreXmlReader:
|
|||
logging.info(
|
||||
"reading emane configuration node(%s) model(%s)", node_id, model_name
|
||||
)
|
||||
node_id = utils.iface_config_id(node_id, iface_id)
|
||||
self.session.emane.set_model_config(node_id, model_name, configs)
|
||||
|
||||
def read_mobility_configs(self) -> None:
|
||||
|
@ -869,6 +880,9 @@ class CoreXmlReader:
|
|||
icon = network_element.get("icon")
|
||||
server = network_element.get("server")
|
||||
options = NodeOptions(name=name, icon=icon, server=server)
|
||||
if node_type == NodeTypes.EMANE:
|
||||
model = network_element.get("model")
|
||||
options.emane = model
|
||||
|
||||
position_element = network_element.find("position")
|
||||
if position_element is not None:
|
||||
|
|
|
@ -7,15 +7,15 @@ from lxml import etree
|
|||
|
||||
from core import utils
|
||||
from core.config import Configuration
|
||||
from core.emane.nodes import EmaneNet
|
||||
from core.emulator.distributed import DistributedServer
|
||||
from core.errors import CoreError
|
||||
from core.nodes.base import CoreNode, CoreNodeBase
|
||||
from core.nodes.interface import CoreInterface
|
||||
from core.nodes.network import CtrlNet
|
||||
from core.xml import corexml
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.emane.emanemanager import EmaneManager
|
||||
from core.emane.emanemanager import EmaneManager, StartData
|
||||
from core.emane.emanemodel import EmaneModel
|
||||
|
||||
_MAC_PREFIX = "02:02"
|
||||
|
@ -78,23 +78,22 @@ def create_file(
|
|||
corexml.write_xml_file(xml_element, file_path, doctype=doctype)
|
||||
|
||||
|
||||
def create_iface_file(
|
||||
iface: CoreInterface, xml_element: etree.Element, doc_name: str, file_name: str
|
||||
def create_node_file(
|
||||
node: CoreNodeBase, xml_element: etree.Element, doc_name: str, file_name: str
|
||||
) -> None:
|
||||
"""
|
||||
Create emane xml for an interface.
|
||||
|
||||
:param iface: interface running emane
|
||||
:param node: node running emane
|
||||
:param xml_element: root element to write to file
|
||||
:param doc_name: name to use in the emane doctype
|
||||
:param file_name: name of xml file
|
||||
:return:
|
||||
"""
|
||||
node = iface.node
|
||||
if iface.is_raw():
|
||||
file_path = os.path.join(node.session.session_dir, file_name)
|
||||
else:
|
||||
if isinstance(node, CoreNode):
|
||||
file_path = os.path.join(node.nodedir, file_name)
|
||||
else:
|
||||
file_path = os.path.join(node.session.session_dir, file_name)
|
||||
create_file(xml_element, doc_name, file_path, node.server)
|
||||
|
||||
|
||||
|
@ -143,11 +142,7 @@ def add_configurations(
|
|||
|
||||
|
||||
def build_platform_xml(
|
||||
emane_manager: "EmaneManager",
|
||||
control_net: CtrlNet,
|
||||
emane_net: EmaneNet,
|
||||
iface: CoreInterface,
|
||||
nem_id: int,
|
||||
emane_manager: "EmaneManager", control_net: CtrlNet, data: "StartData"
|
||||
) -> None:
|
||||
"""
|
||||
Create platform xml for a specific node.
|
||||
|
@ -156,50 +151,62 @@ def build_platform_xml(
|
|||
configurations
|
||||
:param control_net: control net node for this emane
|
||||
network
|
||||
:param emane_net: emane network associated with interface
|
||||
:param iface: interface running emane
|
||||
:param nem_id: nem id to use for this interface
|
||||
:param data: start data for a node connected to emane and associated interfaces
|
||||
:return: the next nem id that can be used for creating platform xml files
|
||||
"""
|
||||
# build nem xml
|
||||
nem_definition = nem_file_name(iface)
|
||||
nem_element = etree.Element(
|
||||
"nem", id=str(nem_id), name=iface.localname, definition=nem_definition
|
||||
)
|
||||
|
||||
# check if this is an external transport, get default config if an interface
|
||||
# specific one does not exist
|
||||
config = emane_manager.get_iface_config(emane_net, iface)
|
||||
if is_external(config):
|
||||
nem_element.set("transport", "external")
|
||||
platform_endpoint = "platformendpoint"
|
||||
add_param(nem_element, platform_endpoint, config[platform_endpoint])
|
||||
transport_endpoint = "transportendpoint"
|
||||
add_param(nem_element, transport_endpoint, config[transport_endpoint])
|
||||
else:
|
||||
transport_name = transport_file_name(iface)
|
||||
transport_element = etree.SubElement(
|
||||
nem_element, "transport", definition=transport_name
|
||||
)
|
||||
add_param(transport_element, "device", iface.name)
|
||||
|
||||
# create top level platform element
|
||||
transport_configs = {"otamanagerdevice", "eventservicedevice"}
|
||||
platform_element = etree.Element("platform")
|
||||
for configuration in emane_manager.emane_config.emulator_config:
|
||||
name = configuration.id
|
||||
if iface.is_raw() and name in transport_configs:
|
||||
if not isinstance(data.node, CoreNode) and name in transport_configs:
|
||||
value = control_net.brname
|
||||
else:
|
||||
value = emane_manager.get_config(name)
|
||||
add_param(platform_element, name, value)
|
||||
platform_element.append(nem_element)
|
||||
mac = _MAC_PREFIX + ":00:00:"
|
||||
mac += f"{(nem_id >> 8) & 0xFF:02X}:{nem_id & 0xFF:02X}"
|
||||
iface.set_mac(mac)
|
||||
|
||||
# create nem xml entries for all interfaces
|
||||
emane_net = data.emane_net
|
||||
for iface in data.ifaces:
|
||||
nem_id = emane_manager.next_nem_id()
|
||||
emane_manager.set_nem(nem_id, iface)
|
||||
emane_manager.write_nem(iface, nem_id)
|
||||
config = emane_manager.get_iface_config(emane_net, iface)
|
||||
emane_net.model.build_xml_files(config, iface)
|
||||
|
||||
# build nem xml
|
||||
nem_definition = nem_file_name(iface)
|
||||
nem_element = etree.Element(
|
||||
"nem", id=str(nem_id), name=iface.localname, definition=nem_definition
|
||||
)
|
||||
|
||||
# check if this is an external transport, get default config if an interface
|
||||
# specific one does not exist
|
||||
config = emane_manager.get_iface_config(emane_net, iface)
|
||||
if is_external(config):
|
||||
nem_element.set("transport", "external")
|
||||
platform_endpoint = "platformendpoint"
|
||||
add_param(nem_element, platform_endpoint, config[platform_endpoint])
|
||||
transport_endpoint = "transportendpoint"
|
||||
add_param(nem_element, transport_endpoint, config[transport_endpoint])
|
||||
else:
|
||||
transport_name = transport_file_name(iface)
|
||||
transport_element = etree.SubElement(
|
||||
nem_element, "transport", definition=transport_name
|
||||
)
|
||||
add_param(transport_element, "device", iface.name)
|
||||
|
||||
# add nem element to platform element
|
||||
platform_element.append(nem_element)
|
||||
|
||||
# generate and assign interface mac address based on nem id
|
||||
mac = _MAC_PREFIX + ":00:00:"
|
||||
mac += f"{(nem_id >> 8) & 0xFF:02X}:{nem_id & 0xFF:02X}"
|
||||
iface.set_mac(mac)
|
||||
|
||||
doc_name = "platform"
|
||||
file_name = f"{iface.name}-platform.xml"
|
||||
create_iface_file(iface, platform_element, doc_name, file_name)
|
||||
file_name = f"{data.node.name}-platform.xml"
|
||||
create_node_file(data.node, platform_element, doc_name, file_name)
|
||||
|
||||
|
||||
def create_transport_xml(iface: CoreInterface, config: Dict[str, str]) -> None:
|
||||
|
@ -220,16 +227,16 @@ def create_transport_xml(iface: CoreInterface, config: Dict[str, str]) -> None:
|
|||
|
||||
# get emane model cnfiguration
|
||||
flowcontrol = config.get("flowcontrolenable", "0") == "1"
|
||||
if iface.is_virtual():
|
||||
if isinstance(iface.node, CoreNode):
|
||||
device_path = "/dev/net/tun_flowctl"
|
||||
if not os.path.exists(device_path):
|
||||
if not iface.node.path_exists(device_path):
|
||||
device_path = "/dev/net/tun"
|
||||
add_param(transport_element, "devicepath", device_path)
|
||||
if flowcontrol:
|
||||
add_param(transport_element, "flowcontrolenable", "on")
|
||||
doc_name = "transport"
|
||||
transport_name = transport_file_name(iface)
|
||||
create_iface_file(iface, transport_element, doc_name, transport_name)
|
||||
create_node_file(iface.node, transport_element, doc_name, transport_name)
|
||||
|
||||
|
||||
def create_phy_xml(
|
||||
|
@ -250,7 +257,7 @@ def create_phy_xml(
|
|||
phy_element, emane_model.phy_config, config, emane_model.config_ignore
|
||||
)
|
||||
file_name = phy_file_name(iface)
|
||||
create_iface_file(iface, phy_element, "phy", file_name)
|
||||
create_node_file(iface.node, phy_element, "phy", file_name)
|
||||
|
||||
|
||||
def create_mac_xml(
|
||||
|
@ -273,7 +280,7 @@ def create_mac_xml(
|
|||
mac_element, emane_model.mac_config, config, emane_model.config_ignore
|
||||
)
|
||||
file_name = mac_file_name(iface)
|
||||
create_iface_file(iface, mac_element, "mac", file_name)
|
||||
create_node_file(iface.node, mac_element, "mac", file_name)
|
||||
|
||||
|
||||
def create_nem_xml(
|
||||
|
@ -298,7 +305,7 @@ def create_nem_xml(
|
|||
phy_name = phy_file_name(iface)
|
||||
etree.SubElement(nem_element, "phy", definition=phy_name)
|
||||
nem_name = nem_file_name(iface)
|
||||
create_iface_file(iface, nem_element, "nem", nem_name)
|
||||
create_node_file(iface.node, nem_element, "nem", nem_name)
|
||||
|
||||
|
||||
def create_event_service_xml(
|
||||
|
@ -351,7 +358,7 @@ def nem_file_name(iface: CoreInterface) -> str:
|
|||
:param iface: interface running emane
|
||||
:return: nem xm file name
|
||||
"""
|
||||
append = "-raw" if iface.is_raw() else ""
|
||||
append = "-raw" if not isinstance(iface.node, CoreNode) else ""
|
||||
return f"{iface.name}-nem{append}.xml"
|
||||
|
||||
|
||||
|
|
|
@ -1,74 +1,51 @@
|
|||
"""
|
||||
Example using gRPC API to create a simple EMANE 80211 network.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
# required imports
|
||||
from core.api.grpc import client
|
||||
from core.api.grpc.core_pb2 import Node, NodeType, Position, SessionState
|
||||
from core.emane.ieee80211abg import EmaneIeee80211abgModel
|
||||
|
||||
# interface helper
|
||||
iface_helper = client.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64")
|
||||
|
||||
def log_event(event):
|
||||
logging.info("event: %s", event)
|
||||
# create grpc client and connect
|
||||
core = client.CoreGrpcClient()
|
||||
core.connect()
|
||||
|
||||
# create session and get id
|
||||
response = core.create_session()
|
||||
session_id = response.session_id
|
||||
|
||||
def main():
|
||||
# helper to create interface addresses
|
||||
interface_helper = client.InterfaceHelper(ip4_prefix="10.83.0.0/24")
|
||||
# change session state to configuration so that nodes get started when added
|
||||
core.set_session_state(session_id, SessionState.CONFIGURATION)
|
||||
|
||||
# create grpc client and start connection context, which auto closes connection
|
||||
core = client.CoreGrpcClient()
|
||||
with core.context_connect():
|
||||
# create session
|
||||
response = core.create_session()
|
||||
logging.info("created session: %s", response)
|
||||
# create emane node
|
||||
position = Position(x=200, y=200)
|
||||
emane = Node(type=NodeType.EMANE, position=position, emane=EmaneIeee80211abgModel.name)
|
||||
response = core.add_node(session_id, emane)
|
||||
emane_id = response.node_id
|
||||
|
||||
# handle events session may broadcast
|
||||
session_id = response.session_id
|
||||
core.events(session_id, log_event)
|
||||
# create node one
|
||||
position = Position(x=100, y=100)
|
||||
n1 = Node(type=NodeType.DEFAULT, position=position, model="mdr")
|
||||
response = core.add_node(session_id, n1)
|
||||
n1_id = response.node_id
|
||||
|
||||
# change session state to configuration so that nodes get started when added
|
||||
response = core.set_session_state(session_id, SessionState.CONFIGURATION)
|
||||
logging.info("set session state: %s", response)
|
||||
# create node two
|
||||
position = Position(x=300, y=100)
|
||||
n2 = Node(type=NodeType.DEFAULT, position=position, model="mdr")
|
||||
response = core.add_node(session_id, n2)
|
||||
n2_id = response.node_id
|
||||
|
||||
# create emane node
|
||||
position = Position(x=200, y=200)
|
||||
emane = Node(type=NodeType.EMANE, position=position)
|
||||
response = core.add_node(session_id, emane)
|
||||
logging.info("created emane: %s", response)
|
||||
emane_id = response.node_id
|
||||
# configure general emane settings
|
||||
core.set_emane_config(session_id, {"eventservicettl": "2"})
|
||||
|
||||
# an emane model must be configured for use, by the emane node
|
||||
core.set_emane_model_config(session_id, emane_id, EmaneIeee80211abgModel.name)
|
||||
# configure emane model settings
|
||||
# using a dict mapping currently support values as strings
|
||||
core.set_emane_model_config(
|
||||
session_id, emane_id, EmaneIeee80211abgModel.name, {"unicastrate": "3"}
|
||||
)
|
||||
|
||||
# create node one
|
||||
position = Position(x=100, y=100)
|
||||
node1 = Node(type=NodeType.DEFAULT, position=position)
|
||||
response = core.add_node(session_id, node1)
|
||||
logging.info("created node: %s", response)
|
||||
node1_id = response.node_id
|
||||
|
||||
# create node two
|
||||
position = Position(x=300, y=100)
|
||||
node2 = Node(type=NodeType.DEFAULT, position=position)
|
||||
response = core.add_node(session_id, node2)
|
||||
logging.info("created node: %s", response)
|
||||
node2_id = response.node_id
|
||||
|
||||
# links nodes to switch
|
||||
interface1 = interface_helper.create_iface(node1_id, 0)
|
||||
response = core.add_link(session_id, node1_id, emane_id, interface1)
|
||||
logging.info("created link: %s", response)
|
||||
interface1 = interface_helper.create_iface(node2_id, 0)
|
||||
response = core.add_link(session_id, node2_id, emane_id, interface1)
|
||||
logging.info("created link: %s", response)
|
||||
|
||||
# change session state
|
||||
response = core.set_session_state(session_id, SessionState.INSTANTIATION)
|
||||
logging.info("set session state: %s", response)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
main()
|
||||
# links nodes to emane
|
||||
iface1 = iface_helper.create_iface(n1_id, 0)
|
||||
core.add_link(session_id, n1_id, emane_id, iface1)
|
||||
iface1 = iface_helper.create_iface(n2_id, 0)
|
||||
core.add_link(session_id, n2_id, emane_id, iface1)
|
||||
|
|
36
daemon/examples/grpc/peertopeer.py
Normal file
36
daemon/examples/grpc/peertopeer.py
Normal 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)
|
|
@ -1,70 +1,44 @@
|
|||
"""
|
||||
Example using gRPC API to create a simple switch network.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
# required imports
|
||||
from core.api.grpc import client
|
||||
from core.api.grpc.core_pb2 import Node, NodeType, Position, SessionState
|
||||
|
||||
# interface helper
|
||||
iface_helper = client.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64")
|
||||
|
||||
def log_event(event):
|
||||
logging.info("event: %s", event)
|
||||
# create grpc client and connect
|
||||
core = client.CoreGrpcClient()
|
||||
core.connect()
|
||||
|
||||
# create session and get id
|
||||
response = core.create_session()
|
||||
session_id = response.session_id
|
||||
|
||||
def main():
|
||||
# helper to create interface addresses
|
||||
interface_helper = client.InterfaceHelper(ip4_prefix="10.83.0.0/24")
|
||||
# change session state to configuration so that nodes get started when added
|
||||
core.set_session_state(session_id, SessionState.CONFIGURATION)
|
||||
|
||||
# create grpc client and start connection context, which auto closes connection
|
||||
core = client.CoreGrpcClient()
|
||||
with core.context_connect():
|
||||
# create session
|
||||
response = core.create_session()
|
||||
logging.info("created session: %s", response)
|
||||
# create switch node
|
||||
position = Position(x=200, y=200)
|
||||
switch = Node(type=NodeType.SWITCH, position=position)
|
||||
response = core.add_node(session_id, switch)
|
||||
switch_id = response.node_id
|
||||
|
||||
# handle events session may broadcast
|
||||
session_id = response.session_id
|
||||
core.events(session_id, log_event)
|
||||
# create node one
|
||||
position = Position(x=100, y=100)
|
||||
n1 = Node(type=NodeType.DEFAULT, position=position, model="PC")
|
||||
response = core.add_node(session_id, n1)
|
||||
n1_id = response.node_id
|
||||
|
||||
# change session state to configuration so that nodes get started when added
|
||||
response = core.set_session_state(session_id, SessionState.CONFIGURATION)
|
||||
logging.info("set session state: %s", response)
|
||||
# create node two
|
||||
position = Position(x=300, y=100)
|
||||
n2 = Node(type=NodeType.DEFAULT, position=position, model="PC")
|
||||
response = core.add_node(session_id, n2)
|
||||
n2_id = response.node_id
|
||||
|
||||
# create switch node
|
||||
position = Position(x=200, y=200)
|
||||
switch = Node(type=NodeType.SWITCH, position=position)
|
||||
response = core.add_node(session_id, switch)
|
||||
logging.info("created switch: %s", response)
|
||||
switch_id = response.node_id
|
||||
# links nodes to switch
|
||||
iface1 = iface_helper.create_iface(n1_id, 0)
|
||||
core.add_link(session_id, n1_id, switch_id, iface1)
|
||||
iface1 = iface_helper.create_iface(n2_id, 0)
|
||||
core.add_link(session_id, n2_id, switch_id, iface1)
|
||||
|
||||
# create node one
|
||||
position = Position(x=100, y=100)
|
||||
node1 = Node(type=NodeType.DEFAULT, position=position, model="PC")
|
||||
response = core.add_node(session_id, node1)
|
||||
logging.info("created node: %s", response)
|
||||
node1_id = response.node_id
|
||||
|
||||
# create node two
|
||||
position = Position(x=300, y=100)
|
||||
node2 = Node(type=NodeType.DEFAULT, position=position, model="PC")
|
||||
response = core.add_node(session_id, node2)
|
||||
logging.info("created node: %s", response)
|
||||
node2_id = response.node_id
|
||||
|
||||
# links nodes to switch
|
||||
interface1 = interface_helper.create_iface(node1_id, 0)
|
||||
response = core.add_link(session_id, node1_id, switch_id, interface1)
|
||||
logging.info("created link: %s", response)
|
||||
interface1 = interface_helper.create_iface(node2_id, 0)
|
||||
response = core.add_link(session_id, node2_id, switch_id, interface1)
|
||||
logging.info("created link: %s", response)
|
||||
|
||||
# change session state
|
||||
response = core.set_session_state(session_id, SessionState.INSTANTIATION)
|
||||
logging.info("set session state: %s", response)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
main()
|
||||
# change session state
|
||||
core.set_session_state(session_id, SessionState.INSTANTIATION)
|
||||
|
|
|
@ -1,82 +1,58 @@
|
|||
"""
|
||||
Example using gRPC API to create a simple wlan network.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
# required imports
|
||||
from core.api.grpc import client
|
||||
from core.api.grpc.core_pb2 import Node, NodeType, Position, SessionState
|
||||
|
||||
# interface helper
|
||||
iface_helper = client.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64")
|
||||
|
||||
def log_event(event):
|
||||
logging.info("event: %s", event)
|
||||
# create grpc client and connect
|
||||
core = client.CoreGrpcClient()
|
||||
core.connect()
|
||||
|
||||
# create session and get id
|
||||
response = core.create_session()
|
||||
session_id = response.session_id
|
||||
|
||||
def main():
|
||||
# helper to create interface addresses
|
||||
interface_helper = client.InterfaceHelper(ip4_prefix="10.83.0.0/24")
|
||||
# change session state to configuration so that nodes get started when added
|
||||
core.set_session_state(session_id, SessionState.CONFIGURATION)
|
||||
|
||||
# create grpc client and start connection context, which auto closes connection
|
||||
core = client.CoreGrpcClient()
|
||||
with core.context_connect():
|
||||
# create session
|
||||
response = core.create_session()
|
||||
logging.info("created session: %s", response)
|
||||
# create wlan node
|
||||
position = Position(x=200, y=200)
|
||||
wlan = Node(type=NodeType.WIRELESS_LAN, position=position)
|
||||
response = core.add_node(session_id, wlan)
|
||||
wlan_id = response.node_id
|
||||
|
||||
# handle events session may broadcast
|
||||
session_id = response.session_id
|
||||
core.events(session_id, log_event)
|
||||
# create node one
|
||||
position = Position(x=100, y=100)
|
||||
n1 = Node(type=NodeType.DEFAULT, position=position, model="mdr")
|
||||
response = core.add_node(session_id, n1)
|
||||
n1_id = response.node_id
|
||||
|
||||
# change session state to configuration so that nodes get started when added
|
||||
response = core.set_session_state(session_id, SessionState.CONFIGURATION)
|
||||
logging.info("set session state: %s", response)
|
||||
# create node two
|
||||
position = Position(x=300, y=100)
|
||||
n2 = Node(type=NodeType.DEFAULT, position=position, model="mdr")
|
||||
response = core.add_node(session_id, n2)
|
||||
n2_id = response.node_id
|
||||
|
||||
# create wlan node
|
||||
position = Position(x=200, y=200)
|
||||
wlan = Node(type=NodeType.WIRELESS_LAN, position=position)
|
||||
response = core.add_node(session_id, wlan)
|
||||
logging.info("created wlan: %s", response)
|
||||
wlan_id = response.node_id
|
||||
# configure wlan using a dict mapping currently
|
||||
# support values as strings
|
||||
core.set_wlan_config(
|
||||
session_id,
|
||||
wlan_id,
|
||||
{
|
||||
"range": "280",
|
||||
"bandwidth": "55000000",
|
||||
"delay": "6000",
|
||||
"jitter": "5",
|
||||
"error": "5",
|
||||
},
|
||||
)
|
||||
|
||||
# change/configure wlan if desired
|
||||
# NOTE: error = loss, and named this way for legacy purposes for now
|
||||
config = {
|
||||
"bandwidth": "54000000",
|
||||
"range": "500",
|
||||
"jitter": "0",
|
||||
"delay": "5000",
|
||||
"error": "0",
|
||||
}
|
||||
response = core.set_wlan_config(session_id, wlan_id, config)
|
||||
logging.info("set wlan config: %s", response)
|
||||
# links nodes to wlan
|
||||
iface1 = iface_helper.create_iface(n1_id, 0)
|
||||
core.add_link(session_id, n1_id, wlan_id, iface1)
|
||||
iface1 = iface_helper.create_iface(n2_id, 0)
|
||||
core.add_link(session_id, n2_id, wlan_id, iface1)
|
||||
|
||||
# create node one
|
||||
position = Position(x=100, y=100)
|
||||
node1 = Node(type=NodeType.DEFAULT, position=position)
|
||||
response = core.add_node(session_id, node1)
|
||||
logging.info("created node: %s", response)
|
||||
node1_id = response.node_id
|
||||
|
||||
# create node two
|
||||
position = Position(x=300, y=100)
|
||||
node2 = Node(type=NodeType.DEFAULT, position=position)
|
||||
response = core.add_node(session_id, node2)
|
||||
logging.info("created node: %s", response)
|
||||
node2_id = response.node_id
|
||||
|
||||
# links nodes to switch
|
||||
interface1 = interface_helper.create_iface(node1_id, 0)
|
||||
response = core.add_link(session_id, node1_id, wlan_id, interface1)
|
||||
logging.info("created link: %s", response)
|
||||
interface1 = interface_helper.create_iface(node2_id, 0)
|
||||
response = core.add_link(session_id, node2_id, wlan_id, interface1)
|
||||
logging.info("created link: %s", response)
|
||||
|
||||
# change session state
|
||||
response = core.set_session_state(session_id, SessionState.INSTANTIATION)
|
||||
logging.info("set session state: %s", response)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
main()
|
||||
# change session state
|
||||
core.set_session_state(session_id, SessionState.INSTANTIATION)
|
||||
|
|
|
@ -1,12 +1,4 @@
|
|||
"""
|
||||
This is a standalone script to run a small EMANE scenario and will not interact
|
||||
with the GUI. You also must have installed OSPF MDR as noted in the documentation
|
||||
installation page.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import time
|
||||
|
||||
# required imports
|
||||
from core.emane.ieee80211abg import EmaneIeee80211abgModel
|
||||
from core.emane.nodes import EmaneNet
|
||||
from core.emulator.coreemu import CoreEmu
|
||||
|
@ -14,56 +6,51 @@ from core.emulator.data import IpPrefixes, NodeOptions
|
|||
from core.emulator.enumerations import EventTypes
|
||||
from core.nodes.base import CoreNode
|
||||
|
||||
NODES = 2
|
||||
EMANE_DELAY = 10
|
||||
# ip nerator for example
|
||||
ip_prefixes = IpPrefixes(ip4_prefix="10.0.0.0/24")
|
||||
|
||||
# create emulator instance for creating sessions and utility methods
|
||||
coreemu = CoreEmu()
|
||||
session = coreemu.create_session()
|
||||
|
||||
def main():
|
||||
# ip generator for example
|
||||
prefixes = IpPrefixes(ip4_prefix="10.83.0.0/16")
|
||||
# location information is required to be set for emane
|
||||
session.location.setrefgeo(47.57917, -122.13232, 2.0)
|
||||
session.location.refscale = 150.0
|
||||
|
||||
# create emulator instance for creating sessions and utility methods
|
||||
coreemu = CoreEmu()
|
||||
session = coreemu.create_session()
|
||||
# must be in configuration state for nodes to start, when using "node_add" below
|
||||
session.set_state(EventTypes.CONFIGURATION_STATE)
|
||||
|
||||
# must be in configuration state for nodes to start, when using "node_add" below
|
||||
session.set_state(EventTypes.CONFIGURATION_STATE)
|
||||
# create emane
|
||||
options = NodeOptions(x=200, y=200, emane=EmaneIeee80211abgModel.name)
|
||||
emane = session.add_node(EmaneNet, options=options)
|
||||
|
||||
# create emane network node, emane determines connectivity based on
|
||||
# location, so the session and nodes must be configured to provide one
|
||||
session.set_location(47.57917, -122.13232, 2.00000, 1.0)
|
||||
options = NodeOptions()
|
||||
options.set_position(80, 50)
|
||||
emane_network = session.add_node(EmaneNet, options=options, _id=100)
|
||||
session.emane.set_model(emane_network, EmaneIeee80211abgModel)
|
||||
# create nodes
|
||||
options = NodeOptions(model="mdr", x=100, y=100)
|
||||
n1 = session.add_node(CoreNode, options=options)
|
||||
options = NodeOptions(model="mdr", x=300, y=100)
|
||||
n2 = session.add_node(CoreNode, options=options)
|
||||
|
||||
# create nodes
|
||||
options = NodeOptions(model="mdr")
|
||||
for i in range(NODES):
|
||||
node = session.add_node(CoreNode, options=options)
|
||||
node.setposition(x=150 * (i + 1), y=150)
|
||||
interface = prefixes.create_iface(node)
|
||||
session.add_link(node.id, emane_network.id, iface1_data=interface)
|
||||
# configure general emane settings
|
||||
config = session.emane.get_configs()
|
||||
config.update({"eventservicettl": "2"})
|
||||
|
||||
# instantiate session
|
||||
session.instantiate()
|
||||
# configure emane model settings
|
||||
# using a dict mapping currently support values as strings
|
||||
session.emane.set_model_config(
|
||||
emane.id, EmaneIeee80211abgModel.name, {"unicastrate": "3"}
|
||||
)
|
||||
|
||||
# OSPF MDR requires some time for routes to be created
|
||||
logging.info("waiting %s seconds for OSPF MDR to create routes", EMANE_DELAY)
|
||||
time.sleep(EMANE_DELAY)
|
||||
# link nodes to emane
|
||||
iface1 = ip_prefixes.create_iface(n1)
|
||||
session.add_link(n1.id, emane.id, iface1)
|
||||
iface1 = ip_prefixes.create_iface(n2)
|
||||
session.add_link(n2.id, emane.id, iface1)
|
||||
|
||||
# get nodes to run example
|
||||
first_node = session.get_node(1, CoreNode)
|
||||
last_node = session.get_node(NODES, CoreNode)
|
||||
address = prefixes.ip4_address(first_node.id)
|
||||
logging.info("node %s pinging %s", last_node.name, address)
|
||||
output = last_node.cmd(f"ping -c 3 {address}")
|
||||
logging.info(output)
|
||||
# start session
|
||||
session.instantiate()
|
||||
|
||||
# shutdown session
|
||||
coreemu.shutdown()
|
||||
# do whatever you like here
|
||||
input("press enter to shutdown")
|
||||
|
||||
|
||||
if __name__ == "__main__" or __name__ == "__builtin__":
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
main()
|
||||
# stop session
|
||||
session.shutdown()
|
||||
|
|
35
daemon/examples/python/peertopeer.py
Normal file
35
daemon/examples/python/peertopeer.py
Normal 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()
|
|
@ -1,54 +1,41 @@
|
|||
"""
|
||||
This is a standalone script to run a small switch based scenario and will not
|
||||
interact with the GUI.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
# required imports
|
||||
from core.emulator.coreemu import CoreEmu
|
||||
from core.emulator.data import IpPrefixes
|
||||
from core.emulator.data import IpPrefixes, NodeOptions
|
||||
from core.emulator.enumerations import EventTypes
|
||||
from core.nodes.base import CoreNode
|
||||
from core.nodes.network import SwitchNode
|
||||
|
||||
NODES = 2
|
||||
# ip nerator for example
|
||||
ip_prefixes = IpPrefixes(ip4_prefix="10.0.0.0/24")
|
||||
|
||||
# create emulator instance for creating sessions and utility methods
|
||||
coreemu = CoreEmu()
|
||||
session = coreemu.create_session()
|
||||
|
||||
def main():
|
||||
# ip generator for example
|
||||
prefixes = IpPrefixes(ip4_prefix="10.83.0.0/16")
|
||||
# must be in configuration state for nodes to start, when using "node_add" below
|
||||
session.set_state(EventTypes.CONFIGURATION_STATE)
|
||||
|
||||
# create emulator instance for creating sessions and utility methods
|
||||
coreemu = CoreEmu()
|
||||
session = coreemu.create_session()
|
||||
# create switch
|
||||
options = NodeOptions(x=200, y=200)
|
||||
switch = session.add_node(SwitchNode, options=options)
|
||||
|
||||
# must be in configuration state for nodes to start, when using "node_add" below
|
||||
session.set_state(EventTypes.CONFIGURATION_STATE)
|
||||
# create nodes
|
||||
options = NodeOptions(x=100, y=100)
|
||||
n1 = session.add_node(CoreNode, options=options)
|
||||
options = NodeOptions(x=300, y=100)
|
||||
n2 = session.add_node(CoreNode, options=options)
|
||||
|
||||
# create switch network node
|
||||
switch = session.add_node(SwitchNode, _id=100)
|
||||
# link nodes to switch
|
||||
iface1 = ip_prefixes.create_iface(n1)
|
||||
session.add_link(n1.id, switch.id, iface1)
|
||||
iface1 = ip_prefixes.create_iface(n2)
|
||||
session.add_link(n2.id, switch.id, iface1)
|
||||
|
||||
# create nodes
|
||||
for _ in range(NODES):
|
||||
node = session.add_node(CoreNode)
|
||||
interface = prefixes.create_iface(node)
|
||||
session.add_link(node.id, switch.id, iface1_data=interface)
|
||||
# start session
|
||||
session.instantiate()
|
||||
|
||||
# instantiate session
|
||||
session.instantiate()
|
||||
# do whatever you like here
|
||||
input("press enter to shutdown")
|
||||
|
||||
# get nodes to run example
|
||||
first_node = session.get_node(1, CoreNode)
|
||||
last_node = session.get_node(NODES, CoreNode)
|
||||
address = prefixes.ip4_address(first_node.id)
|
||||
logging.info("node %s pinging %s", last_node.name, address)
|
||||
output = last_node.cmd(f"ping -c 3 {address}")
|
||||
logging.info(output)
|
||||
|
||||
# shutdown session
|
||||
coreemu.shutdown()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
main()
|
||||
# stop session
|
||||
session.shutdown()
|
||||
|
|
|
@ -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()
|
|
@ -1,10 +1,4 @@
|
|||
"""
|
||||
This is a standalone script to run a small WLAN based scenario and will not
|
||||
interact with the GUI.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
# required imports
|
||||
from core.emulator.coreemu import CoreEmu
|
||||
from core.emulator.data import IpPrefixes, NodeOptions
|
||||
from core.emulator.enumerations import EventTypes
|
||||
|
@ -12,47 +6,50 @@ from core.location.mobility import BasicRangeModel
|
|||
from core.nodes.base import CoreNode
|
||||
from core.nodes.network import WlanNode
|
||||
|
||||
NODES = 2
|
||||
# ip nerator for example
|
||||
ip_prefixes = IpPrefixes(ip4_prefix="10.0.0.0/24")
|
||||
|
||||
# create emulator instance for creating sessions and utility methods
|
||||
coreemu = CoreEmu()
|
||||
session = coreemu.create_session()
|
||||
|
||||
def main():
|
||||
# ip generator for example
|
||||
prefixes = IpPrefixes("10.83.0.0/16")
|
||||
# must be in configuration state for nodes to start, when using "node_add" below
|
||||
session.set_state(EventTypes.CONFIGURATION_STATE)
|
||||
|
||||
# create emulator instance for creating sessions and utility methods
|
||||
coreemu = CoreEmu()
|
||||
session = coreemu.create_session()
|
||||
# create wlan
|
||||
options = NodeOptions(x=200, y=200)
|
||||
wlan = session.add_node(WlanNode, options=options)
|
||||
|
||||
# must be in configuration state for nodes to start, when using "node_add" below
|
||||
session.set_state(EventTypes.CONFIGURATION_STATE)
|
||||
# create nodes
|
||||
options = NodeOptions(model="mdr", x=100, y=100)
|
||||
n1 = session.add_node(CoreNode, options=options)
|
||||
options = NodeOptions(model="mdr", x=300, y=100)
|
||||
n2 = session.add_node(CoreNode, options=options)
|
||||
|
||||
# create wlan network node
|
||||
wlan = session.add_node(WlanNode, _id=100)
|
||||
session.mobility.set_model(wlan, BasicRangeModel)
|
||||
# configuring wlan
|
||||
session.mobility.set_model_config(
|
||||
wlan.id,
|
||||
BasicRangeModel.name,
|
||||
{
|
||||
"range": "280",
|
||||
"bandwidth": "55000000",
|
||||
"delay": "6000",
|
||||
"jitter": "5",
|
||||
"error": "5",
|
||||
},
|
||||
)
|
||||
|
||||
# create nodes, must set a position for wlan basic range model
|
||||
options = NodeOptions(model="mdr")
|
||||
options.set_position(0, 0)
|
||||
for _ in range(NODES):
|
||||
node = session.add_node(CoreNode, options=options)
|
||||
interface = prefixes.create_iface(node)
|
||||
session.add_link(node.id, wlan.id, iface1_data=interface)
|
||||
# link nodes to wlan
|
||||
iface1 = ip_prefixes.create_iface(n1)
|
||||
session.add_link(n1.id, wlan.id, iface1)
|
||||
iface1 = ip_prefixes.create_iface(n2)
|
||||
session.add_link(n2.id, wlan.id, iface1)
|
||||
|
||||
# instantiate session
|
||||
session.instantiate()
|
||||
# start session
|
||||
session.instantiate()
|
||||
|
||||
# get nodes for example run
|
||||
first_node = session.get_node(1, CoreNode)
|
||||
last_node = session.get_node(NODES, CoreNode)
|
||||
address = prefixes.ip4_address(first_node.id)
|
||||
logging.info("node %s pinging %s", last_node.name, address)
|
||||
output = last_node.cmd(f"ping -c 3 {address}")
|
||||
logging.info(output)
|
||||
# do whatever you like here
|
||||
input("press enter to shutdown")
|
||||
|
||||
# shutdown session
|
||||
coreemu.shutdown()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
main()
|
||||
# stop session
|
||||
session.shutdown()
|
||||
|
|
|
@ -1,12 +1,19 @@
|
|||
[tool.poetry]
|
||||
name = "core"
|
||||
version = "7.1.0"
|
||||
version = "7.2.0"
|
||||
description = "CORE Common Open Research Emulator"
|
||||
authors = ["Boeing Research and Technology"]
|
||||
license = "BSD-2-Clause"
|
||||
repository = "https://github.com/coreemu/core"
|
||||
documentation = "https://coreemu.github.io/core/"
|
||||
include = ["core/gui/data/**/*", "core/configservices/*/templates"]
|
||||
include = [
|
||||
"core/api/grpc/*",
|
||||
"core/configservices/*/templates",
|
||||
"core/constants.py",
|
||||
"core/gui/data/**/*",
|
||||
]
|
||||
exclude = ["core/constants.py.in"]
|
||||
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.6"
|
||||
|
|
|
@ -117,6 +117,8 @@ def module_coretlv(patcher, global_coreemu, global_session):
|
|||
@pytest.fixture
|
||||
def grpc_server(module_grpc):
|
||||
yield module_grpc
|
||||
for session in module_grpc.coreemu.sessions.values():
|
||||
session.set_state(EventTypes.CONFIGURATION_STATE)
|
||||
module_grpc.coreemu.shutdown()
|
||||
|
||||
|
||||
|
@ -130,6 +132,7 @@ def session(global_session):
|
|||
@pytest.fixture
|
||||
def coretlv(module_coretlv):
|
||||
session = module_coretlv.session
|
||||
session.set_state(EventTypes.CONFIGURATION_STATE)
|
||||
coreemu = module_coretlv.coreemu
|
||||
coreemu.sessions[session.id] = session
|
||||
yield module_coretlv
|
||||
|
|
|
@ -8,6 +8,7 @@ from xml.etree import ElementTree
|
|||
|
||||
import pytest
|
||||
|
||||
from core import utils
|
||||
from core.emane.bypass import EmaneBypassModel
|
||||
from core.emane.commeffect import EmaneCommEffectModel
|
||||
from core.emane.emanemodel import EmaneModel
|
||||
|
@ -43,6 +44,47 @@ def ping(
|
|||
|
||||
|
||||
class TestEmane:
|
||||
def test_two_emane_interfaces(self, session: Session):
|
||||
"""
|
||||
Test nodes running multiple emane interfaces.
|
||||
|
||||
:param core.emulator.coreemu.EmuSession session: session for test
|
||||
"""
|
||||
# create emane node for networking the core nodes
|
||||
session.set_location(47.57917, -122.13232, 2.00000, 1.0)
|
||||
options = NodeOptions()
|
||||
options.set_position(80, 50)
|
||||
options.emane = EmaneIeee80211abgModel.name
|
||||
emane_net1 = session.add_node(EmaneNet, options=options)
|
||||
options.emane = EmaneRfPipeModel.name
|
||||
emane_net2 = session.add_node(EmaneNet, options=options)
|
||||
|
||||
# create nodes
|
||||
options = NodeOptions(model="mdr")
|
||||
options.set_position(150, 150)
|
||||
node1 = session.add_node(CoreNode, options=options)
|
||||
options.set_position(300, 150)
|
||||
node2 = session.add_node(CoreNode, options=options)
|
||||
|
||||
# create interfaces
|
||||
ip_prefix1 = IpPrefixes("10.0.0.0/24")
|
||||
ip_prefix2 = IpPrefixes("10.0.1.0/24")
|
||||
for i, node in enumerate([node1, node2]):
|
||||
node.setposition(x=150 * (i + 1), y=150)
|
||||
iface_data = ip_prefix1.create_iface(node)
|
||||
session.add_link(node.id, emane_net1.id, iface1_data=iface_data)
|
||||
iface_data = ip_prefix2.create_iface(node)
|
||||
session.add_link(node.id, emane_net2.id, iface1_data=iface_data)
|
||||
|
||||
# instantiate session
|
||||
session.instantiate()
|
||||
|
||||
# ping node2 from node1 on both interfaces and check success
|
||||
status = ping(node1, node2, ip_prefix1, count=5)
|
||||
assert not status
|
||||
status = ping(node1, node2, ip_prefix2, count=5)
|
||||
assert not status
|
||||
|
||||
@pytest.mark.parametrize("model", _EMANE_MODELS)
|
||||
def test_models(
|
||||
self, session: Session, model: Type[EmaneModel], ip_prefixes: IpPrefixes
|
||||
|
@ -161,3 +203,128 @@ class TestEmane:
|
|||
assert session.get_node(node2_id, CoreNode)
|
||||
assert session.get_node(emane_id, EmaneNet)
|
||||
assert value == config_value
|
||||
|
||||
def test_xml_emane_node_config(
|
||||
self, session: Session, tmpdir: TemporaryFile, ip_prefixes: IpPrefixes
|
||||
):
|
||||
# create nodes
|
||||
options = NodeOptions(model="mdr", x=50, y=50)
|
||||
node1 = session.add_node(CoreNode, options=options)
|
||||
iface1_data = ip_prefixes.create_iface(node1)
|
||||
node2 = session.add_node(CoreNode, options=options)
|
||||
iface2_data = ip_prefixes.create_iface(node2)
|
||||
|
||||
# create emane node
|
||||
options = NodeOptions(model=None, emane=EmaneRfPipeModel.name)
|
||||
emane_node = session.add_node(EmaneNet, options=options)
|
||||
|
||||
# create links
|
||||
session.add_link(node1.id, emane_node.id, iface1_data)
|
||||
session.add_link(node2.id, emane_node.id, iface2_data)
|
||||
|
||||
# set node specific conifg
|
||||
datarate = "101"
|
||||
session.emane.set_model_config(
|
||||
node1.id, EmaneRfPipeModel.name, {"datarate": datarate}
|
||||
)
|
||||
|
||||
# instantiate session
|
||||
session.instantiate()
|
||||
|
||||
# save xml
|
||||
xml_file = tmpdir.join("session.xml")
|
||||
file_path = xml_file.strpath
|
||||
session.save_xml(file_path)
|
||||
|
||||
# verify xml file was created and can be parsed
|
||||
assert xml_file.isfile()
|
||||
assert ElementTree.parse(file_path)
|
||||
|
||||
# stop current session, clearing data
|
||||
session.shutdown()
|
||||
|
||||
# verify nodes have been removed from session
|
||||
with pytest.raises(CoreError):
|
||||
assert not session.get_node(node1.id, CoreNode)
|
||||
with pytest.raises(CoreError):
|
||||
assert not session.get_node(node2.id, CoreNode)
|
||||
with pytest.raises(CoreError):
|
||||
assert not session.get_node(emane_node.id, EmaneNet)
|
||||
|
||||
# load saved xml
|
||||
session.open_xml(file_path, start=True)
|
||||
|
||||
# verify nodes have been recreated
|
||||
assert session.get_node(node1.id, CoreNode)
|
||||
assert session.get_node(node2.id, CoreNode)
|
||||
assert session.get_node(emane_node.id, EmaneNet)
|
||||
links = []
|
||||
for node_id in session.nodes:
|
||||
node = session.nodes[node_id]
|
||||
links += node.links()
|
||||
assert len(links) == 2
|
||||
config = session.emane.get_model_config(node1.id, EmaneRfPipeModel.name)
|
||||
assert config["datarate"] == datarate
|
||||
|
||||
def test_xml_emane_interface_config(
|
||||
self, session: Session, tmpdir: TemporaryFile, ip_prefixes: IpPrefixes
|
||||
):
|
||||
# create nodes
|
||||
options = NodeOptions(model="mdr", x=50, y=50)
|
||||
node1 = session.add_node(CoreNode, options=options)
|
||||
iface1_data = ip_prefixes.create_iface(node1)
|
||||
node2 = session.add_node(CoreNode, options=options)
|
||||
iface2_data = ip_prefixes.create_iface(node2)
|
||||
|
||||
# create emane node
|
||||
options = NodeOptions(model=None, emane=EmaneRfPipeModel.name)
|
||||
emane_node = session.add_node(EmaneNet, options=options)
|
||||
|
||||
# create links
|
||||
session.add_link(node1.id, emane_node.id, iface1_data)
|
||||
session.add_link(node2.id, emane_node.id, iface2_data)
|
||||
|
||||
# set node specific conifg
|
||||
datarate = "101"
|
||||
config_id = utils.iface_config_id(node1.id, iface1_data.id)
|
||||
session.emane.set_model_config(
|
||||
config_id, EmaneRfPipeModel.name, {"datarate": datarate}
|
||||
)
|
||||
|
||||
# instantiate session
|
||||
session.instantiate()
|
||||
|
||||
# save xml
|
||||
xml_file = tmpdir.join("session.xml")
|
||||
file_path = xml_file.strpath
|
||||
session.save_xml(file_path)
|
||||
|
||||
# verify xml file was created and can be parsed
|
||||
assert xml_file.isfile()
|
||||
assert ElementTree.parse(file_path)
|
||||
|
||||
# stop current session, clearing data
|
||||
session.shutdown()
|
||||
|
||||
# verify nodes have been removed from session
|
||||
with pytest.raises(CoreError):
|
||||
assert not session.get_node(node1.id, CoreNode)
|
||||
with pytest.raises(CoreError):
|
||||
assert not session.get_node(node2.id, CoreNode)
|
||||
with pytest.raises(CoreError):
|
||||
assert not session.get_node(emane_node.id, EmaneNet)
|
||||
|
||||
# load saved xml
|
||||
session.open_xml(file_path, start=True)
|
||||
|
||||
# verify nodes have been recreated
|
||||
assert session.get_node(node1.id, CoreNode)
|
||||
assert session.get_node(node2.id, CoreNode)
|
||||
assert session.get_node(emane_node.id, EmaneNet)
|
||||
links = []
|
||||
for node_id in session.nodes:
|
||||
node = session.nodes[node_id]
|
||||
links += node.links()
|
||||
assert len(links) == 2
|
||||
config = session.emane.get_model_config(config_id, EmaneRfPipeModel.name)
|
||||
assert config["datarate"] == datarate
|
||||
|
|
420
docs/grpc.md
420
docs/grpc.md
|
@ -1,16 +1,15 @@
|
|||
# Using the gRPC API
|
||||
# gRPC API
|
||||
|
||||
[gRPC](https://grpc.io/) is the main API for interfacing with CORE and used by
|
||||
the python GUI for driving all functionality.
|
||||
* Table of Contents
|
||||
{:toc}
|
||||
|
||||
Currently we are providing a python client that wraps the generated files for
|
||||
leveraging the API, but proto files noted below can also be leveraged to generate
|
||||
bindings for other languages as well.
|
||||
[gRPC](https://grpc.io/) is a client/server API for interfacing with CORE
|
||||
and used by the python GUI for driving all functionality. It is dependent
|
||||
on having a running `core-daemon` instance to be leveraged.
|
||||
|
||||
## HTTP Proxy
|
||||
|
||||
Since gRPC is HTTP2 based, proxy configurations can cause issue. You can either
|
||||
properly account for this issue or clear out your proxy when running if needed.
|
||||
A python client can be created from the raw generated grpc files included
|
||||
with CORE or one can leverage a provided gRPC client that helps encapsulate
|
||||
some of the functionality to try and help make things easier.
|
||||
|
||||
## Python Client
|
||||
|
||||
|
@ -18,6 +17,12 @@ A python client wrapper is provided at
|
|||
[CoreGrpcClient](https://github.com/coreemu/core/blob/master/daemon/core/api/grpc/client.py)
|
||||
to help provide some conveniences when using the API.
|
||||
|
||||
### Client HTTP Proxy
|
||||
|
||||
Since gRPC is HTTP2 based, proxy configurations can cause issues. By default
|
||||
the client disables proxy support to avoid issues when a proxy is present.
|
||||
You can enable and properly account for this issue when needed.
|
||||
|
||||
## Proto Files
|
||||
|
||||
Proto files are used to define the API and protobuf messages that are used for
|
||||
|
@ -30,7 +35,400 @@ what is going on and response message values that would be returned.
|
|||
|
||||
## Examples
|
||||
|
||||
Example usage of this API can be found
|
||||
### Node Models
|
||||
|
||||
When creating nodes of type `NodeType.DEFAULT` these are the default models
|
||||
and the services they map to.
|
||||
|
||||
* mdr
|
||||
* zebra, OSPFv3MDR, IPForward
|
||||
* PC
|
||||
* DefaultRoute
|
||||
* router
|
||||
* zebra, OSPFv2, OSPFv3, IPForward
|
||||
* host
|
||||
* DefaultRoute, SSH
|
||||
|
||||
### Interface Helper
|
||||
|
||||
There is an interface helper class that can be leveraged for convenience
|
||||
when creating interface data for nodes. Alternatively one can manually create
|
||||
a `core.api.grpc.core_pb2.Interface` class instead with appropriate information.
|
||||
|
||||
Manually creating gRPC interface data:
|
||||
```python
|
||||
from core.api.grpc import core_pb2
|
||||
# id is optional and will set to the next available id
|
||||
# name is optional and will default to eth<id>
|
||||
# mac is optional and will result in a randomly generated mac
|
||||
iface_data = core_pb2.Interface(
|
||||
id=0,
|
||||
name="eth0",
|
||||
ip4="10.0.0.1",
|
||||
ip4_mask=24,
|
||||
ip6="2001::",
|
||||
ip6_mask=64,
|
||||
)
|
||||
```
|
||||
|
||||
Leveraging the interface helper class:
|
||||
```python
|
||||
from core.api.grpc import client
|
||||
|
||||
iface_helper = client.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64")
|
||||
# node_id is used to get an ip4/ip6 address indexed from within the above prefixes
|
||||
# iface_id is required and used exactly for that
|
||||
# name is optional and would default to eth<id>
|
||||
# mac is optional and will result in a randomly generated mac
|
||||
iface_data = iface_helper.create_iface(
|
||||
node_id=1, iface_id=0, name="eth0", mac="00:00:00:00:aa:00"
|
||||
)
|
||||
```
|
||||
|
||||
### Listening to Events
|
||||
|
||||
Various events that can occur within a session can be listened to.
|
||||
|
||||
Event types:
|
||||
* session - events for changes in session state and mobility start/stop/pause
|
||||
* node - events for node movements and icon changes
|
||||
* link - events for link configuration changes and wireless link add/delete
|
||||
* config - configuration events when legacy gui joins a session
|
||||
* exception - alert/error events
|
||||
* file - file events when the legacy gui joins a session
|
||||
|
||||
```python
|
||||
from core.api.grpc import core_pb2
|
||||
|
||||
def event_listener(event):
|
||||
print(event)
|
||||
|
||||
# provide no events to listen to all events
|
||||
core.events(session_id, event_listener)
|
||||
|
||||
# provide events to listen to specific events
|
||||
core.events(session_id, event_listener, [core_pb2.EventType.NODE])
|
||||
```
|
||||
|
||||
### Configuring Links
|
||||
|
||||
Links can be configured at the time of creation or during runtime.
|
||||
|
||||
Currently supported configuration options:
|
||||
* bandwidth (bps)
|
||||
* delay (us)
|
||||
* duplicate (%)
|
||||
* jitter (us)
|
||||
* loss (%)
|
||||
|
||||
```python
|
||||
from core.api.grpc import core_pb2
|
||||
|
||||
# configuring when creating a link
|
||||
options = core_pb2.LinkOptions(
|
||||
bandwidth=54_000_000,
|
||||
delay=5000,
|
||||
dup=5,
|
||||
loss=5.5,
|
||||
jitter=0,
|
||||
)
|
||||
core.add_link(session_id, n1_id, n2_id, iface1_data, iface2_data, options)
|
||||
|
||||
# configuring during runtime
|
||||
core.edit_link(session_id, n1_id, n2_id, iface1_id, iface2_id, options)
|
||||
```
|
||||
|
||||
### Peer to Peer Example
|
||||
```python
|
||||
# required imports
|
||||
from core.api.grpc import client
|
||||
from core.api.grpc.core_pb2 import Node, NodeType, Position, SessionState
|
||||
|
||||
# interface helper
|
||||
iface_helper = client.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64")
|
||||
|
||||
# create grpc client and connect
|
||||
core = client.CoreGrpcClient()
|
||||
core.connect()
|
||||
|
||||
# create session and get id
|
||||
response = core.create_session()
|
||||
session_id = response.session_id
|
||||
|
||||
# change session state to configuration so that nodes get started when added
|
||||
core.set_session_state(session_id, SessionState.CONFIGURATION)
|
||||
|
||||
# create node one
|
||||
position = Position(x=100, y=100)
|
||||
n1 = Node(type=NodeType.DEFAULT, position=position, model="PC")
|
||||
response = core.add_node(session_id, n1)
|
||||
n1_id = response.node_id
|
||||
|
||||
# create node two
|
||||
position = Position(x=300, y=100)
|
||||
n2 = Node(type=NodeType.DEFAULT, position=position, model="PC")
|
||||
response = core.add_node(session_id, n2)
|
||||
n2_id = response.node_id
|
||||
|
||||
# links nodes together
|
||||
iface1 = iface_helper.create_iface(n1_id, 0)
|
||||
iface2 = iface_helper.create_iface(n2_id, 0)
|
||||
core.add_link(session_id, n1_id, n2_id, iface1, iface2)
|
||||
|
||||
# change session state
|
||||
core.set_session_state(session_id, SessionState.INSTANTIATION)
|
||||
```
|
||||
|
||||
### Switch/Hub Example
|
||||
```python
|
||||
# required imports
|
||||
from core.api.grpc import client
|
||||
from core.api.grpc.core_pb2 import Node, NodeType, Position, SessionState
|
||||
|
||||
# interface helper
|
||||
iface_helper = client.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64")
|
||||
|
||||
# create grpc client and connect
|
||||
core = client.CoreGrpcClient()
|
||||
core.connect()
|
||||
|
||||
# create session and get id
|
||||
response = core.create_session()
|
||||
session_id = response.session_id
|
||||
|
||||
# change session state to configuration so that nodes get started when added
|
||||
core.set_session_state(session_id, SessionState.CONFIGURATION)
|
||||
|
||||
# create switch node
|
||||
position = Position(x=200, y=200)
|
||||
switch = Node(type=NodeType.SWITCH, position=position)
|
||||
response = core.add_node(session_id, switch)
|
||||
switch_id = response.node_id
|
||||
|
||||
# create node one
|
||||
position = Position(x=100, y=100)
|
||||
n1 = Node(type=NodeType.DEFAULT, position=position, model="PC")
|
||||
response = core.add_node(session_id, n1)
|
||||
n1_id = response.node_id
|
||||
|
||||
# create node two
|
||||
position = Position(x=300, y=100)
|
||||
n2 = Node(type=NodeType.DEFAULT, position=position, model="PC")
|
||||
response = core.add_node(session_id, n2)
|
||||
n2_id = response.node_id
|
||||
|
||||
# links nodes to switch
|
||||
iface1 = iface_helper.create_iface(n1_id, 0)
|
||||
core.add_link(session_id, n1_id, switch_id, iface1)
|
||||
iface1 = iface_helper.create_iface(n2_id, 0)
|
||||
core.add_link(session_id, n2_id, switch_id, iface1)
|
||||
|
||||
# change session state
|
||||
core.set_session_state(session_id, SessionState.INSTANTIATION)
|
||||
```
|
||||
|
||||
### WLAN Example
|
||||
```python
|
||||
# required imports
|
||||
from core.api.grpc import client
|
||||
from core.api.grpc.core_pb2 import Node, NodeType, Position, SessionState
|
||||
|
||||
# interface helper
|
||||
iface_helper = client.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64")
|
||||
|
||||
# create grpc client and connect
|
||||
core = client.CoreGrpcClient()
|
||||
core.connect()
|
||||
|
||||
# create session and get id
|
||||
response = core.create_session()
|
||||
session_id = response.session_id
|
||||
|
||||
# change session state to configuration so that nodes get started when added
|
||||
core.set_session_state(session_id, SessionState.CONFIGURATION)
|
||||
|
||||
# create wlan node
|
||||
position = Position(x=200, y=200)
|
||||
wlan = Node(type=NodeType.WIRELESS_LAN, position=position)
|
||||
response = core.add_node(session_id, wlan)
|
||||
wlan_id = response.node_id
|
||||
|
||||
# create node one
|
||||
position = Position(x=100, y=100)
|
||||
n1 = Node(type=NodeType.DEFAULT, position=position, model="mdr")
|
||||
response = core.add_node(session_id, n1)
|
||||
n1_id = response.node_id
|
||||
|
||||
# create node two
|
||||
position = Position(x=300, y=100)
|
||||
n2 = Node(type=NodeType.DEFAULT, position=position, model="mdr")
|
||||
response = core.add_node(session_id, n2)
|
||||
n2_id = response.node_id
|
||||
|
||||
# configure wlan using a dict mapping currently
|
||||
# support values as strings
|
||||
core.set_wlan_config(session_id, wlan_id, {
|
||||
"range": "280",
|
||||
"bandwidth": "55000000",
|
||||
"delay": "6000",
|
||||
"jitter": "5",
|
||||
"error": "5",
|
||||
})
|
||||
|
||||
# links nodes to wlan
|
||||
iface1 = iface_helper.create_iface(n1_id, 0)
|
||||
core.add_link(session_id, n1_id, wlan_id, iface1)
|
||||
iface1 = iface_helper.create_iface(n2_id, 0)
|
||||
core.add_link(session_id, n2_id, wlan_id, iface1)
|
||||
|
||||
# change session state
|
||||
core.set_session_state(session_id, SessionState.INSTANTIATION)
|
||||
```
|
||||
|
||||
### EMANE Example
|
||||
|
||||
For EMANE you can import and use one of the existing models and
|
||||
use its name for configuration.
|
||||
|
||||
Current models:
|
||||
* core.emane.ieee80211abg.EmaneIeee80211abgModel
|
||||
* core.emane.rfpipe.EmaneRfPipeModel
|
||||
* core.emane.tdma.EmaneTdmaModel
|
||||
* core.emane.bypass.EmaneBypassModel
|
||||
|
||||
Their configurations options are driven dynamically from parsed EMANE manifest files
|
||||
from the installed version of EMANE.
|
||||
|
||||
Options and their purpose can be found at the [EMANE Wiki](https://github.com/adjacentlink/emane/wiki).
|
||||
|
||||
If configuring EMANE global settings or model mac/phy specific settings, any value not provided
|
||||
will use the defaults. When no configuration is used, the defaults are used.
|
||||
|
||||
```python
|
||||
# required imports
|
||||
from core.api.grpc import client
|
||||
from core.api.grpc.core_pb2 import Node, NodeType, Position, SessionState
|
||||
from core.emane.ieee80211abg import EmaneIeee80211abgModel
|
||||
|
||||
# interface helper
|
||||
iface_helper = client.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64")
|
||||
|
||||
# create grpc client and connect
|
||||
core = client.CoreGrpcClient()
|
||||
core.connect()
|
||||
|
||||
# create session and get id
|
||||
response = core.create_session()
|
||||
session_id = response.session_id
|
||||
|
||||
# change session state to configuration so that nodes get started when added
|
||||
core.set_session_state(session_id, SessionState.CONFIGURATION)
|
||||
|
||||
# create emane node
|
||||
position = Position(x=200, y=200)
|
||||
emane = Node(type=NodeType.EMANE, position=position, emane=EmaneIeee80211abgModel.name)
|
||||
response = core.add_node(session_id, emane)
|
||||
emane_id = response.node_id
|
||||
|
||||
# create node one
|
||||
position = Position(x=100, y=100)
|
||||
n1 = Node(type=NodeType.DEFAULT, position=position, model="mdr")
|
||||
response = core.add_node(session_id, n1)
|
||||
n1_id = response.node_id
|
||||
|
||||
# create node two
|
||||
position = Position(x=300, y=100)
|
||||
n2 = Node(type=NodeType.DEFAULT, position=position, model="mdr")
|
||||
response = core.add_node(session_id, n2)
|
||||
n2_id = response.node_id
|
||||
|
||||
# configure general emane settings
|
||||
core.set_emane_config(session_id, {
|
||||
"eventservicettl": "2"
|
||||
})
|
||||
|
||||
# configure emane model settings
|
||||
# using a dict mapping currently support values as strings
|
||||
core.set_emane_model_config(session_id, emane_id, EmaneIeee80211abgModel.name, {
|
||||
"unicastrate": "3",
|
||||
})
|
||||
|
||||
# links nodes to emane
|
||||
iface1 = iface_helper.create_iface(n1_id, 0)
|
||||
core.add_link(session_id, n1_id, emane_id, iface1)
|
||||
iface1 = iface_helper.create_iface(n2_id, 0)
|
||||
core.add_link(session_id, n2_id, emane_id, iface1)
|
||||
|
||||
# change session state
|
||||
core.set_session_state(session_id, SessionState.INSTANTIATION)
|
||||
```
|
||||
|
||||
EMANE Model Configuration:
|
||||
```python
|
||||
# emane network specific config
|
||||
core.set_emane_model_config(session_id, emane_id, EmaneIeee80211abgModel.name, {
|
||||
"unicastrate": "3",
|
||||
})
|
||||
|
||||
# node specific config
|
||||
core.set_emane_model_config(session_id, node_id, EmaneIeee80211abgModel.name, {
|
||||
"unicastrate": "3",
|
||||
})
|
||||
|
||||
# node interface specific config
|
||||
core.set_emane_model_config(session_id, node_id, EmaneIeee80211abgModel.name, {
|
||||
"unicastrate": "3",
|
||||
}, iface_id)
|
||||
```
|
||||
|
||||
## Configuring a Service
|
||||
|
||||
Services help generate and run bash scripts on nodes for a given purpose.
|
||||
|
||||
Configuring the files of a service results in a specific hard coded script being
|
||||
generated, instead of the default scripts, that may leverage dynamic generation.
|
||||
|
||||
The following features can be configured for a service:
|
||||
* files - files that will be generated
|
||||
* directories - directories that will be mounted unique to the node
|
||||
* startup - commands to run start a service
|
||||
* validate - commands to run to validate a service
|
||||
* shutdown - commands to run to stop a service
|
||||
|
||||
Editing service properties:
|
||||
```python
|
||||
# configure a service, for a node, for a given session
|
||||
core.set_node_service(
|
||||
session_id,
|
||||
node_id,
|
||||
service_name,
|
||||
files=["file1.sh", "file2.sh"],
|
||||
directories=["/etc/node"],
|
||||
startup=["bash file1.sh"],
|
||||
validate=[],
|
||||
shutdown=[],
|
||||
)
|
||||
```
|
||||
|
||||
When editing a service file, it must be the name of `config`
|
||||
file that the service will generate.
|
||||
|
||||
Editing a service file:
|
||||
```python
|
||||
# to edit the contents of a generated file you can specify
|
||||
# the service, the file name, and its contents
|
||||
core.set_node_service_file(
|
||||
session_id,
|
||||
node_id,
|
||||
service_name,
|
||||
file_name,
|
||||
"echo hello",
|
||||
)
|
||||
```
|
||||
|
||||
## File Examples
|
||||
|
||||
File versions of the network examples can be found
|
||||
[here](https://github.com/coreemu/core/tree/master/daemon/examples/grpc).
|
||||
These examples will create a session using the gRPC API when the core-daemon is running.
|
||||
|
||||
|
|
|
@ -24,9 +24,9 @@ networking scenarios, security studies, and increasing the size of physical test
|
|||
|[Installation](install.md)|How to install CORE and its requirements|
|
||||
|[GUI](gui.md)|How to use the GUI|
|
||||
|[(BETA) Python GUI](pygui.md)|How to use the BETA python based GUI|
|
||||
|[Python API](python.md)|Covers how to control core directly using python|
|
||||
|[gRPC API](grpc.md)|Covers how control core using gRPC|
|
||||
|[Distributed](distributed.md)|Details for running CORE across multiple servers|
|
||||
|[Python Scripting](scripting.md)|How to write python scripts for creating a CORE session|
|
||||
|[gRPC API](grpc.md)|How to enable and use the gRPC API|
|
||||
|[Node Types](nodetypes.md)|Overview of node types supported within CORE|
|
||||
|[CTRLNET](ctrlnet.md)|How to use control networks to communicate with nodes from host|
|
||||
|[Services](services.md)|Overview of provided services and creating custom ones|
|
||||
|
|
|
@ -74,11 +74,16 @@ sudo apt remove core
|
|||
|
||||
## Automated Installation
|
||||
|
||||
The automated install will install the various tools needed to help automate
|
||||
the CORE installation (python3, pip, pipx, invoke, poetry). The script will
|
||||
also automatically clone, build, and install the latest version of OSPF MDR.
|
||||
Finally it will install CORE scripts and a systemd service, which have
|
||||
been modified to use the installed poetry created virtual environment.
|
||||
> **NOTE:** installing globally can have issues with dependency conflicts etc
|
||||
|
||||
The automated install will install do the following:
|
||||
* install base tools needed for installation
|
||||
* python3, pip, pipx, invoke, poetry
|
||||
* installs system dependencies for building core
|
||||
* installs latest version of [OPSF MDR](https://github.com/USNavalResearchLaboratory/ospf-mdr)
|
||||
* installs core into poetry managed virtual environment or locally, if flag is passed
|
||||
* installs scripts pointing to python interpreter being used
|
||||
* installs systemd service, disabled by default
|
||||
|
||||
After installation has completed you should be able to run the various
|
||||
CORE scripts for running core.
|
||||
|
@ -92,10 +97,11 @@ git clone https://github.com/coreemu/core.git
|
|||
cd core
|
||||
|
||||
# run install script
|
||||
# script usage: install.sh [-d] [-v]
|
||||
# script usage: install.sh [-v] [-d] [-l] [-p <prefix>]
|
||||
#
|
||||
# -v enable verbose install
|
||||
# -d enable developer install
|
||||
# -l enable local install, not compatible with developer install
|
||||
# -p install prefix, defaults to /usr/local
|
||||
./install.sh
|
||||
```
|
||||
|
@ -117,16 +123,17 @@ After the installation complete it will have installed the following scripts.
|
|||
|
||||
| Name | Description |
|
||||
|---|---|
|
||||
| core-cleanup | tool to help removed lingering core created containers, bridges, directories |
|
||||
| core-cli | tool to query, open xml files, and send commands using gRPC |
|
||||
| core-daemon | runs the backed core server providing TLV and gRPC APIs |
|
||||
| core-gui | runs the legacy tcl/tk based GUI |
|
||||
| core-pygui | runs the new python/tk based GUI |
|
||||
| core-cleanup | tool to help removed lingering core created containers, bridges, directories |
|
||||
| core-imn-to-xml | tool to help automate converting a .imn file to .xml format |
|
||||
| core-manage | tool to add, remove, or check for services, models, and node types |
|
||||
| core-pygui | runs the new python/tk based GUI |
|
||||
| core-python | provides a convenience for running the core python virtual environment |
|
||||
| core-route-monitor | tool to help monitor traffic across nodes and feed that to SDT |
|
||||
| core-service-update | tool to update automate modifying a legacy service to match current naming |
|
||||
| coresendmsg | tool to send TLV API commands from command line |
|
||||
| core-cli | tool to query, open xml files, and send commands using gRPC |
|
||||
| core-manage | tool to add, remove, or check for services, models, and node types |
|
||||
|
||||
## Running User Scripts
|
||||
|
||||
|
@ -142,28 +149,57 @@ environment interpreter or to run a script within it.
|
|||
core-python <script>
|
||||
```
|
||||
|
||||
## Manually Install EMANE
|
||||
If CORE was installed locally, then you can run scripts using the default python3
|
||||
interpreter.
|
||||
|
||||
EMANE can be installed from deb or RPM packages or from source. See the
|
||||
[EMANE GitHub](https://github.com/adjacentlink/emane) for full details.
|
||||
```shell
|
||||
python3 <script>
|
||||
```
|
||||
|
||||
There is an invoke task to help with installing EMANE, but has issues,
|
||||
which attempts to build EMANE from source, but has issue on systems with
|
||||
older protobuf-compilers.
|
||||
## Installing EMANE
|
||||
|
||||
> **NOTE:** installng emane for the virtual environment is known to work for 1.21+
|
||||
> **NOTE:** automated install currently targets 1.25
|
||||
|
||||
There is an invoke task to help with installing EMANE, which attempts to
|
||||
build EMANE from source, but has issue on systems with older protobuf-compilers.
|
||||
|
||||
```shell
|
||||
cd <CORE_REPO>
|
||||
|
||||
# install to virtual environment
|
||||
inv install-emane
|
||||
|
||||
# install locally to system python3
|
||||
inv install-emane -l
|
||||
```
|
||||
|
||||
Alternatively, you can
|
||||
Alternatively EMANE can be installed from deb or RPM packages or from source. See the
|
||||
[EMANE GitHub](https://github.com/adjacentlink/emane) for full details.
|
||||
With the caveat that the python bindings need to be installed into CORE's
|
||||
virtualenv, unless installed locally.
|
||||
|
||||
### Installing EMANE Python Bindings for Virtual Environment
|
||||
|
||||
If you need to just install the EMANE python bindings to the CORE virtual
|
||||
environment, since you are installing EMANE itself from pre-built packages.
|
||||
You can run the following
|
||||
|
||||
Leveraging the following wiki:
|
||||
[build EMANE](https://github.com/adjacentlink/emane/wiki/Build)
|
||||
from source and install the python
|
||||
bindings into the core virtual environment.
|
||||
|
||||
The following would install the EMANE python bindings after being
|
||||
successfully built.
|
||||
```shell
|
||||
# clone and build emane python bindings
|
||||
git clone https://github.com/adjacentlink/emane.git
|
||||
cd emane
|
||||
./autogen.sh
|
||||
PYTHON=python3 ./configure --prefix=/usr
|
||||
cd src/python
|
||||
make
|
||||
|
||||
# install to core virtual environment
|
||||
cd <CORE_REPO>/daemon
|
||||
poetry run pip install <EMANE_REPO>/src/python
|
||||
```
|
||||
|
|
426
docs/python.md
Normal file
426
docs/python.md
Normal 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()
|
||||
```
|
|
@ -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)
|
||||
```
|
11
install.sh
11
install.sh
|
@ -14,7 +14,8 @@ fi
|
|||
dev=""
|
||||
verbose=""
|
||||
prefix=""
|
||||
while getopts "dvp:" opt; do
|
||||
local=""
|
||||
while getopts "dvlp:" opt; do
|
||||
case ${opt} in
|
||||
d)
|
||||
dev="-d"
|
||||
|
@ -22,14 +23,18 @@ while getopts "dvp:" opt; do
|
|||
v)
|
||||
verbose="-v"
|
||||
;;
|
||||
l)
|
||||
local="-l"
|
||||
;;
|
||||
p)
|
||||
prefix="-p ${OPTARG}"
|
||||
;;
|
||||
\?)
|
||||
echo "script usage: $(basename $0) [-d] [-v]" >&2
|
||||
echo "script usage: $(basename $0) [-v] [-d] [-l] [-p <prefix>]" >&2
|
||||
echo "" >&2
|
||||
echo "-v enable verbose install" >&2
|
||||
echo "-d enable developer install" >&2
|
||||
echo "-l enable local install, not compatible with developer install" >&2
|
||||
echo "-p install prefix, defaults to /usr/local" >&2
|
||||
exit 1
|
||||
;;
|
||||
|
@ -54,4 +59,4 @@ python3 -m pip install --user pipx
|
|||
python3 -m pipx ensurepath
|
||||
export PATH=$PATH:~/.local/bin
|
||||
pipx install invoke
|
||||
inv install ${dev} ${verbose} ${prefix}
|
||||
inv install ${dev} ${verbose} ${local} ${prefix}
|
||||
|
|
135
tasks.py
135
tasks.py
|
@ -14,6 +14,8 @@ from invoke import task, Context
|
|||
|
||||
DAEMON_DIR: str = "daemon"
|
||||
DEFAULT_PREFIX: str = "/usr/local"
|
||||
EMANE_CHECKOUT: str = "v1.2.5"
|
||||
OSPFMDR_CHECKOUT: str = "26fe5a4401a26760c553fcadfde5311199e89450"
|
||||
|
||||
|
||||
class Progress:
|
||||
|
@ -169,13 +171,18 @@ def install_core(c: Context, hide: bool) -> None:
|
|||
c.run("sudo make install", hide=hide)
|
||||
|
||||
|
||||
def install_poetry(c: Context, dev: bool, hide: bool) -> None:
|
||||
def install_poetry(c: Context, dev: bool, local: bool, hide: bool) -> None:
|
||||
c.run("pipx install poetry", hide=hide)
|
||||
args = "" if dev else "--no-dev"
|
||||
with c.cd(DAEMON_DIR):
|
||||
c.run(f"poetry install {args}", hide=hide)
|
||||
if dev:
|
||||
c.run("poetry run pre-commit install", hide=hide)
|
||||
if local:
|
||||
with c.cd(DAEMON_DIR):
|
||||
c.run("poetry build -f wheel", hide=hide)
|
||||
c.run("sudo python3 -m pip install dist/*")
|
||||
else:
|
||||
args = "" if dev else "--no-dev"
|
||||
with c.cd(DAEMON_DIR):
|
||||
c.run(f"poetry install {args}", hide=hide)
|
||||
if dev:
|
||||
c.run("poetry run pre-commit install", hide=hide)
|
||||
|
||||
|
||||
def install_ospf_mdr(c: Context, os_info: OsInfo, hide: bool) -> None:
|
||||
|
@ -186,12 +193,11 @@ def install_ospf_mdr(c: Context, os_info: OsInfo, hide: bool) -> None:
|
|||
c.run("sudo apt install -y libtool gawk libreadline-dev git", hide=hide)
|
||||
elif os_info.like == OsLike.REDHAT:
|
||||
c.run("sudo yum install -y libtool gawk readline-devel git", hide=hide)
|
||||
clone_dir = "/tmp/ospf-mdr"
|
||||
c.run(
|
||||
f"git clone https://github.com/USNavalResearchLaboratory/ospf-mdr {clone_dir}",
|
||||
hide=hide
|
||||
)
|
||||
with c.cd(clone_dir):
|
||||
ospf_dir = "../ospf-mdr"
|
||||
ospf_url = "https://github.com/USNavalResearchLaboratory/ospf-mdr.git"
|
||||
c.run(f"git clone {ospf_url} {ospf_dir}", hide=hide)
|
||||
with c.cd(ospf_dir):
|
||||
c.run(f"git checkout {OSPFMDR_CHECKOUT}", hide=hide)
|
||||
c.run("./bootstrap.sh", hide=hide)
|
||||
c.run(
|
||||
"./configure --disable-doc --enable-user=root --enable-group=root "
|
||||
|
@ -242,10 +248,11 @@ def install_service(c, verbose=False, prefix=DEFAULT_PREFIX):
|
|||
@task(
|
||||
help={
|
||||
"verbose": "enable verbose",
|
||||
"prefix": f"prefix where scripts are installed, default is {DEFAULT_PREFIX}"
|
||||
"prefix": f"prefix where scripts are installed, default is {DEFAULT_PREFIX}",
|
||||
"local": "determines if core will install to local system, default is False",
|
||||
},
|
||||
)
|
||||
def install_scripts(c, verbose=False, prefix=DEFAULT_PREFIX):
|
||||
def install_scripts(c, local=False, verbose=False, prefix=DEFAULT_PREFIX):
|
||||
"""
|
||||
install core script files, modified to leverage virtual environment
|
||||
"""
|
||||
|
@ -258,7 +265,7 @@ def install_scripts(c, verbose=False, prefix=DEFAULT_PREFIX):
|
|||
lines = f.readlines()
|
||||
first = lines[0].strip()
|
||||
# modify python scripts to point to virtual environment
|
||||
if first == "#!/usr/bin/env python3":
|
||||
if not local and first == "#!/usr/bin/env python3":
|
||||
lines[0] = f"#!{python}\n"
|
||||
temp = NamedTemporaryFile("w", delete=False)
|
||||
for line in lines:
|
||||
|
@ -272,16 +279,17 @@ def install_scripts(c, verbose=False, prefix=DEFAULT_PREFIX):
|
|||
c.run(f"sudo cp {script} {dest}", hide=hide)
|
||||
|
||||
# setup core python helper
|
||||
core_python = bin_dir.joinpath("core-python")
|
||||
temp = NamedTemporaryFile("w", delete=False)
|
||||
temp.writelines([
|
||||
"#!/bin/bash\n",
|
||||
f'exec "{python}" "$@"\n',
|
||||
])
|
||||
temp.close()
|
||||
c.run(f"sudo cp {temp.name} {core_python}", hide=hide)
|
||||
c.run(f"sudo chmod 755 {core_python}", hide=hide)
|
||||
os.unlink(temp.name)
|
||||
if not local:
|
||||
core_python = bin_dir.joinpath("core-python")
|
||||
temp = NamedTemporaryFile("w", delete=False)
|
||||
temp.writelines([
|
||||
"#!/bin/bash\n",
|
||||
f'exec "{python}" "$@"\n',
|
||||
])
|
||||
temp.close()
|
||||
c.run(f"sudo cp {temp.name} {core_python}", hide=hide)
|
||||
c.run(f"sudo chmod 755 {core_python}", hide=hide)
|
||||
os.unlink(temp.name)
|
||||
|
||||
# install core configuration file
|
||||
config_dir = "/etc/core"
|
||||
|
@ -294,13 +302,15 @@ def install_scripts(c, verbose=False, prefix=DEFAULT_PREFIX):
|
|||
help={
|
||||
"dev": "install development mode",
|
||||
"verbose": "enable verbose",
|
||||
"prefix": f"prefix where scripts are installed, default is {DEFAULT_PREFIX}"
|
||||
"local": "determines if core will install to local system, default is False",
|
||||
"prefix": f"prefix where scripts are installed, default is {DEFAULT_PREFIX}",
|
||||
},
|
||||
)
|
||||
def install(c, dev=False, verbose=False, prefix=DEFAULT_PREFIX):
|
||||
def install(c, dev=False, verbose=False, local=False, prefix=DEFAULT_PREFIX):
|
||||
"""
|
||||
install core, poetry, scripts, service, and ospf mdr
|
||||
"""
|
||||
print(f"installing core locally: {local}")
|
||||
print(f"installing core with prefix: {prefix}")
|
||||
c.run("sudo -v", hide=True)
|
||||
p = Progress(verbose)
|
||||
|
@ -316,10 +326,11 @@ def install(c, dev=False, verbose=False, prefix=DEFAULT_PREFIX):
|
|||
build_core(c, hide, prefix)
|
||||
with p.start("installing vcmd/gui"):
|
||||
install_core(c, hide)
|
||||
with p.start("installing poetry virtual environment"):
|
||||
install_poetry(c, dev, hide)
|
||||
install_type = "core" if local else "core virtual environment"
|
||||
with p.start(f"installing {install_type}"):
|
||||
install_poetry(c, dev, local, hide)
|
||||
with p.start("installing scripts and /etc/core"):
|
||||
install_scripts(c, hide, prefix)
|
||||
install_scripts(c, local, hide, prefix)
|
||||
with p.start("installing systemd service"):
|
||||
install_service(c, hide, prefix)
|
||||
with p.start("installing ospf mdr"):
|
||||
|
@ -330,9 +341,10 @@ def install(c, dev=False, verbose=False, prefix=DEFAULT_PREFIX):
|
|||
@task(
|
||||
help={
|
||||
"verbose": "enable verbose",
|
||||
"local": "used determine if core is installed locally, default is False",
|
||||
},
|
||||
)
|
||||
def install_emane(c, verbose=False):
|
||||
def install_emane(c, verbose=False, local=False):
|
||||
"""
|
||||
install emane and the python bindings
|
||||
"""
|
||||
|
@ -340,7 +352,6 @@ def install_emane(c, verbose=False):
|
|||
p = Progress(verbose)
|
||||
hide = not verbose
|
||||
os_info = get_os()
|
||||
emane_dir = "/tmp/emane"
|
||||
with p.start("installing system dependencies"):
|
||||
if os_info.like == OsLike.DEBIAN:
|
||||
c.run(
|
||||
|
@ -358,13 +369,14 @@ def install_emane(c, verbose=False):
|
|||
"protobuf-devel python3-setuptools",
|
||||
hide=hide,
|
||||
)
|
||||
emane_dir = "../emane"
|
||||
emane_python_dir = Path(emane_dir).joinpath("src/python")
|
||||
emane_url = "https://github.com/adjacentlink/emane.git"
|
||||
with p.start("cloning emane"):
|
||||
c.run(
|
||||
f"git clone https://github.com/adjacentlink/emane.git {emane_dir}",
|
||||
hide=hide
|
||||
)
|
||||
c.run(f"git clone {emane_url} {emane_dir}", hide=hide)
|
||||
with p.start("building emane"):
|
||||
with c.cd(emane_dir):
|
||||
c.run(f"git checkout {EMANE_CHECKOUT}", hide=hide)
|
||||
c.run("./autogen.sh", hide=hide)
|
||||
c.run("PYTHON=python3 ./configure --prefix=/usr", hide=hide)
|
||||
c.run("make -j$(nproc)", hide=hide)
|
||||
|
@ -372,18 +384,25 @@ def install_emane(c, verbose=False):
|
|||
with c.cd(emane_dir):
|
||||
c.run("sudo make install", hide=hide)
|
||||
with p.start("installing python binding for core"):
|
||||
with c.cd(DAEMON_DIR):
|
||||
c.run(f"poetry run pip install {emane_dir}/src/python", hide=hide)
|
||||
if local:
|
||||
with c.cd(str(emane_python_dir)):
|
||||
c.run("sudo python3 -m pip install .", hide=hide)
|
||||
else:
|
||||
with c.cd(DAEMON_DIR):
|
||||
c.run(
|
||||
f"poetry run pip install {emane_python_dir.absolute()}", hide=hide
|
||||
)
|
||||
|
||||
|
||||
@task(
|
||||
help={
|
||||
"dev": "uninstall development mode",
|
||||
"verbose": "enable verbose",
|
||||
"local": "determines if core was installed local to system, default is False",
|
||||
"prefix": f"prefix where scripts are installed, default is {DEFAULT_PREFIX}"
|
||||
},
|
||||
)
|
||||
def uninstall(c, dev=False, verbose=False, prefix=DEFAULT_PREFIX):
|
||||
def uninstall(c, dev=False, verbose=False, local=False, prefix=DEFAULT_PREFIX):
|
||||
"""
|
||||
uninstall core, scripts, service, virtual environment, and clean build directory
|
||||
"""
|
||||
|
@ -398,14 +417,18 @@ def uninstall(c, dev=False, verbose=False, prefix=DEFAULT_PREFIX):
|
|||
c.run("make clean", hide=hide)
|
||||
c.run("./bootstrap.sh clean", hide=hide)
|
||||
|
||||
python = get_python(c, warn=True)
|
||||
if python:
|
||||
with c.cd(DAEMON_DIR):
|
||||
if dev:
|
||||
with p.start("uninstalling pre-commit"):
|
||||
c.run("poetry run pre-commit uninstall", hide=hide)
|
||||
with p.start("uninstalling poetry virtual environment"):
|
||||
c.run(f"poetry env remove {python}", hide=hide)
|
||||
if local:
|
||||
with p.start("uninstalling core"):
|
||||
c.run("sudo python3 -m pip uninstall -y core", hide=hide)
|
||||
else:
|
||||
python = get_python(c, warn=True)
|
||||
if python:
|
||||
with c.cd(DAEMON_DIR):
|
||||
if dev:
|
||||
with p.start("uninstalling pre-commit"):
|
||||
c.run("poetry run pre-commit uninstall", hide=hide)
|
||||
with p.start("uninstalling poetry virtual environment"):
|
||||
c.run(f"poetry env remove {python}", hide=hide)
|
||||
|
||||
# remove installed files
|
||||
bin_dir = Path(prefix).joinpath("bin")
|
||||
|
@ -415,10 +438,11 @@ def uninstall(c, dev=False, verbose=False, prefix=DEFAULT_PREFIX):
|
|||
c.run(f"sudo rm -f {dest}", hide=hide)
|
||||
|
||||
# remove core-python symlink
|
||||
core_python = bin_dir.joinpath("core-python")
|
||||
c.run(f"sudo rm -f {core_python}", hide=hide)
|
||||
if not local:
|
||||
core_python = bin_dir.joinpath("core-python")
|
||||
c.run(f"sudo rm -f {core_python}", hide=hide)
|
||||
|
||||
# install service
|
||||
# remove service
|
||||
systemd_dir = Path("/lib/systemd/system/")
|
||||
service_name = "core-daemon.service"
|
||||
service_file = systemd_dir.joinpath(service_name)
|
||||
|
@ -432,15 +456,18 @@ def uninstall(c, dev=False, verbose=False, prefix=DEFAULT_PREFIX):
|
|||
help={
|
||||
"dev": "reinstall development mode",
|
||||
"verbose": "enable verbose",
|
||||
"local": "determines if core will install to local system, default is False",
|
||||
"prefix": f"prefix where scripts are installed, default is {DEFAULT_PREFIX}",
|
||||
"branch": "branch to install latest code from, default is current branch"
|
||||
},
|
||||
)
|
||||
def reinstall(c, dev=False, verbose=False, prefix=DEFAULT_PREFIX, branch=None):
|
||||
def reinstall(
|
||||
c, dev=False, verbose=False, local=False, prefix=DEFAULT_PREFIX, branch=None
|
||||
):
|
||||
"""
|
||||
run the uninstall task, get latest from specified branch, and run install task
|
||||
"""
|
||||
uninstall(c, dev, verbose, prefix)
|
||||
uninstall(c, dev, verbose, local, prefix)
|
||||
hide = not verbose
|
||||
p = Progress(verbose)
|
||||
with p.start("pulling latest code"):
|
||||
|
@ -452,7 +479,7 @@ def reinstall(c, dev=False, verbose=False, prefix=DEFAULT_PREFIX, branch=None):
|
|||
c.run("git pull", hide=hide)
|
||||
if not Path("tasks.py").exists():
|
||||
raise FileNotFoundError(f"missing tasks.py on branch: {branch}")
|
||||
install(c, dev, verbose, prefix)
|
||||
install(c, dev, verbose, local, prefix)
|
||||
|
||||
|
||||
@task
|
||||
|
@ -495,4 +522,4 @@ def test_emane(c):
|
|||
"""
|
||||
pytest = get_pytest(c)
|
||||
with c.cd(DAEMON_DIR):
|
||||
c.run(f"{pytest} -v --lf -x tests/emane", pty=True)
|
||||
c.run(f"sudo {pytest} -v --lf -x tests/emane", pty=True)
|
||||
|
|
Loading…
Reference in a new issue