commit
e0842197e3
117 changed files with 2953 additions and 2749 deletions
37
CHANGELOG.md
37
CHANGELOG.md
|
@ -1,3 +1,40 @@
|
|||
## 2020-06-11 CORE 6.5.0
|
||||
* Breaking Changes
|
||||
* CoreNode.newnetif - both parameters are required and now takes an InterfaceData object as its second parameter
|
||||
* CoreNetworkBase.linkconfig - now takes a LinkOptions parameter instead of a subset of some of the options (ie bandwidth, delay, etc)
|
||||
* \#453 - Session.add_node and Session.get_node now requires the node class you expect to create/retrieve
|
||||
* \#458 - rj45 cleanup to only inherit from one class
|
||||
* Enhancements
|
||||
* fixed issues with handling bad commands for TLV execute messages
|
||||
* removed unused boot.sh from CoreNode types
|
||||
* added linkconfig to CoreNetworkBase and cleaned up function signature
|
||||
* emane position hook now saves geo position to node
|
||||
* emane pathloss support
|
||||
* core.emulator.emudata leveraged dataclass and type hinting
|
||||
* \#459 - updated transport type usage to an enum
|
||||
* \#460 - updated network policy type usage to an enum
|
||||
* Python GUI Enhancements
|
||||
* fixed throughput events do not work for joined sessions
|
||||
* fixed exiting app with a toolbar picker showing
|
||||
* fixed issue with creating interfaces and reusing subnets after deletion
|
||||
* fixed issue with moving text shapes
|
||||
* fixed scaling with custom node selected
|
||||
* fixed toolbar state switching issues
|
||||
* enable/disable toolbar when running stop/start
|
||||
* marker config integrated into toolbar
|
||||
* improved color picker layout
|
||||
* shapes can now be moved while drawing shapes
|
||||
* added observers to toolbar in run mode
|
||||
* gRPC API
|
||||
* node events will now have geo positional data
|
||||
* node geo data is now returned in get_session and get_node calls
|
||||
* \#451 - added wlan link api to allow direct linking/unlinking of wireless links between nodes
|
||||
* \#462 - added streaming call for sending node position/geo changes
|
||||
* \#463 - added streaming call for emane pathloss events
|
||||
* Bugfixes
|
||||
* \#454 - fixed issue creating docker nodes, but containers are now required to have networking tools
|
||||
* \#466 - fixed issue in python gui when xml file is loading nodes with no ip4 addresses
|
||||
|
||||
## 2020-05-11 CORE 6.4.0
|
||||
* Enhancements
|
||||
* updates to core-route-monitor, allow specific session, configurable settings, and properly
|
||||
|
|
|
@ -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, 6.4.0)
|
||||
AC_INIT(core, 6.5.0)
|
||||
|
||||
# autoconf and automake initialization
|
||||
AC_CONFIG_SRCDIR([netns/version.h.in])
|
||||
|
|
|
@ -1,16 +1,14 @@
|
|||
"""
|
||||
gRpc client for interfacing with CORE, when gRPC mode is enabled.
|
||||
gRpc client for interfacing with CORE.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import threading
|
||||
from contextlib import contextmanager
|
||||
from typing import Any, Callable, Dict, Generator, List
|
||||
from typing import Any, Callable, Dict, Generator, Iterable, List
|
||||
|
||||
import grpc
|
||||
import netaddr
|
||||
|
||||
from core import utils
|
||||
from core.api.grpc import configservices_pb2, core_pb2, core_pb2_grpc
|
||||
from core.api.grpc.configservices_pb2 import (
|
||||
GetConfigServiceDefaultsRequest,
|
||||
|
@ -31,6 +29,8 @@ from core.api.grpc.emane_pb2 import (
|
|||
EmaneLinkRequest,
|
||||
EmaneLinkResponse,
|
||||
EmaneModelConfig,
|
||||
EmanePathlossesRequest,
|
||||
EmanePathlossesResponse,
|
||||
GetEmaneConfigRequest,
|
||||
GetEmaneConfigResponse,
|
||||
GetEmaneEventChannelRequest,
|
||||
|
@ -89,7 +89,10 @@ from core.api.grpc.wlan_pb2 import (
|
|||
SetWlanConfigRequest,
|
||||
SetWlanConfigResponse,
|
||||
WlanConfig,
|
||||
WlanLinkRequest,
|
||||
WlanLinkResponse,
|
||||
)
|
||||
from core.emulator.emudata import IpPrefixes
|
||||
|
||||
|
||||
class InterfaceHelper:
|
||||
|
@ -105,78 +108,29 @@ class InterfaceHelper:
|
|||
:param ip6_prefix: ip6 prefix to use for generation
|
||||
:raises ValueError: when both ip4 and ip6 prefixes have not been provided
|
||||
"""
|
||||
if not ip4_prefix and not ip6_prefix:
|
||||
raise ValueError("ip4 or ip6 must be provided")
|
||||
|
||||
self.ip4 = None
|
||||
if ip4_prefix:
|
||||
self.ip4 = netaddr.IPNetwork(ip4_prefix)
|
||||
self.ip6 = None
|
||||
if ip6_prefix:
|
||||
self.ip6 = netaddr.IPNetwork(ip6_prefix)
|
||||
|
||||
def ip4_address(self, node_id: int) -> str:
|
||||
"""
|
||||
Convenience method to return the IP4 address for a node.
|
||||
|
||||
:param node_id: node id to get IP4 address for
|
||||
:return: IP4 address or None
|
||||
"""
|
||||
if not self.ip4:
|
||||
raise ValueError("ip4 prefixes have not been set")
|
||||
return str(self.ip4[node_id])
|
||||
|
||||
def ip6_address(self, node_id: int) -> str:
|
||||
"""
|
||||
Convenience method to return the IP6 address for a node.
|
||||
|
||||
:param node_id: node id to get IP6 address for
|
||||
:return: IP4 address or None
|
||||
"""
|
||||
if not self.ip6:
|
||||
raise ValueError("ip6 prefixes have not been set")
|
||||
return str(self.ip6[node_id])
|
||||
self.prefixes = IpPrefixes(ip4_prefix, ip6_prefix)
|
||||
|
||||
def create_interface(
|
||||
self, node_id: int, interface_id: int, name: str = None, mac: str = None
|
||||
) -> core_pb2.Interface:
|
||||
"""
|
||||
Creates interface data for linking nodes, using the nodes unique id for
|
||||
generation, along with a random mac address, unless provided.
|
||||
Create an interface protobuf object.
|
||||
|
||||
:param node_id: node id to create interface for
|
||||
:param interface_id: interface id for interface
|
||||
:param name: name to set for interface, default is eth{id}
|
||||
:param mac: mac address to use for this interface, default is random
|
||||
generation
|
||||
:return: new interface data for the provided node
|
||||
:param interface_id: interface id
|
||||
:param name: name of interface
|
||||
:param mac: mac address for interface
|
||||
:return: interface protobuf
|
||||
"""
|
||||
# generate ip4 data
|
||||
ip4 = None
|
||||
ip4_mask = None
|
||||
if self.ip4:
|
||||
ip4 = self.ip4_address(node_id)
|
||||
ip4_mask = self.ip4.prefixlen
|
||||
|
||||
# generate ip6 data
|
||||
ip6 = None
|
||||
ip6_mask = None
|
||||
if self.ip6:
|
||||
ip6 = self.ip6_address(node_id)
|
||||
ip6_mask = self.ip6.prefixlen
|
||||
|
||||
# random mac
|
||||
if not mac:
|
||||
mac = utils.random_mac()
|
||||
|
||||
interface_data = self.prefixes.gen_interface(node_id, name, mac)
|
||||
return core_pb2.Interface(
|
||||
id=interface_id,
|
||||
name=name,
|
||||
ip4=ip4,
|
||||
ip4mask=ip4_mask,
|
||||
ip6=ip6,
|
||||
ip6mask=ip6_mask,
|
||||
mac=str(mac),
|
||||
name=interface_data.name,
|
||||
ip4=interface_data.ip4,
|
||||
ip4mask=interface_data.ip4_mask,
|
||||
ip6=interface_data.ip6,
|
||||
ip6mask=interface_data.ip6_mask,
|
||||
mac=interface_data.mac,
|
||||
)
|
||||
|
||||
|
||||
|
@ -285,6 +239,7 @@ class CoreGrpcClient:
|
|||
|
||||
:param session_id: id of session
|
||||
:return: stop session response
|
||||
:raises grpc.RpcError: when session doesn't exist
|
||||
"""
|
||||
request = core_pb2.StopSessionRequest(session_id=session_id)
|
||||
return self.stub.StopSession(request)
|
||||
|
@ -569,6 +524,18 @@ class CoreGrpcClient:
|
|||
)
|
||||
return self.stub.EditNode(request)
|
||||
|
||||
def move_nodes(
|
||||
self, move_iterator: Iterable[core_pb2.MoveNodesRequest]
|
||||
) -> core_pb2.MoveNodesResponse:
|
||||
"""
|
||||
Stream node movements using the provided iterator.
|
||||
|
||||
:param move_iterator: iterator for generating node movements
|
||||
:return: move nodes response
|
||||
:raises grpc.RpcError: when session or nodes do not exist
|
||||
"""
|
||||
return self.stub.MoveNodes(move_iterator)
|
||||
|
||||
def delete_node(self, session_id: int, node_id: int) -> core_pb2.DeleteNodeResponse:
|
||||
"""
|
||||
Delete node from session.
|
||||
|
@ -582,7 +549,12 @@ class CoreGrpcClient:
|
|||
return self.stub.DeleteNode(request)
|
||||
|
||||
def node_command(
|
||||
self, session_id: int, node_id: int, command: str
|
||||
self,
|
||||
session_id: int,
|
||||
node_id: int,
|
||||
command: str,
|
||||
wait: bool = True,
|
||||
shell: bool = False,
|
||||
) -> core_pb2.NodeCommandResponse:
|
||||
"""
|
||||
Send command to a node and get the output.
|
||||
|
@ -590,11 +562,17 @@ class CoreGrpcClient:
|
|||
:param session_id: session id
|
||||
:param node_id: node id
|
||||
:param command: command to run on node
|
||||
:param wait: wait for command to complete
|
||||
:param shell: send shell command
|
||||
:return: response with command combined stdout/stderr
|
||||
:raises grpc.RpcError: when session or node doesn't exist
|
||||
"""
|
||||
request = core_pb2.NodeCommandRequest(
|
||||
session_id=session_id, node_id=node_id, command=command
|
||||
session_id=session_id,
|
||||
node_id=node_id,
|
||||
command=command,
|
||||
wait=wait,
|
||||
shell=shell,
|
||||
)
|
||||
return self.stub.NodeCommand(request)
|
||||
|
||||
|
@ -1072,7 +1050,7 @@ class CoreGrpcClient:
|
|||
session_id: int,
|
||||
node_id: int,
|
||||
model: str,
|
||||
config: Dict[str, str],
|
||||
config: Dict[str, str] = None,
|
||||
interface_id: int = -1,
|
||||
) -> SetEmaneModelConfigResponse:
|
||||
"""
|
||||
|
@ -1096,9 +1074,9 @@ class CoreGrpcClient:
|
|||
|
||||
def get_emane_model_configs(self, session_id: int) -> GetEmaneModelConfigsResponse:
|
||||
"""
|
||||
Get all emane model configurations for a session.
|
||||
Get all EMANE model configurations for a session.
|
||||
|
||||
:param session_id: session id
|
||||
:param session_id: session to get emane model configs
|
||||
:return: response with a dictionary of node/interface ids to configurations
|
||||
:raises grpc.RpcError: when session doesn't exist
|
||||
"""
|
||||
|
@ -1109,9 +1087,10 @@ class CoreGrpcClient:
|
|||
"""
|
||||
Save the current scenario to an XML file.
|
||||
|
||||
:param session_id: session id
|
||||
:param session_id: session to save xml file for
|
||||
:param file_path: local path to save scenario XML file to
|
||||
:return: nothing
|
||||
:raises grpc.RpcError: when session doesn't exist
|
||||
"""
|
||||
request = core_pb2.SaveXmlRequest(session_id=session_id)
|
||||
response = self.stub.SaveXml(request)
|
||||
|
@ -1137,11 +1116,12 @@ class CoreGrpcClient:
|
|||
"""
|
||||
Helps broadcast wireless link/unlink between EMANE nodes.
|
||||
|
||||
:param session_id: session id
|
||||
:param nem_one:
|
||||
:param nem_two:
|
||||
:param session_id: session to emane link
|
||||
:param nem_one: first nem for emane link
|
||||
:param nem_two: second nem for emane link
|
||||
:param linked: True to link, False to unlink
|
||||
:return: core_pb2.EmaneLinkResponse
|
||||
:return: get emane link response
|
||||
:raises grpc.RpcError: when session or nodes related to nems do not exist
|
||||
"""
|
||||
request = EmaneLinkRequest(
|
||||
session_id=session_id, nem_one=nem_one, nem_two=nem_two, linked=linked
|
||||
|
@ -1153,30 +1133,57 @@ class CoreGrpcClient:
|
|||
Retrieves a list of interfaces available on the host machine that are not
|
||||
a part of a CORE session.
|
||||
|
||||
:return: core_pb2.GetInterfacesResponse
|
||||
:return: get interfaces response
|
||||
"""
|
||||
request = core_pb2.GetInterfacesRequest()
|
||||
return self.stub.GetInterfaces(request)
|
||||
|
||||
def get_config_services(self) -> GetConfigServicesResponse:
|
||||
"""
|
||||
Retrieve all known config services.
|
||||
|
||||
:return: get config services response
|
||||
"""
|
||||
request = GetConfigServicesRequest()
|
||||
return self.stub.GetConfigServices(request)
|
||||
|
||||
def get_config_service_defaults(
|
||||
self, name: str
|
||||
) -> GetConfigServiceDefaultsResponse:
|
||||
"""
|
||||
Retrieves config service default values.
|
||||
|
||||
:param name: name of service to get defaults for
|
||||
:return: get config service defaults
|
||||
"""
|
||||
request = GetConfigServiceDefaultsRequest(name=name)
|
||||
return self.stub.GetConfigServiceDefaults(request)
|
||||
|
||||
def get_node_config_service_configs(
|
||||
self, session_id: int
|
||||
) -> GetNodeConfigServiceConfigsResponse:
|
||||
"""
|
||||
Retrieves all node config service configurations for a session.
|
||||
|
||||
:param session_id: session to get config service configurations for
|
||||
:return: get node config service configs response
|
||||
:raises grpc.RpcError: when session doesn't exist
|
||||
"""
|
||||
request = GetNodeConfigServiceConfigsRequest(session_id=session_id)
|
||||
return self.stub.GetNodeConfigServiceConfigs(request)
|
||||
|
||||
def get_node_config_service(
|
||||
self, session_id: int, node_id: int, name: str
|
||||
) -> GetNodeConfigServiceResponse:
|
||||
"""
|
||||
Retrieves information for a specific config service on a node.
|
||||
|
||||
:param session_id: session node belongs to
|
||||
:param node_id: id of node to get service information from
|
||||
:param name: name of service
|
||||
:return: get node config service response
|
||||
:raises grpc.RpcError: when session or node doesn't exist
|
||||
"""
|
||||
request = GetNodeConfigServiceRequest(
|
||||
session_id=session_id, node_id=node_id, name=name
|
||||
)
|
||||
|
@ -1185,25 +1192,92 @@ class CoreGrpcClient:
|
|||
def get_node_config_services(
|
||||
self, session_id: int, node_id: int
|
||||
) -> GetNodeConfigServicesResponse:
|
||||
"""
|
||||
Retrieves the config services currently assigned to a node.
|
||||
|
||||
:param session_id: session node belongs to
|
||||
:param node_id: id of node to get config services for
|
||||
:return: get node config services response
|
||||
:raises grpc.RpcError: when session or node doesn't exist
|
||||
"""
|
||||
request = GetNodeConfigServicesRequest(session_id=session_id, node_id=node_id)
|
||||
return self.stub.GetNodeConfigServices(request)
|
||||
|
||||
def set_node_config_service(
|
||||
self, session_id: int, node_id: int, name: str, config: Dict[str, str]
|
||||
) -> SetNodeConfigServiceResponse:
|
||||
"""
|
||||
Assigns a config service to a node with the provided configuration.
|
||||
|
||||
:param session_id: session node belongs to
|
||||
:param node_id: id of node to assign config service to
|
||||
:param name: name of service
|
||||
:param config: service configuration
|
||||
:return: set node config service response
|
||||
:raises grpc.RpcError: when session or node doesn't exist
|
||||
"""
|
||||
request = SetNodeConfigServiceRequest(
|
||||
session_id=session_id, node_id=node_id, name=name, config=config
|
||||
)
|
||||
return self.stub.SetNodeConfigService(request)
|
||||
|
||||
def get_emane_event_channel(self, session_id: int) -> GetEmaneEventChannelResponse:
|
||||
"""
|
||||
Retrieves the current emane event channel being used for a session.
|
||||
|
||||
:param session_id: session to get emane event channel for
|
||||
:return: emane event channel response
|
||||
:raises grpc.RpcError: when session doesn't exist
|
||||
"""
|
||||
request = GetEmaneEventChannelRequest(session_id=session_id)
|
||||
return self.stub.GetEmaneEventChannel(request)
|
||||
|
||||
def execute_script(self, script: str) -> ExecuteScriptResponse:
|
||||
"""
|
||||
Executes a python script given context of the current CoreEmu object.
|
||||
|
||||
:param script: script to execute
|
||||
:return: execute script response
|
||||
"""
|
||||
request = ExecuteScriptRequest(script=script)
|
||||
return self.stub.ExecuteScript(request)
|
||||
|
||||
def wlan_link(
|
||||
self, session_id: int, wlan: int, node_one: int, node_two: int, linked: bool
|
||||
) -> WlanLinkResponse:
|
||||
"""
|
||||
Links/unlinks nodes on the same WLAN.
|
||||
|
||||
:param session_id: session id containing wlan and nodes
|
||||
:param wlan: wlan nodes must belong to
|
||||
:param node_one: first node of pair to link/unlink
|
||||
:param node_two: second node of pair to link/unlin
|
||||
:param linked: True to link, False to unlink
|
||||
:return: wlan link response
|
||||
:raises grpc.RpcError: when session or one of the nodes do not exist
|
||||
"""
|
||||
request = WlanLinkRequest(
|
||||
session_id=session_id,
|
||||
wlan=wlan,
|
||||
node_one=node_one,
|
||||
node_two=node_two,
|
||||
linked=linked,
|
||||
)
|
||||
return self.stub.WlanLink(request)
|
||||
|
||||
def emane_pathlosses(
|
||||
self, pathloss_iterator: Iterable[EmanePathlossesRequest]
|
||||
) -> EmanePathlossesResponse:
|
||||
"""
|
||||
Stream EMANE pathloss events.
|
||||
|
||||
:param pathloss_iterator: iterator for sending emane pathloss events
|
||||
:return: emane pathloss response
|
||||
:raises grpc.RpcError: when a pathloss event session or one of the nodes do not
|
||||
exist
|
||||
"""
|
||||
return self.stub.EmanePathlosses(pathloss_iterator)
|
||||
|
||||
def connect(self) -> None:
|
||||
"""
|
||||
Open connection to server, must be closed manually.
|
||||
|
|
|
@ -23,11 +23,13 @@ def handle_node_event(event: NodeData) -> core_pb2.NodeEvent:
|
|||
:return: node event that contains node id, name, model, position, and services
|
||||
"""
|
||||
position = core_pb2.Position(x=event.x_position, y=event.y_position)
|
||||
geo = core_pb2.Geo(lat=event.latitude, lon=event.longitude, alt=event.altitude)
|
||||
node_proto = core_pb2.Node(
|
||||
id=event.id,
|
||||
name=event.name,
|
||||
model=event.model,
|
||||
position=position,
|
||||
geo=geo,
|
||||
services=event.services,
|
||||
)
|
||||
return core_pb2.NodeEvent(node=node_proto, source=event.source)
|
||||
|
|
|
@ -2,7 +2,9 @@ import logging
|
|||
import time
|
||||
from typing import Any, Dict, List, Tuple, Type
|
||||
|
||||
import grpc
|
||||
import netaddr
|
||||
from grpc import ServicerContext
|
||||
|
||||
from core import utils
|
||||
from core.api.grpc import common_pb2, core_pb2
|
||||
|
@ -13,7 +15,7 @@ from core.emulator.data import LinkData
|
|||
from core.emulator.emudata import InterfaceData, LinkOptions, NodeOptions
|
||||
from core.emulator.enumerations import LinkTypes, NodeTypes
|
||||
from core.emulator.session import Session
|
||||
from core.nodes.base import NodeBase
|
||||
from core.nodes.base import CoreNode, NodeBase
|
||||
from core.nodes.interface import CoreInterface
|
||||
from core.services.coreservices import CoreService
|
||||
|
||||
|
@ -29,17 +31,19 @@ def add_node_data(node_proto: core_pb2.Node) -> Tuple[NodeTypes, int, NodeOption
|
|||
"""
|
||||
_id = node_proto.id
|
||||
_type = NodeTypes(node_proto.type)
|
||||
options = NodeOptions(name=node_proto.name, model=node_proto.model)
|
||||
options.icon = node_proto.icon
|
||||
options.opaque = node_proto.opaque
|
||||
options.image = node_proto.image
|
||||
options.services = node_proto.services
|
||||
options.config_services = node_proto.config_services
|
||||
options = NodeOptions(
|
||||
name=node_proto.name,
|
||||
model=node_proto.model,
|
||||
icon=node_proto.icon,
|
||||
opaque=node_proto.opaque,
|
||||
image=node_proto.image,
|
||||
services=node_proto.services,
|
||||
config_services=node_proto.config_services,
|
||||
)
|
||||
if node_proto.emane:
|
||||
options.emane = node_proto.emane
|
||||
if node_proto.server:
|
||||
options.server = node_proto.server
|
||||
|
||||
position = node_proto.position
|
||||
options.set_position(position.x, position.y)
|
||||
if node_proto.HasField("geo"):
|
||||
|
@ -57,19 +61,17 @@ def link_interface(interface_proto: core_pb2.Interface) -> InterfaceData:
|
|||
"""
|
||||
interface = None
|
||||
if interface_proto:
|
||||
name = interface_proto.name
|
||||
if name == "":
|
||||
name = None
|
||||
mac = interface_proto.mac
|
||||
if mac == "":
|
||||
mac = None
|
||||
name = interface_proto.name if interface_proto.name else None
|
||||
mac = interface_proto.mac if interface_proto.mac else None
|
||||
ip4 = interface_proto.ip4 if interface_proto.ip4 else None
|
||||
ip6 = interface_proto.ip6 if interface_proto.ip6 else None
|
||||
interface = InterfaceData(
|
||||
_id=interface_proto.id,
|
||||
id=interface_proto.id,
|
||||
name=name,
|
||||
mac=mac,
|
||||
ip4=interface_proto.ip4,
|
||||
ip4=ip4,
|
||||
ip4_mask=interface_proto.ip4mask,
|
||||
ip6=interface_proto.ip6,
|
||||
ip6=ip6,
|
||||
ip6_mask=interface_proto.ip6mask,
|
||||
)
|
||||
return interface
|
||||
|
@ -86,13 +88,8 @@ def add_link_data(
|
|||
"""
|
||||
interface_one = link_interface(link_proto.interface_one)
|
||||
interface_two = link_interface(link_proto.interface_two)
|
||||
|
||||
link_type = None
|
||||
link_type_value = link_proto.type
|
||||
if link_type_value is not None:
|
||||
link_type = LinkTypes(link_type_value)
|
||||
|
||||
options = LinkOptions(_type=link_type)
|
||||
link_type = LinkTypes(link_proto.type)
|
||||
options = LinkOptions(type=link_type)
|
||||
options_data = link_proto.options
|
||||
if options_data:
|
||||
options.delay = options_data.delay
|
||||
|
@ -106,7 +103,6 @@ def add_link_data(
|
|||
options.unidirectional = options_data.unidirectional
|
||||
options.key = options_data.key
|
||||
options.opaque = options_data.opaque
|
||||
|
||||
return interface_one, interface_two, options
|
||||
|
||||
|
||||
|
@ -123,7 +119,8 @@ def create_nodes(
|
|||
funcs = []
|
||||
for node_proto in node_protos:
|
||||
_type, _id, options = add_node_data(node_proto)
|
||||
args = (_type, _id, options)
|
||||
_class = session.get_node_class(_type)
|
||||
args = (_class, _id, options)
|
||||
funcs.append((session.add_node, args, {}))
|
||||
start = time.monotonic()
|
||||
results, exceptions = utils.threadpool(funcs)
|
||||
|
@ -234,6 +231,9 @@ def get_node_proto(session: Session, node: NodeBase) -> core_pb2.Node:
|
|||
position = core_pb2.Position(
|
||||
x=node.position.x, y=node.position.y, z=node.position.z
|
||||
)
|
||||
geo = core_pb2.Geo(
|
||||
lat=node.position.lat, lon=node.position.lon, alt=node.position.alt
|
||||
)
|
||||
services = getattr(node, "services", [])
|
||||
if services is None:
|
||||
services = []
|
||||
|
@ -254,6 +254,7 @@ def get_node_proto(session: Session, node: NodeBase) -> core_pb2.Node:
|
|||
model=model,
|
||||
type=node_type.value,
|
||||
position=position,
|
||||
geo=geo,
|
||||
services=services,
|
||||
icon=node.icon,
|
||||
image=image,
|
||||
|
@ -473,3 +474,23 @@ def interface_to_proto(interface: CoreInterface) -> core_pb2.Interface:
|
|||
ip6=ip6,
|
||||
ip6mask=ip6mask,
|
||||
)
|
||||
|
||||
|
||||
def get_nem_id(node: CoreNode, netif_id: int, context: ServicerContext) -> int:
|
||||
"""
|
||||
Get nem id for a given node and interface id.
|
||||
|
||||
:param node: node to get nem id for
|
||||
:param netif_id: id of interface on node to get nem id for
|
||||
:param context: request context
|
||||
:return: nem id
|
||||
"""
|
||||
netif = node.netif(netif_id)
|
||||
if not netif:
|
||||
message = f"{node.name} missing interface {netif_id}"
|
||||
context.abort(grpc.StatusCode.NOT_FOUND, message)
|
||||
net = netif.net
|
||||
if not isinstance(net, EmaneNet):
|
||||
message = f"{node.name} interface {netif_id} is not an EMANE network"
|
||||
context.abort(grpc.StatusCode.INVALID_ARGUMENT, message)
|
||||
return net.getnemid(netif)
|
||||
|
|
|
@ -6,7 +6,7 @@ import tempfile
|
|||
import threading
|
||||
import time
|
||||
from concurrent import futures
|
||||
from typing import Type
|
||||
from typing import Iterable, Type
|
||||
|
||||
import grpc
|
||||
from grpc import ServicerContext
|
||||
|
@ -39,6 +39,8 @@ from core.api.grpc.core_pb2 import ExecuteScriptResponse
|
|||
from core.api.grpc.emane_pb2 import (
|
||||
EmaneLinkRequest,
|
||||
EmaneLinkResponse,
|
||||
EmanePathlossesRequest,
|
||||
EmanePathlossesResponse,
|
||||
GetEmaneConfigRequest,
|
||||
GetEmaneConfigResponse,
|
||||
GetEmaneEventChannelRequest,
|
||||
|
@ -102,15 +104,18 @@ from core.api.grpc.wlan_pb2 import (
|
|||
GetWlanConfigsResponse,
|
||||
SetWlanConfigRequest,
|
||||
SetWlanConfigResponse,
|
||||
WlanLinkRequest,
|
||||
WlanLinkResponse,
|
||||
)
|
||||
from core.emulator.coreemu import CoreEmu
|
||||
from core.emulator.data import LinkData
|
||||
from core.emulator.emudata import LinkOptions, NodeOptions
|
||||
from core.emulator.enumerations import EventTypes, LinkTypes, MessageFlags
|
||||
from core.emulator.session import Session
|
||||
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 CoreNodeBase, NodeBase
|
||||
from core.nodes.base import CoreNode, CoreNodeBase, NodeBase
|
||||
from core.nodes.network import WlanNode
|
||||
from core.services.coreservices import ServiceManager
|
||||
|
||||
_ONE_DAY_IN_SECONDS = 60 * 60 * 24
|
||||
|
@ -170,21 +175,22 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
|||
return session
|
||||
|
||||
def get_node(
|
||||
self, session: Session, node_id: int, context: ServicerContext
|
||||
) -> NodeBase:
|
||||
self, session: Session, node_id: int, context: ServicerContext, _class: Type[NT]
|
||||
) -> NT:
|
||||
"""
|
||||
Retrieve node given session and node id
|
||||
|
||||
:param session: session that has the node
|
||||
:param node_id: node id
|
||||
:param context:
|
||||
:param context: request
|
||||
:param _class: type of node we are expecting
|
||||
:return: node object that satisfies. If node not found then raise an exception.
|
||||
:raises Exception: raises grpc exception when node does not exist
|
||||
"""
|
||||
try:
|
||||
return session.get_node(node_id)
|
||||
except CoreError:
|
||||
context.abort(grpc.StatusCode.NOT_FOUND, f"node {node_id} not found")
|
||||
return session.get_node(node_id, _class)
|
||||
except CoreError as e:
|
||||
context.abort(grpc.StatusCode.NOT_FOUND, str(e))
|
||||
|
||||
def validate_service(
|
||||
self, name: str, context: ServicerContext
|
||||
|
@ -228,7 +234,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
|||
# add all hooks
|
||||
for hook in request.hooks:
|
||||
state = EventTypes(hook.state)
|
||||
session.add_hook(state, hook.file, None, hook.data)
|
||||
session.add_hook(state, hook.file, hook.data)
|
||||
|
||||
# create nodes
|
||||
_, exceptions = grpcutils.create_nodes(session, request.nodes)
|
||||
|
@ -261,7 +267,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
|||
|
||||
# config service configs
|
||||
for config in request.config_service_configs:
|
||||
node = self.get_node(session, config.node_id, context)
|
||||
node = self.get_node(session, config.node_id, context, CoreNode)
|
||||
service = node.config_services[config.name]
|
||||
if config.config:
|
||||
service.set_config(config.config)
|
||||
|
@ -663,7 +669,8 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
|||
logging.debug("add node: %s", request)
|
||||
session = self.get_session(request.session_id, context)
|
||||
_type, _id, options = grpcutils.add_node_data(request.node)
|
||||
node = session.add_node(_type=_type, _id=_id, options=options)
|
||||
_class = session.get_node_class(_type)
|
||||
node = session.add_node(_class, _id, options)
|
||||
return core_pb2.AddNodeResponse(node_id=node.id)
|
||||
|
||||
def GetNode(
|
||||
|
@ -678,7 +685,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
|||
"""
|
||||
logging.debug("get node: %s", request)
|
||||
session = self.get_session(request.session_id, context)
|
||||
node = self.get_node(session, request.node_id, context)
|
||||
node = self.get_node(session, request.node_id, context, NodeBase)
|
||||
interfaces = []
|
||||
for interface_id in node._netif:
|
||||
interface = node._netif[interface_id]
|
||||
|
@ -687,6 +694,42 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
|||
node_proto = grpcutils.get_node_proto(session, node)
|
||||
return core_pb2.GetNodeResponse(node=node_proto, interfaces=interfaces)
|
||||
|
||||
def MoveNodes(
|
||||
self,
|
||||
request_iterator: Iterable[core_pb2.MoveNodesRequest],
|
||||
context: ServicerContext,
|
||||
) -> core_pb2.MoveNodesResponse:
|
||||
"""
|
||||
Stream node movements
|
||||
|
||||
:param request_iterator: move nodes request iterator
|
||||
:param context: context object
|
||||
:return: move nodes response
|
||||
"""
|
||||
for request in request_iterator:
|
||||
if not request.WhichOneof("move_type"):
|
||||
raise CoreError("move nodes must provide a move type")
|
||||
session = self.get_session(request.session_id, context)
|
||||
node = self.get_node(session, request.node_id, context, NodeBase)
|
||||
options = NodeOptions()
|
||||
has_geo = request.HasField("geo")
|
||||
if has_geo:
|
||||
logging.info("has geo")
|
||||
lat = request.geo.lat
|
||||
lon = request.geo.lon
|
||||
alt = request.geo.alt
|
||||
options.set_location(lat, lon, alt)
|
||||
else:
|
||||
x = request.position.x
|
||||
y = request.position.y
|
||||
logging.info("has pos: %s,%s", x, y)
|
||||
options.set_position(x, y)
|
||||
session.edit_node(node.id, options)
|
||||
source = request.source if request.source else None
|
||||
if not has_geo:
|
||||
session.broadcast_node(node, source=source)
|
||||
return core_pb2.MoveNodesResponse()
|
||||
|
||||
def EditNode(
|
||||
self, request: core_pb2.EditNodeRequest, context: ServicerContext
|
||||
) -> core_pb2.EditNodeResponse:
|
||||
|
@ -699,9 +742,8 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
|||
"""
|
||||
logging.debug("edit node: %s", request)
|
||||
session = self.get_session(request.session_id, context)
|
||||
node = self.get_node(session, request.node_id, context)
|
||||
options = NodeOptions()
|
||||
options.icon = request.icon
|
||||
node = self.get_node(session, request.node_id, context, NodeBase)
|
||||
options = NodeOptions(icon=request.icon)
|
||||
if request.HasField("position"):
|
||||
x = request.position.x
|
||||
y = request.position.y
|
||||
|
@ -751,12 +793,14 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
|||
"""
|
||||
logging.debug("sending node command: %s", request)
|
||||
session = self.get_session(request.session_id, context)
|
||||
node = self.get_node(session, request.node_id, context)
|
||||
node = self.get_node(session, request.node_id, context, CoreNode)
|
||||
try:
|
||||
output = node.cmd(request.command)
|
||||
output = node.cmd(request.command, request.wait, request.shell)
|
||||
return_code = 0
|
||||
except CoreCommandError as e:
|
||||
output = e.stderr
|
||||
return core_pb2.NodeCommandResponse(output=output)
|
||||
return_code = e.returncode
|
||||
return core_pb2.NodeCommandResponse(output=output, return_code=return_code)
|
||||
|
||||
def GetNodeTerminal(
|
||||
self, request: core_pb2.GetNodeTerminalRequest, context: ServicerContext
|
||||
|
@ -770,7 +814,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
|||
"""
|
||||
logging.debug("getting node terminal: %s", request)
|
||||
session = self.get_session(request.session_id, context)
|
||||
node = self.get_node(session, request.node_id, context)
|
||||
node = self.get_node(session, request.node_id, context, CoreNode)
|
||||
terminal = node.termcmdstring("/bin/bash")
|
||||
return core_pb2.GetNodeTerminalResponse(terminal=terminal)
|
||||
|
||||
|
@ -786,7 +830,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
|||
"""
|
||||
logging.debug("get node links: %s", request)
|
||||
session = self.get_session(request.session_id, context)
|
||||
node = self.get_node(session, request.node_id, context)
|
||||
node = self.get_node(session, request.node_id, context, NodeBase)
|
||||
links = get_links(node)
|
||||
return core_pb2.GetNodeLinksResponse(links=links)
|
||||
|
||||
|
@ -803,14 +847,14 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
|||
logging.debug("add link: %s", request)
|
||||
# validate session and nodes
|
||||
session = self.get_session(request.session_id, context)
|
||||
self.get_node(session, request.link.node_one_id, context)
|
||||
self.get_node(session, request.link.node_two_id, context)
|
||||
self.get_node(session, request.link.node_one_id, context, NodeBase)
|
||||
self.get_node(session, request.link.node_two_id, context, NodeBase)
|
||||
|
||||
node_one_id = request.link.node_one_id
|
||||
node_two_id = request.link.node_two_id
|
||||
interface_one, interface_two, options = grpcutils.add_link_data(request.link)
|
||||
node_one_interface, node_two_interface = session.add_link(
|
||||
node_one_id, node_two_id, interface_one, interface_two, link_options=options
|
||||
node_one_id, node_two_id, interface_one, interface_two, options=options
|
||||
)
|
||||
interface_one_proto = None
|
||||
interface_two_proto = None
|
||||
|
@ -913,7 +957,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
|||
session = self.get_session(request.session_id, context)
|
||||
hook = request.hook
|
||||
state = EventTypes(hook.state)
|
||||
session.add_hook(state, hook.file, None, hook.data)
|
||||
session.add_hook(state, hook.file, hook.data)
|
||||
return core_pb2.AddHookResponse(result=True)
|
||||
|
||||
def GetMobilityConfigs(
|
||||
|
@ -994,7 +1038,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
|||
"""
|
||||
logging.debug("mobility action: %s", request)
|
||||
session = self.get_session(request.session_id, context)
|
||||
node = self.get_node(session, request.node_id, context)
|
||||
node = self.get_node(session, request.node_id, context, WlanNode)
|
||||
result = True
|
||||
if request.action == MobilityAction.START:
|
||||
node.mobility.start()
|
||||
|
@ -1121,7 +1165,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
|||
"""
|
||||
logging.debug("get node service file: %s", request)
|
||||
session = self.get_session(request.session_id, context)
|
||||
node = self.get_node(session, request.node_id, context)
|
||||
node = self.get_node(session, request.node_id, context, CoreNode)
|
||||
file_data = session.services.get_service_file(
|
||||
node, request.service, request.file
|
||||
)
|
||||
|
@ -1176,7 +1220,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
|||
"""
|
||||
logging.debug("service action: %s", request)
|
||||
session = self.get_session(request.session_id, context)
|
||||
node = self.get_node(session, request.node_id, context)
|
||||
node = self.get_node(session, request.node_id, context, CoreNode)
|
||||
service = None
|
||||
for current_service in node.services:
|
||||
if current_service.name == request.service:
|
||||
|
@ -1265,7 +1309,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
|||
wlan_config.node_id, BasicRangeModel.name, wlan_config.config
|
||||
)
|
||||
if session.state == EventTypes.RUNTIME_STATE:
|
||||
node = self.get_node(session, wlan_config.node_id, context)
|
||||
node = self.get_node(session, wlan_config.node_id, context, WlanNode)
|
||||
node.updatemodel(wlan_config.config)
|
||||
return SetWlanConfigResponse(result=True)
|
||||
|
||||
|
@ -1546,7 +1590,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
|||
:return: get node config service response
|
||||
"""
|
||||
session = self.get_session(request.session_id, context)
|
||||
node = self.get_node(session, request.node_id, context)
|
||||
node = self.get_node(session, request.node_id, context, CoreNode)
|
||||
self.validate_service(request.name, context)
|
||||
service = node.config_services.get(request.name)
|
||||
if service:
|
||||
|
@ -1628,7 +1672,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
|||
:return: get node config services response
|
||||
"""
|
||||
session = self.get_session(request.session_id, context)
|
||||
node = self.get_node(session, request.node_id, context)
|
||||
node = self.get_node(session, request.node_id, context, CoreNode)
|
||||
services = node.config_services.keys()
|
||||
return GetNodeConfigServicesResponse(services=services)
|
||||
|
||||
|
@ -1643,7 +1687,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
|||
:return: set node config service response
|
||||
"""
|
||||
session = self.get_session(request.session_id, context)
|
||||
node = self.get_node(session, request.node_id, context)
|
||||
node = self.get_node(session, request.node_id, context, CoreNode)
|
||||
self.validate_service(request.name, context)
|
||||
service = node.config_services.get(request.name)
|
||||
if service:
|
||||
|
@ -1684,3 +1728,45 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
|||
if new_sessions:
|
||||
new_session = new_sessions[0]
|
||||
return ExecuteScriptResponse(session_id=new_session)
|
||||
|
||||
def WlanLink(
|
||||
self, request: WlanLinkRequest, context: ServicerContext
|
||||
) -> WlanLinkResponse:
|
||||
session = self.get_session(request.session_id, context)
|
||||
wlan = self.get_node(session, request.wlan, context, WlanNode)
|
||||
if not isinstance(wlan.model, BasicRangeModel):
|
||||
context.abort(
|
||||
grpc.StatusCode.NOT_FOUND,
|
||||
f"wlan node {request.wlan} does not using BasicRangeModel",
|
||||
)
|
||||
n1 = self.get_node(session, request.node_one, context, CoreNode)
|
||||
n2 = self.get_node(session, request.node_two, context, CoreNode)
|
||||
n1_netif, n2_netif = None, None
|
||||
for net, netif1, netif2 in n1.commonnets(n2):
|
||||
if net == wlan:
|
||||
n1_netif = netif1
|
||||
n2_netif = netif2
|
||||
break
|
||||
result = False
|
||||
if n1_netif and n2_netif:
|
||||
if request.linked:
|
||||
wlan.link(n1_netif, n2_netif)
|
||||
else:
|
||||
wlan.unlink(n1_netif, n2_netif)
|
||||
wlan.model.sendlinkmsg(n1_netif, n2_netif, unlink=not request.linked)
|
||||
result = True
|
||||
return WlanLinkResponse(result=result)
|
||||
|
||||
def EmanePathlosses(
|
||||
self,
|
||||
request_iterator: Iterable[EmanePathlossesRequest],
|
||||
context: ServicerContext,
|
||||
) -> EmanePathlossesResponse:
|
||||
for request in request_iterator:
|
||||
session = self.get_session(request.session_id, context)
|
||||
n1 = self.get_node(session, request.node_one, context, CoreNode)
|
||||
nem1 = grpcutils.get_nem_id(n1, request.interface_one_id, context)
|
||||
n2 = self.get_node(session, request.node_two, context, CoreNode)
|
||||
nem2 = grpcutils.get_nem_id(n2, request.interface_two_id, context)
|
||||
session.emane.publish_pathloss(nem1, nem2, request.rx_one, request.rx_two)
|
||||
return EmanePathlossesResponse()
|
||||
|
|
|
@ -41,6 +41,7 @@ from core.emulator.enumerations import (
|
|||
)
|
||||
from core.errors import CoreCommandError, CoreError
|
||||
from core.location.mobility import BasicRangeModel
|
||||
from core.nodes.base import CoreNode, CoreNodeBase, NodeBase
|
||||
from core.nodes.network import WlanNode
|
||||
from core.services.coreservices import ServiceManager, ServiceShim
|
||||
|
||||
|
@ -78,15 +79,9 @@ class CoreHandler(socketserver.BaseRequestHandler):
|
|||
self._sessions_lock = threading.Lock()
|
||||
|
||||
self.handler_threads = []
|
||||
num_threads = int(server.config["numthreads"])
|
||||
if num_threads < 1:
|
||||
raise ValueError(f"invalid number of threads: {num_threads}")
|
||||
|
||||
logging.debug("launching core server handler threads: %s", num_threads)
|
||||
for _ in range(num_threads):
|
||||
thread = threading.Thread(target=self.handler_thread)
|
||||
self.handler_threads.append(thread)
|
||||
thread.start()
|
||||
thread = threading.Thread(target=self.handler_thread, daemon=True)
|
||||
thread.start()
|
||||
self.handler_threads.append(thread)
|
||||
|
||||
self.session = None
|
||||
self.coreemu = server.coreemu
|
||||
|
@ -681,10 +676,11 @@ class CoreHandler(socketserver.BaseRequestHandler):
|
|||
logging.warning("ignoring invalid message: add and delete flag both set")
|
||||
return ()
|
||||
|
||||
node_type = None
|
||||
_class = CoreNode
|
||||
node_type_value = message.get_tlv(NodeTlvs.TYPE.value)
|
||||
if node_type_value is not None:
|
||||
node_type = NodeTypes(node_type_value)
|
||||
_class = self.session.get_node_class(node_type)
|
||||
|
||||
node_id = message.get_tlv(NodeTlvs.NUMBER.value)
|
||||
|
||||
|
@ -719,7 +715,7 @@ class CoreHandler(socketserver.BaseRequestHandler):
|
|||
options.services = services.split("|")
|
||||
|
||||
if message.flags & MessageFlags.ADD.value:
|
||||
node = self.session.add_node(node_type, node_id, options)
|
||||
node = self.session.add_node(_class, node_id, options)
|
||||
if node:
|
||||
if message.flags & MessageFlags.STRING.value:
|
||||
self.node_status_request[node.id] = True
|
||||
|
@ -753,7 +749,7 @@ class CoreHandler(socketserver.BaseRequestHandler):
|
|||
node_two_id = message.get_tlv(LinkTlvs.N2_NUMBER.value)
|
||||
|
||||
interface_one = InterfaceData(
|
||||
_id=message.get_tlv(LinkTlvs.INTERFACE1_NUMBER.value),
|
||||
id=message.get_tlv(LinkTlvs.INTERFACE1_NUMBER.value),
|
||||
name=message.get_tlv(LinkTlvs.INTERFACE1_NAME.value),
|
||||
mac=message.get_tlv(LinkTlvs.INTERFACE1_MAC.value),
|
||||
ip4=message.get_tlv(LinkTlvs.INTERFACE1_IP4.value),
|
||||
|
@ -762,7 +758,7 @@ class CoreHandler(socketserver.BaseRequestHandler):
|
|||
ip6_mask=message.get_tlv(LinkTlvs.INTERFACE1_IP6_MASK.value),
|
||||
)
|
||||
interface_two = InterfaceData(
|
||||
_id=message.get_tlv(LinkTlvs.INTERFACE2_NUMBER.value),
|
||||
id=message.get_tlv(LinkTlvs.INTERFACE2_NUMBER.value),
|
||||
name=message.get_tlv(LinkTlvs.INTERFACE2_NAME.value),
|
||||
mac=message.get_tlv(LinkTlvs.INTERFACE2_MAC.value),
|
||||
ip4=message.get_tlv(LinkTlvs.INTERFACE2_IP4.value),
|
||||
|
@ -776,7 +772,7 @@ class CoreHandler(socketserver.BaseRequestHandler):
|
|||
if link_type_value is not None:
|
||||
link_type = LinkTypes(link_type_value)
|
||||
|
||||
link_options = LinkOptions(_type=link_type)
|
||||
link_options = LinkOptions(type=link_type)
|
||||
link_options.delay = message.get_tlv(LinkTlvs.DELAY.value)
|
||||
link_options.bandwidth = message.get_tlv(LinkTlvs.BANDWIDTH.value)
|
||||
link_options.session = message.get_tlv(LinkTlvs.SESSION.value)
|
||||
|
@ -836,7 +832,7 @@ class CoreHandler(socketserver.BaseRequestHandler):
|
|||
return ()
|
||||
|
||||
try:
|
||||
node = self.session.get_node(node_num)
|
||||
node = self.session.get_node(node_num, CoreNodeBase)
|
||||
|
||||
# build common TLV items for reply
|
||||
tlv_data = b""
|
||||
|
@ -880,12 +876,7 @@ class CoreHandler(socketserver.BaseRequestHandler):
|
|||
except CoreCommandError as e:
|
||||
res = e.stderr
|
||||
status = e.returncode
|
||||
logging.info(
|
||||
"done exec cmd=%s with status=%d res=(%d bytes)",
|
||||
command,
|
||||
status,
|
||||
len(res),
|
||||
)
|
||||
logging.info("done exec cmd=%s with status=%d", command, status)
|
||||
if message.flags & MessageFlags.TEXT.value:
|
||||
tlv_data += coreapi.CoreExecuteTlv.pack(
|
||||
ExecuteTlvs.RESULT.value, res
|
||||
|
@ -1233,7 +1224,7 @@ class CoreHandler(socketserver.BaseRequestHandler):
|
|||
if not node_id:
|
||||
return replies
|
||||
|
||||
node = self.session.get_node(node_id)
|
||||
node = self.session.get_node(node_id, CoreNodeBase)
|
||||
if node is None:
|
||||
logging.warning(
|
||||
"request to configure service for unknown node %s", node_id
|
||||
|
@ -1378,7 +1369,7 @@ class CoreHandler(socketserver.BaseRequestHandler):
|
|||
self.session.mobility.set_model_config(node_id, object_name, parsed_config)
|
||||
if self.session.state == EventTypes.RUNTIME_STATE and parsed_config:
|
||||
try:
|
||||
node = self.session.get_node(node_id)
|
||||
node = self.session.get_node(node_id, WlanNode)
|
||||
if object_name == BasicRangeModel.name:
|
||||
node.updatemodel(parsed_config)
|
||||
except CoreError:
|
||||
|
@ -1504,7 +1495,7 @@ class CoreHandler(socketserver.BaseRequestHandler):
|
|||
return ()
|
||||
state = int(state)
|
||||
state = EventTypes(state)
|
||||
self.session.add_hook(state, file_name, source_name, data)
|
||||
self.session.add_hook(state, file_name, data, source_name)
|
||||
return ()
|
||||
|
||||
# writing a file to the host
|
||||
|
@ -1558,7 +1549,7 @@ class CoreHandler(socketserver.BaseRequestHandler):
|
|||
logging.debug("handling event %s at %s", event_type.name, time.ctime())
|
||||
if event_type.value <= EventTypes.SHUTDOWN_STATE.value:
|
||||
if node_id is not None:
|
||||
node = self.session.get_node(node_id)
|
||||
node = self.session.get_node(node_id, NodeBase)
|
||||
|
||||
# configure mobility models for WLAN added during runtime
|
||||
if event_type == EventTypes.INSTANTIATION_STATE and isinstance(
|
||||
|
@ -1652,7 +1643,7 @@ class CoreHandler(socketserver.BaseRequestHandler):
|
|||
name = event_data.name
|
||||
|
||||
try:
|
||||
node = self.session.get_node(node_id)
|
||||
node = self.session.get_node(node_id, CoreNodeBase)
|
||||
except CoreError:
|
||||
logging.warning(
|
||||
"ignoring event for service '%s', unknown node '%s'", name, node_id
|
||||
|
@ -1888,7 +1879,7 @@ class CoreHandler(socketserver.BaseRequestHandler):
|
|||
data_types = tuple(
|
||||
repeat(ConfigDataTypes.STRING.value, len(ServiceShim.keys))
|
||||
)
|
||||
node = self.session.get_node(node_id)
|
||||
node = self.session.get_node(node_id, CoreNodeBase)
|
||||
values = ServiceShim.tovaluelist(node, service)
|
||||
config_data = ConfigData(
|
||||
message_type=0,
|
||||
|
|
|
@ -10,6 +10,9 @@ from lxml import etree
|
|||
|
||||
from core.config import ConfigGroup, Configuration
|
||||
from core.emane import emanemanifest, emanemodel
|
||||
from core.emane.nodes import EmaneNet
|
||||
from core.emulator.emudata import LinkOptions
|
||||
from core.emulator.enumerations import TransportType
|
||||
from core.nodes.interface import CoreInterface
|
||||
from core.xml import emanexml
|
||||
|
||||
|
@ -78,9 +81,9 @@ class EmaneCommEffectModel(emanemodel.EmaneModel):
|
|||
|
||||
# create and write nem document
|
||||
nem_element = etree.Element("nem", name=f"{self.name} NEM", type="unstructured")
|
||||
transport_type = "virtual"
|
||||
if interface and interface.transport_type == "raw":
|
||||
transport_type = "raw"
|
||||
transport_type = TransportType.VIRTUAL
|
||||
if interface and interface.transport_type == TransportType.RAW:
|
||||
transport_type = TransportType.RAW
|
||||
transport_file = emanexml.transport_file_name(self.id, transport_type)
|
||||
etree.SubElement(nem_element, "transport", definition=transport_file)
|
||||
|
||||
|
@ -112,14 +115,7 @@ class EmaneCommEffectModel(emanemodel.EmaneModel):
|
|||
emanexml.create_file(shim_element, "shim", shim_file)
|
||||
|
||||
def linkconfig(
|
||||
self,
|
||||
netif: CoreInterface,
|
||||
bw: float = None,
|
||||
delay: float = None,
|
||||
loss: float = None,
|
||||
duplicate: float = None,
|
||||
jitter: float = None,
|
||||
netif2: CoreInterface = None,
|
||||
self, netif: CoreInterface, options: LinkOptions, netif2: CoreInterface = None
|
||||
) -> None:
|
||||
"""
|
||||
Generate CommEffect events when a Link Message is received having
|
||||
|
@ -137,18 +133,17 @@ class EmaneCommEffectModel(emanemodel.EmaneModel):
|
|||
# TODO: batch these into multiple events per transmission
|
||||
# TODO: may want to split out seconds portion of delay and jitter
|
||||
event = CommEffectEvent()
|
||||
emane_node = self.session.get_node(self.id)
|
||||
emane_node = self.session.get_node(self.id, EmaneNet)
|
||||
nemid = emane_node.getnemid(netif)
|
||||
nemid2 = emane_node.getnemid(netif2)
|
||||
mbw = bw
|
||||
logging.info("sending comm effect event")
|
||||
event.append(
|
||||
nemid,
|
||||
latency=convert_none(delay),
|
||||
jitter=convert_none(jitter),
|
||||
loss=convert_none(loss),
|
||||
duplicate=convert_none(duplicate),
|
||||
unicast=int(convert_none(bw)),
|
||||
broadcast=int(convert_none(mbw)),
|
||||
latency=convert_none(options.delay),
|
||||
jitter=convert_none(options.jitter),
|
||||
loss=convert_none(options.per),
|
||||
duplicate=convert_none(options.dup),
|
||||
unicast=int(convert_none(options.bandwidth)),
|
||||
broadcast=int(convert_none(options.bandwidth)),
|
||||
)
|
||||
service.publish(nemid2, event)
|
||||
|
|
|
@ -6,7 +6,7 @@ import logging
|
|||
import os
|
||||
import threading
|
||||
from collections import OrderedDict
|
||||
from typing import TYPE_CHECKING, Dict, List, Set, Tuple, Type
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type
|
||||
|
||||
from core import utils
|
||||
from core.config import ConfigGroup, Configuration, ModelManager
|
||||
|
@ -19,18 +19,25 @@ from core.emane.linkmonitor import EmaneLinkMonitor
|
|||
from core.emane.nodes import EmaneNet
|
||||
from core.emane.rfpipe import EmaneRfPipeModel
|
||||
from core.emane.tdma import EmaneTdmaModel
|
||||
from core.emulator.enumerations import ConfigDataTypes, RegisterTlvs
|
||||
from core.emulator.data import LinkData
|
||||
from core.emulator.enumerations import (
|
||||
ConfigDataTypes,
|
||||
LinkTypes,
|
||||
MessageFlags,
|
||||
RegisterTlvs,
|
||||
)
|
||||
from core.errors import CoreCommandError, CoreError
|
||||
from core.nodes.base import CoreNode
|
||||
from core.nodes.base import CoreNode, NodeBase
|
||||
from core.nodes.interface import CoreInterface
|
||||
from core.nodes.network import CtrlNet
|
||||
from core.nodes.physical import Rj45Node
|
||||
from core.xml import emanexml
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.emulator.session import Session
|
||||
|
||||
try:
|
||||
from emane.events import EventService
|
||||
from emane.events import EventService, PathlossEvent
|
||||
from emane.events import LocationEvent
|
||||
from emane.events.eventserviceexception import EventServiceException
|
||||
except ImportError:
|
||||
|
@ -41,6 +48,7 @@ except ImportError:
|
|||
except ImportError:
|
||||
EventService = None
|
||||
LocationEvent = None
|
||||
PathlossEvent = None
|
||||
EventServiceException = None
|
||||
logging.debug("compatible emane python bindings not installed")
|
||||
|
||||
|
@ -458,7 +466,7 @@ class EmaneManager(ModelManager):
|
|||
model_class = self.models[model_name]
|
||||
emane_node.setmodel(model_class, config)
|
||||
|
||||
def nemlookup(self, nemid) -> Tuple[EmaneNet, CoreInterface]:
|
||||
def nemlookup(self, nemid) -> Tuple[Optional[EmaneNet], Optional[CoreInterface]]:
|
||||
"""
|
||||
Look for the given numerical NEM ID and return the first matching
|
||||
EMANE network and NEM interface.
|
||||
|
@ -476,6 +484,29 @@ class EmaneManager(ModelManager):
|
|||
|
||||
return emane_node, netif
|
||||
|
||||
def get_nem_link(
|
||||
self, nem1: int, nem2: int, flags: MessageFlags = MessageFlags.NONE
|
||||
) -> Optional[LinkData]:
|
||||
emane1, netif = self.nemlookup(nem1)
|
||||
if not emane1 or not netif:
|
||||
logging.error("invalid nem: %s", nem1)
|
||||
return None
|
||||
node1 = netif.node
|
||||
emane2, netif = self.nemlookup(nem2)
|
||||
if not emane2 or not netif:
|
||||
logging.error("invalid nem: %s", nem2)
|
||||
return None
|
||||
node2 = netif.node
|
||||
color = self.session.get_link_color(emane1.id)
|
||||
return LinkData(
|
||||
message_type=flags,
|
||||
node1_id=node1.id,
|
||||
node2_id=node2.id,
|
||||
network_id=emane1.id,
|
||||
link_type=LinkTypes.WIRELESS,
|
||||
color=color,
|
||||
)
|
||||
|
||||
def numnems(self) -> int:
|
||||
"""
|
||||
Return the number of NEMs emulated locally.
|
||||
|
@ -567,7 +598,7 @@ class EmaneManager(ModelManager):
|
|||
|
||||
run_emane_on_host = False
|
||||
for node in self.getnodes():
|
||||
if hasattr(node, "transport_type") and node.transport_type == "raw":
|
||||
if isinstance(node, Rj45Node):
|
||||
run_emane_on_host = True
|
||||
continue
|
||||
path = self.session.session_dir
|
||||
|
@ -626,7 +657,7 @@ class EmaneManager(ModelManager):
|
|||
kill_transortd = "killall -q emanetransportd"
|
||||
stop_emane_on_host = False
|
||||
for node in self.getnodes():
|
||||
if hasattr(node, "transport_type") and node.transport_type == "raw":
|
||||
if isinstance(node, Rj45Node):
|
||||
stop_emane_on_host = True
|
||||
continue
|
||||
|
||||
|
@ -801,8 +832,8 @@ class EmaneManager(ModelManager):
|
|||
zbit_check = z.bit_length() > 16 or z < 0
|
||||
if any([xbit_check, ybit_check, zbit_check]):
|
||||
logging.error(
|
||||
"Unable to build node location message, received lat/long/alt exceeds coordinate "
|
||||
"space: NEM %s (%d, %d, %d)",
|
||||
"Unable to build node location message, received lat/long/alt "
|
||||
"exceeds coordinate space: NEM %s (%d, %d, %d)",
|
||||
nemid,
|
||||
x,
|
||||
y,
|
||||
|
@ -812,7 +843,7 @@ class EmaneManager(ModelManager):
|
|||
|
||||
# generate a node message for this location update
|
||||
try:
|
||||
node = self.session.get_node(n)
|
||||
node = self.session.get_node(n, NodeBase)
|
||||
except CoreError:
|
||||
logging.exception(
|
||||
"location event NEM %s has no corresponding node %s", nemid, n
|
||||
|
@ -836,9 +867,23 @@ class EmaneManager(ModelManager):
|
|||
result = True
|
||||
except CoreCommandError:
|
||||
result = False
|
||||
|
||||
return result
|
||||
|
||||
def publish_pathloss(self, nem1: int, nem2: int, rx1: float, rx2: float) -> None:
|
||||
"""
|
||||
Publish pathloss events between provided nems, using provided rx power.
|
||||
:param nem1: interface one for pathloss
|
||||
:param nem2: interface two for pathloss
|
||||
:param rx1: received power from nem2 to nem1
|
||||
:param rx2: received power from nem1 to nem2
|
||||
:return: nothing
|
||||
"""
|
||||
event = PathlossEvent()
|
||||
event.append(nem1, forward=rx1)
|
||||
event.append(nem2, forward=rx2)
|
||||
self.service.publish(nem1, event)
|
||||
self.service.publish(nem2, event)
|
||||
|
||||
|
||||
class EmaneGlobalModel:
|
||||
"""
|
||||
|
|
|
@ -7,9 +7,12 @@ from typing import Dict, List
|
|||
|
||||
from core.config import ConfigGroup, Configuration
|
||||
from core.emane import emanemanifest
|
||||
from core.emulator.enumerations import ConfigDataTypes
|
||||
from core.emane.nodes import EmaneNet
|
||||
from core.emulator.emudata import LinkOptions
|
||||
from core.emulator.enumerations import ConfigDataTypes, TransportType
|
||||
from core.errors import CoreError
|
||||
from core.location.mobility import WirelessModel
|
||||
from core.nodes.base import CoreNode
|
||||
from core.nodes.interface import CoreInterface
|
||||
from core.xml import emanexml
|
||||
|
||||
|
@ -110,9 +113,9 @@ class EmaneModel(WirelessModel):
|
|||
server = interface.node.server
|
||||
|
||||
# check if this is external
|
||||
transport_type = "virtual"
|
||||
if interface and interface.transport_type == "raw":
|
||||
transport_type = "raw"
|
||||
transport_type = TransportType.VIRTUAL
|
||||
if interface and interface.transport_type == TransportType.RAW:
|
||||
transport_type = TransportType.RAW
|
||||
transport_name = emanexml.transport_file_name(self.id, transport_type)
|
||||
|
||||
# create nem xml file
|
||||
|
@ -137,44 +140,31 @@ class EmaneModel(WirelessModel):
|
|||
"""
|
||||
logging.debug("emane model(%s) has no post setup tasks", self.name)
|
||||
|
||||
def update(self, moved: bool, moved_netifs: List[CoreInterface]) -> None:
|
||||
def update(self, moved: List[CoreNode], moved_netifs: List[CoreInterface]) -> None:
|
||||
"""
|
||||
Invoked from MobilityModel when nodes are moved; this causes
|
||||
emane location events to be generated for the nodes in the moved
|
||||
list, making EmaneModels compatible with Ns2ScriptedMobility.
|
||||
|
||||
:param moved: were nodes moved
|
||||
:param moved: moved nodes
|
||||
:param moved_netifs: interfaces that were moved
|
||||
:return: nothing
|
||||
"""
|
||||
try:
|
||||
wlan = self.session.get_node(self.id)
|
||||
wlan = self.session.get_node(self.id, EmaneNet)
|
||||
wlan.setnempositions(moved_netifs)
|
||||
except CoreError:
|
||||
logging.exception("error during update")
|
||||
|
||||
def linkconfig(
|
||||
self,
|
||||
netif: CoreInterface,
|
||||
bw: float = None,
|
||||
delay: float = None,
|
||||
loss: float = None,
|
||||
duplicate: float = None,
|
||||
jitter: float = None,
|
||||
netif2: CoreInterface = None,
|
||||
self, netif: CoreInterface, options: LinkOptions, netif2: CoreInterface = None
|
||||
) -> None:
|
||||
"""
|
||||
Invoked when a Link Message is received. Default is unimplemented.
|
||||
|
||||
:param netif: interface one
|
||||
:param bw: bandwidth to set to
|
||||
:param delay: packet delay to set to
|
||||
:param loss: packet loss to set to
|
||||
:param duplicate: duplicate percentage to set to
|
||||
:param jitter: jitter to set to
|
||||
:param options: options for configuring link
|
||||
:param netif2: interface two
|
||||
:return: nothing
|
||||
"""
|
||||
logging.warning(
|
||||
"emane model(%s) does not support link configuration", self.name
|
||||
)
|
||||
logging.warning("emane model(%s) does not support link config", self.name)
|
||||
|
|
|
@ -285,26 +285,11 @@ class EmaneLinkMonitor:
|
|||
|
||||
def send_link(self, message_type: MessageFlags, link_id: Tuple[int, int]) -> None:
|
||||
nem_one, nem_two = link_id
|
||||
emane_one, netif = self.emane_manager.nemlookup(nem_one)
|
||||
if not emane_one or not netif:
|
||||
logging.error("invalid nem: %s", nem_one)
|
||||
return
|
||||
node_one = netif.node
|
||||
emane_two, netif = self.emane_manager.nemlookup(nem_two)
|
||||
if not emane_two or not netif:
|
||||
logging.error("invalid nem: %s", nem_two)
|
||||
return
|
||||
node_two = netif.node
|
||||
logging.debug(
|
||||
"%s emane link from %s(%s) to %s(%s)",
|
||||
message_type.name,
|
||||
node_one.name,
|
||||
nem_one,
|
||||
node_two.name,
|
||||
nem_two,
|
||||
)
|
||||
label = self.get_link_label(link_id)
|
||||
self.send_message(message_type, label, node_one.id, node_two.id, emane_one.id)
|
||||
link = self.emane_manager.get_nem_link(nem_one, nem_two, message_type)
|
||||
if link:
|
||||
label = self.get_link_label(link_id)
|
||||
link.label = label
|
||||
self.emane_manager.session.broadcast_link(link)
|
||||
|
||||
def send_message(
|
||||
self,
|
||||
|
|
|
@ -6,8 +6,16 @@ share the same MAC+PHY model.
|
|||
import logging
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Type
|
||||
|
||||
from core.emulator.data import LinkData
|
||||
from core.emulator.distributed import DistributedServer
|
||||
from core.emulator.enumerations import LinkTypes, NodeTypes, RegisterTlvs
|
||||
from core.emulator.emudata import LinkOptions
|
||||
from core.emulator.enumerations import (
|
||||
LinkTypes,
|
||||
MessageFlags,
|
||||
NodeTypes,
|
||||
RegisterTlvs,
|
||||
TransportType,
|
||||
)
|
||||
from core.nodes.base import CoreNetworkBase
|
||||
from core.nodes.interface import CoreInterface
|
||||
|
||||
|
@ -48,35 +56,19 @@ class EmaneNet(CoreNetworkBase):
|
|||
) -> None:
|
||||
super().__init__(session, _id, name, start, server)
|
||||
self.conf = ""
|
||||
self.up = False
|
||||
self.nemidmap = {}
|
||||
self.model = None
|
||||
self.mobility = None
|
||||
|
||||
def linkconfig(
|
||||
self,
|
||||
netif: CoreInterface,
|
||||
bw: float = None,
|
||||
delay: float = None,
|
||||
loss: float = None,
|
||||
duplicate: float = None,
|
||||
jitter: float = None,
|
||||
netif2: CoreInterface = None,
|
||||
self, netif: CoreInterface, options: LinkOptions, netif2: CoreInterface = None
|
||||
) -> None:
|
||||
"""
|
||||
The CommEffect model supports link configuration.
|
||||
"""
|
||||
if not self.model:
|
||||
return
|
||||
self.model.linkconfig(
|
||||
netif=netif,
|
||||
bw=bw,
|
||||
delay=delay,
|
||||
loss=loss,
|
||||
duplicate=duplicate,
|
||||
jitter=jitter,
|
||||
netif2=netif2,
|
||||
)
|
||||
self.model.linkconfig(netif, options, netif2)
|
||||
|
||||
def config(self, conf: str) -> None:
|
||||
self.conf = conf
|
||||
|
@ -181,7 +173,7 @@ class EmaneNet(CoreNetworkBase):
|
|||
emanetransportd terminates.
|
||||
"""
|
||||
for netif in self.netifs():
|
||||
if "virtual" in netif.transport_type.lower():
|
||||
if netif.transport_type == TransportType.VIRTUAL:
|
||||
netif.shutdown()
|
||||
netif.poshook = None
|
||||
|
||||
|
@ -204,6 +196,7 @@ class EmaneNet(CoreNetworkBase):
|
|||
lat, lon, alt = self.session.location.getgeo(x, y, z)
|
||||
if node.position.alt is not None:
|
||||
alt = node.position.alt
|
||||
node.position.set_geo(lon, lat, alt)
|
||||
# altitude must be an integer or warning is printed
|
||||
alt = int(round(alt))
|
||||
return nemid, lon, lat, alt
|
||||
|
@ -245,3 +238,27 @@ class EmaneNet(CoreNetworkBase):
|
|||
nemid, lon, lat, alt = position
|
||||
event.append(nemid, latitude=lat, longitude=lon, altitude=alt)
|
||||
self.session.emane.service.publish(0, event)
|
||||
|
||||
def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]:
|
||||
links = super().all_link_data(flags)
|
||||
# gather current emane links
|
||||
nem_ids = set(self.nemidmap.values())
|
||||
emane_manager = self.session.emane
|
||||
emane_links = emane_manager.link_monitor.links
|
||||
considered = set()
|
||||
for link_key in emane_links:
|
||||
considered_key = tuple(sorted(link_key))
|
||||
if considered_key in considered:
|
||||
continue
|
||||
considered.add(considered_key)
|
||||
nem1, nem2 = considered_key
|
||||
# ignore links not related to this node
|
||||
if nem1 not in nem_ids and nem2 not in nem_ids:
|
||||
continue
|
||||
# ignore incomplete links
|
||||
if (nem2, nem1) not in emane_links:
|
||||
continue
|
||||
link = emane_manager.get_nem_link(nem1, nem2)
|
||||
if link:
|
||||
links.append(link)
|
||||
return links
|
||||
|
|
|
@ -3,7 +3,7 @@ import logging
|
|||
import os
|
||||
import signal
|
||||
import sys
|
||||
from typing import Mapping, Type
|
||||
from typing import Dict, List, Type
|
||||
|
||||
import core.services
|
||||
from core import configservices
|
||||
|
@ -36,7 +36,7 @@ class CoreEmu:
|
|||
Provides logic for creating and configuring CORE sessions and the nodes within them.
|
||||
"""
|
||||
|
||||
def __init__(self, config: Mapping[str, str] = None) -> None:
|
||||
def __init__(self, config: Dict[str, str] = None) -> None:
|
||||
"""
|
||||
Create a CoreEmu object.
|
||||
|
||||
|
@ -48,17 +48,17 @@ class CoreEmu:
|
|||
# configuration
|
||||
if config is None:
|
||||
config = {}
|
||||
self.config = config
|
||||
self.config: Dict[str, str] = config
|
||||
|
||||
# session management
|
||||
self.sessions = {}
|
||||
self.sessions: Dict[int, Session] = {}
|
||||
|
||||
# load services
|
||||
self.service_errors = []
|
||||
self.service_errors: List[str] = []
|
||||
self.load_services()
|
||||
|
||||
# config services
|
||||
self.service_manager = ConfigServiceManager()
|
||||
self.service_manager: ConfigServiceManager = ConfigServiceManager()
|
||||
config_services_path = os.path.abspath(os.path.dirname(configservices.__file__))
|
||||
self.service_manager.load(config_services_path)
|
||||
custom_dir = self.config.get("custom_config_services_dir")
|
||||
|
|
|
@ -38,7 +38,7 @@ class EventData:
|
|||
event_type: EventTypes = None
|
||||
name: str = None
|
||||
data: str = None
|
||||
time: float = None
|
||||
time: str = None
|
||||
session: int = None
|
||||
|
||||
|
||||
|
|
|
@ -37,10 +37,10 @@ class DistributedServer:
|
|||
:param name: convenience name to associate with host
|
||||
:param host: host to connect to
|
||||
"""
|
||||
self.name = name
|
||||
self.host = host
|
||||
self.conn = Connection(host, user="root")
|
||||
self.lock = threading.Lock()
|
||||
self.name: str = name
|
||||
self.host: str = host
|
||||
self.conn: Connection = Connection(host, user="root")
|
||||
self.lock: threading.Lock = threading.Lock()
|
||||
|
||||
def remote_cmd(
|
||||
self, cmd: str, env: Dict[str, str] = None, cwd: str = None, wait: bool = True
|
||||
|
@ -117,10 +117,10 @@ class DistributedController:
|
|||
|
||||
:param session: session
|
||||
"""
|
||||
self.session = session
|
||||
self.servers = OrderedDict()
|
||||
self.tunnels = {}
|
||||
self.address = self.session.options.get_config(
|
||||
self.session: "Session" = session
|
||||
self.servers: Dict[str, DistributedServer] = OrderedDict()
|
||||
self.tunnels: Dict[int, Tuple[GreTap, GreTap]] = {}
|
||||
self.address: str = self.session.options.get_config(
|
||||
"distributed_address", default=None
|
||||
)
|
||||
|
||||
|
@ -178,13 +178,10 @@ class DistributedController:
|
|||
"""
|
||||
for node_id in self.session.nodes:
|
||||
node = self.session.nodes[node_id]
|
||||
|
||||
if not isinstance(node, CoreNetwork):
|
||||
continue
|
||||
|
||||
if isinstance(node, CtrlNet) and node.serverintf is not None:
|
||||
continue
|
||||
|
||||
for name in self.servers:
|
||||
server = self.servers[name]
|
||||
self.create_gre_tunnel(node, server)
|
||||
|
@ -195,7 +192,6 @@ class DistributedController:
|
|||
"""
|
||||
Create gre tunnel using a pair of gre taps between the local and remote server.
|
||||
|
||||
|
||||
:param node: node to create gre tunnel for
|
||||
:param server: server to create
|
||||
tunnel for
|
||||
|
@ -212,7 +208,7 @@ class DistributedController:
|
|||
"local tunnel node(%s) to remote(%s) key(%s)", node.name, host, key
|
||||
)
|
||||
local_tap = GreTap(session=self.session, remoteip=host, key=key)
|
||||
local_tap.net_client.create_interface(node.brname, local_tap.localname)
|
||||
local_tap.net_client.set_interface_master(node.brname, local_tap.localname)
|
||||
|
||||
# server to local
|
||||
logging.info(
|
||||
|
@ -221,7 +217,7 @@ class DistributedController:
|
|||
remote_tap = GreTap(
|
||||
session=self.session, remoteip=self.address, key=key, server=server
|
||||
)
|
||||
remote_tap.net_client.create_interface(node.brname, remote_tap.localname)
|
||||
remote_tap.net_client.set_interface_master(node.brname, remote_tap.localname)
|
||||
|
||||
# save tunnels for shutdown
|
||||
tunnel = (local_tap, remote_tap)
|
||||
|
@ -243,15 +239,3 @@ class DistributedController:
|
|||
(self.session.id << 16) ^ utils.hashkey(n1_id) ^ (utils.hashkey(n2_id) << 8)
|
||||
)
|
||||
return key & 0xFFFFFFFF
|
||||
|
||||
def get_tunnel(self, n1_id: int, n2_id: int) -> Tuple[GreTap, GreTap]:
|
||||
"""
|
||||
Return the GreTap between two nodes if it exists.
|
||||
|
||||
:param n1_id: node one id
|
||||
:param n2_id: node two id
|
||||
:return: gre tap between nodes or None
|
||||
"""
|
||||
key = self.tunnel_key(n1_id, n2_id)
|
||||
logging.debug("checking for tunnel key(%s) in: %s", key, self.tunnels)
|
||||
return self.tunnels.get(key)
|
||||
|
|
|
@ -1,90 +1,37 @@
|
|||
from typing import List, Optional
|
||||
from dataclasses import dataclass, field
|
||||
from typing import TYPE_CHECKING, List, Optional
|
||||
|
||||
import netaddr
|
||||
|
||||
from core import utils
|
||||
from core.api.grpc.core_pb2 import LinkOptions
|
||||
from core.emane.nodes import EmaneNet
|
||||
from core.emulator.enumerations import LinkTypes
|
||||
from core.nodes.base import CoreNetworkBase, CoreNode
|
||||
from core.nodes.interface import CoreInterface
|
||||
from core.nodes.physical import PhysicalNode
|
||||
|
||||
|
||||
class IdGen:
|
||||
def __init__(self, _id: int = 0) -> None:
|
||||
self.id = _id
|
||||
|
||||
def next(self) -> int:
|
||||
self.id += 1
|
||||
return self.id
|
||||
|
||||
|
||||
def link_config(
|
||||
network: CoreNetworkBase,
|
||||
interface: CoreInterface,
|
||||
link_options: LinkOptions,
|
||||
devname: str = None,
|
||||
interface_two: CoreInterface = None,
|
||||
) -> None:
|
||||
"""
|
||||
Convenience method for configuring a link,
|
||||
|
||||
:param network: network to configure link for
|
||||
:param interface: interface to configure
|
||||
:param link_options: data to configure link with
|
||||
:param devname: device name, default is None
|
||||
:param interface_two: other interface associated, default is None
|
||||
:return: nothing
|
||||
"""
|
||||
config = {
|
||||
"netif": interface,
|
||||
"bw": link_options.bandwidth,
|
||||
"delay": link_options.delay,
|
||||
"loss": link_options.per,
|
||||
"duplicate": link_options.dup,
|
||||
"jitter": link_options.jitter,
|
||||
"netif2": interface_two,
|
||||
}
|
||||
|
||||
# hacky check here, because physical and emane nodes do not conform to the same
|
||||
# linkconfig interface
|
||||
if not isinstance(network, (EmaneNet, PhysicalNode)):
|
||||
config["devname"] = devname
|
||||
|
||||
network.linkconfig(**config)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.nodes.base import CoreNode
|
||||
|
||||
|
||||
@dataclass
|
||||
class NodeOptions:
|
||||
"""
|
||||
Options for creating and updating nodes within core.
|
||||
"""
|
||||
|
||||
def __init__(self, name: str = None, model: str = "PC", image: str = None) -> None:
|
||||
"""
|
||||
Create a NodeOptions object.
|
||||
|
||||
:param name: name of node, defaults to node class name postfix with its id
|
||||
:param model: defines services for default and physical nodes, defaults to
|
||||
"router"
|
||||
:param image: image to use for docker nodes
|
||||
"""
|
||||
self.name = name
|
||||
self.model = model
|
||||
self.canvas = None
|
||||
self.icon = None
|
||||
self.opaque = None
|
||||
self.services = []
|
||||
self.config_services = []
|
||||
self.x = None
|
||||
self.y = None
|
||||
self.lat = None
|
||||
self.lon = None
|
||||
self.alt = None
|
||||
self.emulation_id = None
|
||||
self.server = None
|
||||
self.image = image
|
||||
self.emane = None
|
||||
name: str = None
|
||||
model: Optional[str] = "PC"
|
||||
canvas: int = None
|
||||
icon: str = None
|
||||
opaque: str = None
|
||||
services: List[str] = field(default_factory=list)
|
||||
config_services: List[str] = field(default_factory=list)
|
||||
x: float = None
|
||||
y: float = None
|
||||
lat: float = None
|
||||
lon: float = None
|
||||
alt: float = None
|
||||
emulation_id: int = None
|
||||
server: str = None
|
||||
image: str = None
|
||||
emane: str = None
|
||||
|
||||
def set_position(self, x: float, y: float) -> None:
|
||||
"""
|
||||
|
@ -111,117 +58,56 @@ class NodeOptions:
|
|||
self.alt = alt
|
||||
|
||||
|
||||
@dataclass
|
||||
class LinkOptions:
|
||||
"""
|
||||
Options for creating and updating links within core.
|
||||
"""
|
||||
|
||||
def __init__(self, _type: LinkTypes = LinkTypes.WIRED) -> None:
|
||||
"""
|
||||
Create a LinkOptions object.
|
||||
|
||||
:param _type: type of link, defaults to
|
||||
wired
|
||||
"""
|
||||
self.type = _type
|
||||
self.session = None
|
||||
self.delay = None
|
||||
self.bandwidth = None
|
||||
self.per = None
|
||||
self.dup = None
|
||||
self.jitter = None
|
||||
self.mer = None
|
||||
self.burst = None
|
||||
self.mburst = None
|
||||
self.gui_attributes = None
|
||||
self.unidirectional = None
|
||||
self.emulation_id = None
|
||||
self.network_id = None
|
||||
self.key = None
|
||||
self.opaque = None
|
||||
type: LinkTypes = LinkTypes.WIRED
|
||||
session: int = None
|
||||
delay: int = None
|
||||
bandwidth: int = None
|
||||
per: float = None
|
||||
dup: int = None
|
||||
jitter: int = None
|
||||
mer: int = None
|
||||
burst: int = None
|
||||
mburst: int = None
|
||||
gui_attributes: str = None
|
||||
unidirectional: bool = None
|
||||
emulation_id: int = None
|
||||
network_id: int = None
|
||||
key: int = None
|
||||
opaque: str = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class InterfaceData:
|
||||
"""
|
||||
Convenience class for storing interface data.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
_id: int,
|
||||
name: str,
|
||||
mac: str,
|
||||
ip4: str,
|
||||
ip4_mask: int,
|
||||
ip6: str,
|
||||
ip6_mask: int,
|
||||
) -> None:
|
||||
"""
|
||||
Creates an InterfaceData object.
|
||||
|
||||
:param _id: interface id
|
||||
:param name: name for interface
|
||||
:param mac: mac address
|
||||
:param ip4: ipv4 address
|
||||
:param ip4_mask: ipv4 bit mask
|
||||
:param ip6: ipv6 address
|
||||
:param ip6_mask: ipv6 bit mask
|
||||
"""
|
||||
self.id = _id
|
||||
self.name = name
|
||||
self.mac = mac
|
||||
self.ip4 = ip4
|
||||
self.ip4_mask = ip4_mask
|
||||
self.ip6 = ip6
|
||||
self.ip6_mask = ip6_mask
|
||||
|
||||
def has_ip4(self) -> bool:
|
||||
"""
|
||||
Determines if interface has an ip4 address.
|
||||
|
||||
:return: True if has ip4, False otherwise
|
||||
"""
|
||||
return all([self.ip4, self.ip4_mask])
|
||||
|
||||
def has_ip6(self) -> bool:
|
||||
"""
|
||||
Determines if interface has an ip6 address.
|
||||
|
||||
:return: True if has ip6, False otherwise
|
||||
"""
|
||||
return all([self.ip6, self.ip6_mask])
|
||||
|
||||
def ip4_address(self) -> Optional[str]:
|
||||
"""
|
||||
Retrieve a string representation of the ip4 address and netmask.
|
||||
|
||||
:return: ip4 string or None
|
||||
"""
|
||||
if self.has_ip4():
|
||||
return f"{self.ip4}/{self.ip4_mask}"
|
||||
else:
|
||||
return None
|
||||
|
||||
def ip6_address(self) -> Optional[str]:
|
||||
"""
|
||||
Retrieve a string representation of the ip6 address and netmask.
|
||||
|
||||
:return: ip4 string or None
|
||||
"""
|
||||
if self.has_ip6():
|
||||
return f"{self.ip6}/{self.ip6_mask}"
|
||||
else:
|
||||
return None
|
||||
id: int = None
|
||||
name: str = None
|
||||
mac: str = None
|
||||
ip4: str = None
|
||||
ip4_mask: int = None
|
||||
ip6: str = None
|
||||
ip6_mask: int = None
|
||||
|
||||
def get_addresses(self) -> List[str]:
|
||||
"""
|
||||
Returns a list of ip4 and ip6 address when present.
|
||||
Returns a list of ip4 and ip6 addresses when present.
|
||||
|
||||
:return: list of addresses
|
||||
"""
|
||||
ip4 = self.ip4_address()
|
||||
ip6 = self.ip6_address()
|
||||
return [i for i in [ip4, ip6] if i]
|
||||
addresses = []
|
||||
if self.ip4 and self.ip4_mask:
|
||||
addresses.append(f"{self.ip4}/{self.ip4_mask}")
|
||||
if self.ip6 and self.ip6_mask:
|
||||
addresses.append(f"{self.ip6}/{self.ip6_mask}")
|
||||
return addresses
|
||||
|
||||
|
||||
class IpPrefixes:
|
||||
|
@ -247,30 +133,63 @@ class IpPrefixes:
|
|||
if ip6_prefix:
|
||||
self.ip6 = netaddr.IPNetwork(ip6_prefix)
|
||||
|
||||
def ip4_address(self, node: CoreNode) -> str:
|
||||
def ip4_address(self, node_id: int) -> str:
|
||||
"""
|
||||
Convenience method to return the IP4 address for a node.
|
||||
|
||||
:param node: node to get IP4 address for
|
||||
:param node_id: node id to get IP4 address for
|
||||
:return: IP4 address or None
|
||||
"""
|
||||
if not self.ip4:
|
||||
raise ValueError("ip4 prefixes have not been set")
|
||||
return str(self.ip4[node.id])
|
||||
return str(self.ip4[node_id])
|
||||
|
||||
def ip6_address(self, node: CoreNode) -> str:
|
||||
def ip6_address(self, node_id: int) -> str:
|
||||
"""
|
||||
Convenience method to return the IP6 address for a node.
|
||||
|
||||
:param node: node to get IP6 address for
|
||||
:param node_id: node id to get IP6 address for
|
||||
:return: IP4 address or None
|
||||
"""
|
||||
if not self.ip6:
|
||||
raise ValueError("ip6 prefixes have not been set")
|
||||
return str(self.ip6[node.id])
|
||||
return str(self.ip6[node_id])
|
||||
|
||||
def gen_interface(self, node_id: int, name: str = None, mac: str = None):
|
||||
"""
|
||||
Creates interface data for linking nodes, using the nodes unique id for
|
||||
generation, along with a random mac address, unless provided.
|
||||
|
||||
:param node_id: node id to create an interface for
|
||||
:param name: name to set for interface, default is eth{id}
|
||||
:param mac: mac address to use for this interface, default is random
|
||||
generation
|
||||
:return: new interface data for the provided node
|
||||
"""
|
||||
# generate ip4 data
|
||||
ip4 = None
|
||||
ip4_mask = None
|
||||
if self.ip4:
|
||||
ip4 = self.ip4_address(node_id)
|
||||
ip4_mask = self.ip4.prefixlen
|
||||
|
||||
# generate ip6 data
|
||||
ip6 = None
|
||||
ip6_mask = None
|
||||
if self.ip6:
|
||||
ip6 = self.ip6_address(node_id)
|
||||
ip6_mask = self.ip6.prefixlen
|
||||
|
||||
# random mac
|
||||
if not mac:
|
||||
mac = utils.random_mac()
|
||||
|
||||
return InterfaceData(
|
||||
name=name, ip4=ip4, ip4_mask=ip4_mask, ip6=ip6, ip6_mask=ip6_mask, mac=mac
|
||||
)
|
||||
|
||||
def create_interface(
|
||||
self, node: CoreNode, name: str = None, mac: str = None
|
||||
self, node: "CoreNode", name: str = None, mac: str = None
|
||||
) -> InterfaceData:
|
||||
"""
|
||||
Creates interface data for linking nodes, using the nodes unique id for
|
||||
|
@ -282,54 +201,6 @@ class IpPrefixes:
|
|||
generation
|
||||
:return: new interface data for the provided node
|
||||
"""
|
||||
# interface id
|
||||
inteface_id = node.newifindex()
|
||||
|
||||
# generate ip4 data
|
||||
ip4 = None
|
||||
ip4_mask = None
|
||||
if self.ip4:
|
||||
ip4 = self.ip4_address(node)
|
||||
ip4_mask = self.ip4.prefixlen
|
||||
|
||||
# generate ip6 data
|
||||
ip6 = None
|
||||
ip6_mask = None
|
||||
if self.ip6:
|
||||
ip6 = self.ip6_address(node)
|
||||
ip6_mask = self.ip6.prefixlen
|
||||
|
||||
# random mac
|
||||
if not mac:
|
||||
mac = utils.random_mac()
|
||||
|
||||
return InterfaceData(
|
||||
_id=inteface_id,
|
||||
name=name,
|
||||
ip4=ip4,
|
||||
ip4_mask=ip4_mask,
|
||||
ip6=ip6,
|
||||
ip6_mask=ip6_mask,
|
||||
mac=mac,
|
||||
)
|
||||
|
||||
|
||||
def create_interface(
|
||||
node: CoreNode, network: CoreNetworkBase, interface_data: InterfaceData
|
||||
):
|
||||
"""
|
||||
Create an interface for a node on a network using provided interface data.
|
||||
|
||||
:param node: node to create interface for
|
||||
:param network: network to associate interface with
|
||||
:param interface_data: interface data
|
||||
:return: created interface
|
||||
"""
|
||||
node.newnetif(
|
||||
network,
|
||||
addrlist=interface_data.get_addresses(),
|
||||
hwaddr=interface_data.mac,
|
||||
ifindex=interface_data.id,
|
||||
ifname=interface_data.name,
|
||||
)
|
||||
return node.netif(interface_data.id)
|
||||
interface = self.gen_interface(node.id, name, mac)
|
||||
interface.id = node.newifindex()
|
||||
return interface
|
||||
|
|
|
@ -117,3 +117,13 @@ class ExceptionLevels(Enum):
|
|||
ERROR = 2
|
||||
WARNING = 3
|
||||
NOTICE = 4
|
||||
|
||||
|
||||
class NetworkPolicy(Enum):
|
||||
ACCEPT = "ACCEPT"
|
||||
DROP = "DROP"
|
||||
|
||||
|
||||
class TransportType(Enum):
|
||||
RAW = "raw"
|
||||
VIRTUAL = "virtual"
|
||||
|
|
|
@ -12,21 +12,22 @@ import subprocess
|
|||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Type
|
||||
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Type, TypeVar
|
||||
|
||||
from core import constants, utils
|
||||
from core.configservice.manager import ConfigServiceManager
|
||||
from core.emane.emanemanager import EmaneManager
|
||||
from core.emane.nodes import EmaneNet
|
||||
from core.emulator.data import ConfigData, EventData, ExceptionData, FileData, LinkData
|
||||
from core.emulator.distributed import DistributedController
|
||||
from core.emulator.emudata import (
|
||||
IdGen,
|
||||
InterfaceData,
|
||||
LinkOptions,
|
||||
NodeOptions,
|
||||
create_interface,
|
||||
link_config,
|
||||
from core.emulator.data import (
|
||||
ConfigData,
|
||||
EventData,
|
||||
ExceptionData,
|
||||
FileData,
|
||||
LinkData,
|
||||
NodeData,
|
||||
)
|
||||
from core.emulator.distributed import DistributedController
|
||||
from core.emulator.emudata import InterfaceData, LinkOptions, NodeOptions
|
||||
from core.emulator.enumerations import (
|
||||
EventTypes,
|
||||
ExceptionLevels,
|
||||
|
@ -41,7 +42,7 @@ from core.location.geo import GeoLocation
|
|||
from core.location.mobility import BasicRangeModel, MobilityManager
|
||||
from core.nodes.base import CoreNetworkBase, CoreNode, CoreNodeBase, NodeBase
|
||||
from core.nodes.docker import DockerNode
|
||||
from core.nodes.interface import CoreInterface, GreTap
|
||||
from core.nodes.interface import CoreInterface
|
||||
from core.nodes.lxd import LxcNode
|
||||
from core.nodes.network import (
|
||||
CtrlNet,
|
||||
|
@ -75,8 +76,10 @@ NODES = {
|
|||
NodeTypes.LXC: LxcNode,
|
||||
}
|
||||
NODES_TYPE = {NODES[x]: x for x in NODES}
|
||||
CONTAINER_NODES = {DockerNode, LxcNode}
|
||||
CTRL_NET_ID = 9001
|
||||
LINK_COLORS = ["green", "blue", "orange", "purple", "turquoise"]
|
||||
NT = TypeVar("NT", bound=NodeBase)
|
||||
|
||||
|
||||
class Session:
|
||||
|
@ -94,63 +97,62 @@ class Session:
|
|||
:param config: session configuration
|
||||
:param mkdir: flag to determine if a directory should be made
|
||||
"""
|
||||
self.id = _id
|
||||
self.id: int = _id
|
||||
|
||||
# define and create session directory when desired
|
||||
self.session_dir = os.path.join(tempfile.gettempdir(), f"pycore.{self.id}")
|
||||
self.session_dir: str = os.path.join(tempfile.gettempdir(), f"pycore.{self.id}")
|
||||
if mkdir:
|
||||
os.mkdir(self.session_dir)
|
||||
|
||||
self.name = None
|
||||
self.file_name = None
|
||||
self.thumbnail = None
|
||||
self.user = None
|
||||
self.event_loop = EventLoop()
|
||||
self.link_colors = {}
|
||||
self.name: Optional[str] = None
|
||||
self.file_name: Optional[str] = None
|
||||
self.thumbnail: Optional[str] = None
|
||||
self.user: Optional[str] = None
|
||||
self.event_loop: EventLoop = EventLoop()
|
||||
self.link_colors: Dict[int, str] = {}
|
||||
|
||||
# dict of nodes: all nodes and nets
|
||||
self.node_id_gen = IdGen()
|
||||
self.nodes = {}
|
||||
self.nodes: Dict[int, NodeBase] = {}
|
||||
self._nodes_lock = threading.Lock()
|
||||
|
||||
self.state = EventTypes.DEFINITION_STATE
|
||||
self._state_time = time.monotonic()
|
||||
self._state_file = os.path.join(self.session_dir, "state")
|
||||
self.state: EventTypes = EventTypes.DEFINITION_STATE
|
||||
self._state_time: float = time.monotonic()
|
||||
self._state_file: str = os.path.join(self.session_dir, "state")
|
||||
|
||||
# hooks handlers
|
||||
self._hooks = {}
|
||||
self._state_hooks = {}
|
||||
self._hooks: Dict[EventTypes, Tuple[str, str]] = {}
|
||||
self._state_hooks: Dict[EventTypes, Callable[[int], None]] = {}
|
||||
self.add_state_hook(
|
||||
state=EventTypes.RUNTIME_STATE, hook=self.runtime_state_hook
|
||||
)
|
||||
|
||||
# handlers for broadcasting information
|
||||
self.event_handlers = []
|
||||
self.exception_handlers = []
|
||||
self.node_handlers = []
|
||||
self.link_handlers = []
|
||||
self.file_handlers = []
|
||||
self.config_handlers = []
|
||||
self.shutdown_handlers = []
|
||||
self.event_handlers: List[Callable[[EventData], None]] = []
|
||||
self.exception_handlers: List[Callable[[ExceptionData], None]] = []
|
||||
self.node_handlers: List[Callable[[NodeData], None]] = []
|
||||
self.link_handlers: List[Callable[[LinkData], None]] = []
|
||||
self.file_handlers: List[Callable[[FileData], None]] = []
|
||||
self.config_handlers: List[Callable[[ConfigData], None]] = []
|
||||
self.shutdown_handlers: List[Callable[[Session], None]] = []
|
||||
|
||||
# session options/metadata
|
||||
self.options = SessionConfig()
|
||||
self.options: SessionConfig = SessionConfig()
|
||||
if not config:
|
||||
config = {}
|
||||
for key in config:
|
||||
value = config[key]
|
||||
self.options.set_config(key, value)
|
||||
self.metadata = {}
|
||||
self.metadata: Dict[str, str] = {}
|
||||
|
||||
# distributed support and logic
|
||||
self.distributed = DistributedController(self)
|
||||
self.distributed: DistributedController = DistributedController(self)
|
||||
|
||||
# initialize session feature helpers
|
||||
self.location = GeoLocation()
|
||||
self.mobility = MobilityManager(session=self)
|
||||
self.services = CoreServices(session=self)
|
||||
self.emane = EmaneManager(session=self)
|
||||
self.sdt = Sdt(session=self)
|
||||
self.location: GeoLocation = GeoLocation()
|
||||
self.mobility: MobilityManager = MobilityManager(self)
|
||||
self.services: CoreServices = CoreServices(self)
|
||||
self.emane: EmaneManager = EmaneManager(self)
|
||||
self.sdt: Sdt = Sdt(self)
|
||||
|
||||
# initialize default node services
|
||||
self.services.default_services = {
|
||||
|
@ -162,7 +164,7 @@ class Session:
|
|||
}
|
||||
|
||||
# config services
|
||||
self.service_manager = None
|
||||
self.service_manager: Optional[ConfigServiceManager] = None
|
||||
|
||||
@classmethod
|
||||
def get_node_class(cls, _type: NodeTypes) -> Type[NodeBase]:
|
||||
|
@ -194,7 +196,10 @@ class Session:
|
|||
def _link_nodes(
|
||||
self, node_one_id: int, node_two_id: int
|
||||
) -> Tuple[
|
||||
CoreNode, CoreNode, CoreNetworkBase, CoreNetworkBase, Tuple[GreTap, GreTap]
|
||||
Optional[CoreNode],
|
||||
Optional[CoreNode],
|
||||
Optional[CoreNetworkBase],
|
||||
Optional[CoreNetworkBase],
|
||||
]:
|
||||
"""
|
||||
Convenience method for retrieving nodes within link data.
|
||||
|
@ -212,24 +217,8 @@ class Session:
|
|||
net_two = None
|
||||
|
||||
# retrieve node one
|
||||
node_one = self.get_node(node_one_id)
|
||||
node_two = self.get_node(node_two_id)
|
||||
|
||||
# both node ids are provided
|
||||
tunnel = self.distributed.get_tunnel(node_one_id, node_two_id)
|
||||
logging.debug("tunnel between nodes: %s", tunnel)
|
||||
if isinstance(tunnel, GreTapBridge):
|
||||
net_one = tunnel
|
||||
if tunnel.remotenum == node_one_id:
|
||||
node_one = None
|
||||
else:
|
||||
node_two = None
|
||||
# physical node connected via gre tap tunnel
|
||||
elif tunnel:
|
||||
if tunnel.remotenum == node_one_id:
|
||||
node_one = None
|
||||
else:
|
||||
node_two = None
|
||||
node_one = self.get_node(node_one_id, NodeBase)
|
||||
node_two = self.get_node(node_two_id, NodeBase)
|
||||
|
||||
if isinstance(node_one, CoreNetworkBase):
|
||||
if not net_one:
|
||||
|
@ -246,14 +235,13 @@ class Session:
|
|||
node_two = None
|
||||
|
||||
logging.debug(
|
||||
"link node types n1(%s) n2(%s) net1(%s) net2(%s) tunnel(%s)",
|
||||
"link node types n1(%s) n2(%s) net1(%s) net2(%s)",
|
||||
node_one,
|
||||
node_two,
|
||||
net_one,
|
||||
net_two,
|
||||
tunnel,
|
||||
)
|
||||
return node_one, node_two, net_one, net_two, tunnel
|
||||
return node_one, node_two, net_one, net_two
|
||||
|
||||
def _link_wireless(self, objects: Iterable[CoreNodeBase], connect: bool) -> None:
|
||||
"""
|
||||
|
@ -300,7 +288,7 @@ class Session:
|
|||
node_two_id: int,
|
||||
interface_one: InterfaceData = None,
|
||||
interface_two: InterfaceData = None,
|
||||
link_options: LinkOptions = None,
|
||||
options: LinkOptions = None,
|
||||
) -> Tuple[CoreInterface, CoreInterface]:
|
||||
"""
|
||||
Add a link between nodes.
|
||||
|
@ -311,15 +299,15 @@ class Session:
|
|||
data, defaults to none
|
||||
:param interface_two: node two interface
|
||||
data, defaults to none
|
||||
:param link_options: data for creating link,
|
||||
:param options: data for creating link,
|
||||
defaults to no options
|
||||
:return: tuple of created core interfaces, depending on link
|
||||
"""
|
||||
if not link_options:
|
||||
link_options = LinkOptions()
|
||||
if not options:
|
||||
options = LinkOptions()
|
||||
|
||||
# get node objects identified by link data
|
||||
node_one, node_two, net_one, net_two, tunnel = self._link_nodes(
|
||||
node_one, node_two, net_one, net_two = self._link_nodes(
|
||||
node_one_id, node_two_id
|
||||
)
|
||||
|
||||
|
@ -333,7 +321,7 @@ class Session:
|
|||
|
||||
try:
|
||||
# wireless link
|
||||
if link_options.type == LinkTypes.WIRELESS:
|
||||
if options.type == LinkTypes.WIRELESS:
|
||||
objects = [node_one, node_two, net_one, net_two]
|
||||
self._link_wireless(objects, connect=True)
|
||||
# wired link
|
||||
|
@ -346,7 +334,7 @@ class Session:
|
|||
node_two.name,
|
||||
)
|
||||
start = self.state.should_start()
|
||||
net_one = self.create_node(cls=PtpNet, start=start)
|
||||
net_one = self.create_node(PtpNet, start=start)
|
||||
|
||||
# node to network
|
||||
if node_one and net_one:
|
||||
|
@ -355,11 +343,11 @@ class Session:
|
|||
node_one.name,
|
||||
net_one.name,
|
||||
)
|
||||
interface = create_interface(node_one, net_one, interface_one)
|
||||
node_one_interface = interface
|
||||
ifindex = node_one.newnetif(net_one, interface_one)
|
||||
node_one_interface = node_one.netif(ifindex)
|
||||
wireless_net = isinstance(net_one, (EmaneNet, WlanNode))
|
||||
if not wireless_net:
|
||||
link_config(net_one, interface, link_options)
|
||||
net_one.linkconfig(node_one_interface, options)
|
||||
|
||||
# network to node
|
||||
if node_two and net_one:
|
||||
|
@ -368,11 +356,11 @@ class Session:
|
|||
node_two.name,
|
||||
net_one.name,
|
||||
)
|
||||
interface = create_interface(node_two, net_one, interface_two)
|
||||
node_two_interface = interface
|
||||
ifindex = node_two.newnetif(net_one, interface_two)
|
||||
node_two_interface = node_two.netif(ifindex)
|
||||
wireless_net = isinstance(net_one, (EmaneNet, WlanNode))
|
||||
if not link_options.unidirectional and not wireless_net:
|
||||
link_config(net_one, interface, link_options)
|
||||
if not options.unidirectional and not wireless_net:
|
||||
net_one.linkconfig(node_two_interface, options)
|
||||
|
||||
# network to network
|
||||
if net_one and net_two:
|
||||
|
@ -383,25 +371,21 @@ class Session:
|
|||
)
|
||||
interface = net_one.linknet(net_two)
|
||||
node_one_interface = interface
|
||||
link_config(net_one, interface, link_options)
|
||||
|
||||
if not link_options.unidirectional:
|
||||
net_one.linkconfig(interface, options)
|
||||
if not options.unidirectional:
|
||||
interface.swapparams("_params_up")
|
||||
link_config(
|
||||
net_two, interface, link_options, devname=interface.name
|
||||
)
|
||||
net_two.linkconfig(interface, options)
|
||||
interface.swapparams("_params_up")
|
||||
|
||||
# a tunnel node was found for the nodes
|
||||
addresses = []
|
||||
if not node_one and all([net_one, interface_one]):
|
||||
addresses.extend(interface_one.get_addresses())
|
||||
|
||||
if not node_two and all([net_two, interface_two]):
|
||||
addresses.extend(interface_two.get_addresses())
|
||||
|
||||
# tunnel node logic
|
||||
key = link_options.key
|
||||
key = options.key
|
||||
if key and isinstance(net_one, TunnelNode):
|
||||
logging.info("setting tunnel key for: %s", net_one.name)
|
||||
net_one.setkey(key)
|
||||
|
@ -412,23 +396,6 @@ class Session:
|
|||
net_two.setkey(key)
|
||||
if addresses:
|
||||
net_two.addrconfig(addresses)
|
||||
|
||||
# physical node connected with tunnel
|
||||
if not net_one and not net_two and (node_one or node_two):
|
||||
if node_one and isinstance(node_one, PhysicalNode):
|
||||
logging.info("adding link for physical node: %s", node_one.name)
|
||||
addresses = interface_one.get_addresses()
|
||||
node_one.adoptnetif(
|
||||
tunnel, interface_one.id, interface_one.mac, addresses
|
||||
)
|
||||
link_config(node_one, tunnel, link_options)
|
||||
elif node_two and isinstance(node_two, PhysicalNode):
|
||||
logging.info("adding link for physical node: %s", node_two.name)
|
||||
addresses = interface_two.get_addresses()
|
||||
node_two.adoptnetif(
|
||||
tunnel, interface_two.id, interface_two.mac, addresses
|
||||
)
|
||||
link_config(node_two, tunnel, link_options)
|
||||
finally:
|
||||
if node_one:
|
||||
node_one.lock.release()
|
||||
|
@ -458,7 +425,7 @@ class Session:
|
|||
:raises core.CoreError: when no common network is found for link being deleted
|
||||
"""
|
||||
# get node objects identified by link data
|
||||
node_one, node_two, net_one, net_two, _tunnel = self._link_nodes(
|
||||
node_one, node_two, net_one, net_two = self._link_nodes(
|
||||
node_one_id, node_two_id
|
||||
)
|
||||
|
||||
|
@ -552,7 +519,7 @@ class Session:
|
|||
node_two_id: int,
|
||||
interface_one_id: int = None,
|
||||
interface_two_id: int = None,
|
||||
link_options: LinkOptions = None,
|
||||
options: LinkOptions = None,
|
||||
) -> None:
|
||||
"""
|
||||
Update link information between nodes.
|
||||
|
@ -561,16 +528,16 @@ class Session:
|
|||
:param node_two_id: node two id
|
||||
:param interface_one_id: interface id for node one
|
||||
:param interface_two_id: interface id for node two
|
||||
:param link_options: data to update link with
|
||||
:param options: data to update link with
|
||||
:return: nothing
|
||||
:raises core.CoreError: when updating a wireless type link, when there is a unknown
|
||||
link between networks
|
||||
"""
|
||||
if not link_options:
|
||||
link_options = LinkOptions()
|
||||
if not options:
|
||||
options = LinkOptions()
|
||||
|
||||
# get node objects identified by link data
|
||||
node_one, node_two, net_one, net_two, _tunnel = self._link_nodes(
|
||||
node_one, node_two, net_one, net_two = self._link_nodes(
|
||||
node_one_id, node_two_id
|
||||
)
|
||||
|
||||
|
@ -581,7 +548,7 @@ class Session:
|
|||
|
||||
try:
|
||||
# wireless link
|
||||
if link_options.type == LinkTypes.WIRELESS:
|
||||
if options.type == LinkTypes.WIRELESS:
|
||||
raise CoreError("cannot update wireless link")
|
||||
else:
|
||||
if not node_one and not node_two:
|
||||
|
@ -599,35 +566,28 @@ class Session:
|
|||
|
||||
if upstream:
|
||||
interface.swapparams("_params_up")
|
||||
link_config(
|
||||
net_one, interface, link_options, devname=interface.name
|
||||
)
|
||||
net_one.linkconfig(interface, options)
|
||||
interface.swapparams("_params_up")
|
||||
else:
|
||||
link_config(net_one, interface, link_options)
|
||||
net_one.linkconfig(interface, options)
|
||||
|
||||
if not link_options.unidirectional:
|
||||
if not options.unidirectional:
|
||||
if upstream:
|
||||
link_config(net_two, interface, link_options)
|
||||
net_two.linkconfig(interface, options)
|
||||
else:
|
||||
interface.swapparams("_params_up")
|
||||
link_config(
|
||||
net_two,
|
||||
interface,
|
||||
link_options,
|
||||
devname=interface.name,
|
||||
)
|
||||
net_two.linkconfig(interface, options)
|
||||
interface.swapparams("_params_up")
|
||||
else:
|
||||
raise CoreError("modify link for unknown nodes")
|
||||
elif not node_one:
|
||||
# node1 = layer 2node, node2 = layer3 node
|
||||
interface = node_two.netif(interface_two_id)
|
||||
link_config(net_one, interface, link_options)
|
||||
net_one.linkconfig(interface, options)
|
||||
elif not node_two:
|
||||
# node2 = layer 2node, node1 = layer3 node
|
||||
interface = node_one.netif(interface_one_id)
|
||||
link_config(net_one, interface, link_options)
|
||||
net_one.linkconfig(interface, options)
|
||||
else:
|
||||
common_networks = node_one.commonnets(node_two)
|
||||
if not common_networks:
|
||||
|
@ -640,60 +600,49 @@ class Session:
|
|||
):
|
||||
continue
|
||||
|
||||
link_config(
|
||||
net_one,
|
||||
interface_one,
|
||||
link_options,
|
||||
interface_two=interface_two,
|
||||
)
|
||||
if not link_options.unidirectional:
|
||||
link_config(
|
||||
net_one,
|
||||
interface_two,
|
||||
link_options,
|
||||
interface_two=interface_one,
|
||||
)
|
||||
net_one.linkconfig(interface_one, options, interface_two)
|
||||
if not options.unidirectional:
|
||||
net_one.linkconfig(interface_two, options, interface_one)
|
||||
finally:
|
||||
if node_one:
|
||||
node_one.lock.release()
|
||||
if node_two:
|
||||
node_two.lock.release()
|
||||
|
||||
def _next_node_id(self) -> int:
|
||||
"""
|
||||
Find the next valid node id, starting from 1.
|
||||
|
||||
:return: next node id
|
||||
"""
|
||||
_id = 1
|
||||
while True:
|
||||
if _id not in self.nodes:
|
||||
break
|
||||
_id += 1
|
||||
return _id
|
||||
|
||||
def add_node(
|
||||
self,
|
||||
_type: NodeTypes = NodeTypes.DEFAULT,
|
||||
_id: int = None,
|
||||
options: NodeOptions = None,
|
||||
_cls: Type[NodeBase] = None,
|
||||
) -> NodeBase:
|
||||
self, _class: Type[NT], _id: int = None, options: NodeOptions = None
|
||||
) -> NT:
|
||||
"""
|
||||
Add a node to the session, based on the provided node data.
|
||||
|
||||
:param _type: type of node to create
|
||||
:param _class: node class to create
|
||||
:param _id: id for node, defaults to None for generated id
|
||||
:param options: data to create node with
|
||||
:param _cls: optional custom class to use for a created node
|
||||
:return: created node
|
||||
:raises core.CoreError: when an invalid node type is given
|
||||
"""
|
||||
# validate node type, get class, or throw error
|
||||
if _cls is None:
|
||||
node_class = self.get_node_class(_type)
|
||||
else:
|
||||
node_class = _cls
|
||||
|
||||
# set node start based on current session state, override and check when rj45
|
||||
start = self.state.should_start()
|
||||
enable_rj45 = self.options.get_config("enablerj45") == "1"
|
||||
if _type == NodeTypes.RJ45 and not enable_rj45:
|
||||
if _class == Rj45Node and not enable_rj45:
|
||||
start = False
|
||||
|
||||
# determine node id
|
||||
if not _id:
|
||||
while True:
|
||||
_id = self.node_id_gen.next()
|
||||
if _id not in self.nodes:
|
||||
break
|
||||
_id = self._next_node_id()
|
||||
|
||||
# generate name if not provided
|
||||
if not options:
|
||||
|
@ -701,7 +650,7 @@ class Session:
|
|||
options.set_position(0, 0)
|
||||
name = options.name
|
||||
if not name:
|
||||
name = f"{node_class.__name__}{_id}"
|
||||
name = f"{_class.__name__}{_id}"
|
||||
|
||||
# verify distributed server
|
||||
server = self.distributed.servers.get(options.server)
|
||||
|
@ -711,24 +660,15 @@ class Session:
|
|||
# create node
|
||||
logging.info(
|
||||
"creating node(%s) id(%s) name(%s) start(%s)",
|
||||
node_class.__name__,
|
||||
_class.__name__,
|
||||
_id,
|
||||
name,
|
||||
start,
|
||||
)
|
||||
if _type in [NodeTypes.DOCKER, NodeTypes.LXC]:
|
||||
node = self.create_node(
|
||||
cls=node_class,
|
||||
_id=_id,
|
||||
name=name,
|
||||
start=start,
|
||||
image=options.image,
|
||||
server=server,
|
||||
)
|
||||
else:
|
||||
node = self.create_node(
|
||||
cls=node_class, _id=_id, name=name, start=start, server=server
|
||||
)
|
||||
kwargs = dict(_id=_id, name=name, start=start, server=server)
|
||||
if _class in CONTAINER_NODES:
|
||||
kwargs["image"] = options.image
|
||||
node = self.create_node(_class, **kwargs)
|
||||
|
||||
# set node attributes
|
||||
node.icon = options.icon
|
||||
|
@ -777,7 +717,7 @@ class Session:
|
|||
:raises core.CoreError: when node to update does not exist
|
||||
"""
|
||||
# get node to update
|
||||
node = self.get_node(node_id)
|
||||
node = self.get_node(node_id, NodeBase)
|
||||
|
||||
# set node position and broadcast it
|
||||
self.set_node_position(node, options)
|
||||
|
@ -873,19 +813,19 @@ class Session:
|
|||
CoreXmlWriter(self).write(file_name)
|
||||
|
||||
def add_hook(
|
||||
self, state: EventTypes, file_name: str, source_name: str, data: str
|
||||
self, state: EventTypes, file_name: str, data: str, source_name: str = None
|
||||
) -> None:
|
||||
"""
|
||||
Store a hook from a received file message.
|
||||
|
||||
:param state: when to run hook
|
||||
:param file_name: file name for hook
|
||||
:param source_name: source name
|
||||
:param data: hook data
|
||||
:param source_name: source name
|
||||
:return: nothing
|
||||
"""
|
||||
logging.info(
|
||||
"setting state hook: %s - %s from %s", state, file_name, source_name
|
||||
"setting state hook: %s - %s source(%s)", state, file_name, source_name
|
||||
)
|
||||
hook = file_name, data
|
||||
state_hooks = self._hooks.setdefault(state, [])
|
||||
|
@ -908,9 +848,7 @@ class Session:
|
|||
:param data: file data
|
||||
:return: nothing
|
||||
"""
|
||||
|
||||
node = self.get_node(node_id)
|
||||
|
||||
node = self.get_node(node_id, CoreNodeBase)
|
||||
if source_name is not None:
|
||||
node.addfile(source_name, file_name)
|
||||
elif data is not None:
|
||||
|
@ -1363,17 +1301,17 @@ class Session:
|
|||
break
|
||||
return node_id
|
||||
|
||||
def create_node(self, cls: Type[NodeBase], *args: Any, **kwargs: Any) -> NodeBase:
|
||||
def create_node(self, _class: Type[NT], *args: Any, **kwargs: Any) -> NT:
|
||||
"""
|
||||
Create an emulation node.
|
||||
|
||||
:param cls: node class to create
|
||||
:param _class: node class to create
|
||||
:param args: list of arguments for the class to create
|
||||
:param kwargs: dictionary of arguments for the class to create
|
||||
:return: the created node instance
|
||||
:raises core.CoreError: when id of the node to create already exists
|
||||
"""
|
||||
node = cls(self, *args, **kwargs)
|
||||
node = _class(self, *args, **kwargs)
|
||||
with self._nodes_lock:
|
||||
if node.id in self.nodes:
|
||||
node.shutdown()
|
||||
|
@ -1381,17 +1319,23 @@ class Session:
|
|||
self.nodes[node.id] = node
|
||||
return node
|
||||
|
||||
def get_node(self, _id: int) -> NodeBase:
|
||||
def get_node(self, _id: int, _class: Type[NT]) -> NT:
|
||||
"""
|
||||
Get a session node.
|
||||
|
||||
:param _id: node id to retrieve
|
||||
:param _class: expected node class
|
||||
:return: node for the given id
|
||||
:raises core.CoreError: when node does not exist
|
||||
"""
|
||||
if _id not in self.nodes:
|
||||
raise CoreError(f"unknown node id {_id}")
|
||||
return self.nodes[_id]
|
||||
node = self.nodes[_id]
|
||||
if not isinstance(node, _class):
|
||||
actual = node.__class__.__name__
|
||||
expected = _class.__name__
|
||||
raise CoreError(f"node class({actual}) is not expected({expected})")
|
||||
return node
|
||||
|
||||
def delete_node(self, _id: int) -> bool:
|
||||
"""
|
||||
|
@ -1425,7 +1369,6 @@ class Session:
|
|||
self.sdt.delete_node(node.id)
|
||||
funcs.append((node.shutdown, [], {}))
|
||||
utils.threadpool(funcs)
|
||||
self.node_id_gen.id = 0
|
||||
|
||||
def write_nodes(self) -> None:
|
||||
"""
|
||||
|
@ -1709,10 +1652,7 @@ class Session:
|
|||
:return: control net
|
||||
:raises CoreError: when control net is not found
|
||||
"""
|
||||
node = self.get_node(CTRL_NET_ID + net_index)
|
||||
if not isinstance(node, CtrlNet):
|
||||
raise CoreError("node is not a valid CtrlNet: %s", node.name)
|
||||
return node
|
||||
return self.get_node(CTRL_NET_ID + net_index, CtrlNet)
|
||||
|
||||
def add_remove_control_net(
|
||||
self, net_index: int, remove: bool = False, conf_required: bool = True
|
||||
|
@ -1788,10 +1728,9 @@ class Session:
|
|||
server_interface,
|
||||
)
|
||||
control_net = self.create_node(
|
||||
cls=CtrlNet,
|
||||
CtrlNet,
|
||||
prefix,
|
||||
_id=_id,
|
||||
prefix=prefix,
|
||||
assign_address=True,
|
||||
updown_script=updown_script,
|
||||
serverintf=server_interface,
|
||||
)
|
||||
|
@ -1820,35 +1759,28 @@ class Session:
|
|||
control_net = self.add_remove_control_net(net_index, remove, conf_required)
|
||||
if not control_net:
|
||||
return
|
||||
|
||||
if not node:
|
||||
return
|
||||
|
||||
# ctrl# already exists
|
||||
if node.netif(control_net.CTRLIF_IDX_BASE + net_index):
|
||||
return
|
||||
|
||||
control_ip = node.id
|
||||
|
||||
try:
|
||||
address = control_net.prefix[control_ip]
|
||||
prefix = control_net.prefix.prefixlen
|
||||
addrlist = [f"{address}/{prefix}"]
|
||||
ip4 = control_net.prefix[node.id]
|
||||
ip4_mask = control_net.prefix.prefixlen
|
||||
interface = InterfaceData(
|
||||
id=control_net.CTRLIF_IDX_BASE + net_index,
|
||||
name=f"ctrl{net_index}",
|
||||
mac=utils.random_mac(),
|
||||
ip4=ip4,
|
||||
ip4_mask=ip4_mask,
|
||||
)
|
||||
ifindex = node.newnetif(control_net, interface)
|
||||
node.netif(ifindex).control = True
|
||||
except ValueError:
|
||||
msg = f"Control interface not added to node {node.id}. "
|
||||
msg += f"Invalid control network prefix ({control_net.prefix}). "
|
||||
msg += "A longer prefix length may be required for this many nodes."
|
||||
logging.exception(msg)
|
||||
return
|
||||
|
||||
interface1 = node.newnetif(
|
||||
net=control_net,
|
||||
ifindex=control_net.CTRLIF_IDX_BASE + net_index,
|
||||
ifname=f"ctrl{net_index}",
|
||||
hwaddr=utils.random_mac(),
|
||||
addrlist=addrlist,
|
||||
)
|
||||
node.netif(interface1).control = True
|
||||
|
||||
def update_control_interface_hosts(
|
||||
self, net_index: int = 0, remove: bool = False
|
||||
|
@ -1959,7 +1891,7 @@ class Session:
|
|||
if not node_id:
|
||||
utils.mute_detach(data)
|
||||
else:
|
||||
node = self.get_node(node_id)
|
||||
node = self.get_node(node_id, CoreNodeBase)
|
||||
node.cmd(data, wait=False)
|
||||
|
||||
def get_link_color(self, network_id: int) -> str:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Any
|
||||
from typing import Any, List
|
||||
|
||||
from core.config import ConfigurableManager, ConfigurableOptions, Configuration
|
||||
from core.emulator.enumerations import ConfigDataTypes, RegisterTlvs
|
||||
|
@ -10,8 +10,8 @@ class SessionConfig(ConfigurableManager, ConfigurableOptions):
|
|||
Provides session configuration.
|
||||
"""
|
||||
|
||||
name = "session"
|
||||
options = [
|
||||
name: str = "session"
|
||||
options: List[Configuration] = [
|
||||
Configuration(
|
||||
_id="controlnet", _type=ConfigDataTypes.STRING, label="Control Network"
|
||||
),
|
||||
|
@ -57,7 +57,7 @@ class SessionConfig(ConfigurableManager, ConfigurableOptions):
|
|||
label="SDT3D URL",
|
||||
),
|
||||
]
|
||||
config_type = RegisterTlvs.UTILITY
|
||||
config_type: RegisterTlvs = RegisterTlvs.UTILITY
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import logging
|
||||
import math
|
||||
import tkinter as tk
|
||||
from tkinter import font, ttk
|
||||
from tkinter import PhotoImage, font, ttk
|
||||
from tkinter.ttk import Progressbar
|
||||
|
||||
import grpc
|
||||
|
@ -104,7 +104,7 @@ class Application(ttk.Frame):
|
|||
self.rowconfigure(0, weight=1)
|
||||
self.columnconfigure(1, weight=1)
|
||||
self.grid(sticky="nsew")
|
||||
self.toolbar = Toolbar(self, self)
|
||||
self.toolbar = Toolbar(self)
|
||||
self.toolbar.grid(sticky="ns")
|
||||
self.right_frame = ttk.Frame(self)
|
||||
self.right_frame.columnconfigure(0, weight=1)
|
||||
|
@ -113,16 +113,15 @@ class Application(ttk.Frame):
|
|||
self.draw_canvas()
|
||||
self.draw_status()
|
||||
self.progress = Progressbar(self.right_frame, mode="indeterminate")
|
||||
self.menubar = Menubar(self.master, self)
|
||||
self.menubar = Menubar(self)
|
||||
self.master.config(menu=self.menubar)
|
||||
|
||||
def draw_canvas(self) -> None:
|
||||
width = self.guiconfig.preferences.width
|
||||
height = self.guiconfig.preferences.height
|
||||
canvas_frame = ttk.Frame(self.right_frame)
|
||||
canvas_frame.rowconfigure(0, weight=1)
|
||||
canvas_frame.columnconfigure(0, weight=1)
|
||||
canvas_frame.grid(sticky="nsew", pady=1)
|
||||
self.canvas = CanvasGraph(canvas_frame, self, self.core, width, height)
|
||||
self.canvas = CanvasGraph(canvas_frame, self, self.core)
|
||||
self.canvas.grid(sticky="nsew")
|
||||
scroll_y = ttk.Scrollbar(canvas_frame, command=self.canvas.yview)
|
||||
scroll_y.grid(row=0, column=1, sticky="ns")
|
||||
|
@ -150,6 +149,8 @@ class Application(ttk.Frame):
|
|||
self.after(0, lambda: ErrorDialog(self, title, message).show())
|
||||
|
||||
def on_closing(self) -> None:
|
||||
if self.toolbar.picker:
|
||||
self.toolbar.picker.destroy()
|
||||
self.menubar.prompt_save_running_session(True)
|
||||
|
||||
def save_config(self) -> None:
|
||||
|
@ -161,5 +162,11 @@ class Application(ttk.Frame):
|
|||
else:
|
||||
self.toolbar.set_design()
|
||||
|
||||
def get_icon(self, image_enum: ImageEnum, width: int) -> PhotoImage:
|
||||
return Images.get(image_enum, int(width * self.app_scale))
|
||||
|
||||
def get_custom_icon(self, image_file: str, width: int) -> PhotoImage:
|
||||
return Images.get_custom(image_file, int(width * self.app_scale))
|
||||
|
||||
def close(self) -> None:
|
||||
self.master.destroy()
|
||||
|
|
|
@ -20,7 +20,6 @@ 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 import tags
|
||||
from core.gui.graph.edges import CanvasEdge
|
||||
from core.gui.graph.node import CanvasNode
|
||||
from core.gui.graph.shape import AnnotationData, Shape
|
||||
|
@ -136,7 +135,7 @@ class CoreClient:
|
|||
return
|
||||
|
||||
if event.HasField("link_event"):
|
||||
self.handle_link_event(event.link_event)
|
||||
self.app.after(0, self.handle_link_event, event.link_event)
|
||||
elif event.HasField("session_event"):
|
||||
logging.info("session event: %s", event)
|
||||
session_event = event.session_event
|
||||
|
@ -155,7 +154,7 @@ class CoreClient:
|
|||
else:
|
||||
logging.warning("unknown session event: %s", session_event)
|
||||
elif event.HasField("node_event"):
|
||||
self.handle_node_event(event.node_event)
|
||||
self.app.after(0, self.handle_node_event, event.node_event)
|
||||
elif event.HasField("config_event"):
|
||||
logging.info("config event: %s", event)
|
||||
elif event.HasField("exception_event"):
|
||||
|
@ -221,7 +220,7 @@ class CoreClient:
|
|||
)
|
||||
return
|
||||
logging.debug("handling throughputs event: %s", event)
|
||||
self.app.canvas.set_throughputs(event)
|
||||
self.app.after(0, self.app.canvas.set_throughputs, event)
|
||||
|
||||
def handle_exception_event(self, event: core_pb2.ExceptionEvent):
|
||||
logging.info("exception event: %s", event)
|
||||
|
@ -331,6 +330,9 @@ class CoreClient:
|
|||
except grpc.RpcError as e:
|
||||
self.app.show_grpc_exception("Join Session Error", e)
|
||||
|
||||
# organize canvas
|
||||
self.app.canvas.organize()
|
||||
|
||||
# update ui to represent current state
|
||||
self.app.after(0, self.app.joined_session_update)
|
||||
|
||||
|
@ -390,9 +392,6 @@ class CoreClient:
|
|||
except ValueError:
|
||||
logging.exception("unknown shape: %s", shape_type)
|
||||
|
||||
for tag in tags.ABOVE_WALLPAPER_TAGS:
|
||||
self.app.canvas.tag_raise(tag)
|
||||
|
||||
def create_new_session(self):
|
||||
"""
|
||||
Create a new session
|
||||
|
@ -838,7 +837,7 @@ class CoreClient:
|
|||
ip4, ip6 = self.interfaces_manager.get_ips(node)
|
||||
ip4_mask = self.interfaces_manager.ip4_mask
|
||||
ip6_mask = self.interfaces_manager.ip6_mask
|
||||
interface_id = len(canvas_node.interfaces)
|
||||
interface_id = canvas_node.next_interface_id()
|
||||
name = f"eth{interface_id}"
|
||||
interface = core_pb2.Interface(
|
||||
id=interface_id,
|
||||
|
@ -848,7 +847,7 @@ class CoreClient:
|
|||
ip6=ip6,
|
||||
ip6mask=ip6_mask,
|
||||
)
|
||||
logging.debug(
|
||||
logging.info(
|
||||
"create node(%s) interface(%s) IPv4(%s) IPv6(%s)",
|
||||
node.name,
|
||||
interface.name,
|
||||
|
@ -887,12 +886,15 @@ class CoreClient:
|
|||
interface_one=src_interface,
|
||||
interface_two=dst_interface,
|
||||
)
|
||||
# assign after creating link proto, since interfaces are copied
|
||||
if src_interface:
|
||||
edge.src_interface = link.interface_one
|
||||
canvas_src_node.interfaces.append(link.interface_one)
|
||||
interface_one = link.interface_one
|
||||
edge.src_interface = interface_one
|
||||
canvas_src_node.interfaces[interface_one.id] = interface_one
|
||||
if dst_interface:
|
||||
edge.dst_interface = link.interface_two
|
||||
canvas_dst_node.interfaces.append(link.interface_two)
|
||||
interface_two = link.interface_two
|
||||
edge.dst_interface = interface_two
|
||||
canvas_dst_node.interfaces[interface_two.id] = interface_two
|
||||
edge.set_link(link)
|
||||
self.links[edge.token] = edge
|
||||
logging.info("Add link between %s and %s", src_node.name, dst_node.name)
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 1.1 KiB |
BIN
daemon/core/gui/data/icons/observe.png
Normal file
BIN
daemon/core/gui/data/icons/observe.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.1 KiB |
|
@ -7,6 +7,7 @@ from typing import TYPE_CHECKING
|
|||
|
||||
from core.gui import validation
|
||||
from core.gui.dialogs.dialog import Dialog
|
||||
from core.gui.themes import PADX, PADY
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
@ -16,7 +17,7 @@ class ColorPickerDialog(Dialog):
|
|||
def __init__(
|
||||
self, master: tk.BaseWidget, app: "Application", initcolor: str = "#000000"
|
||||
):
|
||||
super().__init__(app, "color picker", master=master)
|
||||
super().__init__(app, "Color Picker", master=master)
|
||||
self.red_entry = None
|
||||
self.blue_entry = None
|
||||
self.green_entry = None
|
||||
|
@ -43,42 +44,40 @@ class ColorPickerDialog(Dialog):
|
|||
|
||||
def draw(self):
|
||||
self.top.columnconfigure(0, weight=1)
|
||||
self.top.rowconfigure(3, weight=1)
|
||||
|
||||
# rgb frames
|
||||
frame = ttk.Frame(self.top)
|
||||
frame.columnconfigure(0, weight=1)
|
||||
frame.columnconfigure(1, weight=1)
|
||||
frame.columnconfigure(2, weight=6)
|
||||
frame.columnconfigure(3, weight=2)
|
||||
label = ttk.Label(frame, text="R: ")
|
||||
label.grid(row=0, column=0)
|
||||
self.red_entry = validation.RgbEntry(frame, width=4, textvariable=self.red)
|
||||
self.red_entry.grid(row=0, column=1, sticky="nsew")
|
||||
frame.grid(row=0, column=0, sticky="ew", pady=PADY)
|
||||
frame.columnconfigure(2, weight=3)
|
||||
frame.columnconfigure(3, weight=1)
|
||||
label = ttk.Label(frame, text="R")
|
||||
label.grid(row=0, column=0, padx=PADX)
|
||||
self.red_entry = validation.RgbEntry(frame, width=3, textvariable=self.red)
|
||||
self.red_entry.grid(row=0, column=1, sticky="ew", padx=PADX)
|
||||
scale = ttk.Scale(
|
||||
frame,
|
||||
from_=0,
|
||||
to=255,
|
||||
value=0,
|
||||
# length=200,
|
||||
orient=tk.HORIZONTAL,
|
||||
variable=self.red_scale,
|
||||
command=lambda x: self.scale_callback(self.red_scale, self.red),
|
||||
)
|
||||
scale.grid(row=0, column=2, sticky="nsew")
|
||||
scale.grid(row=0, column=2, sticky="ew", padx=PADX)
|
||||
self.red_label = ttk.Label(
|
||||
frame, background="#%02x%02x%02x" % (self.red.get(), 0, 0), width=5
|
||||
)
|
||||
self.red_label.grid(row=0, column=3, sticky="nsew")
|
||||
frame.grid(row=0, column=0, sticky="nsew")
|
||||
self.red_label.grid(row=0, column=3, sticky="ew")
|
||||
|
||||
frame = ttk.Frame(self.top)
|
||||
frame.columnconfigure(0, weight=1)
|
||||
frame.columnconfigure(1, weight=1)
|
||||
frame.columnconfigure(2, weight=6)
|
||||
frame.columnconfigure(3, weight=2)
|
||||
label = ttk.Label(frame, text="G: ")
|
||||
label.grid(row=0, column=0)
|
||||
self.green_entry = validation.RgbEntry(frame, width=4, textvariable=self.green)
|
||||
self.green_entry.grid(row=0, column=1, sticky="nsew")
|
||||
frame.grid(row=1, column=0, sticky="ew", pady=PADY)
|
||||
frame.columnconfigure(2, weight=3)
|
||||
frame.columnconfigure(3, weight=1)
|
||||
label = ttk.Label(frame, text="G")
|
||||
label.grid(row=0, column=0, padx=PADX)
|
||||
self.green_entry = validation.RgbEntry(frame, width=3, textvariable=self.green)
|
||||
self.green_entry.grid(row=0, column=1, sticky="ew", padx=PADX)
|
||||
scale = ttk.Scale(
|
||||
frame,
|
||||
from_=0,
|
||||
|
@ -88,59 +87,54 @@ class ColorPickerDialog(Dialog):
|
|||
variable=self.green_scale,
|
||||
command=lambda x: self.scale_callback(self.green_scale, self.green),
|
||||
)
|
||||
scale.grid(row=0, column=2, sticky="nsew")
|
||||
scale.grid(row=0, column=2, sticky="ew", padx=PADX)
|
||||
self.green_label = ttk.Label(
|
||||
frame, background="#%02x%02x%02x" % (0, self.green.get(), 0), width=5
|
||||
)
|
||||
self.green_label.grid(row=0, column=3, sticky="nsew")
|
||||
frame.grid(row=1, column=0, sticky="nsew")
|
||||
self.green_label.grid(row=0, column=3, sticky="ew")
|
||||
|
||||
frame = ttk.Frame(self.top)
|
||||
frame.columnconfigure(0, weight=1)
|
||||
frame.columnconfigure(1, weight=1)
|
||||
frame.columnconfigure(2, weight=6)
|
||||
frame.columnconfigure(3, weight=2)
|
||||
label = ttk.Label(frame, text="B: ")
|
||||
label.grid(row=0, column=0)
|
||||
self.blue_entry = validation.RgbEntry(frame, width=4, textvariable=self.blue)
|
||||
self.blue_entry.grid(row=0, column=1, sticky="nsew")
|
||||
frame.grid(row=2, column=0, sticky="ew", pady=PADY)
|
||||
frame.columnconfigure(2, weight=3)
|
||||
frame.columnconfigure(3, weight=1)
|
||||
label = ttk.Label(frame, text="B")
|
||||
label.grid(row=0, column=0, padx=PADX)
|
||||
self.blue_entry = validation.RgbEntry(frame, width=3, textvariable=self.blue)
|
||||
self.blue_entry.grid(row=0, column=1, sticky="ew", padx=PADX)
|
||||
scale = ttk.Scale(
|
||||
frame,
|
||||
from_=0,
|
||||
to=255,
|
||||
value=0,
|
||||
# length=200,
|
||||
orient=tk.HORIZONTAL,
|
||||
variable=self.blue_scale,
|
||||
command=lambda x: self.scale_callback(self.blue_scale, self.blue),
|
||||
)
|
||||
scale.grid(row=0, column=2, sticky="nsew")
|
||||
scale.grid(row=0, column=2, sticky="ew", padx=PADX)
|
||||
self.blue_label = ttk.Label(
|
||||
frame, background="#%02x%02x%02x" % (0, 0, self.blue.get()), width=5
|
||||
)
|
||||
self.blue_label.grid(row=0, column=3, sticky="nsew")
|
||||
frame.grid(row=2, column=0, sticky="nsew")
|
||||
self.blue_label.grid(row=0, column=3, sticky="ew")
|
||||
|
||||
# hex code and color display
|
||||
frame = ttk.Frame(self.top)
|
||||
frame.columnconfigure(0, weight=1)
|
||||
label = ttk.Label(frame, text="Selection: ")
|
||||
label.grid(row=0, column=0, sticky="nsew")
|
||||
frame.rowconfigure(1, weight=1)
|
||||
self.hex_entry = validation.HexEntry(frame, textvariable=self.hex)
|
||||
self.hex_entry.grid(row=1, column=0, sticky="nsew")
|
||||
self.hex_entry.grid(sticky="ew", pady=PADY)
|
||||
self.display = tk.Frame(frame, background=self.color, width=100, height=100)
|
||||
self.display.grid(row=2, column=0)
|
||||
frame.grid(row=3, column=0, sticky="nsew")
|
||||
self.display.grid(sticky="nsew")
|
||||
frame.grid(row=3, column=0, sticky="nsew", pady=PADY)
|
||||
|
||||
# button frame
|
||||
frame = ttk.Frame(self.top)
|
||||
frame.grid(row=4, column=0, sticky="ew")
|
||||
frame.columnconfigure(0, weight=1)
|
||||
frame.columnconfigure(1, weight=1)
|
||||
button = ttk.Button(frame, text="OK", command=self.button_ok)
|
||||
button.grid(row=0, column=0, sticky="nsew")
|
||||
button.grid(row=0, column=0, sticky="ew", padx=PADX)
|
||||
button = ttk.Button(frame, text="Cancel", command=self.destroy)
|
||||
button.grid(row=0, column=1, sticky="nsew")
|
||||
frame.grid(row=4, column=0, sticky="nsew")
|
||||
button.grid(row=0, column=1, sticky="ew")
|
||||
|
||||
def set_bindings(self):
|
||||
self.red_entry.bind("<FocusIn>", lambda x: self.current_focus("rgb"))
|
||||
|
|
|
@ -1,72 +0,0 @@
|
|||
"""
|
||||
marker dialog
|
||||
"""
|
||||
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from core.gui.dialogs.colorpicker import ColorPickerDialog
|
||||
from core.gui.dialogs.dialog import Dialog
|
||||
from core.gui.graph import tags
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
||||
MARKER_THICKNESS = [3, 5, 8, 10]
|
||||
|
||||
|
||||
class MarkerDialog(Dialog):
|
||||
def __init__(self, app: "Application", initcolor: str = "#000000"):
|
||||
super().__init__(app, "Marker Tool", modal=False)
|
||||
self.color = initcolor
|
||||
self.radius = MARKER_THICKNESS[0]
|
||||
self.marker_thickness = tk.IntVar(value=MARKER_THICKNESS[0])
|
||||
self.draw()
|
||||
|
||||
def draw(self):
|
||||
button = ttk.Button(self.top, text="clear", command=self.clear_marker)
|
||||
button.grid(row=0, column=0, sticky="nsew")
|
||||
|
||||
frame = ttk.Frame(self.top)
|
||||
frame.columnconfigure(0, weight=1)
|
||||
frame.columnconfigure(1, weight=4)
|
||||
frame.grid(row=1, column=0, sticky="nsew")
|
||||
label = ttk.Label(frame, text="Thickness: ")
|
||||
label.grid(row=0, column=0, sticky="nsew")
|
||||
combobox = ttk.Combobox(
|
||||
frame,
|
||||
textvariable=self.marker_thickness,
|
||||
values=MARKER_THICKNESS,
|
||||
state="readonly",
|
||||
)
|
||||
combobox.grid(row=0, column=1, sticky="nsew")
|
||||
combobox.bind("<<ComboboxSelected>>", self.change_thickness)
|
||||
frame = ttk.Frame(self.top)
|
||||
frame.columnconfigure(0, weight=1)
|
||||
frame.columnconfigure(1, weight=4)
|
||||
frame.grid(row=2, column=0, sticky="nsew")
|
||||
label = ttk.Label(frame, text="Color: ")
|
||||
label.grid(row=0, column=0, sticky="nsew")
|
||||
label = ttk.Label(frame, background=self.color)
|
||||
label.grid(row=0, column=1, sticky="nsew")
|
||||
label.bind("<Button-1>", self.change_color)
|
||||
|
||||
def clear_marker(self):
|
||||
canvas = self.app.canvas
|
||||
canvas.delete(tags.MARKER)
|
||||
|
||||
def change_color(self, event: tk.Event):
|
||||
color_picker = ColorPickerDialog(self, self.app, self.color)
|
||||
color = color_picker.askcolor()
|
||||
event.widget.configure(background=color)
|
||||
self.color = color
|
||||
|
||||
def change_thickness(self, event: tk.Event):
|
||||
self.radius = self.marker_thickness.get()
|
||||
|
||||
def show(self):
|
||||
super().show()
|
||||
self.geometry(
|
||||
f"+{self.app.canvas.winfo_rootx()}+{self.app.canvas.master.winfo_rooty()}"
|
||||
)
|
|
@ -6,7 +6,7 @@ import grpc
|
|||
|
||||
from core.api.grpc.mobility_pb2 import MobilityAction
|
||||
from core.gui.dialogs.dialog import Dialog
|
||||
from core.gui.images import ImageEnum, Images
|
||||
from core.gui.images import ImageEnum
|
||||
from core.gui.themes import PADX, PADY
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
@ -89,17 +89,17 @@ class MobilityPlayerDialog(Dialog):
|
|||
for i in range(3):
|
||||
frame.columnconfigure(i, weight=1)
|
||||
|
||||
image = Images.get(ImageEnum.START, width=int(ICON_SIZE * self.app.app_scale))
|
||||
image = self.app.get_icon(ImageEnum.START, ICON_SIZE)
|
||||
self.play_button = ttk.Button(frame, image=image, command=self.click_play)
|
||||
self.play_button.image = image
|
||||
self.play_button.grid(row=0, column=0, sticky="ew", padx=PADX)
|
||||
|
||||
image = Images.get(ImageEnum.PAUSE, width=int(ICON_SIZE * self.app.app_scale))
|
||||
image = self.app.get_icon(ImageEnum.PAUSE, ICON_SIZE)
|
||||
self.pause_button = ttk.Button(frame, image=image, command=self.click_pause)
|
||||
self.pause_button.image = image
|
||||
self.pause_button.grid(row=0, column=1, sticky="ew", padx=PADX)
|
||||
|
||||
image = Images.get(ImageEnum.STOP, width=int(ICON_SIZE * self.app.app_scale))
|
||||
image = self.app.get_icon(ImageEnum.STOP, ICON_SIZE)
|
||||
self.stop_button = ttk.Button(frame, image=image, command=self.click_stop)
|
||||
self.stop_button.image = image
|
||||
self.stop_button.grid(row=0, column=2, sticky="ew", padx=PADX)
|
||||
|
|
|
@ -207,8 +207,8 @@ class NodeConfigDialog(Dialog):
|
|||
notebook.grid(sticky="nsew", pady=PADY)
|
||||
self.top.rowconfigure(notebook.grid_info()["row"], weight=1)
|
||||
state = tk.DISABLED if self.app.core.is_runtime() else tk.NORMAL
|
||||
for interface in self.canvas_node.interfaces:
|
||||
logging.info("interface: %s", interface)
|
||||
for interface_id in sorted(self.canvas_node.interfaces):
|
||||
interface = self.canvas_node.interfaces[interface_id]
|
||||
tab = ttk.Frame(notebook, padding=FRAME_PAD)
|
||||
tab.grid(sticky="nsew", pady=PADY)
|
||||
tab.columnconfigure(1, weight=1)
|
||||
|
@ -309,7 +309,7 @@ class NodeConfigDialog(Dialog):
|
|||
self.canvas_node.image = self.image
|
||||
|
||||
# update node interface data
|
||||
for interface in self.canvas_node.interfaces:
|
||||
for interface in self.canvas_node.interfaces.values():
|
||||
data = self.interfaces[interface.id]
|
||||
|
||||
# validate ip4
|
||||
|
|
|
@ -104,7 +104,8 @@ class ObserverDialog(Dialog):
|
|||
self.observers.insert(tk.END, name)
|
||||
self.name.set("")
|
||||
self.cmd.set("")
|
||||
self.app.menubar.draw_custom_observers()
|
||||
self.app.menubar.observers_menu.draw_custom()
|
||||
self.app.toolbar.observers_menu.draw_custom()
|
||||
else:
|
||||
messagebox.showerror("Observer Error", f"{name} already exists")
|
||||
|
||||
|
@ -132,7 +133,8 @@ class ObserverDialog(Dialog):
|
|||
self.observers.selection_clear(0, tk.END)
|
||||
self.save_button.config(state=tk.DISABLED)
|
||||
self.delete_button.config(state=tk.DISABLED)
|
||||
self.app.menubar.draw_custom_observers()
|
||||
self.app.menubar.observers_menu.draw_custom()
|
||||
self.app.toolbar.observers_menu.draw_custom()
|
||||
|
||||
def handle_observer_change(self, event: tk.Event):
|
||||
selection = self.observers.curselection()
|
||||
|
|
|
@ -17,6 +17,8 @@ if TYPE_CHECKING:
|
|||
from core.gui.app import Application
|
||||
from core.gui.graph.node import CanvasNode
|
||||
|
||||
ICON_SIZE = 16
|
||||
|
||||
|
||||
class ServiceConfigDialog(Dialog):
|
||||
def __init__(
|
||||
|
@ -51,10 +53,8 @@ class ServiceConfigDialog(Dialog):
|
|||
self.directory_entry = None
|
||||
self.default_directories = []
|
||||
self.temp_directories = []
|
||||
self.documentnew_img = Images.get(
|
||||
ImageEnum.DOCUMENTNEW, int(16 * app.app_scale)
|
||||
)
|
||||
self.editdelete_img = Images.get(ImageEnum.EDITDELETE, int(16 * app.app_scale))
|
||||
self.documentnew_img = self.app.get_icon(ImageEnum.DOCUMENTNEW, ICON_SIZE)
|
||||
self.editdelete_img = self.app.get_icon(ImageEnum.EDITDELETE, ICON_SIZE)
|
||||
self.notebook = None
|
||||
self.metadata_entry = None
|
||||
self.filename_combobox = None
|
||||
|
|
|
@ -331,8 +331,6 @@ class CanvasEdge(Edge):
|
|||
dst_pos = self.canvas.coords(self.dst)
|
||||
self.move_dst(dst_pos)
|
||||
self.check_wireless()
|
||||
self.canvas.tag_raise(self.src)
|
||||
self.canvas.tag_raise(self.dst)
|
||||
logging.debug("Draw wired link from node %s to node %s", self.src, dst)
|
||||
|
||||
def is_wireless(self) -> bool:
|
||||
|
|
|
@ -20,7 +20,7 @@ from core.gui.graph.enums import GraphMode, ScaleOption
|
|||
from core.gui.graph.node import CanvasNode
|
||||
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, Images, TypeToImage
|
||||
from core.gui.images import ImageEnum, TypeToImage
|
||||
from core.gui.nodeutils import NodeUtils
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
@ -30,6 +30,8 @@ if TYPE_CHECKING:
|
|||
ZOOM_IN = 1.1
|
||||
ZOOM_OUT = 0.9
|
||||
ICON_SIZE = 48
|
||||
MOVE_NODE_MODES = {GraphMode.NODE, GraphMode.SELECT}
|
||||
MOVE_SHAPE_MODES = {GraphMode.ANNOTATION, GraphMode.SELECT}
|
||||
|
||||
|
||||
class ShowVar(BooleanVar):
|
||||
|
@ -41,19 +43,12 @@ class ShowVar(BooleanVar):
|
|||
def state(self) -> str:
|
||||
return tk.NORMAL if self.get() else tk.HIDDEN
|
||||
|
||||
def click_handler(self):
|
||||
def click_handler(self) -> None:
|
||||
self.canvas.itemconfigure(self.tag, state=self.state())
|
||||
|
||||
|
||||
class CanvasGraph(tk.Canvas):
|
||||
def __init__(
|
||||
self,
|
||||
master: tk.Widget,
|
||||
app: "Application",
|
||||
core: "CoreClient",
|
||||
width: int,
|
||||
height: int,
|
||||
):
|
||||
def __init__(self, master: tk.Widget, app: "Application", core: "CoreClient"):
|
||||
super().__init__(master, highlightthickness=0, background="#cccccc")
|
||||
self.app = app
|
||||
self.core = core
|
||||
|
@ -74,6 +69,8 @@ class CanvasGraph(tk.Canvas):
|
|||
self.drawing_edge = None
|
||||
self.rect = None
|
||||
self.shape_drawing = False
|
||||
width = self.app.guiconfig.preferences.width
|
||||
height = self.app.guiconfig.preferences.height
|
||||
self.default_dimensions = (width, height)
|
||||
self.current_dimensions = self.default_dimensions
|
||||
self.ratio = 1.0
|
||||
|
@ -144,7 +141,7 @@ class CanvasGraph(tk.Canvas):
|
|||
self.show_ip6s.set(True)
|
||||
|
||||
# delete any existing drawn items
|
||||
for tag in tags.COMPONENT_TAGS:
|
||||
for tag in tags.RESET_TAGS:
|
||||
self.delete(tag)
|
||||
|
||||
# set the private variables to default value
|
||||
|
@ -293,9 +290,7 @@ class CanvasGraph(tk.Canvas):
|
|||
)
|
||||
# if the gui can't find node's image, default to the "edit-node" image
|
||||
if not image:
|
||||
image = Images.get(
|
||||
ImageEnum.EDITNODE, int(ICON_SIZE * self.app.app_scale)
|
||||
)
|
||||
image = self.app.get_icon(ImageEnum.EDITNODE, ICON_SIZE)
|
||||
x = core_node.position.x
|
||||
y = core_node.position.y
|
||||
node = CanvasNode(self.app, x, y, core_node, image)
|
||||
|
@ -327,20 +322,25 @@ class CanvasGraph(tk.Canvas):
|
|||
self.edges[edge.token] = edge
|
||||
self.core.links[edge.token] = edge
|
||||
if link.HasField("interface_one"):
|
||||
canvas_node_one.interfaces.append(link.interface_one)
|
||||
edge.src_interface = link.interface_one
|
||||
interface_one = link.interface_one
|
||||
self.core.interface_to_edge[
|
||||
(node_one.id, interface_one.id)
|
||||
] = token
|
||||
canvas_node_one.interfaces[interface_one.id] = interface_one
|
||||
edge.src_interface = interface_one
|
||||
if link.HasField("interface_two"):
|
||||
canvas_node_two.interfaces.append(link.interface_two)
|
||||
edge.dst_interface = link.interface_two
|
||||
interface_two = link.interface_two
|
||||
self.core.interface_to_edge[
|
||||
(node_two.id, interface_two.id)
|
||||
] = edge.token
|
||||
canvas_node_two.interfaces[interface_two.id] = interface_two
|
||||
edge.dst_interface = interface_two
|
||||
elif link.options.unidirectional:
|
||||
edge = self.edges[token]
|
||||
edge.asymmetric_link = link
|
||||
else:
|
||||
logging.error("duplicate link received: %s", link)
|
||||
|
||||
# raise the nodes so they on top of the links
|
||||
self.tag_raise(tags.NODE)
|
||||
|
||||
def stopped_session(self):
|
||||
# clear wireless edges
|
||||
for edge in self.wireless_edges.values():
|
||||
|
@ -521,8 +521,8 @@ class CanvasGraph(tk.Canvas):
|
|||
other_interface = edge.dst_interface
|
||||
other_node = self.nodes[other_id]
|
||||
other_node.edges.remove(edge)
|
||||
if other_interface in other_node.interfaces:
|
||||
other_node.interfaces.remove(other_interface)
|
||||
if other_interface:
|
||||
del other_node.interfaces[other_interface.id]
|
||||
if is_wireless:
|
||||
other_node.delete_antenna()
|
||||
|
||||
|
@ -540,12 +540,12 @@ class CanvasGraph(tk.Canvas):
|
|||
del self.edges[edge.token]
|
||||
src_node = self.nodes[edge.src]
|
||||
src_node.edges.discard(edge)
|
||||
if edge.src_interface in src_node.interfaces:
|
||||
src_node.interfaces.remove(edge.src_interface)
|
||||
if edge.src_interface:
|
||||
del src_node.interfaces[edge.src_interface.id]
|
||||
dst_node = self.nodes[edge.dst]
|
||||
dst_node.edges.discard(edge)
|
||||
if edge.dst_interface in dst_node.interfaces:
|
||||
dst_node.interfaces.remove(edge.dst_interface)
|
||||
if edge.dst_interface:
|
||||
del dst_node.interfaces[edge.dst_interface.id]
|
||||
src_wireless = NodeUtils.is_wireless_node(src_node.core_node.type)
|
||||
if src_wireless:
|
||||
dst_node.delete_antenna()
|
||||
|
@ -565,10 +565,10 @@ class CanvasGraph(tk.Canvas):
|
|||
self.offset[0] * factor + event.x * (1 - factor),
|
||||
self.offset[1] * factor + event.y * (1 - factor),
|
||||
)
|
||||
logging.info("ratio: %s", self.ratio)
|
||||
logging.info("offset: %s", self.offset)
|
||||
self.app.statusbar.zoom.config(text="%s" % (int(self.ratio * 100)) + "%")
|
||||
|
||||
logging.debug("ratio: %s", self.ratio)
|
||||
logging.debug("offset: %s", self.offset)
|
||||
zoom_label = f"{self.ratio * 100:.0f}%"
|
||||
self.app.statusbar.zoom.config(text=zoom_label)
|
||||
if self.wallpaper:
|
||||
self.redraw_wallpaper()
|
||||
|
||||
|
@ -590,16 +590,17 @@ class CanvasGraph(tk.Canvas):
|
|||
if self.mode == GraphMode.EDGE and is_node:
|
||||
pos = self.coords(selected)
|
||||
self.drawing_edge = CanvasEdge(self, selected, pos, pos)
|
||||
self.organize()
|
||||
|
||||
if self.mode == GraphMode.ANNOTATION:
|
||||
if is_marker(self.annotation_type):
|
||||
r = self.app.toolbar.marker_tool.radius
|
||||
r = self.app.toolbar.marker_frame.size.get()
|
||||
self.create_oval(
|
||||
x - r,
|
||||
y - r,
|
||||
x + r,
|
||||
y + r,
|
||||
fill=self.app.toolbar.marker_tool.color,
|
||||
fill=self.app.toolbar.marker_frame.color,
|
||||
outline="",
|
||||
tags=(tags.MARKER, tags.ANNOTATION),
|
||||
state=self.show_annotations.state(),
|
||||
|
@ -652,9 +653,6 @@ class CanvasGraph(tk.Canvas):
|
|||
self.select_object(selected, choose_multiple=True)
|
||||
|
||||
def click_motion(self, event: tk.Event):
|
||||
"""
|
||||
Redraw drawing edge according to the current position of the mouse
|
||||
"""
|
||||
x, y = self.canvas_xy(event)
|
||||
if not self.inside_canvas(x, y):
|
||||
if self.select_box:
|
||||
|
@ -676,18 +674,19 @@ class CanvasGraph(tk.Canvas):
|
|||
if is_draw_shape(self.annotation_type) and self.shape_drawing:
|
||||
shape = self.shapes[self.selected]
|
||||
shape.shape_motion(x, y)
|
||||
return
|
||||
elif is_marker(self.annotation_type):
|
||||
r = self.app.toolbar.marker_tool.radius
|
||||
r = self.app.toolbar.marker_frame.size.get()
|
||||
self.create_oval(
|
||||
x - r,
|
||||
y - r,
|
||||
x + r,
|
||||
y + r,
|
||||
fill=self.app.toolbar.marker_tool.color,
|
||||
fill=self.app.toolbar.marker_frame.color,
|
||||
outline="",
|
||||
tags=(tags.MARKER, tags.ANNOTATION),
|
||||
)
|
||||
return
|
||||
return
|
||||
|
||||
if self.mode == GraphMode.EDGE:
|
||||
return
|
||||
|
@ -695,11 +694,11 @@ class CanvasGraph(tk.Canvas):
|
|||
# move selected objects
|
||||
if self.selection:
|
||||
for selected_id in self.selection:
|
||||
if selected_id in self.shapes:
|
||||
if self.mode in MOVE_SHAPE_MODES and selected_id in self.shapes:
|
||||
shape = self.shapes[selected_id]
|
||||
shape.motion(x_offset, y_offset)
|
||||
|
||||
if selected_id in self.nodes:
|
||||
if self.mode in MOVE_NODE_MODES and selected_id in self.nodes:
|
||||
node = self.nodes[selected_id]
|
||||
node.motion(x_offset, y_offset, update=self.core.is_runtime())
|
||||
else:
|
||||
|
@ -714,7 +713,7 @@ class CanvasGraph(tk.Canvas):
|
|||
if not self.app.core.is_runtime():
|
||||
self.delete_selected_objects()
|
||||
else:
|
||||
logging.info("node deletion is disabled during runtime state")
|
||||
logging.debug("node deletion is disabled during runtime state")
|
||||
|
||||
def double_click(self, event: tk.Event):
|
||||
selected = self.get_selected(event)
|
||||
|
@ -733,13 +732,11 @@ class CanvasGraph(tk.Canvas):
|
|||
if not core_node:
|
||||
return
|
||||
try:
|
||||
self.node_draw.image = Images.get(
|
||||
self.node_draw.image_enum, int(ICON_SIZE * self.app.app_scale)
|
||||
)
|
||||
image_enum = self.node_draw.image_enum
|
||||
self.node_draw.image = self.app.get_icon(image_enum, ICON_SIZE)
|
||||
except AttributeError:
|
||||
self.node_draw.image = Images.get_custom(
|
||||
self.node_draw.image_file, int(ICON_SIZE * self.app.app_scale)
|
||||
)
|
||||
image_file = self.node_draw.image_file
|
||||
self.node_draw.image = self.app.get_custom_icon(image_file, ICON_SIZE)
|
||||
node = CanvasNode(self.app, x, y, core_node, self.node_draw.image)
|
||||
self.core.canvas_nodes[core_node.id] = node
|
||||
self.nodes[node.id] = node
|
||||
|
@ -830,10 +827,10 @@ class CanvasGraph(tk.Canvas):
|
|||
self.draw_wallpaper(image)
|
||||
|
||||
def redraw_canvas(self, dimensions: Tuple[int, int] = None):
|
||||
logging.info("redrawing canvas to dimensions: %s", dimensions)
|
||||
logging.debug("redrawing canvas to dimensions: %s", dimensions)
|
||||
|
||||
# reset scale and move back to original position
|
||||
logging.info("resetting scaling: %s %s", self.ratio, self.offset)
|
||||
logging.debug("resetting scaling: %s %s", self.ratio, self.offset)
|
||||
factor = 1 / self.ratio
|
||||
self.scale(tk.ALL, self.offset[0], self.offset[1], factor, factor)
|
||||
self.move(tk.ALL, -self.offset[0], -self.offset[1])
|
||||
|
@ -852,11 +849,11 @@ class CanvasGraph(tk.Canvas):
|
|||
|
||||
def redraw_wallpaper(self):
|
||||
if self.adjust_to_dim.get():
|
||||
logging.info("drawing wallpaper to canvas dimensions")
|
||||
logging.debug("drawing wallpaper to canvas dimensions")
|
||||
self.resize_to_wallpaper()
|
||||
else:
|
||||
option = ScaleOption(self.scale_option.get())
|
||||
logging.info("drawing canvas using scaling option: %s", option)
|
||||
logging.debug("drawing canvas using scaling option: %s", option)
|
||||
if option == ScaleOption.UPPER_LEFT:
|
||||
self.wallpaper_upper_left()
|
||||
elif option == ScaleOption.CENTERED:
|
||||
|
@ -865,10 +862,11 @@ class CanvasGraph(tk.Canvas):
|
|||
self.wallpaper_scaled()
|
||||
elif option == ScaleOption.TILED:
|
||||
logging.warning("tiled background not implemented yet")
|
||||
self.organize()
|
||||
|
||||
# raise items above wallpaper
|
||||
for component in tags.ABOVE_WALLPAPER_TAGS:
|
||||
self.tag_raise(component)
|
||||
def organize(self) -> None:
|
||||
for tag in tags.ORGANIZE_TAGS:
|
||||
self.tag_raise(tag)
|
||||
|
||||
def set_wallpaper(self, filename: str):
|
||||
logging.debug("setting wallpaper: %s", filename)
|
||||
|
@ -902,10 +900,10 @@ class CanvasGraph(tk.Canvas):
|
|||
|
||||
def copy(self):
|
||||
if self.core.is_runtime():
|
||||
logging.info("copy is disabled during runtime state")
|
||||
logging.debug("copy is disabled during runtime state")
|
||||
return
|
||||
if self.selection:
|
||||
logging.info("to copy nodes: %s", self.selection)
|
||||
logging.debug("to copy nodes: %s", self.selection)
|
||||
self.to_copy.clear()
|
||||
for node_id in self.selection.keys():
|
||||
canvas_node = self.nodes[node_id]
|
||||
|
@ -913,7 +911,7 @@ class CanvasGraph(tk.Canvas):
|
|||
|
||||
def paste(self):
|
||||
if self.core.is_runtime():
|
||||
logging.info("paste is disabled during runtime state")
|
||||
logging.debug("paste is disabled during runtime state")
|
||||
return
|
||||
# maps original node canvas id to copy node canvas id
|
||||
copy_map = {}
|
||||
|
@ -1004,14 +1002,12 @@ class CanvasGraph(tk.Canvas):
|
|||
):
|
||||
for custom_node in self.app.guiconfig.nodes:
|
||||
if custom_node.name == canvas_node.core_node.model:
|
||||
img = Images.get_custom(
|
||||
custom_node.image, int(ICON_SIZE * self.app.app_scale)
|
||||
)
|
||||
img = self.app.get_custom_icon(custom_node.image, ICON_SIZE)
|
||||
else:
|
||||
image_enum = TypeToImage.get(
|
||||
canvas_node.core_node.type, canvas_node.core_node.model
|
||||
)
|
||||
img = Images.get(image_enum, int(ICON_SIZE * self.app.app_scale))
|
||||
img = self.app.get_icon(image_enum, ICON_SIZE)
|
||||
|
||||
self.itemconfig(nid, image=img)
|
||||
canvas_node.image = img
|
||||
|
|
|
@ -17,7 +17,7 @@ from core.gui.dialogs.wlanconfig import WlanConfigDialog
|
|||
from core.gui.graph import tags
|
||||
from core.gui.graph.edges import CanvasEdge
|
||||
from core.gui.graph.tooltip import CanvasTooltip
|
||||
from core.gui.images import ImageEnum, Images
|
||||
from core.gui.images import ImageEnum
|
||||
from core.gui.nodeutils import ANTENNA_SIZE, NodeUtils
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
@ -55,7 +55,7 @@ class CanvasNode:
|
|||
)
|
||||
self.tooltip = CanvasTooltip(self.canvas)
|
||||
self.edges = set()
|
||||
self.interfaces = []
|
||||
self.interfaces = {}
|
||||
self.wireless_edges = set()
|
||||
self.antennas = []
|
||||
self.antenna_images = {}
|
||||
|
@ -70,6 +70,12 @@ class CanvasNode:
|
|||
self.context = tk.Menu(self.canvas)
|
||||
themes.style_menu(self.context)
|
||||
|
||||
def next_interface_id(self) -> int:
|
||||
i = 0
|
||||
while i in self.interfaces:
|
||||
i += 1
|
||||
return i
|
||||
|
||||
def setup_bindings(self):
|
||||
self.canvas.tag_bind(self.id, "<Double-Button-1>", self.double_click)
|
||||
self.canvas.tag_bind(self.id, "<Enter>", self.on_enter)
|
||||
|
@ -85,7 +91,7 @@ class CanvasNode:
|
|||
def add_antenna(self):
|
||||
x, y = self.canvas.coords(self.id)
|
||||
offset = len(self.antennas) * 8 * self.app.app_scale
|
||||
img = Images.get(ImageEnum.ANTENNA, int(ANTENNA_SIZE * self.app.app_scale))
|
||||
img = self.app.get_icon(ImageEnum.ANTENNA, ANTENNA_SIZE)
|
||||
antenna_id = self.canvas.create_image(
|
||||
x - 16 + offset,
|
||||
y - int(23 * self.app.app_scale),
|
||||
|
@ -321,9 +327,7 @@ class CanvasNode:
|
|||
def scale_antennas(self):
|
||||
for i in range(len(self.antennas)):
|
||||
antenna_id = self.antennas[i]
|
||||
image = Images.get(
|
||||
ImageEnum.ANTENNA, int(ANTENNA_SIZE * self.app.app_scale)
|
||||
)
|
||||
image = self.app.get_icon(ImageEnum.ANTENNA, ANTENNA_SIZE)
|
||||
self.canvas.itemconfig(antenna_id, image=image)
|
||||
self.antenna_images[antenna_id] = image
|
||||
node_x, node_y = self.canvas.coords(self.id)
|
||||
|
|
|
@ -146,8 +146,7 @@ class Shape:
|
|||
self.canvas.coords(self.id, self.x1, self.y1, x1, y1)
|
||||
|
||||
def shape_complete(self, x: float, y: float):
|
||||
for component in tags.ABOVE_SHAPE:
|
||||
self.canvas.tag_raise(component)
|
||||
self.canvas.organize()
|
||||
s = ShapeDialog(self.app, self)
|
||||
s.show()
|
||||
|
||||
|
@ -158,10 +157,11 @@ class Shape:
|
|||
original_position = self.canvas.coords(self.id)
|
||||
self.canvas.move(self.id, x_offset, y_offset)
|
||||
coords = self.canvas.coords(self.id)
|
||||
if self.shape_type == ShapeType.TEXT:
|
||||
coords = coords * 2
|
||||
if not self.canvas.valid_position(*coords):
|
||||
self.canvas.coords(self.id, original_position)
|
||||
return
|
||||
|
||||
self.canvas.move_selection(self.id, x_offset, y_offset)
|
||||
if self.text_id is not None:
|
||||
self.canvas.move(self.text_id, x_offset, y_offset)
|
||||
|
|
|
@ -11,19 +11,21 @@ NODE = "node"
|
|||
WALLPAPER = "wallpaper"
|
||||
SELECTION = "selectednodes"
|
||||
MARKER = "marker"
|
||||
ABOVE_WALLPAPER_TAGS = [
|
||||
ORGANIZE_TAGS = [
|
||||
WALLPAPER,
|
||||
GRIDLINE,
|
||||
SHAPE,
|
||||
SHAPE_TEXT,
|
||||
EDGE,
|
||||
LINK_LABEL,
|
||||
WIRELESS_EDGE,
|
||||
LINK_LABEL,
|
||||
ANTENNA,
|
||||
NODE,
|
||||
NODE_LABEL,
|
||||
SELECTION,
|
||||
MARKER,
|
||||
]
|
||||
ABOVE_SHAPE = [GRIDLINE, EDGE, LINK_LABEL, WIRELESS_EDGE, ANTENNA, NODE, NODE_LABEL]
|
||||
COMPONENT_TAGS = [
|
||||
RESET_TAGS = [
|
||||
EDGE,
|
||||
NODE,
|
||||
NODE_LABEL,
|
||||
|
|
|
@ -12,7 +12,9 @@ if TYPE_CHECKING:
|
|||
from core.gui.graph.node import CanvasNode
|
||||
|
||||
|
||||
def get_index(interface: "core_pb2.Interface") -> int:
|
||||
def get_index(interface: "core_pb2.Interface") -> Optional[int]:
|
||||
if not interface.ip4:
|
||||
return None
|
||||
net = netaddr.IPNetwork(f"{interface.ip4}/{interface.ip4mask}")
|
||||
ip_value = net.value
|
||||
cidr_value = net.cidr.value
|
||||
|
@ -105,12 +107,12 @@ class InterfaceManager:
|
|||
for interface in interfaces:
|
||||
subnets = self.get_subnets(interface)
|
||||
if subnets not in remaining_subnets:
|
||||
if self.current_subnets == subnets:
|
||||
self.current_subnets = None
|
||||
self.used_subnets.pop(subnets.key(), None)
|
||||
else:
|
||||
index = get_index(interface)
|
||||
subnets.used_indexes.discard(index)
|
||||
if index is not None:
|
||||
subnets.used_indexes.discard(index)
|
||||
self.current_subnets = None
|
||||
|
||||
def joined(self, links: List["core_pb2.Link"]) -> None:
|
||||
interfaces = []
|
||||
|
@ -124,6 +126,8 @@ class InterfaceManager:
|
|||
for interface in interfaces:
|
||||
subnets = self.get_subnets(interface)
|
||||
index = get_index(interface)
|
||||
if index is None:
|
||||
continue
|
||||
subnets.used_indexes.add(index)
|
||||
if subnets.key() not in self.used_subnets:
|
||||
self.used_subnets[subnets.key()] = subnets
|
||||
|
@ -147,7 +151,6 @@ class InterfaceManager:
|
|||
return str(ip4), str(ip6)
|
||||
|
||||
def get_subnets(self, interface: "core_pb2.Interface") -> Subnets:
|
||||
logging.info("get subnets for interface: %s", interface)
|
||||
ip4_subnet = self.ip4_subnets
|
||||
if interface.ip4:
|
||||
ip4_subnet = IPNetwork(f"{interface.ip4}/{interface.ip4mask}").cidr
|
||||
|
|
|
@ -23,23 +23,13 @@ from core.gui.dialogs.sessionoptions import SessionOptionsDialog
|
|||
from core.gui.dialogs.sessions import SessionsDialog
|
||||
from core.gui.dialogs.throughput import ThroughputDialog
|
||||
from core.gui.nodeutils import ICON_SIZE
|
||||
from core.gui.observers import ObserversMenu
|
||||
from core.gui.task import ProgressTask
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
||||
MAX_FILES = 3
|
||||
OBSERVERS = {
|
||||
"List Processes": "ps",
|
||||
"Show Interfaces": "ip address",
|
||||
"IPV4 Routes": "ip -4 route",
|
||||
"IPV6 Routes": "ip -6 route",
|
||||
"Listening Sockets": "ss -tuwnl",
|
||||
"IPv4 MFC Entries": "ip -4 mroute show",
|
||||
"IPv6 MFC Entries": "ip -6 mroute show",
|
||||
"Firewall Rules": "iptables -L",
|
||||
"IPSec Policies": "setkey -DP",
|
||||
}
|
||||
|
||||
|
||||
class Menubar(tk.Menu):
|
||||
|
@ -47,20 +37,17 @@ class Menubar(tk.Menu):
|
|||
Core menubar
|
||||
"""
|
||||
|
||||
def __init__(self, master: tk.Tk, app: "Application", **kwargs) -> None:
|
||||
def __init__(self, app: "Application") -> None:
|
||||
"""
|
||||
Create a CoreMenubar instance
|
||||
"""
|
||||
super().__init__(master, **kwargs)
|
||||
self.master.config(menu=self)
|
||||
super().__init__(app)
|
||||
self.app = app
|
||||
self.core = app.core
|
||||
self.canvas = app.canvas
|
||||
self.recent_menu = None
|
||||
self.edit_menu = None
|
||||
self.observers_menu = None
|
||||
self.observers_var = tk.StringVar(value=tk.NONE)
|
||||
self.observers_custom_index = None
|
||||
self.draw()
|
||||
|
||||
def draw(self) -> None:
|
||||
|
@ -202,42 +189,9 @@ class Menubar(tk.Menu):
|
|||
"""
|
||||
Create observer widget menu item and create the sub menu items inside
|
||||
"""
|
||||
self.observers_menu = tk.Menu(widget_menu)
|
||||
self.observers_menu.add_command(
|
||||
label="Edit Observers", command=self.click_edit_observer_widgets
|
||||
)
|
||||
self.observers_menu.add_separator()
|
||||
self.observers_menu.add_radiobutton(
|
||||
label="None",
|
||||
variable=self.observers_var,
|
||||
value="none",
|
||||
command=lambda: self.core.set_observer(None),
|
||||
)
|
||||
for name in sorted(OBSERVERS):
|
||||
cmd = OBSERVERS[name]
|
||||
self.observers_menu.add_radiobutton(
|
||||
label=name,
|
||||
variable=self.observers_var,
|
||||
value=name,
|
||||
command=partial(self.core.set_observer, cmd),
|
||||
)
|
||||
self.observers_custom_index = self.observers_menu.index(tk.END) + 1
|
||||
self.draw_custom_observers()
|
||||
self.observers_menu = ObserversMenu(widget_menu, self.app)
|
||||
widget_menu.add_cascade(label="Observer Widgets", menu=self.observers_menu)
|
||||
|
||||
def draw_custom_observers(self) -> None:
|
||||
current_observers_index = self.observers_menu.index(tk.END) + 1
|
||||
if self.observers_custom_index < current_observers_index:
|
||||
self.observers_menu.delete(self.observers_custom_index, tk.END)
|
||||
for name in sorted(self.core.custom_observers):
|
||||
observer = self.core.custom_observers[name]
|
||||
self.observers_menu.add_radiobutton(
|
||||
label=name,
|
||||
variable=self.observers_var,
|
||||
value=name,
|
||||
command=partial(self.core.set_observer, observer.cmd),
|
||||
)
|
||||
|
||||
def create_adjacency_menu(self, widget_menu: tk.Menu) -> None:
|
||||
"""
|
||||
Create adjacency menu item and the sub menu items inside
|
||||
|
|
|
@ -22,6 +22,7 @@ class NodeDraw:
|
|||
self.node_type: core_pb2.NodeType = None
|
||||
self.model: Optional[str] = None
|
||||
self.services: Set[str] = set()
|
||||
self.label = None
|
||||
|
||||
@classmethod
|
||||
def from_setup(
|
||||
|
|
66
daemon/core/gui/observers.py
Normal file
66
daemon/core/gui/observers.py
Normal file
|
@ -0,0 +1,66 @@
|
|||
import tkinter as tk
|
||||
from functools import partial
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from core.gui.dialogs.observers import ObserverDialog
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
|
||||
OBSERVERS = {
|
||||
"List Processes": "ps",
|
||||
"Show Interfaces": "ip address",
|
||||
"IPV4 Routes": "ip -4 route",
|
||||
"IPV6 Routes": "ip -6 route",
|
||||
"Listening Sockets": "ss -tuwnl",
|
||||
"IPv4 MFC Entries": "ip -4 mroute show",
|
||||
"IPv6 MFC Entries": "ip -6 mroute show",
|
||||
"Firewall Rules": "iptables -L",
|
||||
"IPSec Policies": "setkey -DP",
|
||||
}
|
||||
|
||||
|
||||
class ObserversMenu(tk.Menu):
|
||||
def __init__(self, master: tk.BaseWidget, app: "Application") -> None:
|
||||
super().__init__(master)
|
||||
self.app = app
|
||||
self.observer = tk.StringVar(value=tk.NONE)
|
||||
self.custom_index = 0
|
||||
self.draw()
|
||||
|
||||
def draw(self) -> None:
|
||||
self.add_command(label="Edit Observers", command=self.click_edit)
|
||||
self.add_separator()
|
||||
self.add_radiobutton(
|
||||
label="None",
|
||||
variable=self.observer,
|
||||
value="none",
|
||||
command=lambda: self.app.core.set_observer(None),
|
||||
)
|
||||
for name in sorted(OBSERVERS):
|
||||
cmd = OBSERVERS[name]
|
||||
self.add_radiobutton(
|
||||
label=name,
|
||||
variable=self.observer,
|
||||
value=name,
|
||||
command=partial(self.app.core.set_observer, cmd),
|
||||
)
|
||||
self.custom_index = self.index(tk.END) + 1
|
||||
self.draw_custom()
|
||||
|
||||
def draw_custom(self) -> None:
|
||||
current_index = self.index(tk.END) + 1
|
||||
if self.custom_index < current_index:
|
||||
self.delete(self.custom_index, tk.END)
|
||||
for name in sorted(self.app.core.custom_observers):
|
||||
observer = self.app.core.custom_observers[name]
|
||||
self.add_radiobutton(
|
||||
label=name,
|
||||
variable=self.observer,
|
||||
value=name,
|
||||
command=partial(self.app.core.set_observer, observer.cmd),
|
||||
)
|
||||
|
||||
def click_edit(self) -> None:
|
||||
dialog = ObserverDialog(self.app)
|
||||
dialog.show()
|
|
@ -13,8 +13,8 @@ if TYPE_CHECKING:
|
|||
|
||||
|
||||
class StatusBar(ttk.Frame):
|
||||
def __init__(self, master: tk.Widget, app: "Application", **kwargs):
|
||||
super().__init__(master, **kwargs)
|
||||
def __init__(self, master: tk.Widget, app: "Application"):
|
||||
super().__init__(master)
|
||||
self.app = app
|
||||
self.status = None
|
||||
self.statusvar = tk.StringVar()
|
||||
|
|
|
@ -5,20 +5,23 @@ from functools import partial
|
|||
from tkinter import ttk
|
||||
from typing import TYPE_CHECKING, Callable
|
||||
|
||||
from PIL.ImageTk import PhotoImage
|
||||
|
||||
from core.api.grpc import core_pb2
|
||||
from core.gui.dialogs.marker import MarkerDialog
|
||||
from core.gui.dialogs.colorpicker import ColorPickerDialog
|
||||
from core.gui.dialogs.runtool import RunToolDialog
|
||||
from core.gui.graph import tags
|
||||
from core.gui.graph.enums import GraphMode
|
||||
from core.gui.graph.shapeutils import ShapeType, is_marker
|
||||
from core.gui.images import ImageEnum, Images
|
||||
from core.gui.images import ImageEnum
|
||||
from core.gui.nodeutils import NodeDraw, NodeUtils
|
||||
from core.gui.observers import ObserversMenu
|
||||
from core.gui.task import ProgressTask
|
||||
from core.gui.themes import Styles
|
||||
from core.gui.tooltip import Tooltip
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.gui.app import Application
|
||||
from PIL import ImageTk
|
||||
|
||||
TOOLBAR_SIZE = 32
|
||||
PICKER_SIZE = 24
|
||||
|
@ -30,8 +33,124 @@ class NodeTypeEnum(Enum):
|
|||
OTHER = 2
|
||||
|
||||
|
||||
def icon(image_enum, width=TOOLBAR_SIZE):
|
||||
return Images.get(image_enum, width)
|
||||
def enable_buttons(frame: ttk.Frame, enabled: bool) -> None:
|
||||
state = tk.NORMAL if enabled else tk.DISABLED
|
||||
for child in frame.winfo_children():
|
||||
child.configure(state=state)
|
||||
|
||||
|
||||
class PickerFrame(ttk.Frame):
|
||||
def __init__(self, app: "Application", button: ttk.Button) -> None:
|
||||
super().__init__(app)
|
||||
self.app = app
|
||||
self.button = button
|
||||
|
||||
def create_node_button(self, node_draw: NodeDraw, func: Callable) -> None:
|
||||
self.create_button(
|
||||
node_draw.label, func, node_draw.image_enum, node_draw.image_file
|
||||
)
|
||||
|
||||
def create_button(
|
||||
self,
|
||||
label: str,
|
||||
func: Callable,
|
||||
image_enum: ImageEnum = None,
|
||||
image_file: str = None,
|
||||
) -> None:
|
||||
if image_enum:
|
||||
bar_image = self.app.get_icon(image_enum, TOOLBAR_SIZE)
|
||||
image = self.app.get_icon(image_enum, PICKER_SIZE)
|
||||
else:
|
||||
bar_image = self.app.get_custom_icon(image_file, TOOLBAR_SIZE)
|
||||
image = self.app.get_custom_icon(image_file, PICKER_SIZE)
|
||||
button = ttk.Button(
|
||||
self, image=image, text=label, compound=tk.TOP, style=Styles.picker_button
|
||||
)
|
||||
button.image = image
|
||||
button.bind("<ButtonRelease-1>", lambda e: func(bar_image))
|
||||
button.grid(pady=1)
|
||||
|
||||
def show(self) -> None:
|
||||
self.button.after(0, self._show)
|
||||
|
||||
def _show(self) -> None:
|
||||
x = self.button.winfo_width() + 1
|
||||
y = self.button.winfo_rooty() - self.app.winfo_rooty() - 1
|
||||
self.place(x=x, y=y)
|
||||
self.app.bind_all("<ButtonRelease-1>", lambda e: self.destroy())
|
||||
self.wait_visibility()
|
||||
self.grab_set()
|
||||
self.wait_window()
|
||||
self.app.unbind_all("<ButtonRelease-1>")
|
||||
|
||||
|
||||
class ButtonBar(ttk.Frame):
|
||||
def __init__(self, master: tk.Widget, app: "Application"):
|
||||
super().__init__(master)
|
||||
self.app = app
|
||||
self.radio_buttons = []
|
||||
|
||||
def create_button(
|
||||
self, image_enum: ImageEnum, func: Callable, tooltip: str, radio: bool = False
|
||||
) -> ttk.Button:
|
||||
image = self.app.get_icon(image_enum, TOOLBAR_SIZE)
|
||||
button = ttk.Button(self, image=image, command=func)
|
||||
button.image = image
|
||||
button.grid(sticky="ew")
|
||||
Tooltip(button, tooltip)
|
||||
if radio:
|
||||
self.radio_buttons.append(button)
|
||||
return button
|
||||
|
||||
def select_radio(self, selected: ttk.Button) -> None:
|
||||
for button in self.radio_buttons:
|
||||
button.state(["!pressed"])
|
||||
selected.state(["pressed"])
|
||||
|
||||
|
||||
class MarkerFrame(ttk.Frame):
|
||||
PAD = 3
|
||||
|
||||
def __init__(self, master: tk.BaseWidget, app: "Application") -> None:
|
||||
super().__init__(master, padding=self.PAD)
|
||||
self.app = app
|
||||
self.color = "#000000"
|
||||
self.size = tk.DoubleVar()
|
||||
self.color_frame = None
|
||||
self.draw()
|
||||
|
||||
def draw(self) -> None:
|
||||
self.columnconfigure(0, weight=1)
|
||||
|
||||
image = self.app.get_icon(ImageEnum.DELETE, 16)
|
||||
button = ttk.Button(self, image=image, width=2, command=self.click_clear)
|
||||
button.image = image
|
||||
button.grid(sticky="ew", pady=self.PAD)
|
||||
Tooltip(button, "Delete Marker")
|
||||
|
||||
sizes = [1, 3, 8, 10]
|
||||
self.size.set(sizes[0])
|
||||
sizes = ttk.Combobox(
|
||||
self, state="readonly", textvariable=self.size, value=sizes, width=2
|
||||
)
|
||||
sizes.grid(sticky="ew", pady=self.PAD)
|
||||
Tooltip(sizes, "Marker Size")
|
||||
|
||||
frame_size = TOOLBAR_SIZE
|
||||
self.color_frame = tk.Frame(
|
||||
self, background=self.color, height=frame_size, width=frame_size
|
||||
)
|
||||
self.color_frame.grid(sticky="ew")
|
||||
self.color_frame.bind("<Button-1>", self.click_color)
|
||||
Tooltip(self.color_frame, "Marker Color")
|
||||
|
||||
def click_clear(self):
|
||||
self.app.canvas.delete(tags.MARKER)
|
||||
|
||||
def click_color(self, _event: tk.Event) -> None:
|
||||
dialog = ColorPickerDialog(self.app, self.app, self.color)
|
||||
self.color = dialog.askcolor()
|
||||
self.color_frame.config(background=self.color)
|
||||
|
||||
|
||||
class Toolbar(ttk.Frame):
|
||||
|
@ -39,11 +158,11 @@ class Toolbar(ttk.Frame):
|
|||
Core toolbar class
|
||||
"""
|
||||
|
||||
def __init__(self, master: tk.Widget, app: "Application", **kwargs):
|
||||
def __init__(self, app: "Application") -> None:
|
||||
"""
|
||||
Create a CoreToolbar instance
|
||||
"""
|
||||
super().__init__(master, **kwargs)
|
||||
super().__init__(app)
|
||||
self.app = app
|
||||
|
||||
# design buttons
|
||||
|
@ -63,316 +182,193 @@ class Toolbar(ttk.Frame):
|
|||
# frames
|
||||
self.design_frame = None
|
||||
self.runtime_frame = None
|
||||
self.node_picker = None
|
||||
self.network_picker = None
|
||||
self.annotation_picker = None
|
||||
self.marker_frame = None
|
||||
self.picker = None
|
||||
|
||||
# dialog
|
||||
self.marker_tool = None
|
||||
# observers
|
||||
self.observers_menu = None
|
||||
|
||||
# these variables help keep track of what images being drawn so that scaling
|
||||
# is possible since ImageTk.PhotoImage does not have resize method
|
||||
self.node_enum = None
|
||||
self.network_enum = None
|
||||
self.annotation_enum = None
|
||||
# is possible since PhotoImage does not have resize method
|
||||
self.current_node = NodeUtils.NODES[0]
|
||||
self.current_network = NodeUtils.NETWORK_NODES[0]
|
||||
self.current_annotation = ShapeType.MARKER
|
||||
self.annotation_enum = ImageEnum.MARKER
|
||||
|
||||
# draw components
|
||||
self.draw()
|
||||
|
||||
def get_icon(self, image_enum, width=TOOLBAR_SIZE):
|
||||
return Images.get(image_enum, int(width * self.app.app_scale))
|
||||
|
||||
def draw(self):
|
||||
def draw(self) -> None:
|
||||
self.columnconfigure(0, weight=1)
|
||||
self.rowconfigure(0, weight=1)
|
||||
self.draw_design_frame()
|
||||
self.draw_runtime_frame()
|
||||
self.design_frame.tkraise()
|
||||
self.marker_frame = MarkerFrame(self, self.app)
|
||||
|
||||
def draw_design_frame(self):
|
||||
self.design_frame = ttk.Frame(self)
|
||||
def draw_design_frame(self) -> None:
|
||||
self.design_frame = ButtonBar(self, self.app)
|
||||
self.design_frame.grid(row=0, column=0, sticky="nsew")
|
||||
self.design_frame.columnconfigure(0, weight=1)
|
||||
self.play_button = self.create_button(
|
||||
self.design_frame,
|
||||
self.get_icon(ImageEnum.START),
|
||||
self.click_start,
|
||||
"start the session",
|
||||
self.play_button = self.design_frame.create_button(
|
||||
ImageEnum.START, self.click_start, "Start Session"
|
||||
)
|
||||
self.select_button = self.create_button(
|
||||
self.design_frame,
|
||||
self.get_icon(ImageEnum.SELECT),
|
||||
self.click_selection,
|
||||
"selection tool",
|
||||
self.select_button = self.design_frame.create_button(
|
||||
ImageEnum.SELECT, self.click_selection, "Selection Tool", radio=True
|
||||
)
|
||||
self.link_button = self.create_button(
|
||||
self.design_frame,
|
||||
self.get_icon(ImageEnum.LINK),
|
||||
self.click_link,
|
||||
"link tool",
|
||||
self.link_button = self.design_frame.create_button(
|
||||
ImageEnum.LINK, self.click_link, "Link Tool", radio=True
|
||||
)
|
||||
self.node_button = self.design_frame.create_button(
|
||||
self.current_node.image_enum,
|
||||
self.draw_node_picker,
|
||||
"Container Nodes",
|
||||
radio=True,
|
||||
)
|
||||
self.network_button = self.design_frame.create_button(
|
||||
self.current_network.image_enum,
|
||||
self.draw_network_picker,
|
||||
"Link Layer Nodes",
|
||||
radio=True,
|
||||
)
|
||||
self.annotation_button = self.design_frame.create_button(
|
||||
self.annotation_enum,
|
||||
self.draw_annotation_picker,
|
||||
"Annotation Tools",
|
||||
radio=True,
|
||||
)
|
||||
self.create_node_button()
|
||||
self.create_network_button()
|
||||
self.create_annotation_button()
|
||||
|
||||
def design_select(self, button: ttk.Button):
|
||||
logging.debug("selecting design button: %s", button)
|
||||
self.select_button.state(["!pressed"])
|
||||
self.link_button.state(["!pressed"])
|
||||
self.node_button.state(["!pressed"])
|
||||
self.network_button.state(["!pressed"])
|
||||
self.annotation_button.state(["!pressed"])
|
||||
button.state(["pressed"])
|
||||
|
||||
def runtime_select(self, button: ttk.Button):
|
||||
logging.debug("selecting runtime button: %s", button)
|
||||
self.runtime_select_button.state(["!pressed"])
|
||||
self.stop_button.state(["!pressed"])
|
||||
self.runtime_marker_button.state(["!pressed"])
|
||||
self.run_command_button.state(["!pressed"])
|
||||
button.state(["pressed"])
|
||||
|
||||
def draw_runtime_frame(self):
|
||||
self.runtime_frame = ttk.Frame(self)
|
||||
def draw_runtime_frame(self) -> None:
|
||||
self.runtime_frame = ButtonBar(self, self.app)
|
||||
self.runtime_frame.grid(row=0, column=0, sticky="nsew")
|
||||
self.runtime_frame.columnconfigure(0, weight=1)
|
||||
self.stop_button = self.create_button(
|
||||
self.runtime_frame,
|
||||
self.get_icon(ImageEnum.STOP),
|
||||
self.click_stop,
|
||||
"stop the session",
|
||||
self.stop_button = self.runtime_frame.create_button(
|
||||
ImageEnum.STOP, self.click_stop, "Stop Session"
|
||||
)
|
||||
self.runtime_select_button = self.create_button(
|
||||
self.runtime_frame,
|
||||
self.get_icon(ImageEnum.SELECT),
|
||||
self.click_runtime_selection,
|
||||
"selection tool",
|
||||
self.runtime_select_button = self.runtime_frame.create_button(
|
||||
ImageEnum.SELECT, self.click_runtime_selection, "Selection Tool", radio=True
|
||||
)
|
||||
self.runtime_marker_button = self.create_button(
|
||||
self.runtime_frame,
|
||||
icon(ImageEnum.MARKER),
|
||||
self.click_marker_button,
|
||||
"marker",
|
||||
self.create_observe_button()
|
||||
self.runtime_marker_button = self.runtime_frame.create_button(
|
||||
ImageEnum.MARKER, self.click_marker_button, "Marker Tool", radio=True
|
||||
)
|
||||
self.run_command_button = self.create_button(
|
||||
self.runtime_frame, icon(ImageEnum.RUN), self.click_run_button, "run"
|
||||
self.run_command_button = self.runtime_frame.create_button(
|
||||
ImageEnum.RUN, self.click_run_button, "Run Tool"
|
||||
)
|
||||
|
||||
def draw_node_picker(self):
|
||||
self.hide_pickers()
|
||||
self.node_picker = ttk.Frame(self.master)
|
||||
def draw_node_picker(self) -> None:
|
||||
self.hide_marker()
|
||||
self.app.canvas.mode = GraphMode.NODE
|
||||
self.app.canvas.node_draw = self.current_node
|
||||
self.design_frame.select_radio(self.node_button)
|
||||
self.picker = PickerFrame(self.app, self.node_button)
|
||||
# draw default nodes
|
||||
for node_draw in NodeUtils.NODES:
|
||||
toolbar_image = self.get_icon(node_draw.image_enum, TOOLBAR_SIZE)
|
||||
image = self.get_icon(node_draw.image_enum, PICKER_SIZE)
|
||||
func = partial(
|
||||
self.update_button,
|
||||
self.node_button,
|
||||
toolbar_image,
|
||||
node_draw,
|
||||
NodeTypeEnum.NODE,
|
||||
node_draw.image_enum,
|
||||
self.update_button, self.node_button, node_draw, NodeTypeEnum.NODE
|
||||
)
|
||||
self.create_picker_button(image, func, self.node_picker, node_draw.label)
|
||||
self.picker.create_node_button(node_draw, func)
|
||||
# draw custom nodes
|
||||
for name in sorted(self.app.core.custom_nodes):
|
||||
node_draw = self.app.core.custom_nodes[name]
|
||||
toolbar_image = Images.get_custom(
|
||||
node_draw.image_file, int(TOOLBAR_SIZE * self.app.app_scale)
|
||||
)
|
||||
image = Images.get_custom(
|
||||
node_draw.image_file, int(PICKER_SIZE * self.app.app_scale)
|
||||
)
|
||||
func = partial(
|
||||
self.update_button,
|
||||
self.node_button,
|
||||
toolbar_image,
|
||||
node_draw,
|
||||
NodeTypeEnum,
|
||||
node_draw.image_file,
|
||||
self.update_button, self.node_button, node_draw, NodeTypeEnum.NODE
|
||||
)
|
||||
self.create_picker_button(image, func, self.node_picker, name)
|
||||
self.design_select(self.node_button)
|
||||
self.node_button.after(
|
||||
0, lambda: self.show_picker(self.node_button, self.node_picker)
|
||||
)
|
||||
self.picker.create_node_button(node_draw, func)
|
||||
self.picker.show()
|
||||
|
||||
def show_picker(self, button: ttk.Button, picker: ttk.Frame):
|
||||
x = self.winfo_width() + 1
|
||||
y = button.winfo_rooty() - picker.master.winfo_rooty() - 1
|
||||
picker.place(x=x, y=y)
|
||||
self.app.bind_all("<ButtonRelease-1>", lambda e: self.hide_pickers())
|
||||
picker.wait_visibility()
|
||||
picker.grab_set()
|
||||
self.wait_window(picker)
|
||||
self.app.unbind_all("<ButtonRelease-1>")
|
||||
|
||||
def create_picker_button(
|
||||
self, image: "ImageTk.PhotoImage", func: Callable, frame: ttk.Frame, label: str
|
||||
):
|
||||
"""
|
||||
Create button and put it on the frame
|
||||
|
||||
:param image: button image
|
||||
:param func: the command that is executed when button is clicked
|
||||
:param frame: frame that contains the button
|
||||
:param label: button label
|
||||
"""
|
||||
button = ttk.Button(
|
||||
frame, image=image, text=label, compound=tk.TOP, style=Styles.picker_button
|
||||
)
|
||||
button.image = image
|
||||
button.bind("<ButtonRelease-1>", lambda e: func())
|
||||
button.grid(pady=1)
|
||||
|
||||
def create_button(
|
||||
self,
|
||||
frame: ttk.Frame,
|
||||
image: "ImageTk.PhotoImage",
|
||||
func: Callable,
|
||||
tooltip: str,
|
||||
):
|
||||
button = ttk.Button(frame, image=image, command=func)
|
||||
button.image = image
|
||||
button.grid(sticky="ew")
|
||||
Tooltip(button, tooltip)
|
||||
return button
|
||||
|
||||
def click_selection(self):
|
||||
logging.debug("clicked selection tool")
|
||||
self.design_select(self.select_button)
|
||||
def click_selection(self) -> None:
|
||||
self.design_frame.select_radio(self.select_button)
|
||||
self.app.canvas.mode = GraphMode.SELECT
|
||||
self.hide_marker()
|
||||
|
||||
def click_runtime_selection(self):
|
||||
logging.debug("clicked selection tool")
|
||||
self.runtime_select(self.runtime_select_button)
|
||||
def click_runtime_selection(self) -> None:
|
||||
self.runtime_frame.select_radio(self.runtime_select_button)
|
||||
self.app.canvas.mode = GraphMode.SELECT
|
||||
self.hide_marker()
|
||||
|
||||
def click_start(self):
|
||||
def click_start(self) -> None:
|
||||
"""
|
||||
Start session handler redraw buttons, send node and link messages to grpc
|
||||
server.
|
||||
"""
|
||||
self.app.menubar.change_menubar_item_state(is_runtime=True)
|
||||
self.app.canvas.mode = GraphMode.SELECT
|
||||
enable_buttons(self.design_frame, enabled=False)
|
||||
task = ProgressTask(
|
||||
self.app, "Start", self.app.core.start_session, self.start_callback
|
||||
)
|
||||
task.start()
|
||||
|
||||
def start_callback(self, response: core_pb2.StartSessionResponse):
|
||||
def start_callback(self, response: core_pb2.StartSessionResponse) -> None:
|
||||
if response.result:
|
||||
self.set_runtime()
|
||||
self.app.core.set_metadata()
|
||||
self.app.core.show_mobility_players()
|
||||
else:
|
||||
enable_buttons(self.design_frame, enabled=True)
|
||||
message = "\n".join(response.exceptions)
|
||||
self.app.show_error("Start Session Error", message)
|
||||
|
||||
def set_runtime(self):
|
||||
def set_runtime(self) -> None:
|
||||
enable_buttons(self.runtime_frame, enabled=True)
|
||||
self.runtime_frame.tkraise()
|
||||
self.click_runtime_selection()
|
||||
self.hide_marker()
|
||||
|
||||
def set_design(self):
|
||||
def set_design(self) -> None:
|
||||
enable_buttons(self.design_frame, enabled=True)
|
||||
self.design_frame.tkraise()
|
||||
self.click_selection()
|
||||
self.hide_marker()
|
||||
|
||||
def click_link(self):
|
||||
logging.debug("Click LINK button")
|
||||
self.design_select(self.link_button)
|
||||
def click_link(self) -> None:
|
||||
self.design_frame.select_radio(self.link_button)
|
||||
self.app.canvas.mode = GraphMode.EDGE
|
||||
self.hide_marker()
|
||||
|
||||
def update_button(
|
||||
self,
|
||||
button: ttk.Button,
|
||||
image: "ImageTk",
|
||||
node_draw: NodeDraw,
|
||||
type_enum,
|
||||
image_enum,
|
||||
):
|
||||
type_enum: NodeTypeEnum,
|
||||
image: PhotoImage,
|
||||
) -> None:
|
||||
logging.debug("update button(%s): %s", button, node_draw)
|
||||
self.hide_pickers()
|
||||
button.configure(image=image)
|
||||
button.image = image
|
||||
self.app.canvas.mode = GraphMode.NODE
|
||||
self.app.canvas.node_draw = node_draw
|
||||
if type_enum == NodeTypeEnum.NODE:
|
||||
self.node_enum = image_enum
|
||||
self.current_node = node_draw
|
||||
elif type_enum == NodeTypeEnum.NETWORK:
|
||||
self.network_enum = image_enum
|
||||
self.current_network = node_draw
|
||||
|
||||
def hide_pickers(self):
|
||||
logging.debug("hiding pickers")
|
||||
if self.node_picker:
|
||||
self.node_picker.destroy()
|
||||
self.node_picker = None
|
||||
if self.network_picker:
|
||||
self.network_picker.destroy()
|
||||
self.network_picker = None
|
||||
if self.annotation_picker:
|
||||
self.annotation_picker.destroy()
|
||||
self.annotation_picker = None
|
||||
|
||||
def create_node_button(self):
|
||||
"""
|
||||
Create network layer button
|
||||
"""
|
||||
image = self.get_icon(ImageEnum.ROUTER, TOOLBAR_SIZE)
|
||||
self.node_button = ttk.Button(
|
||||
self.design_frame, image=image, command=self.draw_node_picker
|
||||
)
|
||||
self.node_button.image = image
|
||||
self.node_button.grid(sticky="ew")
|
||||
Tooltip(self.node_button, "Network-layer virtual nodes")
|
||||
self.node_enum = ImageEnum.ROUTER
|
||||
|
||||
def draw_network_picker(self):
|
||||
def draw_network_picker(self) -> None:
|
||||
"""
|
||||
Draw the options for link-layer button.
|
||||
"""
|
||||
self.hide_pickers()
|
||||
self.network_picker = ttk.Frame(self.master)
|
||||
self.hide_marker()
|
||||
self.app.canvas.mode = GraphMode.NODE
|
||||
self.app.canvas.node_draw = self.current_network
|
||||
self.design_frame.select_radio(self.network_button)
|
||||
self.picker = PickerFrame(self.app, self.network_button)
|
||||
for node_draw in NodeUtils.NETWORK_NODES:
|
||||
toolbar_image = self.get_icon(node_draw.image_enum, TOOLBAR_SIZE)
|
||||
image = self.get_icon(node_draw.image_enum, PICKER_SIZE)
|
||||
self.create_picker_button(
|
||||
image,
|
||||
partial(
|
||||
self.update_button,
|
||||
self.network_button,
|
||||
toolbar_image,
|
||||
node_draw,
|
||||
NodeTypeEnum.NETWORK,
|
||||
node_draw.image_enum,
|
||||
),
|
||||
self.network_picker,
|
||||
node_draw.label,
|
||||
func = partial(
|
||||
self.update_button, self.network_button, node_draw, NodeTypeEnum.NETWORK
|
||||
)
|
||||
self.design_select(self.network_button)
|
||||
self.network_button.after(
|
||||
0, lambda: self.show_picker(self.network_button, self.network_picker)
|
||||
)
|
||||
self.picker.create_node_button(node_draw, func)
|
||||
self.picker.show()
|
||||
|
||||
def create_network_button(self):
|
||||
"""
|
||||
Create link-layer node button and the options that represent different
|
||||
link-layer node types.
|
||||
"""
|
||||
image = self.get_icon(ImageEnum.HUB, TOOLBAR_SIZE)
|
||||
self.network_button = ttk.Button(
|
||||
self.design_frame, image=image, command=self.draw_network_picker
|
||||
)
|
||||
self.network_button.image = image
|
||||
self.network_button.grid(sticky="ew")
|
||||
Tooltip(self.network_button, "link-layer nodes")
|
||||
self.network_enum = ImageEnum.HUB
|
||||
|
||||
def draw_annotation_picker(self):
|
||||
def draw_annotation_picker(self) -> None:
|
||||
"""
|
||||
Draw the options for marker button.
|
||||
"""
|
||||
self.hide_pickers()
|
||||
self.annotation_picker = ttk.Frame(self.master)
|
||||
self.design_frame.select_radio(self.annotation_button)
|
||||
self.app.canvas.mode = GraphMode.ANNOTATION
|
||||
self.app.canvas.annotation_type = self.current_annotation
|
||||
if is_marker(self.current_annotation):
|
||||
self.show_marker()
|
||||
self.picker = PickerFrame(self.app, self.annotation_button)
|
||||
nodes = [
|
||||
(ImageEnum.MARKER, ShapeType.MARKER),
|
||||
(ImageEnum.OVAL, ShapeType.OVAL),
|
||||
|
@ -380,114 +376,90 @@ class Toolbar(ttk.Frame):
|
|||
(ImageEnum.TEXT, ShapeType.TEXT),
|
||||
]
|
||||
for image_enum, shape_type in nodes:
|
||||
toolbar_image = self.get_icon(image_enum, TOOLBAR_SIZE)
|
||||
image = self.get_icon(image_enum, PICKER_SIZE)
|
||||
self.create_picker_button(
|
||||
image,
|
||||
partial(self.update_annotation, toolbar_image, shape_type, image_enum),
|
||||
self.annotation_picker,
|
||||
shape_type.value,
|
||||
)
|
||||
self.design_select(self.annotation_button)
|
||||
self.annotation_button.after(
|
||||
0, lambda: self.show_picker(self.annotation_button, self.annotation_picker)
|
||||
)
|
||||
label = shape_type.value
|
||||
func = partial(self.update_annotation, shape_type, image_enum)
|
||||
self.picker.create_button(label, func, image_enum)
|
||||
self.picker.show()
|
||||
|
||||
def create_annotation_button(self):
|
||||
"""
|
||||
Create marker button and options that represent different marker types
|
||||
"""
|
||||
image = self.get_icon(ImageEnum.MARKER, TOOLBAR_SIZE)
|
||||
self.annotation_button = ttk.Button(
|
||||
self.design_frame, image=image, command=self.draw_annotation_picker
|
||||
)
|
||||
self.annotation_button.image = image
|
||||
self.annotation_button.grid(sticky="ew")
|
||||
Tooltip(self.annotation_button, "background annotation tools")
|
||||
self.annotation_enum = ImageEnum.MARKER
|
||||
|
||||
def create_observe_button(self):
|
||||
def create_observe_button(self) -> None:
|
||||
image = self.app.get_icon(ImageEnum.OBSERVE, TOOLBAR_SIZE)
|
||||
menu_button = ttk.Menubutton(
|
||||
self.runtime_frame, image=icon(ImageEnum.OBSERVE), direction=tk.RIGHT
|
||||
self.runtime_frame, image=image, direction=tk.RIGHT
|
||||
)
|
||||
menu_button.image = image
|
||||
menu_button.grid(sticky="ew")
|
||||
menu = tk.Menu(menu_button, tearoff=0)
|
||||
menu_button["menu"] = menu
|
||||
menu.add_command(label="None")
|
||||
menu.add_command(label="processes")
|
||||
menu.add_command(label="ifconfig")
|
||||
menu.add_command(label="IPv4 routes")
|
||||
menu.add_command(label="IPv6 routes")
|
||||
menu.add_command(label="OSPFv2 neighbors")
|
||||
menu.add_command(label="OSPFv3 neighbors")
|
||||
menu.add_command(label="Listening sockets")
|
||||
menu.add_command(label="IPv4 MFC entries")
|
||||
menu.add_command(label="IPv6 MFC entries")
|
||||
menu.add_command(label="firewall rules")
|
||||
menu.add_command(label="IPSec policies")
|
||||
menu.add_command(label="docker logs")
|
||||
menu.add_command(label="OSPFv3 MDR level")
|
||||
menu.add_command(label="PIM neighbors")
|
||||
menu.add_command(label="Edit...")
|
||||
self.observers_menu = ObserversMenu(menu_button, self.app)
|
||||
menu_button["menu"] = self.observers_menu
|
||||
|
||||
def click_stop(self):
|
||||
def click_stop(self) -> None:
|
||||
"""
|
||||
redraw buttons on the toolbar, send node and link messages to grpc server
|
||||
"""
|
||||
logging.info("clicked stop button")
|
||||
self.app.menubar.change_menubar_item_state(is_runtime=False)
|
||||
self.app.core.close_mobility_players()
|
||||
enable_buttons(self.runtime_frame, enabled=False)
|
||||
task = ProgressTask(
|
||||
self.app, "Stop", self.app.core.stop_session, self.stop_callback
|
||||
)
|
||||
task.start()
|
||||
|
||||
def stop_callback(self, response: core_pb2.StopSessionResponse):
|
||||
def stop_callback(self, response: core_pb2.StopSessionResponse) -> None:
|
||||
self.set_design()
|
||||
self.app.canvas.stopped_session()
|
||||
|
||||
def update_annotation(
|
||||
self, image: "ImageTk.PhotoImage", shape_type: ShapeType, image_enum
|
||||
):
|
||||
logging.debug("clicked annotation: ")
|
||||
self.hide_pickers()
|
||||
self, shape_type: ShapeType, image_enum: ImageEnum, image: PhotoImage
|
||||
) -> None:
|
||||
logging.debug("clicked annotation")
|
||||
self.annotation_button.configure(image=image)
|
||||
self.annotation_button.image = image
|
||||
self.app.canvas.mode = GraphMode.ANNOTATION
|
||||
self.app.canvas.annotation_type = shape_type
|
||||
self.current_annotation = shape_type
|
||||
self.annotation_enum = image_enum
|
||||
if is_marker(shape_type):
|
||||
if self.marker_tool:
|
||||
self.marker_tool.destroy()
|
||||
self.marker_tool = MarkerDialog(self.app)
|
||||
self.marker_tool.show()
|
||||
self.show_marker()
|
||||
else:
|
||||
self.hide_marker()
|
||||
|
||||
def click_run_button(self):
|
||||
def hide_marker(self) -> None:
|
||||
self.marker_frame.grid_forget()
|
||||
|
||||
def show_marker(self) -> None:
|
||||
self.marker_frame.grid()
|
||||
|
||||
def click_run_button(self) -> None:
|
||||
logging.debug("Click on RUN button")
|
||||
dialog = RunToolDialog(self.app)
|
||||
dialog.show()
|
||||
|
||||
def click_marker_button(self):
|
||||
logging.debug("Click on marker button")
|
||||
self.runtime_select(self.runtime_marker_button)
|
||||
def click_marker_button(self) -> None:
|
||||
self.runtime_frame.select_radio(self.runtime_marker_button)
|
||||
self.app.canvas.mode = GraphMode.ANNOTATION
|
||||
self.app.canvas.annotation_type = ShapeType.MARKER
|
||||
if self.marker_tool:
|
||||
self.marker_tool.destroy()
|
||||
self.marker_tool = MarkerDialog(self.app)
|
||||
self.marker_tool.show()
|
||||
self.show_marker()
|
||||
|
||||
def scale_button(self, button, image_enum):
|
||||
image = icon(image_enum, int(TOOLBAR_SIZE * self.app.app_scale))
|
||||
button.config(image=image)
|
||||
button.image = image
|
||||
def scale_button(
|
||||
self, button: ttk.Button, image_enum: ImageEnum = None, image_file: str = None
|
||||
) -> None:
|
||||
image = None
|
||||
if image_enum:
|
||||
image = self.app.get_icon(image_enum, TOOLBAR_SIZE)
|
||||
elif image_file:
|
||||
image = self.app.get_custom_icon(image_file, TOOLBAR_SIZE)
|
||||
if image:
|
||||
button.config(image=image)
|
||||
button.image = image
|
||||
|
||||
def scale(self):
|
||||
def scale(self) -> None:
|
||||
self.scale_button(self.play_button, ImageEnum.START)
|
||||
self.scale_button(self.select_button, ImageEnum.SELECT)
|
||||
self.scale_button(self.link_button, ImageEnum.LINK)
|
||||
self.scale_button(self.node_button, self.node_enum)
|
||||
self.scale_button(self.network_button, self.network_enum)
|
||||
if self.current_node.image_enum:
|
||||
self.scale_button(self.node_button, self.current_node.image_enum)
|
||||
else:
|
||||
self.scale_button(self.node_button, image_file=self.current_node.image_file)
|
||||
self.scale_button(self.network_button, self.current_network.image_enum)
|
||||
self.scale_button(self.annotation_button, self.annotation_enum)
|
||||
self.scale_button(self.runtime_select_button, ImageEnum.SELECT)
|
||||
self.scale_button(self.stop_button, ImageEnum.STOP)
|
||||
|
|
|
@ -6,7 +6,7 @@ import heapq
|
|||
import threading
|
||||
import time
|
||||
from functools import total_ordering
|
||||
from typing import Any, Callable
|
||||
from typing import Any, Callable, Dict, List, Optional, Tuple
|
||||
|
||||
|
||||
class Timer(threading.Thread):
|
||||
|
@ -16,34 +16,33 @@ class Timer(threading.Thread):
|
|||
"""
|
||||
|
||||
def __init__(
|
||||
self, interval: float, function: Callable, args: Any = None, kwargs: Any = None
|
||||
self,
|
||||
interval: float,
|
||||
func: Callable[..., None],
|
||||
args: Tuple[Any] = None,
|
||||
kwargs: Dict[Any, Any] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Create a Timer instance.
|
||||
|
||||
:param interval: time interval
|
||||
:param function: function to call when timer finishes
|
||||
:param func: function to call when timer finishes
|
||||
:param args: function arguments
|
||||
:param kwargs: function keyword arguments
|
||||
"""
|
||||
super().__init__()
|
||||
self.interval = interval
|
||||
self.function = function
|
||||
|
||||
self.finished = threading.Event()
|
||||
self._running = threading.Lock()
|
||||
|
||||
self.interval: float = interval
|
||||
self.func: Callable[..., None] = func
|
||||
self.finished: threading.Event = threading.Event()
|
||||
self._running: threading.Lock = threading.Lock()
|
||||
# validate arguments were provided
|
||||
if args:
|
||||
self.args = args
|
||||
else:
|
||||
self.args = []
|
||||
|
||||
if args is None:
|
||||
args = ()
|
||||
self.args: Tuple[Any] = args
|
||||
# validate keyword arguments were provided
|
||||
if kwargs:
|
||||
self.kwargs = kwargs
|
||||
else:
|
||||
self.kwargs = {}
|
||||
if kwargs is None:
|
||||
kwargs = {}
|
||||
self.kwargs: Dict[Any, Any] = kwargs
|
||||
|
||||
def cancel(self) -> bool:
|
||||
"""
|
||||
|
@ -67,7 +66,7 @@ class Timer(threading.Thread):
|
|||
self.finished.wait(self.interval)
|
||||
with self._running:
|
||||
if not self.finished.is_set():
|
||||
self.function(*self.args, **self.kwargs)
|
||||
self.func(*self.args, **self.kwargs)
|
||||
self.finished.set()
|
||||
|
||||
|
||||
|
@ -78,7 +77,12 @@ class Event:
|
|||
"""
|
||||
|
||||
def __init__(
|
||||
self, eventnum: int, event_time: float, func: Callable, *args: Any, **kwds: Any
|
||||
self,
|
||||
eventnum: int,
|
||||
event_time: float,
|
||||
func: Callable[..., None],
|
||||
*args: Any,
|
||||
**kwds: Any
|
||||
) -> None:
|
||||
"""
|
||||
Create an Event instance.
|
||||
|
@ -89,12 +93,12 @@ class Event:
|
|||
:param args: function arguments
|
||||
:param kwds: function keyword arguments
|
||||
"""
|
||||
self.eventnum = eventnum
|
||||
self.time = event_time
|
||||
self.func = func
|
||||
self.args = args
|
||||
self.kwds = kwds
|
||||
self.canceled = False
|
||||
self.eventnum: int = eventnum
|
||||
self.time: float = event_time
|
||||
self.func: Callable[..., None] = func
|
||||
self.args: Tuple[Any] = args
|
||||
self.kwds: Dict[Any, Any] = kwds
|
||||
self.canceled: bool = False
|
||||
|
||||
def __lt__(self, other: "Event") -> bool:
|
||||
result = self.time < other.time
|
||||
|
@ -118,7 +122,6 @@ class Event:
|
|||
|
||||
:return: nothing
|
||||
"""
|
||||
# XXX not thread-safe
|
||||
self.canceled = True
|
||||
|
||||
|
||||
|
@ -131,14 +134,14 @@ class EventLoop:
|
|||
"""
|
||||
Creates a EventLoop instance.
|
||||
"""
|
||||
self.lock = threading.RLock()
|
||||
self.queue = []
|
||||
self.eventnum = 0
|
||||
self.timer = None
|
||||
self.running = False
|
||||
self.start = None
|
||||
self.lock: threading.RLock = threading.RLock()
|
||||
self.queue: List[Event] = []
|
||||
self.eventnum: int = 0
|
||||
self.timer: Optional[Timer] = None
|
||||
self.running: bool = False
|
||||
self.start: Optional[float] = None
|
||||
|
||||
def __run_events(self) -> None:
|
||||
def _run_events(self) -> None:
|
||||
"""
|
||||
Run events.
|
||||
|
||||
|
@ -161,9 +164,9 @@ class EventLoop:
|
|||
with self.lock:
|
||||
self.timer = None
|
||||
if schedule:
|
||||
self.__schedule_event()
|
||||
self._schedule_event()
|
||||
|
||||
def __schedule_event(self) -> None:
|
||||
def _schedule_event(self) -> None:
|
||||
"""
|
||||
Schedule event.
|
||||
|
||||
|
@ -177,7 +180,7 @@ class EventLoop:
|
|||
delay = self.queue[0].time - time.monotonic()
|
||||
if self.timer:
|
||||
raise ValueError("timer was already set")
|
||||
self.timer = Timer(delay, self.__run_events)
|
||||
self.timer = Timer(delay, self._run_events)
|
||||
self.timer.daemon = True
|
||||
self.timer.start()
|
||||
|
||||
|
@ -194,7 +197,7 @@ class EventLoop:
|
|||
self.start = time.monotonic()
|
||||
for event in self.queue:
|
||||
event.time += self.start
|
||||
self.__schedule_event()
|
||||
self._schedule_event()
|
||||
|
||||
def stop(self) -> None:
|
||||
"""
|
||||
|
@ -242,5 +245,5 @@ class EventLoop:
|
|||
if self.timer is not None and self.timer.cancel():
|
||||
self.timer = None
|
||||
if self.running and self.timer is None:
|
||||
self.__schedule_event()
|
||||
self._schedule_event()
|
||||
return event
|
||||
|
|
|
@ -6,6 +6,7 @@ import logging
|
|||
from typing import Tuple
|
||||
|
||||
import pyproj
|
||||
from pyproj import Transformer
|
||||
|
||||
from core.emulator.enumerations import RegisterTlvs
|
||||
|
||||
|
@ -20,21 +21,23 @@ class GeoLocation:
|
|||
defined projections.
|
||||
"""
|
||||
|
||||
name = "location"
|
||||
config_type = RegisterTlvs.UTILITY
|
||||
name: str = "location"
|
||||
config_type: RegisterTlvs = RegisterTlvs.UTILITY
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""
|
||||
Creates a GeoLocation instance.
|
||||
"""
|
||||
self.to_pixels = pyproj.Transformer.from_crs(
|
||||
self.to_pixels: Transformer = pyproj.Transformer.from_crs(
|
||||
CRS_WGS84, CRS_PROJ, always_xy=True
|
||||
)
|
||||
self.to_geo = pyproj.Transformer.from_crs(CRS_PROJ, CRS_WGS84, always_xy=True)
|
||||
self.refproj = (0.0, 0.0)
|
||||
self.refgeo = (0.0, 0.0, 0.0)
|
||||
self.refxyz = (0.0, 0.0, 0.0)
|
||||
self.refscale = 1.0
|
||||
self.to_geo: Transformer = pyproj.Transformer.from_crs(
|
||||
CRS_PROJ, CRS_WGS84, always_xy=True
|
||||
)
|
||||
self.refproj: Tuple[float, float, float] = (0.0, 0.0, 0.0)
|
||||
self.refgeo: Tuple[float, float, float] = (0.0, 0.0, 0.0)
|
||||
self.refxyz: Tuple[float, float, float] = (0.0, 0.0, 0.0)
|
||||
self.refscale: float = 1.0
|
||||
|
||||
def setrefgeo(self, lat: float, lon: float, alt: float) -> None:
|
||||
"""
|
||||
|
@ -58,7 +61,7 @@ class GeoLocation:
|
|||
self.refxyz = (0.0, 0.0, 0.0)
|
||||
self.refgeo = (0.0, 0.0, 0.0)
|
||||
self.refscale = 1.0
|
||||
self.refproj = self.to_pixels.transform(self.refgeo[0], self.refgeo[1])
|
||||
self.refproj = self.to_pixels.transform(*self.refgeo)
|
||||
|
||||
def pixels2meters(self, value: float) -> float:
|
||||
"""
|
||||
|
|
|
@ -9,11 +9,12 @@ import os
|
|||
import threading
|
||||
import time
|
||||
from functools import total_ordering
|
||||
from typing import TYPE_CHECKING, Dict, List, Tuple
|
||||
from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Tuple
|
||||
|
||||
from core import utils
|
||||
from core.config import ConfigGroup, ConfigurableOptions, Configuration, ModelManager
|
||||
from core.emulator.data import EventData, LinkData
|
||||
from core.emulator.emudata import LinkOptions
|
||||
from core.emulator.enumerations import (
|
||||
ConfigDataTypes,
|
||||
EventTypes,
|
||||
|
@ -22,8 +23,9 @@ from core.emulator.enumerations import (
|
|||
RegisterTlvs,
|
||||
)
|
||||
from core.errors import CoreError
|
||||
from core.nodes.base import CoreNode, NodeBase
|
||||
from core.nodes.base import CoreNode
|
||||
from core.nodes.interface import CoreInterface
|
||||
from core.nodes.network import WlanNode
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.emulator.session import Session
|
||||
|
@ -45,7 +47,7 @@ class MobilityManager(ModelManager):
|
|||
:param session: session this manager is tied to
|
||||
"""
|
||||
super().__init__()
|
||||
self.session = session
|
||||
self.session: "Session" = session
|
||||
self.models[BasicRangeModel.name] = BasicRangeModel
|
||||
self.models[Ns2ScriptedMobility.name] = Ns2ScriptedMobility
|
||||
|
||||
|
@ -75,7 +77,7 @@ class MobilityManager(ModelManager):
|
|||
)
|
||||
|
||||
try:
|
||||
node = self.session.get_node(node_id)
|
||||
node = self.session.get_node(node_id, WlanNode)
|
||||
except CoreError:
|
||||
logging.warning(
|
||||
"skipping mobility configuration for unknown node: %s", node_id
|
||||
|
@ -103,9 +105,8 @@ class MobilityManager(ModelManager):
|
|||
event_type = event_data.event_type
|
||||
node_id = event_data.node
|
||||
name = event_data.name
|
||||
|
||||
try:
|
||||
node = self.session.get_node(node_id)
|
||||
node = self.session.get_node(node_id, WlanNode)
|
||||
except CoreError:
|
||||
logging.exception(
|
||||
"Ignoring event for model '%s', unknown node '%s'", name, node_id
|
||||
|
@ -177,7 +178,7 @@ class MobilityManager(ModelManager):
|
|||
self.session.broadcast_event(event_data)
|
||||
|
||||
def updatewlans(
|
||||
self, moved: List[NodeBase], moved_netifs: List[CoreInterface]
|
||||
self, moved: List[CoreNode], moved_netifs: List[CoreInterface]
|
||||
) -> None:
|
||||
"""
|
||||
A mobility script has caused nodes in the 'moved' list to move.
|
||||
|
@ -190,7 +191,7 @@ class MobilityManager(ModelManager):
|
|||
"""
|
||||
for node_id in self.nodes():
|
||||
try:
|
||||
node = self.session.get_node(node_id)
|
||||
node = self.session.get_node(node_id, WlanNode)
|
||||
except CoreError:
|
||||
continue
|
||||
if node.model:
|
||||
|
@ -203,21 +204,21 @@ class WirelessModel(ConfigurableOptions):
|
|||
Used for managing arbitrary configuration parameters.
|
||||
"""
|
||||
|
||||
config_type = RegisterTlvs.WIRELESS
|
||||
bitmap = None
|
||||
position_callback = None
|
||||
config_type: RegisterTlvs = RegisterTlvs.WIRELESS
|
||||
bitmap: str = None
|
||||
position_callback: Callable[[CoreInterface], None] = None
|
||||
|
||||
def __init__(self, session: "Session", _id: int):
|
||||
def __init__(self, session: "Session", _id: int) -> None:
|
||||
"""
|
||||
Create a WirelessModel instance.
|
||||
|
||||
:param session: core session we are tied to
|
||||
:param _id: object id
|
||||
"""
|
||||
self.session = session
|
||||
self.id = _id
|
||||
self.session: "Session" = session
|
||||
self.id: int = _id
|
||||
|
||||
def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List:
|
||||
def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]:
|
||||
"""
|
||||
May be used if the model can populate the GUI with wireless (green)
|
||||
link lines.
|
||||
|
@ -227,11 +228,11 @@ class WirelessModel(ConfigurableOptions):
|
|||
"""
|
||||
return []
|
||||
|
||||
def update(self, moved: bool, moved_netifs: List[CoreInterface]) -> None:
|
||||
def update(self, moved: List[CoreNode], moved_netifs: List[CoreInterface]) -> None:
|
||||
"""
|
||||
Update this wireless model.
|
||||
|
||||
:param moved: flag is it was moved
|
||||
:param moved: moved nodes
|
||||
:param moved_netifs: moved network interfaces
|
||||
:return: nothing
|
||||
"""
|
||||
|
@ -255,8 +256,8 @@ class BasicRangeModel(WirelessModel):
|
|||
the GUI.
|
||||
"""
|
||||
|
||||
name = "basic_range"
|
||||
options = [
|
||||
name: str = "basic_range"
|
||||
options: List[Configuration] = [
|
||||
Configuration(
|
||||
_id="range",
|
||||
_type=ConfigDataTypes.UINT32,
|
||||
|
@ -298,15 +299,15 @@ class BasicRangeModel(WirelessModel):
|
|||
:param _id: object id
|
||||
"""
|
||||
super().__init__(session, _id)
|
||||
self.session = session
|
||||
self.wlan = session.get_node(_id)
|
||||
self._netifs = {}
|
||||
self._netifslock = threading.Lock()
|
||||
self.range = 0
|
||||
self.bw = None
|
||||
self.delay = None
|
||||
self.loss = None
|
||||
self.jitter = None
|
||||
self.session: "Session" = session
|
||||
self.wlan: WlanNode = session.get_node(_id, WlanNode)
|
||||
self._netifs: Dict[CoreInterface, Tuple[float, float, float]] = {}
|
||||
self._netifslock: threading.Lock = threading.Lock()
|
||||
self.range: int = 0
|
||||
self.bw: Optional[int] = None
|
||||
self.delay: Optional[int] = None
|
||||
self.loss: Optional[float] = None
|
||||
self.jitter: Optional[int] = None
|
||||
|
||||
def _get_config(self, current_value: int, config: Dict[str, str], name: str) -> int:
|
||||
"""
|
||||
|
@ -334,14 +335,13 @@ class BasicRangeModel(WirelessModel):
|
|||
"""
|
||||
with self._netifslock:
|
||||
for netif in self._netifs:
|
||||
self.wlan.linkconfig(
|
||||
netif,
|
||||
bw=self.bw,
|
||||
options = LinkOptions(
|
||||
bandwidth=self.bw,
|
||||
delay=self.delay,
|
||||
loss=self.loss,
|
||||
duplicate=None,
|
||||
per=self.loss,
|
||||
jitter=self.jitter,
|
||||
)
|
||||
self.wlan.linkconfig(netif, options)
|
||||
|
||||
def get_position(self, netif: CoreInterface) -> Tuple[float, float, float]:
|
||||
"""
|
||||
|
@ -374,14 +374,14 @@ class BasicRangeModel(WirelessModel):
|
|||
|
||||
position_callback = set_position
|
||||
|
||||
def update(self, moved: bool, moved_netifs: List[CoreInterface]) -> None:
|
||||
def update(self, moved: List[CoreNode], moved_netifs: List[CoreInterface]) -> None:
|
||||
"""
|
||||
Node positions have changed without recalc. Update positions from
|
||||
node.position, then re-calculate links for those that have moved.
|
||||
Assumes bidirectional links, with one calculation per node pair, where
|
||||
one of the nodes has moved.
|
||||
|
||||
:param moved: flag is it was moved
|
||||
:param moved: moved nodes
|
||||
:param moved_netifs: moved network interfaces
|
||||
:return: nothing
|
||||
"""
|
||||
|
@ -509,11 +509,7 @@ class BasicRangeModel(WirelessModel):
|
|||
:param unlink: unlink or not
|
||||
:return: nothing
|
||||
"""
|
||||
if unlink:
|
||||
message_type = MessageFlags.DELETE
|
||||
else:
|
||||
message_type = MessageFlags.ADD
|
||||
|
||||
message_type = MessageFlags.DELETE if unlink else MessageFlags.ADD
|
||||
link_data = self.create_link_data(netif, netif2, message_type)
|
||||
self.session.broadcast_link(link_data)
|
||||
|
||||
|
@ -539,29 +535,35 @@ class WayPoint:
|
|||
Maintains information regarding waypoints.
|
||||
"""
|
||||
|
||||
def __init__(self, time: float, nodenum: int, coords, speed: float):
|
||||
def __init__(
|
||||
self,
|
||||
_time: float,
|
||||
node_id: int,
|
||||
coords: Tuple[float, float, Optional[float]],
|
||||
speed: float,
|
||||
) -> None:
|
||||
"""
|
||||
Creates a WayPoint instance.
|
||||
|
||||
:param time: waypoint time
|
||||
:param nodenum: node id
|
||||
:param _time: waypoint time
|
||||
:param node_id: node id
|
||||
:param coords: waypoint coordinates
|
||||
:param speed: waypoint speed
|
||||
"""
|
||||
self.time = time
|
||||
self.nodenum = nodenum
|
||||
self.coords = coords
|
||||
self.speed = speed
|
||||
self.time: float = _time
|
||||
self.node_id: int = node_id
|
||||
self.coords: Tuple[float, float, Optional[float]] = coords
|
||||
self.speed: float = speed
|
||||
|
||||
def __eq__(self, other: "WayPoint") -> bool:
|
||||
return (self.time, self.nodenum) == (other.time, other.nodenum)
|
||||
return (self.time, self.node_id) == (other.time, other.node_id)
|
||||
|
||||
def __ne__(self, other: "WayPoint") -> bool:
|
||||
return not self == other
|
||||
|
||||
def __lt__(self, other: "WayPoint") -> bool:
|
||||
if self.time == other.time:
|
||||
return self.nodenum < other.nodenum
|
||||
return self.node_id < other.node_id
|
||||
else:
|
||||
return self.time < other.time
|
||||
|
||||
|
@ -571,12 +573,11 @@ class WayPointMobility(WirelessModel):
|
|||
Abstract class for mobility models that set node waypoints.
|
||||
"""
|
||||
|
||||
name = "waypoint"
|
||||
config_type = RegisterTlvs.MOBILITY
|
||||
|
||||
STATE_STOPPED = 0
|
||||
STATE_RUNNING = 1
|
||||
STATE_PAUSED = 2
|
||||
name: str = "waypoint"
|
||||
config_type: RegisterTlvs = RegisterTlvs.MOBILITY
|
||||
STATE_STOPPED: int = 0
|
||||
STATE_RUNNING: int = 1
|
||||
STATE_PAUSED: int = 2
|
||||
|
||||
def __init__(self, session: "Session", _id: int) -> None:
|
||||
"""
|
||||
|
@ -587,20 +588,21 @@ class WayPointMobility(WirelessModel):
|
|||
:return:
|
||||
"""
|
||||
super().__init__(session=session, _id=_id)
|
||||
self.state = self.STATE_STOPPED
|
||||
self.queue = []
|
||||
self.queue_copy = []
|
||||
self.points = {}
|
||||
self.initial = {}
|
||||
self.lasttime = None
|
||||
self.endtime = None
|
||||
self.wlan = session.get_node(_id)
|
||||
self.state: int = self.STATE_STOPPED
|
||||
self.queue: List[WayPoint] = []
|
||||
self.queue_copy: List[WayPoint] = []
|
||||
self.points: Dict[int, WayPoint] = {}
|
||||
self.initial: Dict[int, WayPoint] = {}
|
||||
self.lasttime: Optional[float] = None
|
||||
self.endtime: Optional[int] = None
|
||||
self.timezero: float = 0.0
|
||||
self.wlan: WlanNode = session.get_node(_id, WlanNode)
|
||||
# these are really set in child class via confmatrix
|
||||
self.loop = False
|
||||
self.refresh_ms = 50
|
||||
self.loop: bool = False
|
||||
self.refresh_ms: int = 50
|
||||
# flag whether to stop scheduling when queue is empty
|
||||
# (ns-3 sets this to False as new waypoints may be added from trace)
|
||||
self.empty_queue_stop = True
|
||||
self.empty_queue_stop: bool = True
|
||||
|
||||
def runround(self) -> None:
|
||||
"""
|
||||
|
@ -688,16 +690,11 @@ class WayPointMobility(WirelessModel):
|
|||
self.setnodeposition(node, x2, y2, z2)
|
||||
del self.points[node.id]
|
||||
return True
|
||||
# speed can be a velocity vector or speed value
|
||||
if isinstance(speed, (float, int)):
|
||||
# linear speed value
|
||||
alpha = math.atan2(y2 - y1, x2 - x1)
|
||||
sx = speed * math.cos(alpha)
|
||||
sy = speed * math.sin(alpha)
|
||||
else:
|
||||
# velocity vector
|
||||
sx = speed[0]
|
||||
sy = speed[1]
|
||||
|
||||
# linear speed value
|
||||
alpha = math.atan2(y2 - y1, x2 - x1)
|
||||
sx = speed * math.cos(alpha)
|
||||
sy = speed * math.sin(alpha)
|
||||
|
||||
# calculate dt * speed = distance moved
|
||||
dx = sx * dt
|
||||
|
@ -740,7 +737,13 @@ class WayPointMobility(WirelessModel):
|
|||
self.session.mobility.updatewlans(moved, moved_netifs)
|
||||
|
||||
def addwaypoint(
|
||||
self, _time: float, nodenum: int, x: float, y: float, z: float, speed: float
|
||||
self,
|
||||
_time: float,
|
||||
nodenum: int,
|
||||
x: float,
|
||||
y: float,
|
||||
z: Optional[float],
|
||||
speed: float,
|
||||
) -> None:
|
||||
"""
|
||||
Waypoints are pushed to a heapq, sorted by time.
|
||||
|
@ -780,7 +783,7 @@ class WayPointMobility(WirelessModel):
|
|||
if self.queue[0].time > now:
|
||||
break
|
||||
wp = heapq.heappop(self.queue)
|
||||
self.points[wp.nodenum] = wp
|
||||
self.points[wp.node_id] = wp
|
||||
|
||||
def copywaypoints(self) -> None:
|
||||
"""
|
||||
|
@ -880,8 +883,8 @@ class Ns2ScriptedMobility(WayPointMobility):
|
|||
BonnMotion.
|
||||
"""
|
||||
|
||||
name = "ns2script"
|
||||
options = [
|
||||
name: str = "ns2script"
|
||||
options: List[Configuration] = [
|
||||
Configuration(
|
||||
_id="file", _type=ConfigDataTypes.STRING, label="mobility script file"
|
||||
),
|
||||
|
@ -927,7 +930,7 @@ class Ns2ScriptedMobility(WayPointMobility):
|
|||
ConfigGroup("ns-2 Mobility Script Parameters", 1, len(cls.configurations()))
|
||||
]
|
||||
|
||||
def __init__(self, session: "Session", _id: int):
|
||||
def __init__(self, session: "Session", _id: int) -> None:
|
||||
"""
|
||||
Creates a Ns2ScriptedMobility instance.
|
||||
|
||||
|
@ -935,17 +938,14 @@ class Ns2ScriptedMobility(WayPointMobility):
|
|||
:param _id: object id
|
||||
"""
|
||||
super().__init__(session, _id)
|
||||
self._netifs = {}
|
||||
self._netifslock = threading.Lock()
|
||||
|
||||
self.file = None
|
||||
self.refresh_ms = None
|
||||
self.loop = None
|
||||
self.autostart = None
|
||||
self.nodemap = {}
|
||||
self.script_start = None
|
||||
self.script_pause = None
|
||||
self.script_stop = None
|
||||
self.file: Optional[str] = None
|
||||
self.refresh_ms: Optional[int] = None
|
||||
self.loop: Optional[bool] = None
|
||||
self.autostart: Optional[str] = None
|
||||
self.nodemap: Dict[int, int] = {}
|
||||
self.script_start: Optional[str] = None
|
||||
self.script_pause: Optional[str] = None
|
||||
self.script_stop: Optional[str] = None
|
||||
|
||||
def update_config(self, config: Dict[str, str]) -> None:
|
||||
self.file = config["file"]
|
||||
|
|
|
@ -6,6 +6,7 @@ import logging
|
|||
import os
|
||||
import shutil
|
||||
import threading
|
||||
from threading import RLock
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Type
|
||||
|
||||
import netaddr
|
||||
|
@ -14,9 +15,10 @@ from core import utils
|
|||
from core.configservice.dependencies import ConfigServiceDependencies
|
||||
from core.constants import MOUNT_BIN, VNODED_BIN
|
||||
from core.emulator.data import LinkData, NodeData
|
||||
from core.emulator.emudata import InterfaceData, LinkOptions
|
||||
from core.emulator.enumerations import LinkTypes, MessageFlags, NodeTypes
|
||||
from core.errors import CoreCommandError, CoreError
|
||||
from core.nodes import client
|
||||
from core.nodes.client import VnodeClient
|
||||
from core.nodes.interface import CoreInterface, TunTap, Veth
|
||||
from core.nodes.netclient import LinuxNetClient, get_net_client
|
||||
|
||||
|
@ -24,7 +26,9 @@ if TYPE_CHECKING:
|
|||
from core.emulator.distributed import DistributedServer
|
||||
from core.emulator.session import Session
|
||||
from core.configservice.base import ConfigService
|
||||
from core.services.coreservices import CoreService
|
||||
|
||||
CoreServices = List[CoreService]
|
||||
ConfigServiceType = Type[ConfigService]
|
||||
|
||||
_DEFAULT_MTU = 1500
|
||||
|
@ -35,7 +39,7 @@ class NodeBase:
|
|||
Base class for CORE nodes (nodes and networks)
|
||||
"""
|
||||
|
||||
apitype = None
|
||||
apitype: Optional[NodeTypes] = None
|
||||
|
||||
# TODO: appears start has no usage, verify and remove
|
||||
def __init__(
|
||||
|
@ -57,27 +61,25 @@ class NodeBase:
|
|||
will run on, default is None for localhost
|
||||
"""
|
||||
|
||||
self.session = session
|
||||
self.session: "Session" = session
|
||||
if _id is None:
|
||||
_id = session.get_node_id()
|
||||
self.id = _id
|
||||
self.id: int = _id
|
||||
if name is None:
|
||||
name = f"o{self.id}"
|
||||
self.name = name
|
||||
self.server = server
|
||||
|
||||
self.type = None
|
||||
self.services = None
|
||||
# ifindex is key, CoreInterface instance is value
|
||||
self._netif = {}
|
||||
self.ifindex = 0
|
||||
self.canvas = None
|
||||
self.icon = None
|
||||
self.opaque = None
|
||||
self.position = Position()
|
||||
|
||||
self.name: str = name
|
||||
self.server: "DistributedServer" = server
|
||||
self.type: Optional[str] = None
|
||||
self.services: CoreServices = []
|
||||
self._netif: Dict[int, CoreInterface] = {}
|
||||
self.ifindex: int = 0
|
||||
self.canvas: Optional[int] = None
|
||||
self.icon: Optional[str] = None
|
||||
self.opaque: Optional[str] = None
|
||||
self.position: Position = Position()
|
||||
self.up: bool = False
|
||||
use_ovs = session.options.get_config("ovs") == "True"
|
||||
self.net_client = get_net_client(use_ovs, self.host_cmd)
|
||||
self.net_client: LinuxNetClient = get_net_client(use_ovs, self.host_cmd)
|
||||
|
||||
def startup(self) -> None:
|
||||
"""
|
||||
|
@ -209,9 +211,7 @@ class NodeBase:
|
|||
server = None
|
||||
if self.server is not None:
|
||||
server = self.server.name
|
||||
services = None
|
||||
if self.services is not None:
|
||||
services = [service.name for service in self.services]
|
||||
services = [service.name for service in self.services]
|
||||
return NodeData(
|
||||
message_type=message_type,
|
||||
id=self.id,
|
||||
|
@ -268,11 +268,9 @@ class CoreNodeBase(NodeBase):
|
|||
will run on, default is None for localhost
|
||||
"""
|
||||
super().__init__(session, _id, name, start, server)
|
||||
self.services = []
|
||||
self.config_services = {}
|
||||
self.nodedir = None
|
||||
self.tmpnodedir = False
|
||||
self.up = False
|
||||
self.config_services: Dict[str, "ConfigService"] = {}
|
||||
self.nodedir: Optional[str] = None
|
||||
self.tmpnodedir: bool = False
|
||||
|
||||
def add_config_service(self, service_class: "ConfigServiceType") -> None:
|
||||
"""
|
||||
|
@ -301,7 +299,7 @@ class CoreNodeBase(NodeBase):
|
|||
|
||||
def start_config_services(self) -> None:
|
||||
"""
|
||||
Determins startup paths and starts configuration services, based on their
|
||||
Determines startup paths and starts configuration services, based on their
|
||||
dependency chains.
|
||||
|
||||
:return: nothing
|
||||
|
@ -333,7 +331,6 @@ class CoreNodeBase(NodeBase):
|
|||
preserve = self.session.options.get_config("preservedir") == "1"
|
||||
if preserve:
|
||||
return
|
||||
|
||||
if self.tmpnodedir:
|
||||
self.host_cmd(f"rm -rf {self.nodedir}")
|
||||
|
||||
|
@ -413,14 +410,14 @@ class CoreNodeBase(NodeBase):
|
|||
netif.setposition()
|
||||
|
||||
def commonnets(
|
||||
self, obj: "CoreNodeBase", want_ctrl: bool = False
|
||||
) -> List[Tuple[NodeBase, CoreInterface, CoreInterface]]:
|
||||
self, node: "CoreNodeBase", want_ctrl: bool = False
|
||||
) -> List[Tuple["CoreNetworkBase", CoreInterface, CoreInterface]]:
|
||||
"""
|
||||
Given another node or net object, return common networks between
|
||||
this node and that object. A list of tuples is returned, with each tuple
|
||||
consisting of (network, interface1, interface2).
|
||||
|
||||
:param obj: object to get common network with
|
||||
:param node: node to get common network with
|
||||
:param want_ctrl: flag set to determine if control network are wanted
|
||||
:return: tuples of common networks
|
||||
"""
|
||||
|
@ -428,11 +425,33 @@ class CoreNodeBase(NodeBase):
|
|||
for netif1 in self.netifs():
|
||||
if not want_ctrl and hasattr(netif1, "control"):
|
||||
continue
|
||||
for netif2 in obj.netifs():
|
||||
for netif2 in node.netifs():
|
||||
if netif1.net == netif2.net:
|
||||
common.append((netif1.net, netif1, netif2))
|
||||
return common
|
||||
|
||||
def nodefile(self, filename: str, contents: str, mode: int = 0o644) -> None:
|
||||
"""
|
||||
Create a node file with a given mode.
|
||||
|
||||
:param filename: name of file to create
|
||||
:param contents: contents of file
|
||||
:param mode: mode for file
|
||||
:return: nothing
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def addfile(self, srcname: str, filename: str) -> None:
|
||||
"""
|
||||
Add a file.
|
||||
|
||||
:param srcname: source file name
|
||||
:param filename: file name to add
|
||||
:return: nothing
|
||||
:raises CoreCommandError: when a non-zero exit status occurs
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def cmd(self, args: str, wait: bool = True, shell: bool = False) -> str:
|
||||
"""
|
||||
Runs a command within a node container.
|
||||
|
@ -469,7 +488,6 @@ class CoreNode(CoreNodeBase):
|
|||
_id: int = None,
|
||||
name: str = None,
|
||||
nodedir: str = None,
|
||||
bootsh: str = "boot.sh",
|
||||
start: bool = True,
|
||||
server: "DistributedServer" = None,
|
||||
) -> None:
|
||||
|
@ -480,25 +498,21 @@ class CoreNode(CoreNodeBase):
|
|||
:param _id: object id
|
||||
:param name: object name
|
||||
:param nodedir: node directory
|
||||
:param bootsh: boot shell to use
|
||||
:param start: start flag
|
||||
:param server: remote server node
|
||||
will run on, default is None for localhost
|
||||
"""
|
||||
super().__init__(session, _id, name, start, server)
|
||||
self.nodedir = nodedir
|
||||
self.ctrlchnlname = os.path.abspath(
|
||||
self.nodedir: Optional[str] = nodedir
|
||||
self.ctrlchnlname: str = os.path.abspath(
|
||||
os.path.join(self.session.session_dir, self.name)
|
||||
)
|
||||
self.client = None
|
||||
self.pid = None
|
||||
self.lock = threading.RLock()
|
||||
self._mounts = []
|
||||
self.bootsh = bootsh
|
||||
|
||||
self.client: Optional[VnodeClient] = None
|
||||
self.pid: Optional[int] = None
|
||||
self.lock: RLock = RLock()
|
||||
self._mounts: List[Tuple[str, str]] = []
|
||||
use_ovs = session.options.get_config("ovs") == "True"
|
||||
self.node_net_client = self.create_node_net_client(use_ovs)
|
||||
|
||||
self.node_net_client: LinuxNetClient = self.create_node_net_client(use_ovs)
|
||||
if start:
|
||||
self.startup()
|
||||
|
||||
|
@ -522,7 +536,6 @@ class CoreNode(CoreNodeBase):
|
|||
self.host_cmd(f"kill -0 {self.pid}")
|
||||
except CoreCommandError:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def startup(self) -> None:
|
||||
|
@ -554,7 +567,7 @@ class CoreNode(CoreNodeBase):
|
|||
logging.debug("node(%s) pid: %s", self.name, self.pid)
|
||||
|
||||
# create vnode client
|
||||
self.client = client.VnodeClient(self.name, self.ctrlchnlname)
|
||||
self.client = VnodeClient(self.name, self.ctrlchnlname)
|
||||
|
||||
# bring up the loopback interface
|
||||
logging.debug("bringing up loopback interface")
|
||||
|
@ -833,53 +846,36 @@ class CoreNode(CoreNodeBase):
|
|||
interface_name = self.ifname(ifindex)
|
||||
self.node_net_client.device_up(interface_name)
|
||||
|
||||
def newnetif(
|
||||
self,
|
||||
net: "CoreNetworkBase" = None,
|
||||
addrlist: List[str] = None,
|
||||
hwaddr: str = None,
|
||||
ifindex: int = None,
|
||||
ifname: str = None,
|
||||
) -> int:
|
||||
def newnetif(self, net: "CoreNetworkBase", interface: InterfaceData) -> int:
|
||||
"""
|
||||
Create a new network interface.
|
||||
|
||||
:param net: network to associate with
|
||||
:param addrlist: addresses to add on the interface
|
||||
:param hwaddr: hardware address to set for interface
|
||||
:param ifindex: index of interface to create
|
||||
:param ifname: name for interface
|
||||
:param interface: interface data for new interface
|
||||
:return: interface index
|
||||
"""
|
||||
if not addrlist:
|
||||
addrlist = []
|
||||
|
||||
addresses = interface.get_addresses()
|
||||
with self.lock:
|
||||
# TODO: emane specific code
|
||||
if net is not None and net.is_emane is True:
|
||||
ifindex = self.newtuntap(ifindex, ifname)
|
||||
if net.is_emane is True:
|
||||
ifindex = self.newtuntap(interface.id, interface.name)
|
||||
# TUN/TAP is not ready for addressing yet; the device may
|
||||
# take some time to appear, and installing it into a
|
||||
# namespace after it has been bound removes addressing;
|
||||
# save addresses with the interface now
|
||||
self.attachnet(ifindex, net)
|
||||
netif = self.netif(ifindex)
|
||||
netif.sethwaddr(hwaddr)
|
||||
for address in utils.make_tuple(addrlist):
|
||||
netif.sethwaddr(interface.mac)
|
||||
for address in addresses:
|
||||
netif.addaddr(address)
|
||||
return ifindex
|
||||
else:
|
||||
ifindex = self.newveth(ifindex, ifname)
|
||||
|
||||
if net is not None:
|
||||
self.attachnet(ifindex, net)
|
||||
|
||||
if hwaddr:
|
||||
self.sethwaddr(ifindex, hwaddr)
|
||||
|
||||
for address in utils.make_tuple(addrlist):
|
||||
ifindex = self.newveth(interface.id, interface.name)
|
||||
self.attachnet(ifindex, net)
|
||||
if interface.mac:
|
||||
self.sethwaddr(ifindex, interface.mac)
|
||||
for address in addresses:
|
||||
self.addaddr(ifindex, address)
|
||||
|
||||
self.ifup(ifindex)
|
||||
return ifindex
|
||||
|
||||
|
@ -992,6 +988,7 @@ class CoreNetworkBase(NodeBase):
|
|||
will run on, default is None for localhost
|
||||
"""
|
||||
super().__init__(session, _id, name, start, server)
|
||||
self.brname = None
|
||||
self._linked = {}
|
||||
self._linked_lock = threading.Lock()
|
||||
|
||||
|
@ -1020,7 +1017,7 @@ class CoreNetworkBase(NodeBase):
|
|||
"""
|
||||
pass
|
||||
|
||||
def getlinknetif(self, net: "CoreNetworkBase") -> CoreInterface:
|
||||
def getlinknetif(self, net: "CoreNetworkBase") -> Optional[CoreInterface]:
|
||||
"""
|
||||
Return the interface of that links this net with another net.
|
||||
|
||||
|
@ -1028,7 +1025,7 @@ class CoreNetworkBase(NodeBase):
|
|||
:return: interface the provided network is linked to
|
||||
"""
|
||||
for netif in self.netifs():
|
||||
if hasattr(netif, "othernet") and netif.othernet == net:
|
||||
if getattr(netif, "othernet", None) == net:
|
||||
return netif
|
||||
return None
|
||||
|
||||
|
@ -1072,11 +1069,11 @@ class CoreNetworkBase(NodeBase):
|
|||
for netif in self.netifs(sort=True):
|
||||
if not hasattr(netif, "node"):
|
||||
continue
|
||||
linked_node = netif.node
|
||||
uni = False
|
||||
linked_node = netif.node
|
||||
if linked_node is None:
|
||||
# two layer-2 switches/hubs linked together via linknet()
|
||||
if not hasattr(netif, "othernet"):
|
||||
if not netif.othernet:
|
||||
continue
|
||||
linked_node = netif.othernet
|
||||
if linked_node.id == self.id:
|
||||
|
@ -1149,6 +1146,19 @@ class CoreNetworkBase(NodeBase):
|
|||
|
||||
return all_links
|
||||
|
||||
def linkconfig(
|
||||
self, netif: CoreInterface, options: LinkOptions, netif2: CoreInterface = None
|
||||
) -> None:
|
||||
"""
|
||||
Configure link parameters by applying tc queuing disciplines on the interface.
|
||||
|
||||
:param netif: interface one
|
||||
:param options: options for configuring link
|
||||
:param netif2: interface two
|
||||
:return: nothing
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class Position:
|
||||
"""
|
||||
|
@ -1163,12 +1173,12 @@ class Position:
|
|||
:param y: y position
|
||||
:param z: z position
|
||||
"""
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.z = z
|
||||
self.lon = None
|
||||
self.lat = None
|
||||
self.alt = None
|
||||
self.x: float = x
|
||||
self.y: float = y
|
||||
self.z: float = z
|
||||
self.lon: Optional[float] = None
|
||||
self.lat: Optional[float] = None
|
||||
self.alt: Optional[float] = None
|
||||
|
||||
def set(self, x: float = None, y: float = None, z: float = None) -> bool:
|
||||
"""
|
||||
|
|
|
@ -20,8 +20,8 @@ class VnodeClient:
|
|||
:param name: name for client
|
||||
:param ctrlchnlname: control channel name
|
||||
"""
|
||||
self.name = name
|
||||
self.ctrlchnlname = ctrlchnlname
|
||||
self.name: str = name
|
||||
self.ctrlchnlname: str = ctrlchnlname
|
||||
|
||||
def _verify_connection(self) -> None:
|
||||
"""
|
||||
|
|
|
@ -2,7 +2,7 @@ import json
|
|||
import logging
|
||||
import os
|
||||
from tempfile import NamedTemporaryFile
|
||||
from typing import TYPE_CHECKING, Callable, Dict
|
||||
from typing import TYPE_CHECKING, Callable, Dict, Optional
|
||||
|
||||
from core import utils
|
||||
from core.emulator.distributed import DistributedServer
|
||||
|
@ -17,15 +17,16 @@ if TYPE_CHECKING:
|
|||
|
||||
class DockerClient:
|
||||
def __init__(self, name: str, image: str, run: Callable[..., str]) -> None:
|
||||
self.name = name
|
||||
self.image = image
|
||||
self.run = run
|
||||
self.pid = None
|
||||
self.name: str = name
|
||||
self.image: str = image
|
||||
self.run: Callable[..., str] = run
|
||||
self.pid: Optional[str] = None
|
||||
|
||||
def create_container(self) -> str:
|
||||
self.run(
|
||||
f"docker run -td --init --net=none --hostname {self.name} --name {self.name} "
|
||||
f"--sysctl net.ipv6.conf.all.disable_ipv6=0 {self.image} /bin/bash"
|
||||
f"docker run -td --init --net=none --hostname {self.name} "
|
||||
f"--name {self.name} --sysctl net.ipv6.conf.all.disable_ipv6=0 "
|
||||
f"--privileged {self.image} /bin/bash"
|
||||
)
|
||||
self.pid = self.get_pid()
|
||||
return self.pid
|
||||
|
@ -35,7 +36,7 @@ class DockerClient:
|
|||
output = self.run(args)
|
||||
data = json.loads(output)
|
||||
if not data:
|
||||
raise CoreCommandError(-1, args, f"docker({self.name}) not present")
|
||||
raise CoreCommandError(1, args, f"docker({self.name}) not present")
|
||||
return data[0]
|
||||
|
||||
def is_alive(self) -> bool:
|
||||
|
@ -53,11 +54,7 @@ class DockerClient:
|
|||
return utils.cmd(f"docker exec {self.name} {cmd}", wait=wait, shell=shell)
|
||||
|
||||
def create_ns_cmd(self, cmd: str) -> str:
|
||||
return f"nsenter -t {self.pid} -u -i -p -n {cmd}"
|
||||
|
||||
def ns_cmd(self, cmd: str, wait: bool) -> str:
|
||||
args = f"nsenter -t {self.pid} -u -i -p -n {cmd}"
|
||||
return utils.cmd(args, wait=wait)
|
||||
return f"nsenter -t {self.pid} -a {cmd}"
|
||||
|
||||
def get_pid(self) -> str:
|
||||
args = f"docker inspect -f '{{{{.State.Pid}}}}' {self.name}"
|
||||
|
@ -80,7 +77,6 @@ class DockerNode(CoreNode):
|
|||
_id: int = None,
|
||||
name: str = None,
|
||||
nodedir: str = None,
|
||||
bootsh: str = "boot.sh",
|
||||
start: bool = True,
|
||||
server: DistributedServer = None,
|
||||
image: str = None
|
||||
|
@ -92,7 +88,6 @@ class DockerNode(CoreNode):
|
|||
:param _id: object id
|
||||
:param name: object name
|
||||
:param nodedir: node directory
|
||||
:param bootsh: boot shell to use
|
||||
:param start: start flag
|
||||
:param server: remote server node
|
||||
will run on, default is None for localhost
|
||||
|
@ -100,8 +95,8 @@ class DockerNode(CoreNode):
|
|||
"""
|
||||
if image is None:
|
||||
image = "ubuntu"
|
||||
self.image = image
|
||||
super().__init__(session, _id, name, nodedir, bootsh, start, server)
|
||||
self.image: str = image
|
||||
super().__init__(session, _id, name, nodedir, start, server)
|
||||
|
||||
def create_node_net_client(self, use_ovs: bool) -> LinuxNetClient:
|
||||
"""
|
||||
|
|
|
@ -4,12 +4,12 @@ virtual ethernet classes that implement the interfaces available under Linux.
|
|||
|
||||
import logging
|
||||
import time
|
||||
from typing import TYPE_CHECKING, Callable, Dict, List, Tuple
|
||||
from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Tuple
|
||||
|
||||
from core import utils
|
||||
from core.emulator.enumerations import MessageFlags
|
||||
from core.emulator.enumerations import MessageFlags, TransportType
|
||||
from core.errors import CoreCommandError
|
||||
from core.nodes.netclient import get_net_client
|
||||
from core.nodes.netclient import LinuxNetClient, get_net_client
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.emulator.distributed import DistributedServer
|
||||
|
@ -27,6 +27,7 @@ class CoreInterface:
|
|||
session: "Session",
|
||||
node: "CoreNode",
|
||||
name: str,
|
||||
localname: str,
|
||||
mtu: int,
|
||||
server: "DistributedServer" = None,
|
||||
) -> None:
|
||||
|
@ -36,33 +37,35 @@ class CoreInterface:
|
|||
:param session: core session instance
|
||||
:param node: node for interface
|
||||
:param name: interface name
|
||||
:param localname: interface local name
|
||||
:param mtu: mtu value
|
||||
:param server: remote server node
|
||||
will run on, default is None for localhost
|
||||
"""
|
||||
self.session = session
|
||||
self.node = node
|
||||
self.name = name
|
||||
if not isinstance(mtu, int):
|
||||
raise ValueError
|
||||
self.mtu = mtu
|
||||
self.net = None
|
||||
self._params = {}
|
||||
self.addrlist = []
|
||||
self.hwaddr = None
|
||||
self.session: "Session" = session
|
||||
self.node: "CoreNode" = node
|
||||
self.name: str = name
|
||||
self.localname: str = localname
|
||||
self.up: bool = False
|
||||
self.mtu: int = mtu
|
||||
self.net: Optional[CoreNetworkBase] = None
|
||||
self.othernet: Optional[CoreNetworkBase] = None
|
||||
self._params: Dict[str, float] = {}
|
||||
self.addrlist: List[str] = []
|
||||
self.hwaddr: Optional[str] = None
|
||||
# placeholder position hook
|
||||
self.poshook = lambda x: None
|
||||
self.poshook: Callable[[CoreInterface], None] = lambda x: None
|
||||
# used with EMANE
|
||||
self.transport_type = None
|
||||
self.transport_type: Optional[TransportType] = None
|
||||
# node interface index
|
||||
self.netindex = None
|
||||
self.netindex: Optional[int] = None
|
||||
# net interface index
|
||||
self.netifi = None
|
||||
self.netifi: Optional[int] = None
|
||||
# index used to find flow data
|
||||
self.flow_id = None
|
||||
self.server = server
|
||||
self.flow_id: Optional[int] = None
|
||||
self.server: Optional["DistributedServer"] = server
|
||||
use_ovs = session.options.get_config("ovs") == "True"
|
||||
self.net_client = get_net_client(use_ovs, self.host_cmd)
|
||||
self.net_client: LinuxNetClient = get_net_client(use_ovs, self.host_cmd)
|
||||
|
||||
def host_cmd(
|
||||
self,
|
||||
|
@ -258,9 +261,7 @@ class Veth(CoreInterface):
|
|||
:raises CoreCommandError: when there is a command exception
|
||||
"""
|
||||
# note that net arg is ignored
|
||||
super().__init__(session, node, name, mtu, server)
|
||||
self.localname = localname
|
||||
self.up = False
|
||||
super().__init__(session, node, name, localname, mtu, server)
|
||||
if start:
|
||||
self.startup()
|
||||
|
||||
|
@ -326,10 +327,8 @@ class TunTap(CoreInterface):
|
|||
will run on, default is None for localhost
|
||||
:param start: start flag
|
||||
"""
|
||||
super().__init__(session, node, name, mtu, server)
|
||||
self.localname = localname
|
||||
self.up = False
|
||||
self.transport_type = "virtual"
|
||||
super().__init__(session, node, name, localname, mtu, server)
|
||||
self.transport_type = TransportType.VIRTUAL
|
||||
if start:
|
||||
self.startup()
|
||||
|
||||
|
@ -509,22 +508,17 @@ class GreTap(CoreInterface):
|
|||
will run on, default is None for localhost
|
||||
:raises CoreCommandError: when there is a command exception
|
||||
"""
|
||||
super().__init__(session, node, name, mtu, server)
|
||||
if _id is None:
|
||||
# from PyCoreObj
|
||||
_id = ((id(self) >> 16) ^ (id(self) & 0xFFFF)) & 0xFFFF
|
||||
self.id = _id
|
||||
sessionid = self.session.short_session_id()
|
||||
# interface name on the local host machine
|
||||
self.localname = f"gt.{self.id}.{sessionid}"
|
||||
self.transport_type = "raw"
|
||||
sessionid = session.short_session_id()
|
||||
localname = f"gt.{self.id}.{sessionid}"
|
||||
super().__init__(session, node, name, localname, mtu, server)
|
||||
self.transport_type = TransportType.RAW
|
||||
if not start:
|
||||
self.up = False
|
||||
return
|
||||
|
||||
if remoteip is None:
|
||||
raise ValueError("missing remote IP required for GRE TAP device")
|
||||
|
||||
self.net_client.create_gretap(self.localname, remoteip, localip, ttl, key)
|
||||
self.net_client.device_up(self.localname)
|
||||
self.up = True
|
||||
|
|
|
@ -3,7 +3,7 @@ import logging
|
|||
import os
|
||||
import time
|
||||
from tempfile import NamedTemporaryFile
|
||||
from typing import TYPE_CHECKING, Callable, Dict
|
||||
from typing import TYPE_CHECKING, Callable, Dict, Optional
|
||||
|
||||
from core import utils
|
||||
from core.emulator.distributed import DistributedServer
|
||||
|
@ -18,10 +18,10 @@ if TYPE_CHECKING:
|
|||
|
||||
class LxdClient:
|
||||
def __init__(self, name: str, image: str, run: Callable[..., str]) -> None:
|
||||
self.name = name
|
||||
self.image = image
|
||||
self.run = run
|
||||
self.pid = None
|
||||
self.name: str = name
|
||||
self.image: str = image
|
||||
self.run: Callable[..., str] = run
|
||||
self.pid: Optional[int] = None
|
||||
|
||||
def create_container(self) -> int:
|
||||
self.run(f"lxc launch {self.image} {self.name}")
|
||||
|
@ -34,7 +34,7 @@ class LxdClient:
|
|||
output = self.run(args)
|
||||
data = json.loads(output)
|
||||
if not data:
|
||||
raise CoreCommandError(-1, args, f"LXC({self.name}) not present")
|
||||
raise CoreCommandError(1, args, f"LXC({self.name}) not present")
|
||||
return data[0]
|
||||
|
||||
def is_alive(self) -> bool:
|
||||
|
@ -74,7 +74,6 @@ class LxcNode(CoreNode):
|
|||
_id: int = None,
|
||||
name: str = None,
|
||||
nodedir: str = None,
|
||||
bootsh: str = "boot.sh",
|
||||
start: bool = True,
|
||||
server: DistributedServer = None,
|
||||
image: str = None,
|
||||
|
@ -86,7 +85,6 @@ class LxcNode(CoreNode):
|
|||
:param _id: object id
|
||||
:param name: object name
|
||||
:param nodedir: node directory
|
||||
:param bootsh: boot shell to use
|
||||
:param start: start flag
|
||||
:param server: remote server node
|
||||
will run on, default is None for localhost
|
||||
|
@ -94,8 +92,8 @@ class LxcNode(CoreNode):
|
|||
"""
|
||||
if image is None:
|
||||
image = "ubuntu"
|
||||
self.image = image
|
||||
super().__init__(session, _id, name, nodedir, bootsh, start, server)
|
||||
self.image: str = image
|
||||
super().__init__(session, _id, name, nodedir, start, server)
|
||||
|
||||
def alive(self) -> bool:
|
||||
"""
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
"""
|
||||
Clients for dealing with bridge/interface commands.
|
||||
"""
|
||||
import json
|
||||
from typing import Callable
|
||||
|
||||
import netaddr
|
||||
|
@ -20,7 +19,7 @@ class LinuxNetClient:
|
|||
|
||||
:param run: function to run commands with
|
||||
"""
|
||||
self.run = run
|
||||
self.run: Callable[..., str] = run
|
||||
|
||||
def set_hostname(self, name: str) -> None:
|
||||
"""
|
||||
|
@ -71,13 +70,22 @@ class LinuxNetClient:
|
|||
|
||||
def device_show(self, device: str) -> str:
|
||||
"""
|
||||
Show information for a device.
|
||||
Show link information for a device.
|
||||
|
||||
:param device: device to get information for
|
||||
:return: device information
|
||||
"""
|
||||
return self.run(f"{IP_BIN} link show {device}")
|
||||
|
||||
def address_show(self, device: str) -> str:
|
||||
"""
|
||||
Show address information for a device.
|
||||
|
||||
:param device: device name
|
||||
:return: address information
|
||||
"""
|
||||
return self.run(f"{IP_BIN} address show {device}")
|
||||
|
||||
def get_mac(self, device: str) -> str:
|
||||
"""
|
||||
Retrieve MAC address for a given device.
|
||||
|
@ -114,7 +122,8 @@ class LinuxNetClient:
|
|||
:return: nothing
|
||||
"""
|
||||
self.run(
|
||||
f"[ -e /sys/class/net/{device} ] && {IP_BIN} -6 address flush dev {device} || true",
|
||||
f"[ -e /sys/class/net/{device} ] && "
|
||||
f"{IP_BIN} address flush dev {device} || true",
|
||||
shell=True,
|
||||
)
|
||||
|
||||
|
@ -241,9 +250,9 @@ class LinuxNetClient:
|
|||
self.device_down(name)
|
||||
self.run(f"{IP_BIN} link delete {name} type bridge")
|
||||
|
||||
def create_interface(self, bridge_name: str, interface_name: str) -> None:
|
||||
def set_interface_master(self, bridge_name: str, interface_name: str) -> None:
|
||||
"""
|
||||
Create an interface associated with a Linux bridge.
|
||||
Assign interface master to a Linux bridge.
|
||||
|
||||
:param bridge_name: bridge name
|
||||
:param interface_name: interface name
|
||||
|
@ -269,12 +278,13 @@ class LinuxNetClient:
|
|||
:param _id: node id to check bridges for
|
||||
:return: True if there are existing bridges, False otherwise
|
||||
"""
|
||||
output = self.run(f"{IP_BIN} -j link show type bridge")
|
||||
bridges = json.loads(output)
|
||||
for bridge in bridges:
|
||||
name = bridge.get("ifname")
|
||||
if not name:
|
||||
output = self.run(f"{IP_BIN} -o link show type bridge")
|
||||
lines = output.split("\n")
|
||||
for line in lines:
|
||||
values = line.split(":")
|
||||
if not len(values) >= 2:
|
||||
continue
|
||||
name = values[1]
|
||||
fields = name.split(".")
|
||||
if len(fields) != 3:
|
||||
continue
|
||||
|
@ -320,7 +330,7 @@ class OvsNetClient(LinuxNetClient):
|
|||
self.device_down(name)
|
||||
self.run(f"{OVS_BIN} del-br {name}")
|
||||
|
||||
def create_interface(self, bridge_name: str, interface_name: str) -> None:
|
||||
def set_interface_master(self, bridge_name: str, interface_name: str) -> None:
|
||||
"""
|
||||
Create an interface associated with a network bridge.
|
||||
|
||||
|
|
|
@ -12,7 +12,14 @@ import netaddr
|
|||
from core import utils
|
||||
from core.constants import EBTABLES_BIN, TC_BIN
|
||||
from core.emulator.data import LinkData, NodeData
|
||||
from core.emulator.enumerations import LinkTypes, MessageFlags, NodeTypes, RegisterTlvs
|
||||
from core.emulator.emudata import LinkOptions
|
||||
from core.emulator.enumerations import (
|
||||
LinkTypes,
|
||||
MessageFlags,
|
||||
NetworkPolicy,
|
||||
NodeTypes,
|
||||
RegisterTlvs,
|
||||
)
|
||||
from core.errors import CoreCommandError, CoreError
|
||||
from core.nodes.base import CoreNetworkBase
|
||||
from core.nodes.interface import CoreInterface, GreTap, Veth
|
||||
|
@ -21,7 +28,7 @@ from core.nodes.netclient import get_net_client
|
|||
if TYPE_CHECKING:
|
||||
from core.emulator.distributed import DistributedServer
|
||||
from core.emulator.session import Session
|
||||
from core.location.mobility import WirelessModel
|
||||
from core.location.mobility import WirelessModel, WayPointMobility
|
||||
|
||||
WirelessModelType = Type[WirelessModel]
|
||||
|
||||
|
@ -36,26 +43,26 @@ class EbtablesQueue:
|
|||
"""
|
||||
|
||||
# update rate is every 300ms
|
||||
rate = 0.3
|
||||
rate: float = 0.3
|
||||
# ebtables
|
||||
atomic_file = "/tmp/pycore.ebtables.atomic"
|
||||
atomic_file: str = "/tmp/pycore.ebtables.atomic"
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""
|
||||
Initialize the helper class, but don't start the update thread
|
||||
until a WLAN is instantiated.
|
||||
"""
|
||||
self.doupdateloop = False
|
||||
self.updatethread = None
|
||||
self.doupdateloop: bool = False
|
||||
self.updatethread: Optional[threading.Thread] = None
|
||||
# this lock protects cmds and updates lists
|
||||
self.updatelock = threading.Lock()
|
||||
self.updatelock: threading.Lock = threading.Lock()
|
||||
# list of pending ebtables commands
|
||||
self.cmds = []
|
||||
self.cmds: List[str] = []
|
||||
# list of WLANs requiring update
|
||||
self.updates = []
|
||||
self.updates: List["CoreNetwork"] = []
|
||||
# timestamps of last WLAN update; this keeps track of WLANs that are
|
||||
# using this queue
|
||||
self.last_update_time = {}
|
||||
self.last_update_time: Dict["CoreNetwork", float] = {}
|
||||
|
||||
def startupdateloop(self, wlan: "CoreNetwork") -> None:
|
||||
"""
|
||||
|
@ -65,10 +72,8 @@ class EbtablesQueue:
|
|||
"""
|
||||
with self.updatelock:
|
||||
self.last_update_time[wlan] = time.monotonic()
|
||||
|
||||
if self.doupdateloop:
|
||||
return
|
||||
|
||||
self.doupdateloop = True
|
||||
self.updatethread = threading.Thread(target=self.updateloop, daemon=True)
|
||||
self.updatethread.start()
|
||||
|
@ -86,10 +91,8 @@ class EbtablesQueue:
|
|||
logging.exception(
|
||||
"error deleting last update time for wlan, ignored before: %s", wlan
|
||||
)
|
||||
|
||||
if len(self.last_update_time) > 0:
|
||||
return
|
||||
|
||||
self.doupdateloop = False
|
||||
if self.updatethread:
|
||||
self.updatethread.join()
|
||||
|
@ -208,21 +211,21 @@ class EbtablesQueue:
|
|||
wlan.has_ebtables_chain = True
|
||||
self.cmds.extend(
|
||||
[
|
||||
f"-N {wlan.brname} -P {wlan.policy}",
|
||||
f"-N {wlan.brname} -P {wlan.policy.value}",
|
||||
f"-A FORWARD --logical-in {wlan.brname} -j {wlan.brname}",
|
||||
]
|
||||
)
|
||||
# rebuild the chain
|
||||
for netif1, v in wlan._linked.items():
|
||||
for netif2, linked in v.items():
|
||||
if wlan.policy == "DROP" and linked:
|
||||
if wlan.policy == NetworkPolicy.DROP and linked:
|
||||
self.cmds.extend(
|
||||
[
|
||||
f"-A {wlan.brname} -i {netif1.localname} -o {netif2.localname} -j ACCEPT",
|
||||
f"-A {wlan.brname} -o {netif1.localname} -i {netif2.localname} -j ACCEPT",
|
||||
]
|
||||
)
|
||||
elif wlan.policy == "ACCEPT" and not linked:
|
||||
elif wlan.policy == NetworkPolicy.ACCEPT and not linked:
|
||||
self.cmds.extend(
|
||||
[
|
||||
f"-A {wlan.brname} -i {netif1.localname} -o {netif2.localname} -j DROP",
|
||||
|
@ -233,7 +236,7 @@ class EbtablesQueue:
|
|||
|
||||
# a global object because all WLANs share the same queue
|
||||
# cannot have multiple threads invoking the ebtables commnd
|
||||
ebq = EbtablesQueue()
|
||||
ebq: EbtablesQueue = EbtablesQueue()
|
||||
|
||||
|
||||
def ebtablescmds(call: Callable[..., str], cmds: List[str]) -> None:
|
||||
|
@ -254,7 +257,7 @@ class CoreNetwork(CoreNetworkBase):
|
|||
Provides linux bridge network functionality for core nodes.
|
||||
"""
|
||||
|
||||
policy = "DROP"
|
||||
policy: NetworkPolicy = NetworkPolicy.DROP
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
@ -263,7 +266,7 @@ class CoreNetwork(CoreNetworkBase):
|
|||
name: str = None,
|
||||
start: bool = True,
|
||||
server: "DistributedServer" = None,
|
||||
policy: str = None,
|
||||
policy: NetworkPolicy = None,
|
||||
) -> None:
|
||||
"""
|
||||
Creates a LxBrNet instance.
|
||||
|
@ -281,11 +284,10 @@ class CoreNetwork(CoreNetworkBase):
|
|||
name = str(self.id)
|
||||
if policy is not None:
|
||||
self.policy = policy
|
||||
self.name = name
|
||||
self.name: Optional[str] = name
|
||||
sessionid = self.session.short_session_id()
|
||||
self.brname = f"b.{self.id}.{sessionid}"
|
||||
self.up = False
|
||||
self.has_ebtables_chain = False
|
||||
self.brname: str = f"b.{self.id}.{sessionid}"
|
||||
self.has_ebtables_chain: bool = False
|
||||
if start:
|
||||
self.startup()
|
||||
ebq.startupdateloop(self)
|
||||
|
@ -365,7 +367,7 @@ class CoreNetwork(CoreNetworkBase):
|
|||
:return: nothing
|
||||
"""
|
||||
if self.up:
|
||||
netif.net_client.create_interface(self.brname, netif.localname)
|
||||
netif.net_client.set_interface_master(self.brname, netif.localname)
|
||||
super().attach(netif)
|
||||
|
||||
def detach(self, netif: CoreInterface) -> None:
|
||||
|
@ -397,12 +399,12 @@ class CoreNetwork(CoreNetworkBase):
|
|||
try:
|
||||
linked = self._linked[netif1][netif2]
|
||||
except KeyError:
|
||||
if self.policy == "ACCEPT":
|
||||
if self.policy == NetworkPolicy.ACCEPT:
|
||||
linked = True
|
||||
elif self.policy == "DROP":
|
||||
elif self.policy == NetworkPolicy.DROP:
|
||||
linked = False
|
||||
else:
|
||||
raise Exception(f"unknown policy: {self.policy}")
|
||||
raise Exception(f"unknown policy: {self.policy.value}")
|
||||
self._linked[netif1][netif2] = linked
|
||||
|
||||
return linked
|
||||
|
@ -440,41 +442,27 @@ class CoreNetwork(CoreNetworkBase):
|
|||
ebq.ebchange(self)
|
||||
|
||||
def linkconfig(
|
||||
self,
|
||||
netif: CoreInterface,
|
||||
bw: float = None,
|
||||
delay: float = None,
|
||||
loss: float = None,
|
||||
duplicate: float = None,
|
||||
jitter: float = None,
|
||||
netif2: float = None,
|
||||
devname: str = None,
|
||||
self, netif: CoreInterface, options: LinkOptions, netif2: CoreInterface = None
|
||||
) -> None:
|
||||
"""
|
||||
Configure link parameters by applying tc queuing disciplines on the interface.
|
||||
|
||||
:param netif: interface one
|
||||
:param bw: bandwidth to set to
|
||||
:param delay: packet delay to set to
|
||||
:param loss: packet loss to set to
|
||||
:param duplicate: duplicate percentage to set to
|
||||
:param jitter: jitter to set to
|
||||
:param options: options for configuring link
|
||||
:param netif2: interface two
|
||||
:param devname: device name
|
||||
:return: nothing
|
||||
"""
|
||||
if devname is None:
|
||||
devname = netif.localname
|
||||
devname = netif.localname
|
||||
tc = f"{TC_BIN} qdisc replace dev {devname}"
|
||||
parent = "root"
|
||||
changed = False
|
||||
bw = options.bandwidth
|
||||
if netif.setparam("bw", bw):
|
||||
# from tc-tbf(8): minimum value for burst is rate / kernel_hz
|
||||
if bw is not None:
|
||||
burst = max(2 * netif.mtu, bw / 1000)
|
||||
# max IP payload
|
||||
limit = 0xFFFF
|
||||
tbf = f"tbf rate {bw} burst {burst} limit {limit}"
|
||||
burst = max(2 * netif.mtu, int(bw / 1000))
|
||||
# max IP payload
|
||||
limit = 0xFFFF
|
||||
tbf = f"tbf rate {bw} burst {burst} limit {limit}"
|
||||
if bw > 0:
|
||||
if self.up:
|
||||
cmd = f"{tc} {parent} handle 1: {tbf}"
|
||||
|
@ -492,13 +480,17 @@ class CoreNetwork(CoreNetworkBase):
|
|||
if netif.getparam("has_tbf"):
|
||||
parent = "parent 1:1"
|
||||
netem = "netem"
|
||||
delay = options.delay
|
||||
changed = max(changed, netif.setparam("delay", delay))
|
||||
loss = options.per
|
||||
if loss is not None:
|
||||
loss = float(loss)
|
||||
changed = max(changed, netif.setparam("loss", loss))
|
||||
duplicate = options.dup
|
||||
if duplicate is not None:
|
||||
duplicate = int(duplicate)
|
||||
changed = max(changed, netif.setparam("duplicate", duplicate))
|
||||
jitter = options.jitter
|
||||
changed = max(changed, netif.setparam("jitter", jitter))
|
||||
if not changed:
|
||||
return
|
||||
|
@ -565,9 +557,8 @@ class CoreNetwork(CoreNetworkBase):
|
|||
|
||||
netif = Veth(self.session, None, name, localname, start=self.up)
|
||||
self.attach(netif)
|
||||
if net.up:
|
||||
# this is similar to net.attach() but uses netif.name instead of localname
|
||||
netif.net_client.create_interface(net.brname, netif.name)
|
||||
if net.up and net.brname:
|
||||
netif.net_client.set_interface_master(net.brname, netif.name)
|
||||
i = net.newifindex()
|
||||
net._netif[i] = netif
|
||||
with net._linked_lock:
|
||||
|
@ -585,7 +576,7 @@ class CoreNetwork(CoreNetworkBase):
|
|||
:return: interface the provided network is linked to
|
||||
"""
|
||||
for netif in self.netifs():
|
||||
if hasattr(netif, "othernet") and netif.othernet == net:
|
||||
if netif.othernet == net:
|
||||
return netif
|
||||
return None
|
||||
|
||||
|
@ -615,7 +606,7 @@ class GreTapBridge(CoreNetwork):
|
|||
remoteip: str = None,
|
||||
_id: int = None,
|
||||
name: str = None,
|
||||
policy: str = "ACCEPT",
|
||||
policy: NetworkPolicy = NetworkPolicy.ACCEPT,
|
||||
localip: str = None,
|
||||
ttl: int = 255,
|
||||
key: int = None,
|
||||
|
@ -638,17 +629,16 @@ class GreTapBridge(CoreNetwork):
|
|||
will run on, default is None for localhost
|
||||
"""
|
||||
CoreNetwork.__init__(self, session, _id, name, False, server, policy)
|
||||
self.grekey = key
|
||||
if self.grekey is None:
|
||||
self.grekey = self.session.id ^ self.id
|
||||
self.localnum = None
|
||||
self.remotenum = None
|
||||
self.remoteip = remoteip
|
||||
self.localip = localip
|
||||
self.ttl = ttl
|
||||
if remoteip is None:
|
||||
self.gretap = None
|
||||
else:
|
||||
if key is None:
|
||||
key = self.session.id ^ self.id
|
||||
self.grekey: int = key
|
||||
self.localnum: Optional[int] = None
|
||||
self.remotenum: Optional[int] = None
|
||||
self.remoteip: Optional[str] = remoteip
|
||||
self.localip: Optional[str] = localip
|
||||
self.ttl: int = ttl
|
||||
self.gretap: Optional[GreTap] = None
|
||||
if remoteip is not None:
|
||||
self.gretap = GreTap(
|
||||
node=self,
|
||||
session=session,
|
||||
|
@ -723,10 +713,10 @@ class CtrlNet(CoreNetwork):
|
|||
Control network functionality.
|
||||
"""
|
||||
|
||||
policy = "ACCEPT"
|
||||
policy: NetworkPolicy = NetworkPolicy.ACCEPT
|
||||
# base control interface index
|
||||
CTRLIF_IDX_BASE = 99
|
||||
DEFAULT_PREFIX_LIST = [
|
||||
CTRLIF_IDX_BASE: int = 99
|
||||
DEFAULT_PREFIX_LIST: List[str] = [
|
||||
"172.16.0.0/24 172.16.1.0/24 172.16.2.0/24 172.16.3.0/24 172.16.4.0/24",
|
||||
"172.17.0.0/24 172.17.1.0/24 172.17.2.0/24 172.17.3.0/24 172.17.4.0/24",
|
||||
"172.18.0.0/24 172.18.1.0/24 172.18.2.0/24 172.18.3.0/24 172.18.4.0/24",
|
||||
|
@ -736,15 +726,15 @@ class CtrlNet(CoreNetwork):
|
|||
def __init__(
|
||||
self,
|
||||
session: "Session",
|
||||
prefix: str,
|
||||
_id: int = None,
|
||||
name: str = None,
|
||||
prefix: str = None,
|
||||
hostid: int = None,
|
||||
start: bool = True,
|
||||
server: "DistributedServer" = None,
|
||||
assign_address: bool = True,
|
||||
updown_script: str = None,
|
||||
serverintf: CoreInterface = None,
|
||||
serverintf: str = None,
|
||||
) -> None:
|
||||
"""
|
||||
Creates a CtrlNet instance.
|
||||
|
@ -762,11 +752,11 @@ class CtrlNet(CoreNetwork):
|
|||
:param serverintf: server interface
|
||||
:return:
|
||||
"""
|
||||
self.prefix = netaddr.IPNetwork(prefix).cidr
|
||||
self.hostid = hostid
|
||||
self.assign_address = assign_address
|
||||
self.updown_script = updown_script
|
||||
self.serverintf = serverintf
|
||||
self.prefix: netaddr.IPNetwork = netaddr.IPNetwork(prefix).cidr
|
||||
self.hostid: Optional[int] = hostid
|
||||
self.assign_address: bool = assign_address
|
||||
self.updown_script: Optional[str] = updown_script
|
||||
self.serverintf: Optional[str] = serverintf
|
||||
super().__init__(session, _id, name, start, server)
|
||||
|
||||
def add_addresses(self, index: int) -> None:
|
||||
|
@ -817,7 +807,7 @@ class CtrlNet(CoreNetwork):
|
|||
self.host_cmd(f"{self.updown_script} {self.brname} startup")
|
||||
|
||||
if self.serverintf:
|
||||
self.net_client.create_interface(self.brname, self.serverintf)
|
||||
self.net_client.set_interface_master(self.brname, self.serverintf)
|
||||
|
||||
def shutdown(self) -> None:
|
||||
"""
|
||||
|
@ -863,7 +853,7 @@ class PtpNet(CoreNetwork):
|
|||
Peer to peer network node.
|
||||
"""
|
||||
|
||||
policy = "ACCEPT"
|
||||
policy: NetworkPolicy = NetworkPolicy.ACCEPT
|
||||
|
||||
def attach(self, netif: CoreInterface) -> None:
|
||||
"""
|
||||
|
@ -993,9 +983,9 @@ class SwitchNode(CoreNetwork):
|
|||
Provides switch functionality within a core node.
|
||||
"""
|
||||
|
||||
apitype = NodeTypes.SWITCH
|
||||
policy = "ACCEPT"
|
||||
type = "lanswitch"
|
||||
apitype: NodeTypes = NodeTypes.SWITCH
|
||||
policy: NetworkPolicy = NetworkPolicy.ACCEPT
|
||||
type: str = "lanswitch"
|
||||
|
||||
|
||||
class HubNode(CoreNetwork):
|
||||
|
@ -1004,9 +994,9 @@ class HubNode(CoreNetwork):
|
|||
ports by turning off MAC address learning.
|
||||
"""
|
||||
|
||||
apitype = NodeTypes.HUB
|
||||
policy = "ACCEPT"
|
||||
type = "hub"
|
||||
apitype: NodeTypes = NodeTypes.HUB
|
||||
policy: NetworkPolicy = NetworkPolicy.ACCEPT
|
||||
type: str = "hub"
|
||||
|
||||
def startup(self) -> None:
|
||||
"""
|
||||
|
@ -1023,10 +1013,10 @@ class WlanNode(CoreNetwork):
|
|||
Provides wireless lan functionality within a core node.
|
||||
"""
|
||||
|
||||
apitype = NodeTypes.WIRELESS_LAN
|
||||
linktype = LinkTypes.WIRED
|
||||
policy = "DROP"
|
||||
type = "wlan"
|
||||
apitype: NodeTypes = NodeTypes.WIRELESS_LAN
|
||||
linktype: LinkTypes = LinkTypes.WIRED
|
||||
policy: NetworkPolicy = NetworkPolicy.DROP
|
||||
type: str = "wlan"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
@ -1035,7 +1025,7 @@ class WlanNode(CoreNetwork):
|
|||
name: str = None,
|
||||
start: bool = True,
|
||||
server: "DistributedServer" = None,
|
||||
policy: str = None,
|
||||
policy: NetworkPolicy = None,
|
||||
) -> None:
|
||||
"""
|
||||
Create a WlanNode instance.
|
||||
|
@ -1050,8 +1040,8 @@ class WlanNode(CoreNetwork):
|
|||
"""
|
||||
super().__init__(session, _id, name, start, server, policy)
|
||||
# wireless and mobility models (BasicRangeModel, Ns2WaypointMobility)
|
||||
self.model = None
|
||||
self.mobility = None
|
||||
self.model: Optional[WirelessModel] = None
|
||||
self.mobility: Optional[WayPointMobility] = None
|
||||
|
||||
def startup(self) -> None:
|
||||
"""
|
||||
|
@ -1127,6 +1117,6 @@ class TunnelNode(GreTapBridge):
|
|||
Provides tunnel functionality in a core node.
|
||||
"""
|
||||
|
||||
apitype = NodeTypes.TUNNEL
|
||||
policy = "ACCEPT"
|
||||
type = "tunnel"
|
||||
apitype: NodeTypes = NodeTypes.TUNNEL
|
||||
policy: NetworkPolicy = NetworkPolicy.ACCEPT
|
||||
type: str = "tunnel"
|
||||
|
|
|
@ -5,15 +5,16 @@ PhysicalNode class for including real systems in the emulated network.
|
|||
import logging
|
||||
import os
|
||||
import threading
|
||||
from typing import IO, TYPE_CHECKING, List, Optional
|
||||
from typing import IO, TYPE_CHECKING, List, Optional, Tuple
|
||||
|
||||
from core import utils
|
||||
from core.constants import MOUNT_BIN, UMOUNT_BIN
|
||||
from core.emulator.distributed import DistributedServer
|
||||
from core.emulator.enumerations import NodeTypes
|
||||
from core.emulator.emudata import InterfaceData, LinkOptions
|
||||
from core.emulator.enumerations import NodeTypes, TransportType
|
||||
from core.errors import CoreCommandError, CoreError
|
||||
from core.nodes.base import CoreNetworkBase, CoreNodeBase
|
||||
from core.nodes.interface import CoreInterface, Veth
|
||||
from core.nodes.interface import CoreInterface
|
||||
from core.nodes.network import CoreNetwork, GreTap
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
@ -23,7 +24,7 @@ if TYPE_CHECKING:
|
|||
class PhysicalNode(CoreNodeBase):
|
||||
def __init__(
|
||||
self,
|
||||
session,
|
||||
session: "Session",
|
||||
_id: int = None,
|
||||
name: str = None,
|
||||
nodedir: str = None,
|
||||
|
@ -33,10 +34,10 @@ class PhysicalNode(CoreNodeBase):
|
|||
super().__init__(session, _id, name, start, server)
|
||||
if not self.server:
|
||||
raise CoreError("physical nodes must be assigned to a remote server")
|
||||
self.nodedir = nodedir
|
||||
self.up = start
|
||||
self.lock = threading.RLock()
|
||||
self._mounts = []
|
||||
self.nodedir: Optional[str] = nodedir
|
||||
self.up: bool = start
|
||||
self.lock: threading.RLock = threading.RLock()
|
||||
self._mounts: List[Tuple[str, str]] = []
|
||||
if start:
|
||||
self.startup()
|
||||
|
||||
|
@ -112,7 +113,7 @@ class PhysicalNode(CoreNodeBase):
|
|||
logging.exception("trying to delete unknown address: %s", addr)
|
||||
|
||||
if self.up:
|
||||
self.net_client.delete_address(interface.name, str(addr))
|
||||
self.net_client.delete_address(interface.name, addr)
|
||||
|
||||
def adoptnetif(
|
||||
self, netif: CoreInterface, ifindex: int, hwaddr: str, addrlist: List[str]
|
||||
|
@ -125,47 +126,27 @@ class PhysicalNode(CoreNodeBase):
|
|||
netif.name = f"gt{ifindex}"
|
||||
netif.node = self
|
||||
self.addnetif(netif, ifindex)
|
||||
|
||||
# use a more reasonable name, e.g. "gt0" instead of "gt.56286.150"
|
||||
if self.up:
|
||||
self.net_client.device_down(netif.localname)
|
||||
self.net_client.device_name(netif.localname, netif.name)
|
||||
|
||||
netif.localname = netif.name
|
||||
|
||||
if hwaddr:
|
||||
self.sethwaddr(ifindex, hwaddr)
|
||||
|
||||
for addr in utils.make_tuple(addrlist):
|
||||
for addr in addrlist:
|
||||
self.addaddr(ifindex, addr)
|
||||
|
||||
if self.up:
|
||||
self.net_client.device_up(netif.localname)
|
||||
|
||||
def linkconfig(
|
||||
self,
|
||||
netif: CoreInterface,
|
||||
bw: float = None,
|
||||
delay: float = None,
|
||||
loss: float = None,
|
||||
duplicate: float = None,
|
||||
jitter: float = None,
|
||||
netif2: CoreInterface = None,
|
||||
self, netif: CoreInterface, options: LinkOptions, netif2: CoreInterface = None
|
||||
) -> None:
|
||||
"""
|
||||
Apply tc queing disciplines using linkconfig.
|
||||
"""
|
||||
linux_bridge = CoreNetwork(session=self.session, start=False)
|
||||
linux_bridge.up = True
|
||||
linux_bridge.linkconfig(
|
||||
netif,
|
||||
bw=bw,
|
||||
delay=delay,
|
||||
loss=loss,
|
||||
duplicate=duplicate,
|
||||
jitter=jitter,
|
||||
netif2=netif2,
|
||||
)
|
||||
linux_bridge.linkconfig(netif, options, netif2)
|
||||
del linux_bridge
|
||||
|
||||
def newifindex(self) -> int:
|
||||
|
@ -176,37 +157,25 @@ class PhysicalNode(CoreNodeBase):
|
|||
self.ifindex += 1
|
||||
return ifindex
|
||||
|
||||
def newnetif(
|
||||
self,
|
||||
net: Veth = None,
|
||||
addrlist: List[str] = None,
|
||||
hwaddr: str = None,
|
||||
ifindex: int = None,
|
||||
ifname: str = None,
|
||||
) -> int:
|
||||
def newnetif(self, net: CoreNetworkBase, interface: InterfaceData) -> int:
|
||||
logging.info("creating interface")
|
||||
if not addrlist:
|
||||
addrlist = []
|
||||
|
||||
if self.up and net is None:
|
||||
raise NotImplementedError
|
||||
|
||||
addresses = interface.get_addresses()
|
||||
ifindex = interface.id
|
||||
if ifindex is None:
|
||||
ifindex = self.newifindex()
|
||||
|
||||
if ifname is None:
|
||||
ifname = f"gt{ifindex}"
|
||||
|
||||
name = interface.name
|
||||
if name is None:
|
||||
name = f"gt{ifindex}"
|
||||
if self.up:
|
||||
# this is reached when this node is linked to a network node
|
||||
# tunnel to net not built yet, so build it now and adopt it
|
||||
_, remote_tap = self.session.distributed.create_gre_tunnel(net, self.server)
|
||||
self.adoptnetif(remote_tap, ifindex, hwaddr, addrlist)
|
||||
self.adoptnetif(remote_tap, ifindex, interface.mac, addresses)
|
||||
return ifindex
|
||||
else:
|
||||
# this is reached when configuring services (self.up=False)
|
||||
netif = GreTap(node=self, name=ifname, session=self.session, start=False)
|
||||
self.adoptnetif(netif, ifindex, hwaddr, addrlist)
|
||||
netif = GreTap(node=self, name=name, session=self.session, start=False)
|
||||
self.adoptnetif(netif, ifindex, interface.mac, addresses)
|
||||
return ifindex
|
||||
|
||||
def privatedir(self, path: str) -> None:
|
||||
|
@ -258,14 +227,14 @@ class PhysicalNode(CoreNodeBase):
|
|||
return self.host_cmd(args, wait=wait)
|
||||
|
||||
|
||||
class Rj45Node(CoreNodeBase, CoreInterface):
|
||||
class Rj45Node(CoreNodeBase):
|
||||
"""
|
||||
RJ45Node is a physical interface on the host linked to the emulated
|
||||
network.
|
||||
"""
|
||||
|
||||
apitype = NodeTypes.RJ45
|
||||
type = "rj45"
|
||||
apitype: NodeTypes = NodeTypes.RJ45
|
||||
type: str = "rj45"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
@ -287,16 +256,13 @@ class Rj45Node(CoreNodeBase, CoreInterface):
|
|||
:param server: remote server node
|
||||
will run on, default is None for localhost
|
||||
"""
|
||||
CoreNodeBase.__init__(self, session, _id, name, start, server)
|
||||
CoreInterface.__init__(self, session, self, name, mtu, server)
|
||||
self.lock = threading.RLock()
|
||||
self.ifindex = None
|
||||
# the following are PyCoreNetIf attributes
|
||||
self.transport_type = "raw"
|
||||
self.localname = name
|
||||
self.old_up = False
|
||||
self.old_addrs = []
|
||||
|
||||
super().__init__(session, _id, name, start, server)
|
||||
self.interface = CoreInterface(session, self, name, name, mtu, server)
|
||||
self.interface.transport_type = TransportType.RAW
|
||||
self.lock: threading.RLock = threading.RLock()
|
||||
self.ifindex: Optional[int] = None
|
||||
self.old_up: bool = False
|
||||
self.old_addrs: List[Tuple[str, Optional[str]]] = []
|
||||
if start:
|
||||
self.startup()
|
||||
|
||||
|
@ -309,7 +275,7 @@ class Rj45Node(CoreNodeBase, CoreInterface):
|
|||
"""
|
||||
# interface will also be marked up during net.attach()
|
||||
self.savestate()
|
||||
self.net_client.device_up(self.localname)
|
||||
self.net_client.device_up(self.interface.localname)
|
||||
self.up = True
|
||||
|
||||
def shutdown(self) -> None:
|
||||
|
@ -321,78 +287,39 @@ class Rj45Node(CoreNodeBase, CoreInterface):
|
|||
"""
|
||||
if not self.up:
|
||||
return
|
||||
|
||||
localname = self.interface.localname
|
||||
self.net_client.device_down(localname)
|
||||
self.net_client.device_flush(localname)
|
||||
try:
|
||||
self.net_client.device_down(self.localname)
|
||||
self.net_client.device_flush(self.localname)
|
||||
self.net_client.delete_tc(self.localname)
|
||||
self.net_client.delete_tc(localname)
|
||||
except CoreCommandError:
|
||||
logging.exception("error shutting down")
|
||||
|
||||
pass
|
||||
self.up = False
|
||||
self.restorestate()
|
||||
|
||||
# TODO: issue in that both classes inherited from provide the same method with
|
||||
# different signatures
|
||||
def attachnet(self, net: CoreNetworkBase) -> None:
|
||||
"""
|
||||
Attach a network.
|
||||
|
||||
:param net: network to attach
|
||||
:return: nothing
|
||||
"""
|
||||
CoreInterface.attachnet(self, net)
|
||||
|
||||
# TODO: issue in that both classes inherited from provide the same method with
|
||||
# different signatures
|
||||
def detachnet(self) -> None:
|
||||
"""
|
||||
Detach a network.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
CoreInterface.detachnet(self)
|
||||
|
||||
def newnetif(
|
||||
self,
|
||||
net: CoreNetworkBase = None,
|
||||
addrlist: List[str] = None,
|
||||
hwaddr: str = None,
|
||||
ifindex: int = None,
|
||||
ifname: str = None,
|
||||
) -> int:
|
||||
def newnetif(self, net: CoreNetworkBase, interface: InterfaceData) -> int:
|
||||
"""
|
||||
This is called when linking with another node. Since this node
|
||||
represents an interface, we do not create another object here,
|
||||
but attach ourselves to the given network.
|
||||
|
||||
:param net: new network instance
|
||||
:param addrlist: address list
|
||||
:param hwaddr: hardware address
|
||||
:param ifindex: interface index
|
||||
:param ifname: interface name
|
||||
:param interface: interface data for new interface
|
||||
:return: interface index
|
||||
:raises ValueError: when an interface has already been created, one max
|
||||
"""
|
||||
with self.lock:
|
||||
ifindex = interface.id
|
||||
if ifindex is None:
|
||||
ifindex = 0
|
||||
|
||||
if self.net is not None:
|
||||
if self.interface.net is not None:
|
||||
raise ValueError("RJ45 nodes support at most 1 network interface")
|
||||
|
||||
self._netif[ifindex] = self
|
||||
# PyCoreNetIf.node is self
|
||||
self.node = self
|
||||
self._netif[ifindex] = self.interface
|
||||
self.ifindex = ifindex
|
||||
|
||||
if net is not None:
|
||||
self.attachnet(net)
|
||||
|
||||
if addrlist:
|
||||
for addr in utils.make_tuple(addrlist):
|
||||
self.addaddr(addr)
|
||||
|
||||
self.interface.attachnet(net)
|
||||
for addr in interface.get_addresses():
|
||||
self.addaddr(addr)
|
||||
return ifindex
|
||||
|
||||
def delnetif(self, ifindex: int) -> None:
|
||||
|
@ -404,9 +331,7 @@ class Rj45Node(CoreNodeBase, CoreInterface):
|
|||
"""
|
||||
if ifindex is None:
|
||||
ifindex = 0
|
||||
|
||||
self._netif.pop(ifindex)
|
||||
|
||||
if ifindex == self.ifindex:
|
||||
self.shutdown()
|
||||
else:
|
||||
|
@ -424,15 +349,12 @@ class Rj45Node(CoreNodeBase, CoreInterface):
|
|||
:param net: network to retrieve
|
||||
:return: a network interface
|
||||
"""
|
||||
if net is not None and net == self.net:
|
||||
return self
|
||||
|
||||
if net is not None and net == self.interface.net:
|
||||
return self.interface
|
||||
if ifindex is None:
|
||||
ifindex = 0
|
||||
|
||||
if ifindex == self.ifindex:
|
||||
return self
|
||||
|
||||
return self.interface
|
||||
return None
|
||||
|
||||
def getifindex(self, netif: CoreInterface) -> Optional[int]:
|
||||
|
@ -443,7 +365,7 @@ class Rj45Node(CoreNodeBase, CoreInterface):
|
|||
index for
|
||||
:return: interface index, None otherwise
|
||||
"""
|
||||
if netif != self:
|
||||
if netif != self.interface:
|
||||
return None
|
||||
return self.ifindex
|
||||
|
||||
|
@ -458,7 +380,7 @@ class Rj45Node(CoreNodeBase, CoreInterface):
|
|||
addr = utils.validate_ip(addr)
|
||||
if self.up:
|
||||
self.net_client.create_address(self.name, addr)
|
||||
CoreInterface.addaddr(self, addr)
|
||||
self.interface.addaddr(addr)
|
||||
|
||||
def deladdr(self, addr: str) -> None:
|
||||
"""
|
||||
|
@ -469,8 +391,8 @@ class Rj45Node(CoreNodeBase, CoreInterface):
|
|||
:raises CoreCommandError: when there is a command exception
|
||||
"""
|
||||
if self.up:
|
||||
self.net_client.delete_address(self.name, str(addr))
|
||||
CoreInterface.deladdr(self, addr)
|
||||
self.net_client.delete_address(self.name, addr)
|
||||
self.interface.deladdr(addr)
|
||||
|
||||
def savestate(self) -> None:
|
||||
"""
|
||||
|
@ -481,14 +403,14 @@ class Rj45Node(CoreNodeBase, CoreInterface):
|
|||
:raises CoreCommandError: when there is a command exception
|
||||
"""
|
||||
self.old_up = False
|
||||
self.old_addrs = []
|
||||
output = self.net_client.device_show(self.localname)
|
||||
self.old_addrs: List[Tuple[str, Optional[str]]] = []
|
||||
localname = self.interface.localname
|
||||
output = self.net_client.address_show(localname)
|
||||
for line in output.split("\n"):
|
||||
items = line.split()
|
||||
if len(items) < 2:
|
||||
continue
|
||||
|
||||
if items[1] == f"{self.localname}:":
|
||||
if items[1] == f"{localname}:":
|
||||
flags = items[2][1:-1].split(",")
|
||||
if "UP" in flags:
|
||||
self.old_up = True
|
||||
|
@ -498,6 +420,7 @@ class Rj45Node(CoreNodeBase, CoreInterface):
|
|||
if items[1][:4] == "fe80":
|
||||
continue
|
||||
self.old_addrs.append((items[1], None))
|
||||
logging.info("saved rj45 state: addrs(%s) up(%s)", self.old_addrs, self.old_up)
|
||||
|
||||
def restorestate(self) -> None:
|
||||
"""
|
||||
|
@ -506,16 +429,12 @@ class Rj45Node(CoreNodeBase, CoreInterface):
|
|||
:return: nothing
|
||||
:raises CoreCommandError: when there is a command exception
|
||||
"""
|
||||
localname = self.interface.localname
|
||||
logging.info("restoring rj45 state: %s", localname)
|
||||
for addr in self.old_addrs:
|
||||
if addr[1] is None:
|
||||
self.net_client.create_address(self.localname, addr[0])
|
||||
else:
|
||||
self.net_client.create_address(
|
||||
self.localname, addr[0], broadcast=addr[1]
|
||||
)
|
||||
|
||||
self.net_client.create_address(localname, addr[0], addr[1])
|
||||
if self.old_up:
|
||||
self.net_client.device_up(self.localname)
|
||||
self.net_client.device_up(localname)
|
||||
|
||||
def setposition(self, x: float = None, y: float = None, z: float = None) -> None:
|
||||
"""
|
||||
|
@ -526,8 +445,8 @@ class Rj45Node(CoreNodeBase, CoreInterface):
|
|||
:param z: z position
|
||||
:return: True if position changed, False otherwise
|
||||
"""
|
||||
CoreNodeBase.setposition(self, x, y, z)
|
||||
CoreInterface.setposition(self)
|
||||
super().setposition(x, y, z)
|
||||
self.interface.setposition()
|
||||
|
||||
def termcmdstring(self, sh: str) -> str:
|
||||
"""
|
||||
|
|
|
@ -5,7 +5,7 @@ sdt.py: Scripted Display Tool (SDT3D) helper
|
|||
import logging
|
||||
import socket
|
||||
import threading
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
from typing import IO, TYPE_CHECKING, Dict, Optional, Set, Tuple
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from core import constants
|
||||
|
@ -42,11 +42,11 @@ class Sdt:
|
|||
when a node position or link has changed.
|
||||
"""
|
||||
|
||||
DEFAULT_SDT_URL = "tcp://127.0.0.1:50000/"
|
||||
DEFAULT_SDT_URL: str = "tcp://127.0.0.1:50000/"
|
||||
# default altitude (in meters) for flyto view
|
||||
DEFAULT_ALT = 2500
|
||||
DEFAULT_ALT: int = 2500
|
||||
# TODO: read in user"s nodes.conf here; below are default node types from the GUI
|
||||
DEFAULT_SPRITES = [
|
||||
DEFAULT_SPRITES: Dict[str, str] = [
|
||||
("router", "router.gif"),
|
||||
("host", "host.gif"),
|
||||
("PC", "pc.gif"),
|
||||
|
@ -65,14 +65,14 @@ class Sdt:
|
|||
|
||||
:param session: session this manager is tied to
|
||||
"""
|
||||
self.session = session
|
||||
self.lock = threading.Lock()
|
||||
self.sock = None
|
||||
self.connected = False
|
||||
self.url = self.DEFAULT_SDT_URL
|
||||
self.address = None
|
||||
self.protocol = None
|
||||
self.network_layers = set()
|
||||
self.session: "Session" = session
|
||||
self.lock: threading.Lock = threading.Lock()
|
||||
self.sock: Optional[IO] = None
|
||||
self.connected: bool = False
|
||||
self.url: str = self.DEFAULT_SDT_URL
|
||||
self.address: Optional[Tuple[Optional[str], Optional[int]]] = None
|
||||
self.protocol: Optional[str] = None
|
||||
self.network_layers: Set[str] = set()
|
||||
self.session.node_handlers.append(self.handle_node_update)
|
||||
self.session.link_handlers.append(self.handle_link_update)
|
||||
|
||||
|
@ -344,7 +344,7 @@ class Sdt:
|
|||
"""
|
||||
result = False
|
||||
try:
|
||||
node = self.session.get_node(node_id)
|
||||
node = self.session.get_node(node_id, NodeBase)
|
||||
result = isinstance(node, (WlanNode, EmaneNet))
|
||||
except CoreError:
|
||||
pass
|
||||
|
|
|
@ -39,7 +39,7 @@ class ServiceDependencies:
|
|||
that all services will be booted and that all dependencies exist within the services provided.
|
||||
"""
|
||||
|
||||
def __init__(self, services: List["CoreService"]) -> None:
|
||||
def __init__(self, services: List[Type["CoreService"]]) -> None:
|
||||
# helpers to check validity
|
||||
self.dependents = {}
|
||||
self.booted = set()
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from core.emane.nodes import EmaneNet
|
||||
from core.errors import CoreError
|
||||
from core.services.coreservices import CoreService
|
||||
from core.xml import emanexml
|
||||
|
||||
|
@ -20,8 +21,8 @@ class EmaneTransportService(CoreService):
|
|||
if filename == cls.configs[0]:
|
||||
transport_commands = []
|
||||
for interface in node.netifs(sort=True):
|
||||
network_node = node.session.get_node(interface.net.id)
|
||||
if isinstance(network_node, EmaneNet):
|
||||
try:
|
||||
network_node = node.session.get_node(interface.net.id, EmaneNet)
|
||||
config = node.session.emane.get_configs(
|
||||
network_node.id, network_node.model.name
|
||||
)
|
||||
|
@ -32,6 +33,8 @@ class EmaneTransportService(CoreService):
|
|||
% nem_id
|
||||
)
|
||||
transport_commands.append(command)
|
||||
except CoreError:
|
||||
pass
|
||||
transport_commands = "\n".join(transport_commands)
|
||||
return """
|
||||
emanegentransportxml -o ../ ../platform%s.xml
|
||||
|
|
|
@ -354,7 +354,7 @@ class FrrService(CoreService):
|
|||
for peerifc in ifc.net.netifs():
|
||||
if peerifc == ifc:
|
||||
continue
|
||||
if isinstance(peerifc, Rj45Node):
|
||||
if isinstance(peerifc.node, Rj45Node):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
|
|
@ -272,7 +272,7 @@ class QuaggaService(CoreService):
|
|||
for peerifc in ifc.net.netifs():
|
||||
if peerifc == ifc:
|
||||
continue
|
||||
if isinstance(peerifc, Rj45Node):
|
||||
if isinstance(peerifc.node, Rj45Node):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
|
|
@ -158,19 +158,6 @@ def which(command: str, required: bool) -> str:
|
|||
return found_path
|
||||
|
||||
|
||||
def make_tuple(obj: Generic[T]) -> Tuple[T]:
|
||||
"""
|
||||
Create a tuple from an object, or return the object itself.
|
||||
|
||||
:param obj: object to convert to a tuple
|
||||
:return: converted tuple or the object itself
|
||||
"""
|
||||
if hasattr(obj, "__iter__"):
|
||||
return tuple(obj)
|
||||
else:
|
||||
return (obj,)
|
||||
|
||||
|
||||
def make_tuple_fromstr(s: str, value_type: Callable[[str], T]) -> Tuple[T]:
|
||||
"""
|
||||
Create a tuple from a string.
|
||||
|
@ -228,17 +215,21 @@ def cmd(
|
|||
if shell is False:
|
||||
args = shlex.split(args)
|
||||
try:
|
||||
p = Popen(args, stdout=PIPE, stderr=PIPE, env=env, cwd=cwd, shell=shell)
|
||||
output = PIPE if wait else DEVNULL
|
||||
p = Popen(args, stdout=output, stderr=output, env=env, cwd=cwd, shell=shell)
|
||||
if wait:
|
||||
stdout, stderr = p.communicate()
|
||||
stdout = stdout.decode("utf-8").strip()
|
||||
stderr = stderr.decode("utf-8").strip()
|
||||
status = p.wait()
|
||||
if status != 0:
|
||||
raise CoreCommandError(status, args, stdout, stderr)
|
||||
return stdout.decode("utf-8").strip()
|
||||
return stdout
|
||||
else:
|
||||
return ""
|
||||
except OSError:
|
||||
raise CoreCommandError(-1, args)
|
||||
except OSError as e:
|
||||
logging.error("cmd error: %s", e.strerror)
|
||||
raise CoreCommandError(1, args, "", e.strerror)
|
||||
|
||||
|
||||
def file_munge(pathname: str, header: str, text: str) -> None:
|
||||
|
|
|
@ -10,7 +10,7 @@ from core.emulator.data import LinkData
|
|||
from core.emulator.emudata import InterfaceData, LinkOptions, NodeOptions
|
||||
from core.emulator.enumerations import EventTypes, NodeTypes
|
||||
from core.errors import CoreXmlError
|
||||
from core.nodes.base import CoreNetworkBase, CoreNodeBase, NodeBase
|
||||
from core.nodes.base import CoreNodeBase, NodeBase
|
||||
from core.nodes.docker import DockerNode
|
||||
from core.nodes.lxd import LxcNode
|
||||
from core.nodes.network import CtrlNet, WlanNode
|
||||
|
@ -66,7 +66,15 @@ def create_interface_data(interface_element: etree.Element) -> InterfaceData:
|
|||
ip4_mask = get_int(interface_element, "ip4_mask")
|
||||
ip6 = interface_element.get("ip6")
|
||||
ip6_mask = get_int(interface_element, "ip6_mask")
|
||||
return InterfaceData(interface_id, name, mac, ip4, ip4_mask, ip6, ip6_mask)
|
||||
return InterfaceData(
|
||||
id=interface_id,
|
||||
name=name,
|
||||
mac=mac,
|
||||
ip4=ip4,
|
||||
ip4_mask=ip4_mask,
|
||||
ip6=ip6,
|
||||
ip6_mask=ip6_mask,
|
||||
)
|
||||
|
||||
|
||||
def create_emane_config(session: "Session") -> etree.Element:
|
||||
|
@ -505,9 +513,9 @@ class CoreXmlWriter:
|
|||
ip6_mask: int,
|
||||
) -> etree.Element:
|
||||
interface = etree.Element(element_name)
|
||||
node = self.session.get_node(node_id)
|
||||
node = self.session.get_node(node_id, NodeBase)
|
||||
interface_name = None
|
||||
if not isinstance(node, CoreNetworkBase):
|
||||
if isinstance(node, CoreNodeBase):
|
||||
node_interface = node.netif(interface_id)
|
||||
interface_name = node_interface.name
|
||||
|
||||
|
@ -523,7 +531,6 @@ class CoreXmlWriter:
|
|||
add_attribute(interface, "ip4_mask", ip4_mask)
|
||||
add_attribute(interface, "ip6", ip6)
|
||||
add_attribute(interface, "ip6_mask", ip6_mask)
|
||||
|
||||
return interface
|
||||
|
||||
def create_link_element(self, link_data: LinkData) -> etree.Element:
|
||||
|
@ -560,8 +567,8 @@ class CoreXmlWriter:
|
|||
link_element.append(interface_two)
|
||||
|
||||
# check for options, don't write for emane/wlan links
|
||||
node_one = self.session.get_node(link_data.node1_id)
|
||||
node_two = self.session.get_node(link_data.node2_id)
|
||||
node_one = self.session.get_node(link_data.node1_id, NodeBase)
|
||||
node_two = self.session.get_node(link_data.node2_id, NodeBase)
|
||||
is_node_one_wireless = isinstance(node_one, (WlanNode, EmaneNet))
|
||||
is_node_two_wireless = isinstance(node_two, (WlanNode, EmaneNet))
|
||||
if not any([is_node_one_wireless, is_node_two_wireless]):
|
||||
|
@ -663,7 +670,7 @@ class CoreXmlReader:
|
|||
state = EventTypes(state)
|
||||
data = hook.text
|
||||
logging.info("reading hook: state(%s) name(%s)", state, name)
|
||||
self.session.add_hook(state, name, None, data)
|
||||
self.session.add_hook(state, name, data)
|
||||
|
||||
def read_session_origin(self) -> None:
|
||||
session_origin = self.scenario.find("session_origin")
|
||||
|
@ -833,14 +840,14 @@ class CoreXmlReader:
|
|||
icon = device_element.get("icon")
|
||||
clazz = device_element.get("class")
|
||||
image = device_element.get("image")
|
||||
options = NodeOptions(name, model, image)
|
||||
options.icon = icon
|
||||
options = NodeOptions(name=name, model=model, image=image, icon=icon)
|
||||
|
||||
node_type = NodeTypes.DEFAULT
|
||||
if clazz == "docker":
|
||||
node_type = NodeTypes.DOCKER
|
||||
elif clazz == "lxc":
|
||||
node_type = NodeTypes.LXC
|
||||
_class = self.session.get_node_class(node_type)
|
||||
|
||||
service_elements = device_element.find("services")
|
||||
if service_elements is not None:
|
||||
|
@ -866,15 +873,15 @@ class CoreXmlReader:
|
|||
options.set_location(lat, lon, alt)
|
||||
|
||||
logging.info("reading node id(%s) model(%s) name(%s)", node_id, model, name)
|
||||
self.session.add_node(_type=node_type, _id=node_id, options=options)
|
||||
self.session.add_node(_class, node_id, options)
|
||||
|
||||
def read_network(self, network_element: etree.Element) -> None:
|
||||
node_id = get_int(network_element, "id")
|
||||
name = network_element.get("name")
|
||||
node_type = NodeTypes[network_element.get("type")]
|
||||
_class = self.session.get_node_class(node_type)
|
||||
icon = network_element.get("icon")
|
||||
options = NodeOptions(name)
|
||||
options.icon = icon
|
||||
options = NodeOptions(name=name, icon=icon)
|
||||
|
||||
position_element = network_element.find("position")
|
||||
if position_element is not None:
|
||||
|
@ -892,7 +899,7 @@ class CoreXmlReader:
|
|||
logging.info(
|
||||
"reading node id(%s) node_type(%s) name(%s)", node_id, node_type, name
|
||||
)
|
||||
self.session.add_node(_type=node_type, _id=node_id, options=options)
|
||||
self.session.add_node(_class, node_id, options)
|
||||
|
||||
def read_configservice_configs(self) -> None:
|
||||
configservice_configs = self.scenario.find("configservice_configurations")
|
||||
|
@ -902,7 +909,7 @@ class CoreXmlReader:
|
|||
for configservice_element in configservice_configs.iterchildren():
|
||||
name = configservice_element.get("name")
|
||||
node_id = get_int(configservice_element, "node")
|
||||
node = self.session.get_node(node_id)
|
||||
node = self.session.get_node(node_id, CoreNodeBase)
|
||||
service = node.config_services[name]
|
||||
|
||||
configs_element = configservice_element.find("configs")
|
||||
|
|
|
@ -9,6 +9,7 @@ from core import utils
|
|||
from core.config import Configuration
|
||||
from core.emane.nodes import EmaneNet
|
||||
from core.emulator.distributed import DistributedServer
|
||||
from core.emulator.enumerations import TransportType
|
||||
from core.nodes.interface import CoreInterface
|
||||
from core.nodes.network import CtrlNet
|
||||
from core.xml import corexml
|
||||
|
@ -182,7 +183,7 @@ def build_node_platform_xml(
|
|||
transport_type = netif.transport_type
|
||||
if not transport_type:
|
||||
logging.info("warning: %s interface type unsupported!", netif.name)
|
||||
transport_type = "raw"
|
||||
transport_type = TransportType.RAW
|
||||
transport_file = transport_file_name(node.id, transport_type)
|
||||
transport_element = etree.SubElement(
|
||||
nem_element, "transport", definition=transport_file
|
||||
|
@ -196,7 +197,7 @@ def build_node_platform_xml(
|
|||
|
||||
# merging code
|
||||
key = netif.node.id
|
||||
if netif.transport_type == "raw":
|
||||
if netif.transport_type == TransportType.RAW:
|
||||
key = "host"
|
||||
otadev = control_net.brname
|
||||
eventdev = control_net.brname
|
||||
|
@ -276,8 +277,8 @@ def build_xml_files(emane_manager: "EmaneManager", node: EmaneNet) -> None:
|
|||
# build XML for specific interface (NEM) configs
|
||||
need_virtual = False
|
||||
need_raw = False
|
||||
vtype = "virtual"
|
||||
rtype = "raw"
|
||||
vtype = TransportType.VIRTUAL
|
||||
rtype = TransportType.RAW
|
||||
|
||||
for netif in node.netifs():
|
||||
# check for interface specific emane configuration and write xml files
|
||||
|
@ -286,7 +287,7 @@ def build_xml_files(emane_manager: "EmaneManager", node: EmaneNet) -> None:
|
|||
node.model.build_xml_files(config, netif)
|
||||
|
||||
# check transport type needed for interface
|
||||
if "virtual" in netif.transport_type:
|
||||
if netif.transport_type == TransportType.VIRTUAL:
|
||||
need_virtual = True
|
||||
vtype = netif.transport_type
|
||||
else:
|
||||
|
@ -301,7 +302,7 @@ def build_xml_files(emane_manager: "EmaneManager", node: EmaneNet) -> None:
|
|||
|
||||
|
||||
def build_transport_xml(
|
||||
emane_manager: "EmaneManager", node: EmaneNet, transport_type: str
|
||||
emane_manager: "EmaneManager", node: EmaneNet, transport_type: TransportType
|
||||
) -> None:
|
||||
"""
|
||||
Build transport xml file for node and transport type.
|
||||
|
@ -314,8 +315,8 @@ def build_transport_xml(
|
|||
"""
|
||||
transport_element = etree.Element(
|
||||
"transport",
|
||||
name=f"{transport_type.capitalize()} Transport",
|
||||
library=f"trans{transport_type.lower()}",
|
||||
name=f"{transport_type.value.capitalize()} Transport",
|
||||
library=f"trans{transport_type.value.lower()}",
|
||||
)
|
||||
|
||||
# add bitrate
|
||||
|
@ -325,7 +326,7 @@ def build_transport_xml(
|
|||
config = emane_manager.get_configs(node.id, node.model.name)
|
||||
flowcontrol = config.get("flowcontrolenable", "0") == "1"
|
||||
|
||||
if "virtual" in transport_type.lower():
|
||||
if transport_type == TransportType.VIRTUAL:
|
||||
device_path = "/dev/net/tun_flowctl"
|
||||
if not os.path.exists(device_path):
|
||||
device_path = "/dev/net/tun"
|
||||
|
@ -482,7 +483,7 @@ def create_event_service_xml(
|
|||
create_file(event_element, "emaneeventmsgsvc", file_path, server)
|
||||
|
||||
|
||||
def transport_file_name(node_id: int, transport_type: str) -> str:
|
||||
def transport_file_name(node_id: int, transport_type: TransportType) -> str:
|
||||
"""
|
||||
Create name for a transport xml file.
|
||||
|
||||
|
@ -490,7 +491,7 @@ def transport_file_name(node_id: int, transport_type: str) -> str:
|
|||
:param transport_type: transport type to generate transport file
|
||||
:return:
|
||||
"""
|
||||
return f"n{node_id}trans{transport_type}.xml"
|
||||
return f"n{node_id}trans{transport_type.value}.xml"
|
||||
|
||||
|
||||
def _basename(emane_model: "EmaneModel", interface: CoreInterface = None) -> str:
|
||||
|
@ -521,7 +522,7 @@ def nem_file_name(emane_model: "EmaneModel", interface: CoreInterface = None) ->
|
|||
"""
|
||||
basename = _basename(emane_model, interface)
|
||||
append = ""
|
||||
if interface and interface.transport_type == "raw":
|
||||
if interface and interface.transport_type == TransportType.RAW:
|
||||
append = "_raw"
|
||||
return f"{basename}nem{append}.xml"
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ listenaddr = localhost
|
|||
port = 4038
|
||||
grpcaddress = localhost
|
||||
grpcport = 50051
|
||||
numthreads = 1
|
||||
quagga_bin_search = "/usr/local/bin /usr/bin /usr/lib/quagga"
|
||||
quagga_sbin_search = "/usr/local/sbin /usr/sbin /usr/lib/quagga"
|
||||
frr_bin_search = "/usr/local/bin /usr/bin /usr/lib/frr"
|
||||
|
|
|
@ -2,7 +2,9 @@ import logging
|
|||
|
||||
from core.emulator.coreemu import CoreEmu
|
||||
from core.emulator.emudata import IpPrefixes, NodeOptions
|
||||
from core.emulator.enumerations import EventTypes, NodeTypes
|
||||
from core.emulator.enumerations import EventTypes
|
||||
from core.nodes.base import CoreNode
|
||||
from core.nodes.network import SwitchNode
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
@ -13,16 +15,16 @@ if __name__ == "__main__":
|
|||
coreemu = CoreEmu()
|
||||
session = coreemu.create_session()
|
||||
session.set_state(EventTypes.CONFIGURATION_STATE)
|
||||
switch = session.add_node(_type=NodeTypes.SWITCH)
|
||||
switch = session.add_node(SwitchNode)
|
||||
|
||||
# node one
|
||||
options.config_services = ["DefaultRoute", "IPForward"]
|
||||
node_one = session.add_node(options=options)
|
||||
node_one = session.add_node(CoreNode, options=options)
|
||||
interface = prefixes.create_interface(node_one)
|
||||
session.add_link(node_one.id, switch.id, interface_one=interface)
|
||||
|
||||
# node two
|
||||
node_two = session.add_node(options=options)
|
||||
node_two = session.add_node(CoreNode, options=options)
|
||||
interface = prefixes.create_interface(node_two)
|
||||
session.add_link(node_two.id, switch.id, interface_one=interface)
|
||||
|
||||
|
|
|
@ -2,7 +2,9 @@ import logging
|
|||
|
||||
from core.emulator.coreemu import CoreEmu
|
||||
from core.emulator.emudata import IpPrefixes, NodeOptions
|
||||
from core.emulator.enumerations import EventTypes, NodeTypes
|
||||
from core.emulator.enumerations import EventTypes
|
||||
from core.nodes.base import CoreNode
|
||||
from core.nodes.docker import DockerNode
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
@ -15,11 +17,11 @@ if __name__ == "__main__":
|
|||
options = NodeOptions(model=None, image="ubuntu")
|
||||
|
||||
# create node one
|
||||
node_one = session.add_node(_type=NodeTypes.DOCKER, options=options)
|
||||
node_one = session.add_node(DockerNode, options=options)
|
||||
interface_one = prefixes.create_interface(node_one)
|
||||
|
||||
# create node two
|
||||
node_two = session.add_node()
|
||||
node_two = session.add_node(CoreNode)
|
||||
interface_two = prefixes.create_interface(node_two)
|
||||
|
||||
# add link
|
||||
|
|
|
@ -2,7 +2,8 @@ import logging
|
|||
|
||||
from core.emulator.coreemu import CoreEmu
|
||||
from core.emulator.emudata import IpPrefixes, NodeOptions
|
||||
from core.emulator.enumerations import EventTypes, NodeTypes
|
||||
from core.emulator.enumerations import EventTypes
|
||||
from core.nodes.docker import DockerNode
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
@ -17,11 +18,11 @@ if __name__ == "__main__":
|
|||
options = NodeOptions(model=None, image="ubuntu")
|
||||
|
||||
# create node one
|
||||
node_one = session.add_node(_type=NodeTypes.DOCKER, options=options)
|
||||
node_one = session.add_node(DockerNode, options=options)
|
||||
interface_one = prefixes.create_interface(node_one)
|
||||
|
||||
# create node two
|
||||
node_two = session.add_node(_type=NodeTypes.DOCKER, options=options)
|
||||
node_two = session.add_node(DockerNode, options=options)
|
||||
interface_two = prefixes.create_interface(node_two)
|
||||
|
||||
# add link
|
||||
|
|
|
@ -2,7 +2,10 @@ import logging
|
|||
|
||||
from core.emulator.coreemu import CoreEmu
|
||||
from core.emulator.emudata import IpPrefixes, NodeOptions
|
||||
from core.emulator.enumerations import EventTypes, NodeTypes
|
||||
from core.emulator.enumerations import EventTypes
|
||||
from core.nodes.base import CoreNode
|
||||
from core.nodes.docker import DockerNode
|
||||
from core.nodes.network import SwitchNode
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
@ -16,18 +19,18 @@ if __name__ == "__main__":
|
|||
options = NodeOptions(model=None, image="ubuntu")
|
||||
|
||||
# create switch
|
||||
switch = session.add_node(_type=NodeTypes.SWITCH)
|
||||
switch = session.add_node(SwitchNode)
|
||||
|
||||
# node one
|
||||
node_one = session.add_node(_type=NodeTypes.DOCKER, options=options)
|
||||
node_one = session.add_node(DockerNode, options=options)
|
||||
interface_one = prefixes.create_interface(node_one)
|
||||
|
||||
# node two
|
||||
node_two = session.add_node(_type=NodeTypes.DOCKER, options=options)
|
||||
node_two = session.add_node(DockerNode, options=options)
|
||||
interface_two = prefixes.create_interface(node_two)
|
||||
|
||||
# node three
|
||||
node_three = session.add_node()
|
||||
node_three = session.add_node(CoreNode)
|
||||
interface_three = prefixes.create_interface(node_three)
|
||||
|
||||
# add links
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import argparse
|
||||
import logging
|
||||
|
||||
from core.api.grpc import client, core_pb2
|
||||
from core.api.grpc import client
|
||||
from core.api.grpc.core_pb2 import Node, NodeType, Position, SessionState
|
||||
|
||||
|
||||
def log_event(event):
|
||||
|
@ -26,13 +27,11 @@ def main(args):
|
|||
core.events(session_id, log_event)
|
||||
|
||||
# change session state
|
||||
response = core.set_session_state(
|
||||
session_id, core_pb2.SessionState.CONFIGURATION
|
||||
)
|
||||
response = core.set_session_state(session_id, SessionState.CONFIGURATION)
|
||||
logging.info("set session state: %s", response)
|
||||
|
||||
# create switch node
|
||||
switch = core_pb2.Node(type=core_pb2.NodeType.SWITCH)
|
||||
switch = Node(type=NodeType.SWITCH)
|
||||
response = core.add_node(session_id, switch)
|
||||
logging.info("created switch: %s", response)
|
||||
switch_id = response.node_id
|
||||
|
@ -41,8 +40,8 @@ def main(args):
|
|||
interface_helper = client.InterfaceHelper(ip4_prefix="10.83.0.0/16")
|
||||
|
||||
# create node one
|
||||
position = core_pb2.Position(x=100, y=50)
|
||||
node = core_pb2.Node(position=position)
|
||||
position = Position(x=100, y=50)
|
||||
node = Node(position=position)
|
||||
response = core.add_node(session_id, node)
|
||||
logging.info("created node one: %s", response)
|
||||
node_one_id = response.node_id
|
||||
|
@ -53,8 +52,8 @@ def main(args):
|
|||
logging.info("created link from node one to switch: %s", response)
|
||||
|
||||
# create node two
|
||||
position = core_pb2.Position(x=200, y=50)
|
||||
node = core_pb2.Node(position=position, server=server_name)
|
||||
position = Position(x=200, y=50)
|
||||
node = Node(position=position, server=server_name)
|
||||
response = core.add_node(session_id, node)
|
||||
logging.info("created node two: %s", response)
|
||||
node_two_id = response.node_id
|
||||
|
@ -65,9 +64,7 @@ def main(args):
|
|||
logging.info("created link from node two to switch: %s", response)
|
||||
|
||||
# change session state
|
||||
response = core.set_session_state(
|
||||
session_id, core_pb2.SessionState.INSTANTIATION
|
||||
)
|
||||
response = core.set_session_state(session_id, SessionState.INSTANTIATION)
|
||||
logging.info("set session state: %s", response)
|
||||
|
||||
|
||||
|
|
74
daemon/examples/grpc/emane80211.py
Normal file
74
daemon/examples/grpc/emane80211.py
Normal file
|
@ -0,0 +1,74 @@
|
|||
"""
|
||||
Example using gRPC API to create a simple EMANE 80211 network.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from core.api.grpc import client
|
||||
from core.api.grpc.core_pb2 import Node, NodeType, Position, SessionState
|
||||
from core.emane.ieee80211abg import EmaneIeee80211abgModel
|
||||
|
||||
|
||||
def log_event(event):
|
||||
logging.info("event: %s", event)
|
||||
|
||||
|
||||
def main():
|
||||
# helper to create interface addresses
|
||||
interface_helper = client.InterfaceHelper(ip4_prefix="10.83.0.0/24")
|
||||
|
||||
# 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)
|
||||
|
||||
# handle events session may broadcast
|
||||
session_id = response.session_id
|
||||
core.events(session_id, log_event)
|
||||
|
||||
# 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 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
|
||||
|
||||
# an emane model must be configured for use, by the emane node
|
||||
core.set_emane_model_config(session_id, emane_id, EmaneIeee80211abgModel.name)
|
||||
|
||||
# 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
|
||||
interface_one = interface_helper.create_interface(node1_id, 0)
|
||||
response = core.add_link(session_id, node1_id, emane_id, interface_one)
|
||||
logging.info("created link: %s", response)
|
||||
interface_one = interface_helper.create_interface(node2_id, 0)
|
||||
response = core.add_link(session_id, node2_id, emane_id, interface_one)
|
||||
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()
|
|
@ -1,54 +0,0 @@
|
|||
import logging
|
||||
|
||||
from core.api.grpc import client, core_pb2
|
||||
|
||||
|
||||
def log_event(event):
|
||||
logging.info("event: %s", event)
|
||||
|
||||
|
||||
def main():
|
||||
core = client.CoreGrpcClient()
|
||||
|
||||
with core.context_connect():
|
||||
# create session
|
||||
response = core.create_session()
|
||||
session_id = response.session_id
|
||||
logging.info("created session: %s", response)
|
||||
|
||||
# create nodes for session
|
||||
nodes = []
|
||||
position = core_pb2.Position(x=50, y=100)
|
||||
switch = core_pb2.Node(id=1, type=core_pb2.NodeType.SWITCH, position=position)
|
||||
nodes.append(switch)
|
||||
for i in range(2, 50):
|
||||
position = core_pb2.Position(x=50 + 50 * i, y=50)
|
||||
node = core_pb2.Node(id=i, position=position, model="PC")
|
||||
nodes.append(node)
|
||||
|
||||
# create links
|
||||
interface_helper = client.InterfaceHelper(ip4_prefix="10.83.0.0/16")
|
||||
links = []
|
||||
for node in nodes:
|
||||
interface_one = interface_helper.create_interface(node.id, 0)
|
||||
link = core_pb2.Link(
|
||||
type=core_pb2.LinkType.WIRED,
|
||||
node_one_id=node.id,
|
||||
node_two_id=switch.id,
|
||||
interface_one=interface_one,
|
||||
)
|
||||
links.append(link)
|
||||
|
||||
# start session
|
||||
response = core.start_session(session_id, nodes, links)
|
||||
logging.info("started session: %s", response)
|
||||
|
||||
input("press enter to shutdown session")
|
||||
|
||||
response = core.stop_session(session_id)
|
||||
logging.info("stop sessionL %s", response)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
main()
|
|
@ -1,6 +1,11 @@
|
|||
"""
|
||||
Example using gRPC API to create a simple switch network.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from core.api.grpc import client, core_pb2
|
||||
from core.api.grpc import client
|
||||
from core.api.grpc.core_pb2 import Node, NodeType, Position, SessionState
|
||||
|
||||
|
||||
def log_event(event):
|
||||
|
@ -8,8 +13,11 @@ def log_event(event):
|
|||
|
||||
|
||||
def main():
|
||||
core = client.CoreGrpcClient()
|
||||
# helper to create interface addresses
|
||||
interface_helper = client.InterfaceHelper(ip4_prefix="10.83.0.0/24")
|
||||
|
||||
# create grpc client and start connection context, which auto closes connection
|
||||
core = client.CoreGrpcClient()
|
||||
with core.context_connect():
|
||||
# create session
|
||||
response = core.create_session()
|
||||
|
@ -19,38 +27,41 @@ def main():
|
|||
session_id = response.session_id
|
||||
core.events(session_id, log_event)
|
||||
|
||||
# change session state
|
||||
response = core.set_session_state(
|
||||
session_id, core_pb2.SessionState.CONFIGURATION
|
||||
)
|
||||
# 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 switch node
|
||||
switch = core_pb2.Node(type=core_pb2.NodeType.SWITCH)
|
||||
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
|
||||
|
||||
# helper to create interfaces
|
||||
interface_helper = client.InterfaceHelper(ip4_prefix="10.83.0.0/16")
|
||||
# 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
|
||||
|
||||
for i in range(2):
|
||||
# create node
|
||||
position = core_pb2.Position(x=50 + 50 * i, y=50)
|
||||
node = core_pb2.Node(position=position)
|
||||
response = core.add_node(session_id, node)
|
||||
logging.info("created node: %s", response)
|
||||
node_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
|
||||
|
||||
# create link
|
||||
interface_one = interface_helper.create_interface(node_id, 0)
|
||||
response = core.add_link(session_id, node_id, switch_id, interface_one)
|
||||
logging.info("created link: %s", response)
|
||||
# links nodes to switch
|
||||
interface_one = interface_helper.create_interface(node1_id, 0)
|
||||
response = core.add_link(session_id, node1_id, switch_id, interface_one)
|
||||
logging.info("created link: %s", response)
|
||||
interface_one = interface_helper.create_interface(node2_id, 0)
|
||||
response = core.add_link(session_id, node2_id, switch_id, interface_one)
|
||||
logging.info("created link: %s", response)
|
||||
|
||||
# change session state
|
||||
response = core.set_session_state(
|
||||
session_id, core_pb2.SessionState.INSTANTIATION
|
||||
)
|
||||
response = core.set_session_state(session_id, SessionState.INSTANTIATION)
|
||||
logging.info("set session state: %s", response)
|
||||
|
||||
|
||||
|
|
82
daemon/examples/grpc/wlan.py
Normal file
82
daemon/examples/grpc/wlan.py
Normal file
|
@ -0,0 +1,82 @@
|
|||
"""
|
||||
Example using gRPC API to create a simple wlan network.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from core.api.grpc import client
|
||||
from core.api.grpc.core_pb2 import Node, NodeType, Position, SessionState
|
||||
|
||||
|
||||
def log_event(event):
|
||||
logging.info("event: %s", event)
|
||||
|
||||
|
||||
def main():
|
||||
# helper to create interface addresses
|
||||
interface_helper = client.InterfaceHelper(ip4_prefix="10.83.0.0/24")
|
||||
|
||||
# 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)
|
||||
|
||||
# handle events session may broadcast
|
||||
session_id = response.session_id
|
||||
core.events(session_id, log_event)
|
||||
|
||||
# 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 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
|
||||
|
||||
# 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)
|
||||
|
||||
# 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
|
||||
interface_one = interface_helper.create_interface(node1_id, 0)
|
||||
response = core.add_link(session_id, node1_id, wlan_id, interface_one)
|
||||
logging.info("created link: %s", response)
|
||||
interface_one = interface_helper.create_interface(node2_id, 0)
|
||||
response = core.add_link(session_id, node2_id, wlan_id, interface_one)
|
||||
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()
|
|
@ -2,7 +2,9 @@ import logging
|
|||
|
||||
from core.emulator.coreemu import CoreEmu
|
||||
from core.emulator.emudata import IpPrefixes, NodeOptions
|
||||
from core.emulator.enumerations import EventTypes, NodeTypes
|
||||
from core.emulator.enumerations import EventTypes
|
||||
from core.nodes.base import CoreNode
|
||||
from core.nodes.lxd import LxcNode
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
@ -15,11 +17,11 @@ if __name__ == "__main__":
|
|||
options = NodeOptions(image="ubuntu")
|
||||
|
||||
# create node one
|
||||
node_one = session.add_node(_type=NodeTypes.LXC, options=options)
|
||||
node_one = session.add_node(LxcNode, options=options)
|
||||
interface_one = prefixes.create_interface(node_one)
|
||||
|
||||
# create node two
|
||||
node_two = session.add_node()
|
||||
node_two = session.add_node(CoreNode)
|
||||
interface_two = prefixes.create_interface(node_two)
|
||||
|
||||
# add link
|
||||
|
|
|
@ -2,7 +2,8 @@ import logging
|
|||
|
||||
from core.emulator.coreemu import CoreEmu
|
||||
from core.emulator.emudata import IpPrefixes, NodeOptions
|
||||
from core.emulator.enumerations import EventTypes, NodeTypes
|
||||
from core.emulator.enumerations import EventTypes
|
||||
from core.nodes.lxd import LxcNode
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
@ -17,11 +18,11 @@ if __name__ == "__main__":
|
|||
options = NodeOptions(image="ubuntu:18.04")
|
||||
|
||||
# create node one
|
||||
node_one = session.add_node(_type=NodeTypes.LXC, options=options)
|
||||
node_one = session.add_node(LxcNode, options=options)
|
||||
interface_one = prefixes.create_interface(node_one)
|
||||
|
||||
# create node two
|
||||
node_two = session.add_node(_type=NodeTypes.LXC, options=options)
|
||||
node_two = session.add_node(LxcNode, options=options)
|
||||
interface_two = prefixes.create_interface(node_two)
|
||||
|
||||
# add link
|
||||
|
|
|
@ -2,7 +2,10 @@ import logging
|
|||
|
||||
from core.emulator.coreemu import CoreEmu
|
||||
from core.emulator.emudata import IpPrefixes, NodeOptions
|
||||
from core.emulator.enumerations import EventTypes, NodeTypes
|
||||
from core.emulator.enumerations import EventTypes
|
||||
from core.nodes.base import CoreNode
|
||||
from core.nodes.lxd import LxcNode
|
||||
from core.nodes.network import SwitchNode
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
@ -16,18 +19,18 @@ if __name__ == "__main__":
|
|||
options = NodeOptions(image="ubuntu")
|
||||
|
||||
# create switch
|
||||
switch = session.add_node(_type=NodeTypes.SWITCH)
|
||||
switch = session.add_node(SwitchNode)
|
||||
|
||||
# node one
|
||||
node_one = session.add_node(_type=NodeTypes.LXC, options=options)
|
||||
node_one = session.add_node(LxcNode, options=options)
|
||||
interface_one = prefixes.create_interface(node_one)
|
||||
|
||||
# node two
|
||||
node_two = session.add_node(_type=NodeTypes.LXC, options=options)
|
||||
node_two = session.add_node(LxcNode, options=options)
|
||||
interface_two = prefixes.create_interface(node_two)
|
||||
|
||||
# node three
|
||||
node_three = session.add_node()
|
||||
node_three = session.add_node(CoreNode)
|
||||
interface_three = prefixes.create_interface(node_three)
|
||||
|
||||
# add links
|
||||
|
|
|
@ -7,9 +7,11 @@ import argparse
|
|||
import logging
|
||||
|
||||
from core.emane.ieee80211abg import EmaneIeee80211abgModel
|
||||
from core.emane.nodes import EmaneNet
|
||||
from core.emulator.coreemu import CoreEmu
|
||||
from core.emulator.emudata import IpPrefixes, NodeOptions
|
||||
from core.emulator.enumerations import EventTypes, NodeTypes
|
||||
from core.emulator.enumerations import EventTypes
|
||||
from core.nodes.base import CoreNode
|
||||
|
||||
|
||||
def parse(name):
|
||||
|
@ -50,11 +52,11 @@ def main(args):
|
|||
# create local node, switch, and remote nodes
|
||||
options = NodeOptions(model="mdr")
|
||||
options.set_position(0, 0)
|
||||
node_one = session.add_node(options=options)
|
||||
emane_net = session.add_node(_type=NodeTypes.EMANE)
|
||||
node_one = session.add_node(CoreNode, options=options)
|
||||
emane_net = session.add_node(EmaneNet)
|
||||
session.emane.set_model(emane_net, EmaneIeee80211abgModel)
|
||||
options.server = server_name
|
||||
node_two = session.add_node(options=options)
|
||||
node_two = session.add_node(CoreNode, options=options)
|
||||
|
||||
# create node interfaces and link
|
||||
interface_one = prefixes.create_interface(node_one)
|
||||
|
|
|
@ -8,7 +8,8 @@ import logging
|
|||
|
||||
from core.emulator.coreemu import CoreEmu
|
||||
from core.emulator.emudata import IpPrefixes, NodeOptions
|
||||
from core.emulator.enumerations import EventTypes, NodeTypes
|
||||
from core.emulator.enumerations import EventTypes
|
||||
from core.nodes.lxd import LxcNode
|
||||
|
||||
|
||||
def parse(name):
|
||||
|
@ -42,9 +43,9 @@ def main(args):
|
|||
|
||||
# create local node, switch, and remote nodes
|
||||
options = NodeOptions(image="ubuntu:18.04")
|
||||
node_one = session.add_node(_type=NodeTypes.LXC, options=options)
|
||||
node_one = session.add_node(LxcNode, options=options)
|
||||
options.server = server_name
|
||||
node_two = session.add_node(_type=NodeTypes.LXC, options=options)
|
||||
node_two = session.add_node(LxcNode, options=options)
|
||||
|
||||
# create node interfaces and link
|
||||
interface_one = prefixes.create_interface(node_one)
|
||||
|
|
|
@ -9,6 +9,7 @@ import logging
|
|||
from core.emulator.coreemu import CoreEmu
|
||||
from core.emulator.emudata import IpPrefixes, NodeOptions
|
||||
from core.emulator.enumerations import EventTypes
|
||||
from core.nodes.base import CoreNode
|
||||
|
||||
|
||||
def parse(name):
|
||||
|
@ -42,9 +43,9 @@ def main(args):
|
|||
|
||||
# create local node, switch, and remote nodes
|
||||
options = NodeOptions()
|
||||
node_one = session.add_node(options=options)
|
||||
node_one = session.add_node(CoreNode, options=options)
|
||||
options.server = server_name
|
||||
node_two = session.add_node(options=options)
|
||||
node_two = session.add_node(CoreNode, options=options)
|
||||
|
||||
# create node interfaces and link
|
||||
interface_one = prefixes.create_interface(node_one)
|
||||
|
|
|
@ -8,7 +8,9 @@ import logging
|
|||
|
||||
from core.emulator.coreemu import CoreEmu
|
||||
from core.emulator.emudata import IpPrefixes, NodeOptions
|
||||
from core.emulator.enumerations import EventTypes, NodeTypes
|
||||
from core.emulator.enumerations import EventTypes
|
||||
from core.nodes.base import CoreNode
|
||||
from core.nodes.network import SwitchNode
|
||||
|
||||
|
||||
def parse(name):
|
||||
|
@ -43,11 +45,11 @@ def main(args):
|
|||
session.set_state(EventTypes.CONFIGURATION_STATE)
|
||||
|
||||
# create local node, switch, and remote nodes
|
||||
node_one = session.add_node()
|
||||
switch = session.add_node(_type=NodeTypes.SWITCH)
|
||||
node_one = session.add_node(CoreNode)
|
||||
switch = session.add_node(SwitchNode)
|
||||
options = NodeOptions()
|
||||
options.server = server_name
|
||||
node_two = session.add_node(options=options)
|
||||
node_two = session.add_node(CoreNode, options=options)
|
||||
|
||||
# create node interfaces and link
|
||||
interface_one = prefixes.create_interface(node_one)
|
||||
|
|
|
@ -8,9 +8,11 @@ import logging
|
|||
import time
|
||||
|
||||
from core.emane.ieee80211abg import EmaneIeee80211abgModel
|
||||
from core.emane.nodes import EmaneNet
|
||||
from core.emulator.coreemu import CoreEmu
|
||||
from core.emulator.emudata import IpPrefixes, NodeOptions
|
||||
from core.emulator.enumerations import EventTypes, NodeTypes
|
||||
from core.emulator.enumerations import EventTypes
|
||||
from core.nodes.base import CoreNode
|
||||
|
||||
NODES = 2
|
||||
EMANE_DELAY = 10
|
||||
|
@ -32,13 +34,13 @@ def main():
|
|||
session.set_location(47.57917, -122.13232, 2.00000, 1.0)
|
||||
options = NodeOptions()
|
||||
options.set_position(80, 50)
|
||||
emane_network = session.add_node(_type=NodeTypes.EMANE, options=options, _id=100)
|
||||
emane_network = session.add_node(EmaneNet, options=options, _id=100)
|
||||
session.emane.set_model(emane_network, EmaneIeee80211abgModel)
|
||||
|
||||
# create nodes
|
||||
options = NodeOptions(model="mdr")
|
||||
for i in range(NODES):
|
||||
node = session.add_node(options=options)
|
||||
node = session.add_node(CoreNode, options=options)
|
||||
node.setposition(x=150 * (i + 1), y=150)
|
||||
interface = prefixes.create_interface(node)
|
||||
session.add_link(node.id, emane_network.id, interface_one=interface)
|
||||
|
@ -51,8 +53,8 @@ def main():
|
|||
time.sleep(EMANE_DELAY)
|
||||
|
||||
# get nodes to run example
|
||||
first_node = session.get_node(1)
|
||||
last_node = session.get_node(NODES)
|
||||
first_node = session.get_node(1, CoreNode)
|
||||
last_node = session.get_node(NODES, CoreNode)
|
||||
address = prefixes.ip4_address(first_node)
|
||||
logging.info("node %s pinging %s", last_node.name, address)
|
||||
output = last_node.cmd(f"ping -c 3 {address}")
|
||||
|
|
|
@ -7,7 +7,9 @@ import logging
|
|||
|
||||
from core.emulator.coreemu import CoreEmu
|
||||
from core.emulator.emudata import IpPrefixes
|
||||
from core.emulator.enumerations import EventTypes, NodeTypes
|
||||
from core.emulator.enumerations import EventTypes
|
||||
from core.nodes.base import CoreNode
|
||||
from core.nodes.network import SwitchNode
|
||||
|
||||
NODES = 2
|
||||
|
||||
|
@ -24,11 +26,11 @@ def main():
|
|||
session.set_state(EventTypes.CONFIGURATION_STATE)
|
||||
|
||||
# create switch network node
|
||||
switch = session.add_node(_type=NodeTypes.SWITCH, _id=100)
|
||||
switch = session.add_node(SwitchNode, _id=100)
|
||||
|
||||
# create nodes
|
||||
for _ in range(NODES):
|
||||
node = session.add_node()
|
||||
node = session.add_node(CoreNode)
|
||||
interface = prefixes.create_interface(node)
|
||||
session.add_link(node.id, switch.id, interface_one=interface)
|
||||
|
||||
|
@ -36,8 +38,8 @@ def main():
|
|||
session.instantiate()
|
||||
|
||||
# get nodes to run example
|
||||
first_node = session.get_node(1)
|
||||
last_node = session.get_node(NODES)
|
||||
first_node = session.get_node(1, CoreNode)
|
||||
last_node = session.get_node(NODES, CoreNode)
|
||||
address = prefixes.ip4_address(first_node)
|
||||
logging.info("node %s pinging %s", last_node.name, address)
|
||||
output = last_node.cmd(f"ping -c 3 {address}")
|
||||
|
|
|
@ -7,8 +7,11 @@ same CoreEmu instance the GUI is using.
|
|||
|
||||
import logging
|
||||
|
||||
from core.emulator.coreemu import CoreEmu
|
||||
from core.emulator.emudata import IpPrefixes
|
||||
from core.emulator.enumerations import EventTypes, NodeTypes
|
||||
from core.emulator.enumerations import EventTypes
|
||||
from core.nodes.base import CoreNode
|
||||
from core.nodes.network import SwitchNode
|
||||
|
||||
NODES = 2
|
||||
|
||||
|
@ -18,18 +21,18 @@ def main():
|
|||
prefixes = IpPrefixes("10.83.0.0/16")
|
||||
|
||||
# create emulator instance for creating sessions and utility methods
|
||||
coreemu = globals()["coreemu"]
|
||||
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(_type=NodeTypes.SWITCH)
|
||||
switch = session.add_node(SwitchNode)
|
||||
|
||||
# create nodes
|
||||
for _ in range(NODES):
|
||||
node = session.add_node()
|
||||
node = session.add_node(CoreNode)
|
||||
interface = prefixes.create_interface(node)
|
||||
session.add_link(node.id, switch.id, interface_one=interface)
|
||||
|
||||
|
|
|
@ -7,8 +7,10 @@ import logging
|
|||
|
||||
from core.emulator.coreemu import CoreEmu
|
||||
from core.emulator.emudata import IpPrefixes, NodeOptions
|
||||
from core.emulator.enumerations import EventTypes, NodeTypes
|
||||
from core.emulator.enumerations import EventTypes
|
||||
from core.location.mobility import BasicRangeModel
|
||||
from core.nodes.base import CoreNode
|
||||
from core.nodes.network import WlanNode
|
||||
|
||||
NODES = 2
|
||||
|
||||
|
@ -25,14 +27,14 @@ def main():
|
|||
session.set_state(EventTypes.CONFIGURATION_STATE)
|
||||
|
||||
# create wlan network node
|
||||
wlan = session.add_node(_type=NodeTypes.WIRELESS_LAN, _id=100)
|
||||
wlan = session.add_node(WlanNode, _id=100)
|
||||
session.mobility.set_model(wlan, BasicRangeModel)
|
||||
|
||||
# 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(options=options)
|
||||
node = session.add_node(CoreNode, options=options)
|
||||
interface = prefixes.create_interface(node)
|
||||
session.add_link(node.id, wlan.id, interface_one=interface)
|
||||
|
||||
|
@ -40,8 +42,8 @@ def main():
|
|||
session.instantiate()
|
||||
|
||||
# get nodes for example run
|
||||
first_node = session.get_node(1)
|
||||
last_node = session.get_node(NODES)
|
||||
first_node = session.get_node(1, CoreNode)
|
||||
last_node = session.get_node(NODES, CoreNode)
|
||||
address = prefixes.ip4_address(first_node)
|
||||
logging.info("node %s pinging %s", last_node.name, address)
|
||||
output = last_node.cmd(f"ping -c 3 {address}")
|
||||
|
|
|
@ -61,6 +61,8 @@ service CoreApi {
|
|||
}
|
||||
rpc GetNodeTerminal (GetNodeTerminalRequest) returns (GetNodeTerminalResponse) {
|
||||
}
|
||||
rpc MoveNodes (stream MoveNodesRequest) returns (MoveNodesResponse) {
|
||||
}
|
||||
|
||||
// link rpc
|
||||
rpc GetNodeLinks (GetNodeLinksRequest) returns (GetNodeLinksResponse) {
|
||||
|
@ -129,6 +131,8 @@ service CoreApi {
|
|||
}
|
||||
rpc SetWlanConfig (wlan.SetWlanConfigRequest) returns (wlan.SetWlanConfigResponse) {
|
||||
}
|
||||
rpc WlanLink (wlan.WlanLinkRequest) returns (wlan.WlanLinkResponse) {
|
||||
}
|
||||
|
||||
// emane rpc
|
||||
rpc GetEmaneConfig (emane.GetEmaneConfigRequest) returns (emane.GetEmaneConfigResponse) {
|
||||
|
@ -145,6 +149,8 @@ service CoreApi {
|
|||
}
|
||||
rpc GetEmaneEventChannel (emane.GetEmaneEventChannelRequest) returns (emane.GetEmaneEventChannelResponse) {
|
||||
}
|
||||
rpc EmanePathlosses (stream emane.EmanePathlossesRequest) returns (emane.EmanePathlossesResponse) {
|
||||
}
|
||||
|
||||
// xml rpc
|
||||
rpc SaveXml (SaveXmlRequest) returns (SaveXmlResponse) {
|
||||
|
@ -444,14 +450,30 @@ message GetNodeTerminalResponse {
|
|||
string terminal = 1;
|
||||
}
|
||||
|
||||
message MoveNodesRequest {
|
||||
int32 session_id = 1;
|
||||
int32 node_id = 2;
|
||||
string source = 3;
|
||||
oneof move_type {
|
||||
Position position = 4;
|
||||
Geo geo = 5;
|
||||
}
|
||||
}
|
||||
|
||||
message MoveNodesResponse {
|
||||
}
|
||||
|
||||
message NodeCommandRequest {
|
||||
int32 session_id = 1;
|
||||
int32 node_id = 2;
|
||||
string command = 3;
|
||||
bool wait = 4;
|
||||
bool shell = 5;
|
||||
}
|
||||
|
||||
message NodeCommandResponse {
|
||||
string output = 1;
|
||||
int32 return_code = 2;
|
||||
}
|
||||
|
||||
message GetNodeLinksRequest {
|
||||
|
|
|
@ -90,3 +90,16 @@ message EmaneModelConfig {
|
|||
string model = 3;
|
||||
map<string, string> config = 4;
|
||||
}
|
||||
|
||||
message EmanePathlossesRequest {
|
||||
int32 session_id = 1;
|
||||
int32 node_one = 2;
|
||||
float rx_one = 3;
|
||||
int32 interface_one_id = 4;
|
||||
int32 node_two = 5;
|
||||
float rx_two = 6;
|
||||
int32 interface_two_id = 7;
|
||||
}
|
||||
|
||||
message EmanePathlossesResponse {
|
||||
}
|
||||
|
|
|
@ -34,3 +34,15 @@ message SetWlanConfigRequest {
|
|||
message SetWlanConfigResponse {
|
||||
bool result = 1;
|
||||
}
|
||||
|
||||
message WlanLinkRequest {
|
||||
int32 session_id = 1;
|
||||
int32 wlan = 2;
|
||||
int32 node_one = 3;
|
||||
int32 node_two = 4;
|
||||
bool linked = 5;
|
||||
}
|
||||
|
||||
message WlanLinkResponse {
|
||||
bool result = 1;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
core-daemon: the CORE daemon is a server process that receives CORE API
|
||||
messages and instantiates emulated nodes and networks within the kernel. Various
|
||||
|
@ -93,12 +93,10 @@ def get_merged_config(filename):
|
|||
# these are the defaults used in the config file
|
||||
default_log = os.path.join(constants.CORE_CONF_DIR, "logging.conf")
|
||||
default_grpc_port = "50051"
|
||||
default_threads = "1"
|
||||
default_address = "localhost"
|
||||
defaults = {
|
||||
"port": str(CORE_API_PORT),
|
||||
"listenaddr": default_address,
|
||||
"numthreads": default_threads,
|
||||
"grpcport": default_grpc_port,
|
||||
"grpcaddress": default_address,
|
||||
"logfile": default_log
|
||||
|
@ -110,8 +108,6 @@ def get_merged_config(filename):
|
|||
help=f"read config from specified file; default = {filename}")
|
||||
parser.add_argument("-p", "--port", dest="port", type=int,
|
||||
help=f"port number to listen on; default = {CORE_API_PORT}")
|
||||
parser.add_argument("-n", "--numthreads", dest="numthreads", type=int,
|
||||
help=f"number of server threads; default = {default_threads}")
|
||||
parser.add_argument("--ovs", action="store_true", help="enable experimental ovs mode, default is false")
|
||||
parser.add_argument("--grpc-port", dest="grpcport",
|
||||
help=f"grpc port to listen on; default {default_grpc_port}")
|
||||
|
@ -148,14 +144,9 @@ def main():
|
|||
|
||||
:return: nothing
|
||||
"""
|
||||
# get a configuration merged from config file and command-line arguments
|
||||
cfg = get_merged_config(f"{CORE_CONF_DIR}/core.conf")
|
||||
|
||||
# load logging configuration
|
||||
load_logging_config(cfg["logfile"])
|
||||
|
||||
banner()
|
||||
|
||||
try:
|
||||
cored(cfg)
|
||||
except KeyboardInterrupt:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import re
|
||||
import sys
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
core-manage: Helper tool to add, remove, or check for services, models, and
|
||||
node types in a CORE installation.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import logging
|
||||
from logging.handlers import TimedRotatingFileHandler
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import enum
|
||||
import select
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import re
|
||||
from io import TextIOWrapper
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
coresendmsg: utility for generating CORE messages
|
||||
"""
|
||||
|
@ -19,7 +19,7 @@ def print_available_tlvs(t, tlv_class):
|
|||
"""
|
||||
print(f"TLVs available for {t} message:")
|
||||
for tlv in sorted([tlv for tlv in tlv_class.tlv_type_map], key=lambda x: x.name):
|
||||
print(f"{tlv.value}:{tlv.name}")
|
||||
print(tlv.name.lower())
|
||||
|
||||
|
||||
def print_examples(name):
|
||||
|
@ -27,27 +27,26 @@ def print_examples(name):
|
|||
Print example usage of this script.
|
||||
"""
|
||||
examples = [
|
||||
("link n1number=2 n2number=3 delay=15000",
|
||||
"set a 15ms delay on the link between n2 and n3"),
|
||||
("link n1number=2 n2number=3 guiattr=\"color=blue\"",
|
||||
"change the color of the link between n2 and n3"),
|
||||
("node number=3 xpos=125 ypos=525",
|
||||
("node number=3 x_position=125 y_position=525",
|
||||
"move node number 3 to x,y=(125,525)"),
|
||||
("node number=4 icon=/usr/local/share/core/icons/normal/router_red.gif",
|
||||
"change node number 4\"s icon to red"),
|
||||
("node flags=add number=5 type=0 name=\"n5\" xpos=500 ypos=500",
|
||||
("node flags=add number=5 type=0 name=\"n5\" x_position=500 y_position=500",
|
||||
"add a new router node n5"),
|
||||
("link flags=add n1number=4 n2number=5 if1ip4=\"10.0.3.2\" " \
|
||||
"if1ip4mask=24 if2ip4=\"10.0.3.1\" if2ip4mask=24",
|
||||
("link n1_number=2 n2_number=3 delay=15000",
|
||||
"set a 15ms delay on the link between n2 and n3"),
|
||||
("link n1_number=2 n2_number=3 gui_attributes=\"color=blue\"",
|
||||
"change the color of the link between n2 and n3"),
|
||||
("link flags=add n1_number=4 n2_number=5 interface1_ip4=\"10.0.3.2\" "
|
||||
"interface1_ip4_mask=24 interface2_ip4=\"10.0.3.1\" interface2_ip4_mask=24",
|
||||
"link node n5 with n4 using the given interface addresses"),
|
||||
("exec flags=str,txt node=1 num=1000 cmd=\"uname -a\" -l",
|
||||
("execute flags=string,text node=1 number=1000 command=\"uname -a\" -l",
|
||||
"run a command on node 1 and wait for the result"),
|
||||
("exec node=2 num=1001 cmd=\"killall ospfd\"",
|
||||
("execute node=2 number=1001 command=\"killall ospfd\"",
|
||||
"run a command on node 2 and ignore the result"),
|
||||
("file flags=add node=1 name=\"/var/log/test.log\" data=\"Hello World.\"",
|
||||
("file flags=add node=1 name=\"/var/log/test.log\" data=\"hello world.\"",
|
||||
"write a test.log file on node 1 with the given contents"),
|
||||
("file flags=add node=2 name=\"test.log\" " \
|
||||
"srcname=\"./test.log\"",
|
||||
("file flags=add node=2 name=\"test.log\" source_name=\"./test.log\"",
|
||||
"move a test.log file from host to node 2"),
|
||||
]
|
||||
print(f"Example {name} invocations:")
|
||||
|
@ -152,12 +151,16 @@ def main():
|
|||
"""
|
||||
Parse command-line arguments to build and send a CORE message.
|
||||
"""
|
||||
types = [message_type.name for message_type in MessageTypes]
|
||||
flags = [flag.name for flag in MessageFlags]
|
||||
usagestr = "usage: %prog [-h|-H] [options] [message-type] [flags=flags] "
|
||||
usagestr += "[message-TLVs]\n\n"
|
||||
usagestr += f"Supported message types:\n {types}\n"
|
||||
usagestr += f"Supported message flags (flags=f1,f2,...):\n {flags}"
|
||||
types = [message_type.name.lower() for message_type in MessageTypes]
|
||||
flags = [flag.name.lower() for flag in MessageFlags]
|
||||
types_usage = " ".join(types)
|
||||
flags_usage = " ".join(flags)
|
||||
usagestr = (
|
||||
"usage: %prog [-h|-H] [options] [message-type] [flags=flags] "
|
||||
"[message-TLVs]\n\n"
|
||||
f"Supported message types:\n {types_usage}\n"
|
||||
f"Supported message flags (flags=f1,f2,...):\n {flags_usage}"
|
||||
)
|
||||
parser = optparse.OptionParser(usage=usagestr)
|
||||
default_address = "localhost"
|
||||
default_session = None
|
||||
|
@ -171,7 +174,6 @@ def main():
|
|||
tlvs=False,
|
||||
tcp=default_tcp
|
||||
)
|
||||
|
||||
parser.add_option("-H", dest="examples", action="store_true",
|
||||
help="show example usage help message and exit")
|
||||
parser.add_option("-p", "--port", dest="port", type=int,
|
||||
|
@ -188,9 +190,9 @@ def main():
|
|||
help=f"Use TCP instead of UDP and connect to a session default: {default_tcp}")
|
||||
|
||||
def usage(msg=None, err=0):
|
||||
sys.stdout.write("\n")
|
||||
print()
|
||||
if msg:
|
||||
sys.stdout.write(msg + "\n\n")
|
||||
print(f"{msg}\n")
|
||||
parser.print_help()
|
||||
sys.exit(err)
|
||||
|
||||
|
@ -204,9 +206,10 @@ def main():
|
|||
|
||||
# given a message type t, determine the message and TLV classes
|
||||
t = args.pop(0)
|
||||
t = t.lower()
|
||||
if t not in types:
|
||||
usage(f"Unknown message type requested: {t}")
|
||||
message_type = MessageTypes[t]
|
||||
message_type = MessageTypes[t.upper()]
|
||||
msg_cls = coreapi.CLASS_MAP[message_type.value]
|
||||
tlv_cls = msg_cls.tlv_class
|
||||
|
||||
|
@ -222,26 +225,23 @@ def main():
|
|||
typevalue = a.split("=")
|
||||
if len(typevalue) < 2:
|
||||
usage(f"Use \"type=value\" syntax instead of \"{a}\".")
|
||||
tlv_typestr = typevalue[0]
|
||||
tlv_typestr = typevalue[0].lower()
|
||||
tlv_valstr = "=".join(typevalue[1:])
|
||||
if tlv_typestr == "flags":
|
||||
flagstr = tlv_valstr
|
||||
continue
|
||||
|
||||
tlv_name = tlv_typestr
|
||||
try:
|
||||
tlv_type = tlv_cls.tlv_type_map[tlv_name]
|
||||
tlv_type = tlv_cls.tlv_type_map[tlv_typestr.upper()]
|
||||
tlvdata += tlv_cls.pack_string(tlv_type.value, tlv_valstr)
|
||||
except KeyError:
|
||||
usage(f"Unknown TLV: \"{tlv_name}\"")
|
||||
usage(f"Unknown TLV: \"{tlv_typestr}\"")
|
||||
|
||||
flags = 0
|
||||
for f in flagstr.split(","):
|
||||
if f == "":
|
||||
continue
|
||||
|
||||
try:
|
||||
flag_enum = MessageFlags[f]
|
||||
flag_enum = MessageFlags[f.upper()]
|
||||
n = flag_enum.value
|
||||
flags |= n
|
||||
except KeyError:
|
||||
|
|
|
@ -44,8 +44,8 @@ class PatchManager:
|
|||
|
||||
|
||||
class MockServer:
|
||||
def __init__(self, config, coreemu):
|
||||
self.config = config
|
||||
def __init__(self, coreemu):
|
||||
self.config = {}
|
||||
self.coreemu = coreemu
|
||||
|
||||
|
||||
|
@ -108,7 +108,7 @@ def module_grpc(global_coreemu):
|
|||
def module_coretlv(patcher, global_coreemu, global_session):
|
||||
request_mock = MagicMock()
|
||||
request_mock.fileno = MagicMock(return_value=1)
|
||||
server = MockServer({"numthreads": "1"}, global_coreemu)
|
||||
server = MockServer(global_coreemu)
|
||||
request_handler = CoreHandler(request_mock, "", server)
|
||||
request_handler.session = global_session
|
||||
request_handler.add_session_handlers()
|
||||
|
|
|
@ -2,18 +2,22 @@
|
|||
Unit tests for testing CORE EMANE networks.
|
||||
"""
|
||||
import os
|
||||
from tempfile import TemporaryFile
|
||||
from xml.etree import ElementTree
|
||||
|
||||
import pytest
|
||||
|
||||
from core.emane.bypass import EmaneBypassModel
|
||||
from core.emane.commeffect import EmaneCommEffectModel
|
||||
from core.emane.emanemodel import EmaneModel
|
||||
from core.emane.ieee80211abg import EmaneIeee80211abgModel
|
||||
from core.emane.nodes import EmaneNet
|
||||
from core.emane.rfpipe import EmaneRfPipeModel
|
||||
from core.emane.tdma import EmaneTdmaModel
|
||||
from core.emulator.emudata import NodeOptions
|
||||
from core.emulator.enumerations import NodeTypes
|
||||
from core.emulator.emudata import IpPrefixes, NodeOptions
|
||||
from core.emulator.session import Session
|
||||
from core.errors import CoreCommandError, CoreError
|
||||
from core.nodes.base import CoreNode
|
||||
|
||||
_EMANE_MODELS = [
|
||||
EmaneIeee80211abgModel,
|
||||
|
@ -25,8 +29,10 @@ _EMANE_MODELS = [
|
|||
_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
def ping(from_node, to_node, ip_prefixes, count=3):
|
||||
address = ip_prefixes.ip4_address(to_node)
|
||||
def ping(
|
||||
from_node: CoreNode, to_node: CoreNode, ip_prefixes: IpPrefixes, count: int = 3
|
||||
):
|
||||
address = ip_prefixes.ip4_address(to_node.id)
|
||||
try:
|
||||
from_node.cmd(f"ping -c {count} {address}")
|
||||
status = 0
|
||||
|
@ -37,7 +43,7 @@ def ping(from_node, to_node, ip_prefixes, count=3):
|
|||
|
||||
class TestEmane:
|
||||
@pytest.mark.parametrize("model", _EMANE_MODELS)
|
||||
def test_models(self, session, model, ip_prefixes):
|
||||
def test_models(self, session: Session, model: EmaneModel, ip_prefixes: IpPrefixes):
|
||||
"""
|
||||
Test emane models within a basic network.
|
||||
|
||||
|
@ -50,7 +56,7 @@ class TestEmane:
|
|||
session.set_location(47.57917, -122.13232, 2.00000, 1.0)
|
||||
options = NodeOptions()
|
||||
options.set_position(80, 50)
|
||||
emane_network = session.add_node(_type=NodeTypes.EMANE, options=options)
|
||||
emane_network = session.add_node(EmaneNet, options=options)
|
||||
session.emane.set_model(emane_network, model)
|
||||
|
||||
# configure tdma
|
||||
|
@ -64,9 +70,9 @@ class TestEmane:
|
|||
# create nodes
|
||||
options = NodeOptions(model="mdr")
|
||||
options.set_position(150, 150)
|
||||
node_one = session.add_node(options=options)
|
||||
node_one = session.add_node(CoreNode, options=options)
|
||||
options.set_position(300, 150)
|
||||
node_two = session.add_node(options=options)
|
||||
node_two = session.add_node(CoreNode, options=options)
|
||||
|
||||
for i, node in enumerate([node_one, node_two]):
|
||||
node.setposition(x=150 * (i + 1), y=150)
|
||||
|
@ -80,7 +86,9 @@ class TestEmane:
|
|||
status = ping(node_one, node_two, ip_prefixes, count=5)
|
||||
assert not status
|
||||
|
||||
def test_xml_emane(self, session, tmpdir, ip_prefixes):
|
||||
def test_xml_emane(
|
||||
self, session: Session, tmpdir: TemporaryFile, ip_prefixes: IpPrefixes
|
||||
):
|
||||
"""
|
||||
Test xml client methods for emane.
|
||||
|
||||
|
@ -92,7 +100,7 @@ class TestEmane:
|
|||
session.set_location(47.57917, -122.13232, 2.00000, 1.0)
|
||||
options = NodeOptions()
|
||||
options.set_position(80, 50)
|
||||
emane_network = session.add_node(_type=NodeTypes.EMANE, options=options)
|
||||
emane_network = session.add_node(EmaneNet, options=options)
|
||||
config_key = "txpower"
|
||||
config_value = "10"
|
||||
session.emane.set_model(
|
||||
|
@ -102,9 +110,9 @@ class TestEmane:
|
|||
# create nodes
|
||||
options = NodeOptions(model="mdr")
|
||||
options.set_position(150, 150)
|
||||
node_one = session.add_node(options=options)
|
||||
node_one = session.add_node(CoreNode, options=options)
|
||||
options.set_position(300, 150)
|
||||
node_two = session.add_node(options=options)
|
||||
node_two = session.add_node(CoreNode, options=options)
|
||||
|
||||
for i, node in enumerate([node_one, node_two]):
|
||||
node.setposition(x=150 * (i + 1), y=150)
|
||||
|
@ -133,9 +141,9 @@ class TestEmane:
|
|||
|
||||
# verify nodes have been removed from session
|
||||
with pytest.raises(CoreError):
|
||||
assert not session.get_node(n1_id)
|
||||
assert not session.get_node(n1_id, CoreNode)
|
||||
with pytest.raises(CoreError):
|
||||
assert not session.get_node(n2_id)
|
||||
assert not session.get_node(n2_id, CoreNode)
|
||||
|
||||
# load saved xml
|
||||
session.open_xml(file_path, start=True)
|
||||
|
@ -146,7 +154,7 @@ class TestEmane:
|
|||
)
|
||||
|
||||
# verify nodes and configuration were restored
|
||||
assert session.get_node(n1_id)
|
||||
assert session.get_node(n2_id)
|
||||
assert session.get_node(emane_id)
|
||||
assert session.get_node(n1_id, CoreNode)
|
||||
assert session.get_node(n2_id, CoreNode)
|
||||
assert session.get_node(emane_id, EmaneNet)
|
||||
assert value == config_value
|
||||
|
|
|
@ -7,8 +7,10 @@ from core.config import (
|
|||
ModelManager,
|
||||
)
|
||||
from core.emane.ieee80211abg import EmaneIeee80211abgModel
|
||||
from core.emulator.enumerations import ConfigDataTypes, NodeTypes
|
||||
from core.emulator.enumerations import ConfigDataTypes
|
||||
from core.emulator.session import Session
|
||||
from core.location.mobility import BasicRangeModel
|
||||
from core.nodes.network import WlanNode
|
||||
|
||||
|
||||
class TestConfigurableOptions(ConfigurableOptions):
|
||||
|
@ -40,7 +42,7 @@ class TestConf:
|
|||
def test_nodes(self):
|
||||
# given
|
||||
config_manager = ConfigurableManager()
|
||||
test_config = {1: 2}
|
||||
test_config = {"1": "2"}
|
||||
node_id = 1
|
||||
config_manager.set_configs(test_config)
|
||||
config_manager.set_configs(test_config, node_id=node_id)
|
||||
|
@ -55,7 +57,7 @@ class TestConf:
|
|||
def test_config_reset_all(self):
|
||||
# given
|
||||
config_manager = ConfigurableManager()
|
||||
test_config = {1: 2}
|
||||
test_config = {"1": "2"}
|
||||
node_id = 1
|
||||
config_manager.set_configs(test_config)
|
||||
config_manager.set_configs(test_config, node_id=node_id)
|
||||
|
@ -69,7 +71,7 @@ class TestConf:
|
|||
def test_config_reset_node(self):
|
||||
# given
|
||||
config_manager = ConfigurableManager()
|
||||
test_config = {1: 2}
|
||||
test_config = {"1": "2"}
|
||||
node_id = 1
|
||||
config_manager.set_configs(test_config)
|
||||
config_manager.set_configs(test_config, node_id=node_id)
|
||||
|
@ -84,7 +86,7 @@ class TestConf:
|
|||
def test_configs_setget(self):
|
||||
# given
|
||||
config_manager = ConfigurableManager()
|
||||
test_config = {1: 2}
|
||||
test_config = {"1": "2"}
|
||||
node_id = 1
|
||||
config_manager.set_configs(test_config)
|
||||
config_manager.set_configs(test_config, node_id=node_id)
|
||||
|
@ -145,9 +147,9 @@ class TestConf:
|
|||
with pytest.raises(ValueError):
|
||||
manager.get_model_config(1, bad_name)
|
||||
|
||||
def test_model_set(self, session):
|
||||
def test_model_set(self, session: Session):
|
||||
# given
|
||||
wlan_node = session.add_node(_type=NodeTypes.WIRELESS_LAN)
|
||||
wlan_node = session.add_node(WlanNode)
|
||||
|
||||
# when
|
||||
session.mobility.set_model(wlan_node, BasicRangeModel)
|
||||
|
@ -155,17 +157,17 @@ class TestConf:
|
|||
# then
|
||||
assert session.mobility.get_model_config(wlan_node.id, BasicRangeModel.name)
|
||||
|
||||
def test_model_set_error(self, session):
|
||||
def test_model_set_error(self, session: Session):
|
||||
# given
|
||||
wlan_node = session.add_node(_type=NodeTypes.WIRELESS_LAN)
|
||||
wlan_node = session.add_node(WlanNode)
|
||||
|
||||
# when / then
|
||||
with pytest.raises(ValueError):
|
||||
session.mobility.set_model(wlan_node, EmaneIeee80211abgModel)
|
||||
|
||||
def test_get_models(self, session):
|
||||
def test_get_models(self, session: Session):
|
||||
# given
|
||||
wlan_node = session.add_node(_type=NodeTypes.WIRELESS_LAN)
|
||||
wlan_node = session.add_node(WlanNode)
|
||||
session.mobility.set_model(wlan_node, BasicRangeModel)
|
||||
|
||||
# when
|
||||
|
|
|
@ -4,21 +4,25 @@ Unit tests for testing basic CORE networks.
|
|||
|
||||
import os
|
||||
import threading
|
||||
from typing import Type
|
||||
|
||||
import pytest
|
||||
|
||||
from core.emulator.emudata import NodeOptions
|
||||
from core.emulator.enumerations import MessageFlags, NodeTypes
|
||||
from core.emulator.emudata import IpPrefixes, NodeOptions
|
||||
from core.emulator.enumerations import MessageFlags
|
||||
from core.emulator.session import Session
|
||||
from core.errors import CoreCommandError
|
||||
from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility
|
||||
from core.nodes.base import CoreNode, NodeBase
|
||||
from core.nodes.network import HubNode, PtpNet, SwitchNode, WlanNode
|
||||
|
||||
_PATH = os.path.abspath(os.path.dirname(__file__))
|
||||
_MOBILITY_FILE = os.path.join(_PATH, "mobility.scen")
|
||||
_WIRED = [NodeTypes.PEER_TO_PEER, NodeTypes.HUB, NodeTypes.SWITCH]
|
||||
_WIRED = [PtpNet, HubNode, SwitchNode]
|
||||
|
||||
|
||||
def ping(from_node, to_node, ip_prefixes):
|
||||
address = ip_prefixes.ip4_address(to_node)
|
||||
def ping(from_node: CoreNode, to_node: CoreNode, ip_prefixes: IpPrefixes):
|
||||
address = ip_prefixes.ip4_address(to_node.id)
|
||||
try:
|
||||
from_node.cmd(f"ping -c 1 {address}")
|
||||
status = 0
|
||||
|
@ -29,7 +33,9 @@ def ping(from_node, to_node, ip_prefixes):
|
|||
|
||||
class TestCore:
|
||||
@pytest.mark.parametrize("net_type", _WIRED)
|
||||
def test_wired_ping(self, session, net_type, ip_prefixes):
|
||||
def test_wired_ping(
|
||||
self, session: Session, net_type: Type[NodeBase], ip_prefixes: IpPrefixes
|
||||
):
|
||||
"""
|
||||
Test ptp node network.
|
||||
|
||||
|
@ -39,11 +45,11 @@ class TestCore:
|
|||
"""
|
||||
|
||||
# create net node
|
||||
net_node = session.add_node(_type=net_type)
|
||||
net_node = session.add_node(net_type)
|
||||
|
||||
# create nodes
|
||||
node_one = session.add_node()
|
||||
node_two = session.add_node()
|
||||
node_one = session.add_node(CoreNode)
|
||||
node_two = session.add_node(CoreNode)
|
||||
|
||||
# link nodes to net node
|
||||
for node in [node_one, node_two]:
|
||||
|
@ -57,7 +63,7 @@ class TestCore:
|
|||
status = ping(node_one, node_two, ip_prefixes)
|
||||
assert not status
|
||||
|
||||
def test_vnode_client(self, request, session, ip_prefixes):
|
||||
def test_vnode_client(self, request, session: Session, ip_prefixes: IpPrefixes):
|
||||
"""
|
||||
Test vnode client methods.
|
||||
|
||||
|
@ -66,11 +72,11 @@ class TestCore:
|
|||
:param ip_prefixes: generates ip addresses for nodes
|
||||
"""
|
||||
# create ptp
|
||||
ptp_node = session.add_node(_type=NodeTypes.PEER_TO_PEER)
|
||||
ptp_node = session.add_node(PtpNet)
|
||||
|
||||
# create nodes
|
||||
node_one = session.add_node()
|
||||
node_two = session.add_node()
|
||||
node_one = session.add_node(CoreNode)
|
||||
node_two = session.add_node(CoreNode)
|
||||
|
||||
# link nodes to ptp net
|
||||
for node in [node_one, node_two]:
|
||||
|
@ -90,7 +96,7 @@ class TestCore:
|
|||
if not request.config.getoption("mock"):
|
||||
assert client.check_cmd("echo hello") == "hello"
|
||||
|
||||
def test_netif(self, session, ip_prefixes):
|
||||
def test_netif(self, session: Session, ip_prefixes: IpPrefixes):
|
||||
"""
|
||||
Test netif methods.
|
||||
|
||||
|
@ -99,11 +105,11 @@ class TestCore:
|
|||
"""
|
||||
|
||||
# create ptp
|
||||
ptp_node = session.add_node(_type=NodeTypes.PEER_TO_PEER)
|
||||
ptp_node = session.add_node(PtpNet)
|
||||
|
||||
# create nodes
|
||||
node_one = session.add_node()
|
||||
node_two = session.add_node()
|
||||
node_one = session.add_node(CoreNode)
|
||||
node_two = session.add_node(CoreNode)
|
||||
|
||||
# link nodes to ptp net
|
||||
for node in [node_one, node_two]:
|
||||
|
@ -121,8 +127,8 @@ class TestCore:
|
|||
assert node_two.commonnets(node_one)
|
||||
|
||||
# check we can retrieve netif index
|
||||
assert node_one.getifindex(0)
|
||||
assert node_two.getifindex(0)
|
||||
assert node_one.ifname(0)
|
||||
assert node_two.ifname(0)
|
||||
|
||||
# check interface parameters
|
||||
interface = node_one.netif(0)
|
||||
|
@ -134,7 +140,7 @@ class TestCore:
|
|||
node_one.delnetif(0)
|
||||
assert not node_one.netif(0)
|
||||
|
||||
def test_wlan_ping(self, session, ip_prefixes):
|
||||
def test_wlan_ping(self, session: Session, ip_prefixes: IpPrefixes):
|
||||
"""
|
||||
Test basic wlan network.
|
||||
|
||||
|
@ -143,14 +149,14 @@ class TestCore:
|
|||
"""
|
||||
|
||||
# create wlan
|
||||
wlan_node = session.add_node(_type=NodeTypes.WIRELESS_LAN)
|
||||
wlan_node = session.add_node(WlanNode)
|
||||
session.mobility.set_model(wlan_node, BasicRangeModel)
|
||||
|
||||
# create nodes
|
||||
options = NodeOptions(model="mdr")
|
||||
options.set_position(0, 0)
|
||||
node_one = session.add_node(options=options)
|
||||
node_two = session.add_node(options=options)
|
||||
node_one = session.add_node(CoreNode, options=options)
|
||||
node_two = session.add_node(CoreNode, options=options)
|
||||
|
||||
# link nodes
|
||||
for node in [node_one, node_two]:
|
||||
|
@ -164,7 +170,7 @@ class TestCore:
|
|||
status = ping(node_one, node_two, ip_prefixes)
|
||||
assert not status
|
||||
|
||||
def test_mobility(self, session, ip_prefixes):
|
||||
def test_mobility(self, session: Session, ip_prefixes: IpPrefixes):
|
||||
"""
|
||||
Test basic wlan network.
|
||||
|
||||
|
@ -173,14 +179,14 @@ class TestCore:
|
|||
"""
|
||||
|
||||
# create wlan
|
||||
wlan_node = session.add_node(_type=NodeTypes.WIRELESS_LAN)
|
||||
wlan_node = session.add_node(WlanNode)
|
||||
session.mobility.set_model(wlan_node, BasicRangeModel)
|
||||
|
||||
# create nodes
|
||||
options = NodeOptions(model="mdr")
|
||||
options.set_position(0, 0)
|
||||
node_one = session.add_node(options=options)
|
||||
node_two = session.add_node(options=options)
|
||||
node_one = session.add_node(CoreNode, options=options)
|
||||
node_two = session.add_node(CoreNode, options=options)
|
||||
|
||||
# link nodes
|
||||
for node in [node_one, node_two]:
|
||||
|
|
|
@ -1,18 +1,19 @@
|
|||
from core.emulator.emudata import NodeOptions
|
||||
from core.emulator.enumerations import NodeTypes
|
||||
from core.emulator.session import Session
|
||||
from core.nodes.base import CoreNode
|
||||
from core.nodes.network import HubNode
|
||||
|
||||
|
||||
class TestDistributed:
|
||||
def test_remote_node(self, session):
|
||||
def test_remote_node(self, session: Session):
|
||||
# given
|
||||
server_name = "core2"
|
||||
host = "127.0.0.1"
|
||||
|
||||
# when
|
||||
session.distributed.add_server(server_name, host)
|
||||
options = NodeOptions()
|
||||
options.server = server_name
|
||||
node = session.add_node(options=options)
|
||||
options = NodeOptions(server=server_name)
|
||||
node = session.add_node(CoreNode, options=options)
|
||||
session.instantiate()
|
||||
|
||||
# then
|
||||
|
@ -20,7 +21,7 @@ class TestDistributed:
|
|||
assert node.server.name == server_name
|
||||
assert node.server.host == host
|
||||
|
||||
def test_remote_bridge(self, session):
|
||||
def test_remote_bridge(self, session: Session):
|
||||
# given
|
||||
server_name = "core2"
|
||||
host = "127.0.0.1"
|
||||
|
@ -28,9 +29,8 @@ class TestDistributed:
|
|||
|
||||
# when
|
||||
session.distributed.add_server(server_name, host)
|
||||
options = NodeOptions()
|
||||
options.server = server_name
|
||||
node = session.add_node(_type=NodeTypes.HUB, options=options)
|
||||
options = NodeOptions(server=server_name)
|
||||
node = session.add_node(HubNode, options=options)
|
||||
session.instantiate()
|
||||
|
||||
# then
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import time
|
||||
from queue import Queue
|
||||
from tempfile import TemporaryFile
|
||||
from typing import Optional
|
||||
|
||||
import grpc
|
||||
import pytest
|
||||
|
@ -9,21 +11,25 @@ from core.api.grpc import core_pb2
|
|||
from core.api.grpc.client import CoreGrpcClient, InterfaceHelper
|
||||
from core.api.grpc.emane_pb2 import EmaneModelConfig
|
||||
from core.api.grpc.mobility_pb2 import MobilityAction, MobilityConfig
|
||||
from core.api.grpc.server import CoreGrpcServer
|
||||
from core.api.grpc.services_pb2 import ServiceAction, ServiceConfig, ServiceFileConfig
|
||||
from core.api.grpc.wlan_pb2 import WlanConfig
|
||||
from core.api.tlv.dataconversion import ConfigShim
|
||||
from core.api.tlv.enumerations import ConfigFlags
|
||||
from core.emane.ieee80211abg import EmaneIeee80211abgModel
|
||||
from core.emulator.data import EventData
|
||||
from core.emulator.emudata import NodeOptions
|
||||
from core.emane.nodes import EmaneNet
|
||||
from core.emulator.data import EventData, NodeData
|
||||
from core.emulator.emudata import IpPrefixes, NodeOptions
|
||||
from core.emulator.enumerations import EventTypes, ExceptionLevels, NodeTypes
|
||||
from core.errors import CoreError
|
||||
from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility
|
||||
from core.nodes.base import CoreNode
|
||||
from core.nodes.network import SwitchNode, WlanNode
|
||||
from core.xml.corexml import CoreXmlWriter
|
||||
|
||||
|
||||
class TestGrpc:
|
||||
def test_start_session(self, grpc_server):
|
||||
def test_start_session(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
|
@ -156,7 +162,9 @@ class TestGrpc:
|
|||
assert service_file.data == service_file_config.data
|
||||
|
||||
@pytest.mark.parametrize("session_id", [None, 6013])
|
||||
def test_create_session(self, grpc_server, session_id):
|
||||
def test_create_session(
|
||||
self, grpc_server: CoreGrpcServer, session_id: Optional[int]
|
||||
):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
|
||||
|
@ -175,7 +183,9 @@ class TestGrpc:
|
|||
assert session.id == session_id
|
||||
|
||||
@pytest.mark.parametrize("session_id, expected", [(None, True), (6013, False)])
|
||||
def test_delete_session(self, grpc_server, session_id, expected):
|
||||
def test_delete_session(
|
||||
self, grpc_server: CoreGrpcServer, session_id: Optional[int], expected: bool
|
||||
):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
|
@ -190,11 +200,11 @@ class TestGrpc:
|
|||
assert response.result is expected
|
||||
assert grpc_server.coreemu.sessions.get(session_id) is None
|
||||
|
||||
def test_get_session(self, grpc_server):
|
||||
def test_get_session(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
session.add_node()
|
||||
session.add_node(CoreNode)
|
||||
session.set_state(EventTypes.DEFINITION_STATE)
|
||||
|
||||
# then
|
||||
|
@ -206,7 +216,7 @@ class TestGrpc:
|
|||
assert len(response.session.nodes) == 1
|
||||
assert len(response.session.links) == 0
|
||||
|
||||
def test_get_sessions(self, grpc_server):
|
||||
def test_get_sessions(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
|
@ -224,7 +234,7 @@ class TestGrpc:
|
|||
assert len(response.sessions) == 1
|
||||
assert found_session is not None
|
||||
|
||||
def test_get_session_options(self, grpc_server):
|
||||
def test_get_session_options(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
|
@ -236,7 +246,7 @@ class TestGrpc:
|
|||
# then
|
||||
assert len(response.config) > 0
|
||||
|
||||
def test_get_session_location(self, grpc_server):
|
||||
def test_get_session_location(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
|
@ -254,7 +264,7 @@ class TestGrpc:
|
|||
assert response.location.lon == 0
|
||||
assert response.location.alt == 0
|
||||
|
||||
def test_set_session_location(self, grpc_server):
|
||||
def test_set_session_location(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
|
@ -281,7 +291,7 @@ class TestGrpc:
|
|||
assert session.location.refscale == scale
|
||||
assert session.location.refgeo == lat_lon_alt
|
||||
|
||||
def test_set_session_options(self, grpc_server):
|
||||
def test_set_session_options(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
|
@ -298,7 +308,7 @@ class TestGrpc:
|
|||
config = session.options.get_configs()
|
||||
assert len(config) > 0
|
||||
|
||||
def test_set_session_metadata(self, grpc_server):
|
||||
def test_set_session_metadata(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
|
@ -313,7 +323,7 @@ class TestGrpc:
|
|||
assert response.result is True
|
||||
assert session.metadata[key] == value
|
||||
|
||||
def test_get_session_metadata(self, grpc_server):
|
||||
def test_get_session_metadata(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
|
@ -328,7 +338,7 @@ class TestGrpc:
|
|||
# then
|
||||
assert response.config[key] == value
|
||||
|
||||
def test_set_session_state(self, grpc_server):
|
||||
def test_set_session_state(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
|
@ -343,7 +353,7 @@ class TestGrpc:
|
|||
assert response.result is True
|
||||
assert session.state == EventTypes.DEFINITION_STATE
|
||||
|
||||
def test_add_node(self, grpc_server):
|
||||
def test_add_node(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
|
@ -355,13 +365,13 @@ class TestGrpc:
|
|||
|
||||
# then
|
||||
assert response.node_id is not None
|
||||
assert session.get_node(response.node_id) is not None
|
||||
assert session.get_node(response.node_id, CoreNode) is not None
|
||||
|
||||
def test_get_node(self, grpc_server):
|
||||
def test_get_node(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
node = session.add_node()
|
||||
node = session.add_node(CoreNode)
|
||||
|
||||
# then
|
||||
with client.context_connect():
|
||||
|
@ -370,11 +380,11 @@ class TestGrpc:
|
|||
# then
|
||||
assert response.node.id == node.id
|
||||
|
||||
def test_edit_node(self, grpc_server):
|
||||
def test_edit_node(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
node = session.add_node()
|
||||
node = session.add_node(CoreNode)
|
||||
|
||||
# then
|
||||
x, y = 10, 10
|
||||
|
@ -388,11 +398,13 @@ class TestGrpc:
|
|||
assert node.position.y == y
|
||||
|
||||
@pytest.mark.parametrize("node_id, expected", [(1, True), (2, False)])
|
||||
def test_delete_node(self, grpc_server, node_id, expected):
|
||||
def test_delete_node(
|
||||
self, grpc_server: CoreGrpcServer, node_id: int, expected: bool
|
||||
):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
node = session.add_node()
|
||||
node = session.add_node(CoreNode)
|
||||
|
||||
# then
|
||||
with client.context_connect():
|
||||
|
@ -402,9 +414,9 @@ class TestGrpc:
|
|||
assert response.result is expected
|
||||
if expected is True:
|
||||
with pytest.raises(CoreError):
|
||||
assert session.get_node(node.id)
|
||||
assert session.get_node(node.id, CoreNode)
|
||||
|
||||
def test_node_command(self, request, grpc_server):
|
||||
def test_node_command(self, request, grpc_server: CoreGrpcServer):
|
||||
if request.config.getoption("mock"):
|
||||
pytest.skip("mocking calls")
|
||||
|
||||
|
@ -413,7 +425,7 @@ class TestGrpc:
|
|||
session = grpc_server.coreemu.create_session()
|
||||
session.set_state(EventTypes.CONFIGURATION_STATE)
|
||||
options = NodeOptions(model="Host")
|
||||
node = session.add_node(options=options)
|
||||
node = session.add_node(CoreNode, options=options)
|
||||
session.instantiate()
|
||||
output = "hello world"
|
||||
|
||||
|
@ -425,13 +437,13 @@ class TestGrpc:
|
|||
# then
|
||||
assert response.output == output
|
||||
|
||||
def test_get_node_terminal(self, grpc_server):
|
||||
def test_get_node_terminal(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
session.set_state(EventTypes.CONFIGURATION_STATE)
|
||||
options = NodeOptions(model="Host")
|
||||
node = session.add_node(options=options)
|
||||
node = session.add_node(CoreNode, options=options)
|
||||
session.instantiate()
|
||||
|
||||
# then
|
||||
|
@ -441,13 +453,13 @@ class TestGrpc:
|
|||
# then
|
||||
assert response.terminal is not None
|
||||
|
||||
def test_get_hooks(self, grpc_server):
|
||||
def test_get_hooks(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
file_name = "test"
|
||||
file_data = "echo hello"
|
||||
session.add_hook(EventTypes.RUNTIME_STATE, file_name, None, file_data)
|
||||
session.add_hook(EventTypes.RUNTIME_STATE, file_name, file_data)
|
||||
|
||||
# then
|
||||
with client.context_connect():
|
||||
|
@ -460,7 +472,7 @@ class TestGrpc:
|
|||
assert hook.file == file_name
|
||||
assert hook.data == file_data
|
||||
|
||||
def test_add_hook(self, grpc_server):
|
||||
def test_add_hook(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
|
@ -476,7 +488,7 @@ class TestGrpc:
|
|||
# then
|
||||
assert response.result is True
|
||||
|
||||
def test_save_xml(self, grpc_server, tmpdir):
|
||||
def test_save_xml(self, grpc_server: CoreGrpcServer, tmpdir: TemporaryFile):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
|
@ -489,7 +501,7 @@ class TestGrpc:
|
|||
# then
|
||||
assert tmp.exists()
|
||||
|
||||
def test_open_xml_hook(self, grpc_server, tmpdir):
|
||||
def test_open_xml_hook(self, grpc_server: CoreGrpcServer, tmpdir: TemporaryFile):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
|
@ -504,12 +516,12 @@ class TestGrpc:
|
|||
assert response.result is True
|
||||
assert response.session_id is not None
|
||||
|
||||
def test_get_node_links(self, grpc_server, ip_prefixes):
|
||||
def test_get_node_links(self, grpc_server: CoreGrpcServer, ip_prefixes: IpPrefixes):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
switch = session.add_node(_type=NodeTypes.SWITCH)
|
||||
node = session.add_node()
|
||||
switch = session.add_node(SwitchNode)
|
||||
node = session.add_node(CoreNode)
|
||||
interface = ip_prefixes.create_interface(node)
|
||||
session.add_link(node.id, switch.id, interface)
|
||||
|
||||
|
@ -520,12 +532,14 @@ class TestGrpc:
|
|||
# then
|
||||
assert len(response.links) == 1
|
||||
|
||||
def test_get_node_links_exception(self, grpc_server, ip_prefixes):
|
||||
def test_get_node_links_exception(
|
||||
self, grpc_server: CoreGrpcServer, ip_prefixes: IpPrefixes
|
||||
):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
switch = session.add_node(_type=NodeTypes.SWITCH)
|
||||
node = session.add_node()
|
||||
switch = session.add_node(SwitchNode)
|
||||
node = session.add_node(CoreNode)
|
||||
interface = ip_prefixes.create_interface(node)
|
||||
session.add_link(node.id, switch.id, interface)
|
||||
|
||||
|
@ -534,12 +548,14 @@ class TestGrpc:
|
|||
with client.context_connect():
|
||||
client.get_node_links(session.id, 3)
|
||||
|
||||
def test_add_link(self, grpc_server, interface_helper):
|
||||
def test_add_link(
|
||||
self, grpc_server: CoreGrpcServer, interface_helper: InterfaceHelper
|
||||
):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
switch = session.add_node(_type=NodeTypes.SWITCH)
|
||||
node = session.add_node()
|
||||
switch = session.add_node(SwitchNode)
|
||||
node = session.add_node(CoreNode)
|
||||
assert len(switch.all_link_data()) == 0
|
||||
|
||||
# then
|
||||
|
@ -551,11 +567,13 @@ class TestGrpc:
|
|||
assert response.result is True
|
||||
assert len(switch.all_link_data()) == 1
|
||||
|
||||
def test_add_link_exception(self, grpc_server, interface_helper):
|
||||
def test_add_link_exception(
|
||||
self, grpc_server: CoreGrpcServer, interface_helper: InterfaceHelper
|
||||
):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
node = session.add_node()
|
||||
node = session.add_node(CoreNode)
|
||||
|
||||
# then
|
||||
interface = interface_helper.create_interface(node.id, 0)
|
||||
|
@ -563,12 +581,12 @@ class TestGrpc:
|
|||
with client.context_connect():
|
||||
client.add_link(session.id, 1, 3, interface)
|
||||
|
||||
def test_edit_link(self, grpc_server, ip_prefixes):
|
||||
def test_edit_link(self, grpc_server: CoreGrpcServer, ip_prefixes: IpPrefixes):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
switch = session.add_node(_type=NodeTypes.SWITCH)
|
||||
node = session.add_node()
|
||||
switch = session.add_node(SwitchNode)
|
||||
node = session.add_node(CoreNode)
|
||||
interface = ip_prefixes.create_interface(node)
|
||||
session.add_link(node.id, switch.id, interface)
|
||||
options = core_pb2.LinkOptions(bandwidth=30000)
|
||||
|
@ -586,13 +604,13 @@ class TestGrpc:
|
|||
link = switch.all_link_data()[0]
|
||||
assert options.bandwidth == link.bandwidth
|
||||
|
||||
def test_delete_link(self, grpc_server, ip_prefixes):
|
||||
def test_delete_link(self, grpc_server: CoreGrpcServer, ip_prefixes: IpPrefixes):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
node_one = session.add_node()
|
||||
node_one = session.add_node(CoreNode)
|
||||
interface_one = ip_prefixes.create_interface(node_one)
|
||||
node_two = session.add_node()
|
||||
node_two = session.add_node(CoreNode)
|
||||
interface_two = ip_prefixes.create_interface(node_two)
|
||||
session.add_link(node_one.id, node_two.id, interface_one, interface_two)
|
||||
link_node = None
|
||||
|
@ -613,11 +631,11 @@ class TestGrpc:
|
|||
assert response.result is True
|
||||
assert len(link_node.all_link_data(0)) == 0
|
||||
|
||||
def test_get_wlan_config(self, grpc_server):
|
||||
def test_get_wlan_config(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
wlan = session.add_node(_type=NodeTypes.WIRELESS_LAN)
|
||||
wlan = session.add_node(WlanNode)
|
||||
|
||||
# then
|
||||
with client.context_connect():
|
||||
|
@ -626,12 +644,12 @@ class TestGrpc:
|
|||
# then
|
||||
assert len(response.config) > 0
|
||||
|
||||
def test_set_wlan_config(self, grpc_server):
|
||||
def test_set_wlan_config(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
session.set_state(EventTypes.CONFIGURATION_STATE)
|
||||
wlan = session.add_node(_type=NodeTypes.WIRELESS_LAN)
|
||||
wlan = session.add_node(WlanNode)
|
||||
wlan.setmodel(BasicRangeModel, BasicRangeModel.default_values())
|
||||
session.instantiate()
|
||||
range_key = "range"
|
||||
|
@ -658,7 +676,7 @@ class TestGrpc:
|
|||
assert config[range_key] == range_value
|
||||
assert wlan.model.range == int(range_value)
|
||||
|
||||
def test_get_emane_config(self, grpc_server):
|
||||
def test_get_emane_config(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
|
@ -670,7 +688,7 @@ class TestGrpc:
|
|||
# then
|
||||
assert len(response.config) > 0
|
||||
|
||||
def test_set_emane_config(self, grpc_server):
|
||||
def test_set_emane_config(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
|
@ -687,14 +705,13 @@ class TestGrpc:
|
|||
assert len(config) > 1
|
||||
assert config[config_key] == config_value
|
||||
|
||||
def test_get_emane_model_configs(self, grpc_server):
|
||||
def test_get_emane_model_configs(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
session.set_location(47.57917, -122.13232, 2.00000, 1.0)
|
||||
options = NodeOptions()
|
||||
options.emane = EmaneIeee80211abgModel.name
|
||||
emane_network = session.add_node(_type=NodeTypes.EMANE, options=options)
|
||||
options = NodeOptions(emane=EmaneIeee80211abgModel.name)
|
||||
emane_network = session.add_node(EmaneNet, options=options)
|
||||
session.emane.set_model(emane_network, EmaneIeee80211abgModel)
|
||||
config_key = "platform_id_start"
|
||||
config_value = "2"
|
||||
|
@ -714,14 +731,13 @@ class TestGrpc:
|
|||
assert len(model_config.config) > 0
|
||||
assert model_config.interface == -1
|
||||
|
||||
def test_set_emane_model_config(self, grpc_server):
|
||||
def test_set_emane_model_config(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
session.set_location(47.57917, -122.13232, 2.00000, 1.0)
|
||||
options = NodeOptions()
|
||||
options.emane = EmaneIeee80211abgModel.name
|
||||
emane_network = session.add_node(_type=NodeTypes.EMANE, options=options)
|
||||
options = NodeOptions(emane=EmaneIeee80211abgModel.name)
|
||||
emane_network = session.add_node(EmaneNet, options=options)
|
||||
session.emane.set_model(emane_network, EmaneIeee80211abgModel)
|
||||
config_key = "bandwidth"
|
||||
config_value = "900000"
|
||||
|
@ -742,14 +758,13 @@ class TestGrpc:
|
|||
)
|
||||
assert config[config_key] == config_value
|
||||
|
||||
def test_get_emane_model_config(self, grpc_server):
|
||||
def test_get_emane_model_config(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
session.set_location(47.57917, -122.13232, 2.00000, 1.0)
|
||||
options = NodeOptions()
|
||||
options.emane = EmaneIeee80211abgModel.name
|
||||
emane_network = session.add_node(_type=NodeTypes.EMANE, options=options)
|
||||
options = NodeOptions(emane=EmaneIeee80211abgModel.name)
|
||||
emane_network = session.add_node(EmaneNet, options=options)
|
||||
session.emane.set_model(emane_network, EmaneIeee80211abgModel)
|
||||
|
||||
# then
|
||||
|
@ -761,7 +776,7 @@ class TestGrpc:
|
|||
# then
|
||||
assert len(response.config) > 0
|
||||
|
||||
def test_get_emane_models(self, grpc_server):
|
||||
def test_get_emane_models(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
|
@ -773,11 +788,11 @@ class TestGrpc:
|
|||
# then
|
||||
assert len(response.models) > 0
|
||||
|
||||
def test_get_mobility_configs(self, grpc_server):
|
||||
def test_get_mobility_configs(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
wlan = session.add_node(_type=NodeTypes.WIRELESS_LAN)
|
||||
wlan = session.add_node(WlanNode)
|
||||
session.mobility.set_model_config(wlan.id, Ns2ScriptedMobility.name, {})
|
||||
|
||||
# then
|
||||
|
@ -790,11 +805,11 @@ class TestGrpc:
|
|||
mapped_config = response.configs[wlan.id]
|
||||
assert len(mapped_config.config) > 0
|
||||
|
||||
def test_get_mobility_config(self, grpc_server):
|
||||
def test_get_mobility_config(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
wlan = session.add_node(_type=NodeTypes.WIRELESS_LAN)
|
||||
wlan = session.add_node(WlanNode)
|
||||
session.mobility.set_model_config(wlan.id, Ns2ScriptedMobility.name, {})
|
||||
|
||||
# then
|
||||
|
@ -804,11 +819,11 @@ class TestGrpc:
|
|||
# then
|
||||
assert len(response.config) > 0
|
||||
|
||||
def test_set_mobility_config(self, grpc_server):
|
||||
def test_set_mobility_config(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
wlan = session.add_node(_type=NodeTypes.WIRELESS_LAN)
|
||||
wlan = session.add_node(WlanNode)
|
||||
config_key = "refresh_ms"
|
||||
config_value = "60"
|
||||
|
||||
|
@ -823,11 +838,11 @@ class TestGrpc:
|
|||
config = session.mobility.get_model_config(wlan.id, Ns2ScriptedMobility.name)
|
||||
assert config[config_key] == config_value
|
||||
|
||||
def test_mobility_action(self, grpc_server):
|
||||
def test_mobility_action(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
wlan = session.add_node(_type=NodeTypes.WIRELESS_LAN)
|
||||
wlan = session.add_node(WlanNode)
|
||||
session.mobility.set_model_config(wlan.id, Ns2ScriptedMobility.name, {})
|
||||
session.instantiate()
|
||||
|
||||
|
@ -838,7 +853,7 @@ class TestGrpc:
|
|||
# then
|
||||
assert response.result is True
|
||||
|
||||
def test_get_services(self, grpc_server):
|
||||
def test_get_services(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
|
||||
|
@ -849,7 +864,7 @@ class TestGrpc:
|
|||
# then
|
||||
assert len(response.services) > 0
|
||||
|
||||
def test_get_service_defaults(self, grpc_server):
|
||||
def test_get_service_defaults(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
|
@ -861,7 +876,7 @@ class TestGrpc:
|
|||
# then
|
||||
assert len(response.defaults) > 0
|
||||
|
||||
def test_set_service_defaults(self, grpc_server):
|
||||
def test_set_service_defaults(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
|
@ -876,11 +891,11 @@ class TestGrpc:
|
|||
assert response.result is True
|
||||
assert session.services.default_services[node_type] == services
|
||||
|
||||
def test_get_node_service_configs(self, grpc_server):
|
||||
def test_get_node_service_configs(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
node = session.add_node()
|
||||
node = session.add_node(CoreNode)
|
||||
service_name = "DefaultRoute"
|
||||
session.services.set_service(node.id, service_name)
|
||||
|
||||
|
@ -894,11 +909,11 @@ class TestGrpc:
|
|||
assert service_config.node_id == node.id
|
||||
assert service_config.service == service_name
|
||||
|
||||
def test_get_node_service(self, grpc_server):
|
||||
def test_get_node_service(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
node = session.add_node()
|
||||
node = session.add_node(CoreNode)
|
||||
|
||||
# then
|
||||
with client.context_connect():
|
||||
|
@ -907,11 +922,11 @@ class TestGrpc:
|
|||
# then
|
||||
assert len(response.service.configs) > 0
|
||||
|
||||
def test_get_node_service_file(self, grpc_server):
|
||||
def test_get_node_service_file(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
node = session.add_node()
|
||||
node = session.add_node(CoreNode)
|
||||
|
||||
# then
|
||||
with client.context_connect():
|
||||
|
@ -922,11 +937,11 @@ class TestGrpc:
|
|||
# then
|
||||
assert response.data is not None
|
||||
|
||||
def test_set_node_service(self, grpc_server):
|
||||
def test_set_node_service(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
node = session.add_node()
|
||||
node = session.add_node(CoreNode)
|
||||
service_name = "DefaultRoute"
|
||||
validate = ["echo hello"]
|
||||
|
||||
|
@ -943,11 +958,11 @@ class TestGrpc:
|
|||
)
|
||||
assert service.validate == tuple(validate)
|
||||
|
||||
def test_set_node_service_file(self, grpc_server):
|
||||
def test_set_node_service_file(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
node = session.add_node()
|
||||
node = session.add_node(CoreNode)
|
||||
service_name = "DefaultRoute"
|
||||
file_name = "defaultroute.sh"
|
||||
file_data = "echo hello"
|
||||
|
@ -963,11 +978,11 @@ class TestGrpc:
|
|||
service_file = session.services.get_service_file(node, service_name, file_name)
|
||||
assert service_file.data == file_data
|
||||
|
||||
def test_service_action(self, grpc_server):
|
||||
def test_service_action(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
node = session.add_node()
|
||||
node = session.add_node(CoreNode)
|
||||
service_name = "DefaultRoute"
|
||||
|
||||
# then
|
||||
|
@ -979,16 +994,23 @@ class TestGrpc:
|
|||
# then
|
||||
assert response.result is True
|
||||
|
||||
def test_node_events(self, grpc_server):
|
||||
def test_node_events(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
node = session.add_node()
|
||||
node = session.add_node(CoreNode)
|
||||
node.position.lat = 10.0
|
||||
node.position.lon = 20.0
|
||||
node.position.alt = 5.0
|
||||
queue = Queue()
|
||||
|
||||
def handle_event(event_data):
|
||||
assert event_data.session_id == session.id
|
||||
assert event_data.HasField("node_event")
|
||||
event_node = event_data.node_event.node
|
||||
assert event_node.geo.lat == node.position.lat
|
||||
assert event_node.geo.lon == node.position.lon
|
||||
assert event_node.geo.alt == node.position.alt
|
||||
queue.put(event_data)
|
||||
|
||||
# then
|
||||
|
@ -1000,12 +1022,12 @@ class TestGrpc:
|
|||
# then
|
||||
queue.get(timeout=5)
|
||||
|
||||
def test_link_events(self, grpc_server, ip_prefixes):
|
||||
def test_link_events(self, grpc_server: CoreGrpcServer, ip_prefixes: IpPrefixes):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
wlan = session.add_node(_type=NodeTypes.WIRELESS_LAN)
|
||||
node = session.add_node()
|
||||
wlan = session.add_node(WlanNode)
|
||||
node = session.add_node(CoreNode)
|
||||
interface = ip_prefixes.create_interface(node)
|
||||
session.add_link(node.id, wlan.id, interface)
|
||||
link_data = wlan.all_link_data()[0]
|
||||
|
@ -1025,7 +1047,7 @@ class TestGrpc:
|
|||
# then
|
||||
queue.get(timeout=5)
|
||||
|
||||
def test_throughputs(self, request, grpc_server):
|
||||
def test_throughputs(self, request, grpc_server: CoreGrpcServer):
|
||||
if request.config.getoption("mock"):
|
||||
pytest.skip("mocking calls")
|
||||
|
||||
|
@ -1046,7 +1068,7 @@ class TestGrpc:
|
|||
# then
|
||||
queue.get(timeout=5)
|
||||
|
||||
def test_session_events(self, grpc_server):
|
||||
def test_session_events(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
|
@ -1069,7 +1091,7 @@ class TestGrpc:
|
|||
# then
|
||||
queue.get(timeout=5)
|
||||
|
||||
def test_config_events(self, grpc_server):
|
||||
def test_config_events(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
|
@ -1093,7 +1115,7 @@ class TestGrpc:
|
|||
# then
|
||||
queue.get(timeout=5)
|
||||
|
||||
def test_exception_events(self, grpc_server):
|
||||
def test_exception_events(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
|
@ -1122,11 +1144,11 @@ class TestGrpc:
|
|||
# then
|
||||
queue.get(timeout=5)
|
||||
|
||||
def test_file_events(self, grpc_server):
|
||||
def test_file_events(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
node = session.add_node()
|
||||
node = session.add_node(CoreNode)
|
||||
queue = Queue()
|
||||
|
||||
def handle_event(event_data):
|
||||
|
@ -1145,3 +1167,71 @@ class TestGrpc:
|
|||
|
||||
# then
|
||||
queue.get(timeout=5)
|
||||
|
||||
def test_move_nodes(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
node = session.add_node(CoreNode)
|
||||
x, y = 10.0, 15.0
|
||||
|
||||
def move_iter():
|
||||
yield core_pb2.MoveNodesRequest(
|
||||
session_id=session.id,
|
||||
node_id=node.id,
|
||||
position=core_pb2.Position(x=x, y=y),
|
||||
)
|
||||
|
||||
# then
|
||||
with client.context_connect():
|
||||
client.move_nodes(move_iter())
|
||||
|
||||
# assert
|
||||
assert node.position.x == x
|
||||
assert node.position.y == y
|
||||
|
||||
def test_move_nodes_geo(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
session = grpc_server.coreemu.create_session()
|
||||
node = session.add_node(CoreNode)
|
||||
lon, lat, alt = 10.0, 15.0, 5.0
|
||||
queue = Queue()
|
||||
|
||||
def node_handler(node_data: NodeData):
|
||||
assert node_data.longitude == lon
|
||||
assert node_data.latitude == lat
|
||||
assert node_data.altitude == alt
|
||||
queue.put(node_data)
|
||||
|
||||
session.node_handlers.append(node_handler)
|
||||
|
||||
def move_iter():
|
||||
yield core_pb2.MoveNodesRequest(
|
||||
session_id=session.id,
|
||||
node_id=node.id,
|
||||
geo=core_pb2.Geo(lon=lon, lat=lat, alt=alt),
|
||||
)
|
||||
|
||||
# then
|
||||
with client.context_connect():
|
||||
client.move_nodes(move_iter())
|
||||
|
||||
# assert
|
||||
assert node.position.lon == lon
|
||||
assert node.position.lat == lat
|
||||
assert node.position.alt == alt
|
||||
assert queue.get(timeout=5)
|
||||
|
||||
def test_move_nodes_exception(self, grpc_server: CoreGrpcServer):
|
||||
# given
|
||||
client = CoreGrpcClient()
|
||||
grpc_server.coreemu.create_session()
|
||||
|
||||
def move_iter():
|
||||
yield core_pb2.MoveNodesRequest()
|
||||
|
||||
# then
|
||||
with pytest.raises(grpc.RpcError):
|
||||
with client.context_connect():
|
||||
client.move_nodes(move_iter())
|
||||
|
|
|
@ -3,6 +3,7 @@ Tests for testing tlv message handling.
|
|||
"""
|
||||
import os
|
||||
import time
|
||||
from typing import Optional
|
||||
|
||||
import mock
|
||||
import netaddr
|
||||
|
@ -10,6 +11,7 @@ import pytest
|
|||
from mock import MagicMock
|
||||
|
||||
from core.api.tlv import coreapi
|
||||
from core.api.tlv.corehandlers import CoreHandler
|
||||
from core.api.tlv.enumerations import (
|
||||
ConfigFlags,
|
||||
ConfigTlvs,
|
||||
|
@ -24,9 +26,11 @@ from core.emane.ieee80211abg import EmaneIeee80211abgModel
|
|||
from core.emulator.enumerations import EventTypes, MessageFlags, NodeTypes, RegisterTlvs
|
||||
from core.errors import CoreError
|
||||
from core.location.mobility import BasicRangeModel
|
||||
from core.nodes.base import CoreNode, NodeBase
|
||||
from core.nodes.network import SwitchNode, WlanNode
|
||||
|
||||
|
||||
def dict_to_str(values):
|
||||
def dict_to_str(values) -> str:
|
||||
return "|".join(f"{x}={values[x]}" for x in values)
|
||||
|
||||
|
||||
|
@ -42,7 +46,9 @@ class TestGui:
|
|||
(NodeTypes.TUNNEL, None),
|
||||
],
|
||||
)
|
||||
def test_node_add(self, coretlv, node_type, model):
|
||||
def test_node_add(
|
||||
self, coretlv: CoreHandler, node_type: NodeTypes, model: Optional[str]
|
||||
):
|
||||
node_id = 1
|
||||
message = coreapi.CoreNodeMessage.create(
|
||||
MessageFlags.ADD.value,
|
||||
|
@ -57,12 +63,11 @@ class TestGui:
|
|||
)
|
||||
|
||||
coretlv.handle_message(message)
|
||||
assert coretlv.session.get_node(node_id, NodeBase) is not None
|
||||
|
||||
assert coretlv.session.get_node(node_id) is not None
|
||||
|
||||
def test_node_update(self, coretlv):
|
||||
def test_node_update(self, coretlv: CoreHandler):
|
||||
node_id = 1
|
||||
coretlv.session.add_node(_id=node_id)
|
||||
coretlv.session.add_node(CoreNode, _id=node_id)
|
||||
x = 50
|
||||
y = 100
|
||||
message = coreapi.CoreNodeMessage.create(
|
||||
|
@ -76,14 +81,14 @@ class TestGui:
|
|||
|
||||
coretlv.handle_message(message)
|
||||
|
||||
node = coretlv.session.get_node(node_id)
|
||||
node = coretlv.session.get_node(node_id, NodeBase)
|
||||
assert node is not None
|
||||
assert node.position.x == x
|
||||
assert node.position.y == y
|
||||
|
||||
def test_node_delete(self, coretlv):
|
||||
def test_node_delete(self, coretlv: CoreHandler):
|
||||
node_id = 1
|
||||
coretlv.session.add_node(_id=node_id)
|
||||
coretlv.session.add_node(CoreNode, _id=node_id)
|
||||
message = coreapi.CoreNodeMessage.create(
|
||||
MessageFlags.DELETE.value, [(NodeTlvs.NUMBER, node_id)]
|
||||
)
|
||||
|
@ -91,13 +96,13 @@ class TestGui:
|
|||
coretlv.handle_message(message)
|
||||
|
||||
with pytest.raises(CoreError):
|
||||
coretlv.session.get_node(node_id)
|
||||
coretlv.session.get_node(node_id, NodeBase)
|
||||
|
||||
def test_link_add_node_to_net(self, coretlv):
|
||||
def test_link_add_node_to_net(self, coretlv: CoreHandler):
|
||||
node_one = 1
|
||||
coretlv.session.add_node(_id=node_one)
|
||||
coretlv.session.add_node(CoreNode, _id=node_one)
|
||||
switch = 2
|
||||
coretlv.session.add_node(_id=switch, _type=NodeTypes.SWITCH)
|
||||
coretlv.session.add_node(SwitchNode, _id=switch)
|
||||
ip_prefix = netaddr.IPNetwork("10.0.0.0/24")
|
||||
interface_one = str(ip_prefix[node_one])
|
||||
message = coreapi.CoreLinkMessage.create(
|
||||
|
@ -113,15 +118,15 @@ class TestGui:
|
|||
|
||||
coretlv.handle_message(message)
|
||||
|
||||
switch_node = coretlv.session.get_node(switch)
|
||||
switch_node = coretlv.session.get_node(switch, SwitchNode)
|
||||
all_links = switch_node.all_link_data()
|
||||
assert len(all_links) == 1
|
||||
|
||||
def test_link_add_net_to_node(self, coretlv):
|
||||
def test_link_add_net_to_node(self, coretlv: CoreHandler):
|
||||
node_one = 1
|
||||
coretlv.session.add_node(_id=node_one)
|
||||
coretlv.session.add_node(CoreNode, _id=node_one)
|
||||
switch = 2
|
||||
coretlv.session.add_node(_id=switch, _type=NodeTypes.SWITCH)
|
||||
coretlv.session.add_node(SwitchNode, _id=switch)
|
||||
ip_prefix = netaddr.IPNetwork("10.0.0.0/24")
|
||||
interface_one = str(ip_prefix[node_one])
|
||||
message = coreapi.CoreLinkMessage.create(
|
||||
|
@ -137,15 +142,15 @@ class TestGui:
|
|||
|
||||
coretlv.handle_message(message)
|
||||
|
||||
switch_node = coretlv.session.get_node(switch)
|
||||
switch_node = coretlv.session.get_node(switch, SwitchNode)
|
||||
all_links = switch_node.all_link_data()
|
||||
assert len(all_links) == 1
|
||||
|
||||
def test_link_add_node_to_node(self, coretlv):
|
||||
def test_link_add_node_to_node(self, coretlv: CoreHandler):
|
||||
node_one = 1
|
||||
coretlv.session.add_node(_id=node_one)
|
||||
coretlv.session.add_node(CoreNode, _id=node_one)
|
||||
node_two = 2
|
||||
coretlv.session.add_node(_id=node_two)
|
||||
coretlv.session.add_node(CoreNode, _id=node_two)
|
||||
ip_prefix = netaddr.IPNetwork("10.0.0.0/24")
|
||||
interface_one = str(ip_prefix[node_one])
|
||||
interface_two = str(ip_prefix[node_two])
|
||||
|
@ -171,11 +176,11 @@ class TestGui:
|
|||
all_links += node.all_link_data()
|
||||
assert len(all_links) == 1
|
||||
|
||||
def test_link_update(self, coretlv):
|
||||
def test_link_update(self, coretlv: CoreHandler):
|
||||
node_one = 1
|
||||
coretlv.session.add_node(_id=node_one)
|
||||
coretlv.session.add_node(CoreNode, _id=node_one)
|
||||
switch = 2
|
||||
coretlv.session.add_node(_id=switch, _type=NodeTypes.SWITCH)
|
||||
coretlv.session.add_node(SwitchNode, _id=switch)
|
||||
ip_prefix = netaddr.IPNetwork("10.0.0.0/24")
|
||||
interface_one = str(ip_prefix[node_one])
|
||||
message = coreapi.CoreLinkMessage.create(
|
||||
|
@ -189,7 +194,7 @@ class TestGui:
|
|||
],
|
||||
)
|
||||
coretlv.handle_message(message)
|
||||
switch_node = coretlv.session.get_node(switch)
|
||||
switch_node = coretlv.session.get_node(switch, SwitchNode)
|
||||
all_links = switch_node.all_link_data()
|
||||
assert len(all_links) == 1
|
||||
link = all_links[0]
|
||||
|
@ -207,17 +212,17 @@ class TestGui:
|
|||
)
|
||||
coretlv.handle_message(message)
|
||||
|
||||
switch_node = coretlv.session.get_node(switch)
|
||||
switch_node = coretlv.session.get_node(switch, SwitchNode)
|
||||
all_links = switch_node.all_link_data()
|
||||
assert len(all_links) == 1
|
||||
link = all_links[0]
|
||||
assert link.bandwidth == bandwidth
|
||||
|
||||
def test_link_delete_node_to_node(self, coretlv):
|
||||
def test_link_delete_node_to_node(self, coretlv: CoreHandler):
|
||||
node_one = 1
|
||||
coretlv.session.add_node(_id=node_one)
|
||||
coretlv.session.add_node(CoreNode, _id=node_one)
|
||||
node_two = 2
|
||||
coretlv.session.add_node(_id=node_two)
|
||||
coretlv.session.add_node(CoreNode, _id=node_two)
|
||||
ip_prefix = netaddr.IPNetwork("10.0.0.0/24")
|
||||
interface_one = str(ip_prefix[node_one])
|
||||
interface_two = str(ip_prefix[node_two])
|
||||
|
@ -257,11 +262,11 @@ class TestGui:
|
|||
all_links += node.all_link_data()
|
||||
assert len(all_links) == 0
|
||||
|
||||
def test_link_delete_node_to_net(self, coretlv):
|
||||
def test_link_delete_node_to_net(self, coretlv: CoreHandler):
|
||||
node_one = 1
|
||||
coretlv.session.add_node(_id=node_one)
|
||||
coretlv.session.add_node(CoreNode, _id=node_one)
|
||||
switch = 2
|
||||
coretlv.session.add_node(_id=switch, _type=NodeTypes.SWITCH)
|
||||
coretlv.session.add_node(SwitchNode, _id=switch)
|
||||
ip_prefix = netaddr.IPNetwork("10.0.0.0/24")
|
||||
interface_one = str(ip_prefix[node_one])
|
||||
message = coreapi.CoreLinkMessage.create(
|
||||
|
@ -275,7 +280,7 @@ class TestGui:
|
|||
],
|
||||
)
|
||||
coretlv.handle_message(message)
|
||||
switch_node = coretlv.session.get_node(switch)
|
||||
switch_node = coretlv.session.get_node(switch, SwitchNode)
|
||||
all_links = switch_node.all_link_data()
|
||||
assert len(all_links) == 1
|
||||
|
||||
|
@ -289,15 +294,15 @@ class TestGui:
|
|||
)
|
||||
coretlv.handle_message(message)
|
||||
|
||||
switch_node = coretlv.session.get_node(switch)
|
||||
switch_node = coretlv.session.get_node(switch, SwitchNode)
|
||||
all_links = switch_node.all_link_data()
|
||||
assert len(all_links) == 0
|
||||
|
||||
def test_link_delete_net_to_node(self, coretlv):
|
||||
def test_link_delete_net_to_node(self, coretlv: CoreHandler):
|
||||
node_one = 1
|
||||
coretlv.session.add_node(_id=node_one)
|
||||
coretlv.session.add_node(CoreNode, _id=node_one)
|
||||
switch = 2
|
||||
coretlv.session.add_node(_id=switch, _type=NodeTypes.SWITCH)
|
||||
coretlv.session.add_node(SwitchNode, _id=switch)
|
||||
ip_prefix = netaddr.IPNetwork("10.0.0.0/24")
|
||||
interface_one = str(ip_prefix[node_one])
|
||||
message = coreapi.CoreLinkMessage.create(
|
||||
|
@ -311,7 +316,7 @@ class TestGui:
|
|||
],
|
||||
)
|
||||
coretlv.handle_message(message)
|
||||
switch_node = coretlv.session.get_node(switch)
|
||||
switch_node = coretlv.session.get_node(switch, SwitchNode)
|
||||
all_links = switch_node.all_link_data()
|
||||
assert len(all_links) == 1
|
||||
|
||||
|
@ -325,11 +330,11 @@ class TestGui:
|
|||
)
|
||||
coretlv.handle_message(message)
|
||||
|
||||
switch_node = coretlv.session.get_node(switch)
|
||||
switch_node = coretlv.session.get_node(switch, SwitchNode)
|
||||
all_links = switch_node.all_link_data()
|
||||
assert len(all_links) == 0
|
||||
|
||||
def test_session_update(self, coretlv):
|
||||
def test_session_update(self, coretlv: CoreHandler):
|
||||
session_id = coretlv.session.id
|
||||
name = "test"
|
||||
message = coreapi.CoreSessionMessage.create(
|
||||
|
@ -340,7 +345,7 @@ class TestGui:
|
|||
|
||||
assert coretlv.session.name == name
|
||||
|
||||
def test_session_query(self, coretlv):
|
||||
def test_session_query(self, coretlv: CoreHandler):
|
||||
coretlv.dispatch_replies = mock.MagicMock()
|
||||
message = coreapi.CoreSessionMessage.create(MessageFlags.STRING.value, [])
|
||||
|
||||
|
@ -350,7 +355,7 @@ class TestGui:
|
|||
replies = args[0]
|
||||
assert len(replies) == 1
|
||||
|
||||
def test_session_join(self, coretlv):
|
||||
def test_session_join(self, coretlv: CoreHandler):
|
||||
coretlv.dispatch_replies = mock.MagicMock()
|
||||
session_id = coretlv.session.id
|
||||
message = coreapi.CoreSessionMessage.create(
|
||||
|
@ -361,7 +366,7 @@ class TestGui:
|
|||
|
||||
assert coretlv.session.id == session_id
|
||||
|
||||
def test_session_delete(self, coretlv):
|
||||
def test_session_delete(self, coretlv: CoreHandler):
|
||||
assert len(coretlv.coreemu.sessions) == 1
|
||||
session_id = coretlv.session.id
|
||||
message = coreapi.CoreSessionMessage.create(
|
||||
|
@ -372,7 +377,7 @@ class TestGui:
|
|||
|
||||
assert len(coretlv.coreemu.sessions) == 0
|
||||
|
||||
def test_file_hook_add(self, coretlv):
|
||||
def test_file_hook_add(self, coretlv: CoreHandler):
|
||||
state = EventTypes.DATACOLLECT_STATE
|
||||
assert coretlv.session._hooks.get(state) is None
|
||||
file_name = "test.sh"
|
||||
|
@ -394,8 +399,8 @@ class TestGui:
|
|||
assert file_name == name
|
||||
assert file_data == data
|
||||
|
||||
def test_file_service_file_set(self, coretlv):
|
||||
node = coretlv.session.add_node()
|
||||
def test_file_service_file_set(self, coretlv: CoreHandler):
|
||||
node = coretlv.session.add_node(CoreNode)
|
||||
service = "DefaultRoute"
|
||||
file_name = "defaultroute.sh"
|
||||
file_data = "echo hello"
|
||||
|
@ -416,9 +421,9 @@ class TestGui:
|
|||
)
|
||||
assert file_data == service_file.data
|
||||
|
||||
def test_file_node_file_copy(self, request, coretlv):
|
||||
def test_file_node_file_copy(self, request, coretlv: CoreHandler):
|
||||
file_name = "/var/log/test/node.log"
|
||||
node = coretlv.session.add_node()
|
||||
node = coretlv.session.add_node(CoreNode)
|
||||
node.makenodedir()
|
||||
file_data = "echo hello"
|
||||
message = coreapi.CoreFileMessage.create(
|
||||
|
@ -438,9 +443,9 @@ class TestGui:
|
|||
create_path = os.path.join(node.nodedir, created_directory, basename)
|
||||
assert os.path.exists(create_path)
|
||||
|
||||
def test_exec_node_tty(self, coretlv):
|
||||
def test_exec_node_tty(self, coretlv: CoreHandler):
|
||||
coretlv.dispatch_replies = mock.MagicMock()
|
||||
node = coretlv.session.add_node()
|
||||
node = coretlv.session.add_node(CoreNode)
|
||||
message = coreapi.CoreExecMessage.create(
|
||||
MessageFlags.TTY.value,
|
||||
[
|
||||
|
@ -456,12 +461,12 @@ class TestGui:
|
|||
replies = args[0]
|
||||
assert len(replies) == 1
|
||||
|
||||
def test_exec_local_command(self, request, coretlv):
|
||||
def test_exec_local_command(self, request, coretlv: CoreHandler):
|
||||
if request.config.getoption("mock"):
|
||||
pytest.skip("mocking calls")
|
||||
|
||||
coretlv.dispatch_replies = mock.MagicMock()
|
||||
node = coretlv.session.add_node()
|
||||
node = coretlv.session.add_node(CoreNode)
|
||||
cmd = "echo hello"
|
||||
message = coreapi.CoreExecMessage.create(
|
||||
MessageFlags.TEXT.value | MessageFlags.LOCAL.value,
|
||||
|
@ -478,9 +483,9 @@ class TestGui:
|
|||
replies = args[0]
|
||||
assert len(replies) == 1
|
||||
|
||||
def test_exec_node_command(self, coretlv):
|
||||
def test_exec_node_command(self, coretlv: CoreHandler):
|
||||
coretlv.dispatch_replies = mock.MagicMock()
|
||||
node = coretlv.session.add_node()
|
||||
node = coretlv.session.add_node(CoreNode)
|
||||
cmd = "echo hello"
|
||||
message = coreapi.CoreExecMessage.create(
|
||||
MessageFlags.TEXT.value,
|
||||
|
@ -506,16 +511,16 @@ class TestGui:
|
|||
EventTypes.DEFINITION_STATE,
|
||||
],
|
||||
)
|
||||
def test_event_state(self, coretlv, state):
|
||||
def test_event_state(self, coretlv: CoreHandler, state: EventTypes):
|
||||
message = coreapi.CoreEventMessage.create(0, [(EventTlvs.TYPE, state.value)])
|
||||
|
||||
coretlv.handle_message(message)
|
||||
|
||||
assert coretlv.session.state == state
|
||||
|
||||
def test_event_schedule(self, coretlv):
|
||||
def test_event_schedule(self, coretlv: CoreHandler):
|
||||
coretlv.session.add_event = mock.MagicMock()
|
||||
node = coretlv.session.add_node()
|
||||
node = coretlv.session.add_node(CoreNode)
|
||||
message = coreapi.CoreEventMessage.create(
|
||||
MessageFlags.ADD.value,
|
||||
[
|
||||
|
@ -531,10 +536,10 @@ class TestGui:
|
|||
|
||||
coretlv.session.add_event.assert_called_once()
|
||||
|
||||
def test_event_save_xml(self, coretlv, tmpdir):
|
||||
def test_event_save_xml(self, coretlv: CoreHandler, tmpdir):
|
||||
xml_file = tmpdir.join("coretlv.session.xml")
|
||||
file_path = xml_file.strpath
|
||||
coretlv.session.add_node()
|
||||
coretlv.session.add_node(CoreNode)
|
||||
message = coreapi.CoreEventMessage.create(
|
||||
0,
|
||||
[(EventTlvs.TYPE, EventTypes.FILE_SAVE.value), (EventTlvs.NAME, file_path)],
|
||||
|
@ -544,10 +549,10 @@ class TestGui:
|
|||
|
||||
assert os.path.exists(file_path)
|
||||
|
||||
def test_event_open_xml(self, coretlv, tmpdir):
|
||||
def test_event_open_xml(self, coretlv: CoreHandler, tmpdir):
|
||||
xml_file = tmpdir.join("coretlv.session.xml")
|
||||
file_path = xml_file.strpath
|
||||
node = coretlv.session.add_node()
|
||||
node = coretlv.session.add_node(CoreNode)
|
||||
coretlv.session.save_xml(file_path)
|
||||
coretlv.session.delete_node(node.id)
|
||||
message = coreapi.CoreEventMessage.create(
|
||||
|
@ -556,8 +561,7 @@ class TestGui:
|
|||
)
|
||||
|
||||
coretlv.handle_message(message)
|
||||
|
||||
assert coretlv.session.get_node(node.id)
|
||||
assert coretlv.session.get_node(node.id, NodeBase)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"state",
|
||||
|
@ -569,9 +573,9 @@ class TestGui:
|
|||
EventTypes.RECONFIGURE,
|
||||
],
|
||||
)
|
||||
def test_event_service(self, coretlv, state):
|
||||
def test_event_service(self, coretlv: CoreHandler, state: EventTypes):
|
||||
coretlv.session.broadcast_event = mock.MagicMock()
|
||||
node = coretlv.session.add_node()
|
||||
node = coretlv.session.add_node(CoreNode)
|
||||
message = coreapi.CoreEventMessage.create(
|
||||
0,
|
||||
[
|
||||
|
@ -595,21 +599,21 @@ class TestGui:
|
|||
EventTypes.RECONFIGURE,
|
||||
],
|
||||
)
|
||||
def test_event_mobility(self, coretlv, state):
|
||||
def test_event_mobility(self, coretlv: CoreHandler, state: EventTypes):
|
||||
message = coreapi.CoreEventMessage.create(
|
||||
0, [(EventTlvs.TYPE, state.value), (EventTlvs.NAME, "mobility:ns2script")]
|
||||
)
|
||||
|
||||
coretlv.handle_message(message)
|
||||
|
||||
def test_register_gui(self, coretlv):
|
||||
def test_register_gui(self, coretlv: CoreHandler):
|
||||
message = coreapi.CoreRegMessage.create(0, [(RegisterTlvs.GUI, "gui")])
|
||||
coretlv.handle_message(message)
|
||||
|
||||
def test_register_xml(self, coretlv, tmpdir):
|
||||
def test_register_xml(self, coretlv: CoreHandler, tmpdir):
|
||||
xml_file = tmpdir.join("coretlv.session.xml")
|
||||
file_path = xml_file.strpath
|
||||
node = coretlv.session.add_node()
|
||||
node = coretlv.session.add_node(CoreNode)
|
||||
coretlv.session.save_xml(file_path)
|
||||
coretlv.session.delete_node(node.id)
|
||||
message = coreapi.CoreRegMessage.create(
|
||||
|
@ -619,15 +623,16 @@ class TestGui:
|
|||
|
||||
coretlv.handle_message(message)
|
||||
|
||||
assert coretlv.coreemu.sessions[1].get_node(node.id)
|
||||
assert coretlv.coreemu.sessions[1].get_node(node.id, CoreNode)
|
||||
|
||||
def test_register_python(self, coretlv, tmpdir):
|
||||
def test_register_python(self, coretlv: CoreHandler, tmpdir):
|
||||
xml_file = tmpdir.join("test.py")
|
||||
file_path = xml_file.strpath
|
||||
with open(file_path, "w") as f:
|
||||
f.write("from core.nodes.base import CoreNode\n")
|
||||
f.write("coreemu = globals()['coreemu']\n")
|
||||
f.write(f"session = coreemu.sessions[{coretlv.session.id}]\n")
|
||||
f.write("session.add_node()\n")
|
||||
f.write("session.add_node(CoreNode)\n")
|
||||
message = coreapi.CoreRegMessage.create(
|
||||
0, [(RegisterTlvs.EXECUTE_SERVER, file_path)]
|
||||
)
|
||||
|
@ -637,7 +642,7 @@ class TestGui:
|
|||
|
||||
assert len(coretlv.session.nodes) == 1
|
||||
|
||||
def test_config_all(self, coretlv):
|
||||
def test_config_all(self, coretlv: CoreHandler):
|
||||
message = coreapi.CoreConfMessage.create(
|
||||
MessageFlags.ADD.value,
|
||||
[(ConfigTlvs.OBJECT, "all"), (ConfigTlvs.TYPE, ConfigFlags.RESET.value)],
|
||||
|
@ -648,7 +653,7 @@ class TestGui:
|
|||
|
||||
assert coretlv.session.location.refxyz == (0, 0, 0)
|
||||
|
||||
def test_config_options_request(self, coretlv):
|
||||
def test_config_options_request(self, coretlv: CoreHandler):
|
||||
message = coreapi.CoreConfMessage.create(
|
||||
0,
|
||||
[
|
||||
|
@ -662,7 +667,7 @@ class TestGui:
|
|||
|
||||
coretlv.handle_broadcast_config.assert_called_once()
|
||||
|
||||
def test_config_options_update(self, coretlv):
|
||||
def test_config_options_update(self, coretlv: CoreHandler):
|
||||
test_key = "test"
|
||||
test_value = "test"
|
||||
values = {test_key: test_value}
|
||||
|
@ -679,7 +684,7 @@ class TestGui:
|
|||
|
||||
assert coretlv.session.options.get_config(test_key) == test_value
|
||||
|
||||
def test_config_location_reset(self, coretlv):
|
||||
def test_config_location_reset(self, coretlv: CoreHandler):
|
||||
message = coreapi.CoreConfMessage.create(
|
||||
0,
|
||||
[
|
||||
|
@ -693,7 +698,7 @@ class TestGui:
|
|||
|
||||
assert coretlv.session.location.refxyz == (0, 0, 0)
|
||||
|
||||
def test_config_location_update(self, coretlv):
|
||||
def test_config_location_update(self, coretlv: CoreHandler):
|
||||
message = coreapi.CoreConfMessage.create(
|
||||
0,
|
||||
[
|
||||
|
@ -709,7 +714,7 @@ class TestGui:
|
|||
assert coretlv.session.location.refgeo == (70, 50, 0)
|
||||
assert coretlv.session.location.refscale == 0.5
|
||||
|
||||
def test_config_metadata_request(self, coretlv):
|
||||
def test_config_metadata_request(self, coretlv: CoreHandler):
|
||||
message = coreapi.CoreConfMessage.create(
|
||||
0,
|
||||
[
|
||||
|
@ -723,7 +728,7 @@ class TestGui:
|
|||
|
||||
coretlv.handle_broadcast_config.assert_called_once()
|
||||
|
||||
def test_config_metadata_update(self, coretlv):
|
||||
def test_config_metadata_update(self, coretlv: CoreHandler):
|
||||
test_key = "test"
|
||||
test_value = "test"
|
||||
values = {test_key: test_value}
|
||||
|
@ -740,7 +745,7 @@ class TestGui:
|
|||
|
||||
assert coretlv.session.metadata[test_key] == test_value
|
||||
|
||||
def test_config_broker_request(self, coretlv):
|
||||
def test_config_broker_request(self, coretlv: CoreHandler):
|
||||
server = "test"
|
||||
host = "10.0.0.1"
|
||||
port = 50000
|
||||
|
@ -758,7 +763,7 @@ class TestGui:
|
|||
|
||||
coretlv.session.distributed.add_server.assert_called_once_with(server, host)
|
||||
|
||||
def test_config_services_request_all(self, coretlv):
|
||||
def test_config_services_request_all(self, coretlv: CoreHandler):
|
||||
message = coreapi.CoreConfMessage.create(
|
||||
0,
|
||||
[
|
||||
|
@ -772,8 +777,8 @@ class TestGui:
|
|||
|
||||
coretlv.handle_broadcast_config.assert_called_once()
|
||||
|
||||
def test_config_services_request_specific(self, coretlv):
|
||||
node = coretlv.session.add_node()
|
||||
def test_config_services_request_specific(self, coretlv: CoreHandler):
|
||||
node = coretlv.session.add_node(CoreNode)
|
||||
message = coreapi.CoreConfMessage.create(
|
||||
0,
|
||||
[
|
||||
|
@ -789,8 +794,8 @@ class TestGui:
|
|||
|
||||
coretlv.handle_broadcast_config.assert_called_once()
|
||||
|
||||
def test_config_services_request_specific_file(self, coretlv):
|
||||
node = coretlv.session.add_node()
|
||||
def test_config_services_request_specific_file(self, coretlv: CoreHandler):
|
||||
node = coretlv.session.add_node(CoreNode)
|
||||
message = coreapi.CoreConfMessage.create(
|
||||
0,
|
||||
[
|
||||
|
@ -806,8 +811,8 @@ class TestGui:
|
|||
|
||||
coretlv.session.broadcast_file.assert_called_once()
|
||||
|
||||
def test_config_services_reset(self, coretlv):
|
||||
node = coretlv.session.add_node()
|
||||
def test_config_services_reset(self, coretlv: CoreHandler):
|
||||
node = coretlv.session.add_node(CoreNode)
|
||||
service = "DefaultRoute"
|
||||
coretlv.session.services.set_service(node.id, service)
|
||||
message = coreapi.CoreConfMessage.create(
|
||||
|
@ -823,8 +828,8 @@ class TestGui:
|
|||
|
||||
assert coretlv.session.services.get_service(node.id, service) is None
|
||||
|
||||
def test_config_services_set(self, coretlv):
|
||||
node = coretlv.session.add_node()
|
||||
def test_config_services_set(self, coretlv: CoreHandler):
|
||||
node = coretlv.session.add_node(CoreNode)
|
||||
service = "DefaultRoute"
|
||||
values = {"meta": "metadata"}
|
||||
message = coreapi.CoreConfMessage.create(
|
||||
|
@ -843,8 +848,8 @@ class TestGui:
|
|||
|
||||
assert coretlv.session.services.get_service(node.id, service) is not None
|
||||
|
||||
def test_config_mobility_reset(self, coretlv):
|
||||
wlan = coretlv.session.add_node(_type=NodeTypes.WIRELESS_LAN)
|
||||
def test_config_mobility_reset(self, coretlv: CoreHandler):
|
||||
wlan = coretlv.session.add_node(WlanNode)
|
||||
message = coreapi.CoreConfMessage.create(
|
||||
0,
|
||||
[
|
||||
|
@ -859,8 +864,8 @@ class TestGui:
|
|||
|
||||
assert len(coretlv.session.mobility.node_configurations) == 0
|
||||
|
||||
def test_config_mobility_model_request(self, coretlv):
|
||||
wlan = coretlv.session.add_node(_type=NodeTypes.WIRELESS_LAN)
|
||||
def test_config_mobility_model_request(self, coretlv: CoreHandler):
|
||||
wlan = coretlv.session.add_node(WlanNode)
|
||||
message = coreapi.CoreConfMessage.create(
|
||||
0,
|
||||
[
|
||||
|
@ -875,8 +880,8 @@ class TestGui:
|
|||
|
||||
coretlv.handle_broadcast_config.assert_called_once()
|
||||
|
||||
def test_config_mobility_model_update(self, coretlv):
|
||||
wlan = coretlv.session.add_node(_type=NodeTypes.WIRELESS_LAN)
|
||||
def test_config_mobility_model_update(self, coretlv: CoreHandler):
|
||||
wlan = coretlv.session.add_node(WlanNode)
|
||||
config_key = "range"
|
||||
config_value = "1000"
|
||||
values = {config_key: config_value}
|
||||
|
@ -897,8 +902,8 @@ class TestGui:
|
|||
)
|
||||
assert config[config_key] == config_value
|
||||
|
||||
def test_config_emane_model_request(self, coretlv):
|
||||
wlan = coretlv.session.add_node(_type=NodeTypes.WIRELESS_LAN)
|
||||
def test_config_emane_model_request(self, coretlv: CoreHandler):
|
||||
wlan = coretlv.session.add_node(WlanNode)
|
||||
message = coreapi.CoreConfMessage.create(
|
||||
0,
|
||||
[
|
||||
|
@ -913,8 +918,8 @@ class TestGui:
|
|||
|
||||
coretlv.handle_broadcast_config.assert_called_once()
|
||||
|
||||
def test_config_emane_model_update(self, coretlv):
|
||||
wlan = coretlv.session.add_node(_type=NodeTypes.WIRELESS_LAN)
|
||||
def test_config_emane_model_update(self, coretlv: CoreHandler):
|
||||
wlan = coretlv.session.add_node(WlanNode)
|
||||
config_key = "distance"
|
||||
config_value = "50051"
|
||||
values = {config_key: config_value}
|
||||
|
@ -935,7 +940,7 @@ class TestGui:
|
|||
)
|
||||
assert config[config_key] == config_value
|
||||
|
||||
def test_config_emane_request(self, coretlv):
|
||||
def test_config_emane_request(self, coretlv: CoreHandler):
|
||||
message = coreapi.CoreConfMessage.create(
|
||||
0,
|
||||
[
|
||||
|
@ -949,7 +954,7 @@ class TestGui:
|
|||
|
||||
coretlv.handle_broadcast_config.assert_called_once()
|
||||
|
||||
def test_config_emane_update(self, coretlv):
|
||||
def test_config_emane_update(self, coretlv: CoreHandler):
|
||||
config_key = "eventservicedevice"
|
||||
config_value = "eth4"
|
||||
values = {config_key: config_value}
|
||||
|
|
|
@ -1,11 +1,17 @@
|
|||
from core.emulator.emudata import LinkOptions
|
||||
from core.emulator.enumerations import NodeTypes
|
||||
from typing import Tuple
|
||||
|
||||
from core.emulator.emudata import IpPrefixes, LinkOptions
|
||||
from core.emulator.session import Session
|
||||
from core.nodes.base import CoreNode
|
||||
from core.nodes.network import SwitchNode
|
||||
|
||||
|
||||
def create_ptp_network(session, ip_prefixes):
|
||||
def create_ptp_network(
|
||||
session: Session, ip_prefixes: IpPrefixes
|
||||
) -> Tuple[CoreNode, CoreNode]:
|
||||
# create nodes
|
||||
node_one = session.add_node()
|
||||
node_two = session.add_node()
|
||||
node_one = session.add_node(CoreNode)
|
||||
node_two = session.add_node(CoreNode)
|
||||
|
||||
# link nodes to net node
|
||||
interface_one = ip_prefixes.create_interface(node_one)
|
||||
|
@ -19,10 +25,10 @@ def create_ptp_network(session, ip_prefixes):
|
|||
|
||||
|
||||
class TestLinks:
|
||||
def test_ptp(self, session, ip_prefixes):
|
||||
def test_ptp(self, session: Session, ip_prefixes: IpPrefixes):
|
||||
# given
|
||||
node_one = session.add_node()
|
||||
node_two = session.add_node()
|
||||
node_one = session.add_node(CoreNode)
|
||||
node_two = session.add_node(CoreNode)
|
||||
interface_one = ip_prefixes.create_interface(node_one)
|
||||
interface_two = ip_prefixes.create_interface(node_two)
|
||||
|
||||
|
@ -33,10 +39,10 @@ class TestLinks:
|
|||
assert node_one.netif(interface_one.id)
|
||||
assert node_two.netif(interface_two.id)
|
||||
|
||||
def test_node_to_net(self, session, ip_prefixes):
|
||||
def test_node_to_net(self, session: Session, ip_prefixes: IpPrefixes):
|
||||
# given
|
||||
node_one = session.add_node()
|
||||
node_two = session.add_node(_type=NodeTypes.SWITCH)
|
||||
node_one = session.add_node(CoreNode)
|
||||
node_two = session.add_node(SwitchNode)
|
||||
interface_one = ip_prefixes.create_interface(node_one)
|
||||
|
||||
# when
|
||||
|
@ -46,10 +52,10 @@ class TestLinks:
|
|||
assert node_two.all_link_data()
|
||||
assert node_one.netif(interface_one.id)
|
||||
|
||||
def test_net_to_node(self, session, ip_prefixes):
|
||||
def test_net_to_node(self, session: Session, ip_prefixes: IpPrefixes):
|
||||
# given
|
||||
node_one = session.add_node(_type=NodeTypes.SWITCH)
|
||||
node_two = session.add_node()
|
||||
node_one = session.add_node(SwitchNode)
|
||||
node_two = session.add_node(CoreNode)
|
||||
interface_two = ip_prefixes.create_interface(node_two)
|
||||
|
||||
# when
|
||||
|
@ -61,8 +67,8 @@ class TestLinks:
|
|||
|
||||
def test_net_to_net(self, session):
|
||||
# given
|
||||
node_one = session.add_node(_type=NodeTypes.SWITCH)
|
||||
node_two = session.add_node(_type=NodeTypes.SWITCH)
|
||||
node_one = session.add_node(SwitchNode)
|
||||
node_two = session.add_node(SwitchNode)
|
||||
|
||||
# when
|
||||
session.add_link(node_one.id, node_two.id)
|
||||
|
@ -70,15 +76,15 @@ class TestLinks:
|
|||
# then
|
||||
assert node_one.all_link_data()
|
||||
|
||||
def test_link_update(self, session, ip_prefixes):
|
||||
def test_link_update(self, session: Session, ip_prefixes: IpPrefixes):
|
||||
# given
|
||||
delay = 50
|
||||
bandwidth = 5000000
|
||||
per = 25
|
||||
dup = 25
|
||||
jitter = 10
|
||||
node_one = session.add_node()
|
||||
node_two = session.add_node(_type=NodeTypes.SWITCH)
|
||||
node_one = session.add_node(CoreNode)
|
||||
node_two = session.add_node(SwitchNode)
|
||||
interface_one_data = ip_prefixes.create_interface(node_one)
|
||||
session.add_link(node_one.id, node_two.id, interface_one_data)
|
||||
interface_one = node_one.netif(interface_one_data.id)
|
||||
|
@ -99,7 +105,7 @@ class TestLinks:
|
|||
node_one.id,
|
||||
node_two.id,
|
||||
interface_one_id=interface_one_data.id,
|
||||
link_options=link_options,
|
||||
options=link_options,
|
||||
)
|
||||
|
||||
# then
|
||||
|
@ -109,10 +115,10 @@ class TestLinks:
|
|||
assert interface_one.getparam("duplicate") == dup
|
||||
assert interface_one.getparam("jitter") == jitter
|
||||
|
||||
def test_link_delete(self, session, ip_prefixes):
|
||||
def test_link_delete(self, session: Session, ip_prefixes: IpPrefixes):
|
||||
# given
|
||||
node_one = session.add_node()
|
||||
node_two = session.add_node()
|
||||
node_one = session.add_node(CoreNode)
|
||||
node_two = session.add_node(CoreNode)
|
||||
interface_one = ip_prefixes.create_interface(node_one)
|
||||
interface_two = ip_prefixes.create_interface(node_two)
|
||||
session.add_link(node_one.id, node_two.id, interface_one, interface_two)
|
||||
|
|
|
@ -2,15 +2,17 @@ import pytest
|
|||
|
||||
from core.location.mobility import WayPoint
|
||||
|
||||
POSITION = (0.0, 0.0, 0.0)
|
||||
|
||||
|
||||
class TestMobility:
|
||||
@pytest.mark.parametrize(
|
||||
"wp1, wp2, expected",
|
||||
[
|
||||
(WayPoint(10.0, 1, [0, 0], 1.0), WayPoint(1.0, 2, [0, 0], 1.0), False),
|
||||
(WayPoint(1.0, 1, [0, 0], 1.0), WayPoint(10.0, 2, [0, 0], 1.0), True),
|
||||
(WayPoint(1.0, 1, [0, 0], 1.0), WayPoint(1.0, 2, [0, 0], 1.0), True),
|
||||
(WayPoint(1.0, 2, [0, 0], 1.0), WayPoint(1.0, 1, [0, 0], 1.0), False),
|
||||
(WayPoint(10.0, 1, POSITION, 1.0), WayPoint(1.0, 2, POSITION, 1.0), False),
|
||||
(WayPoint(1.0, 1, POSITION, 1.0), WayPoint(10.0, 2, POSITION, 1.0), True),
|
||||
(WayPoint(1.0, 1, POSITION, 1.0), WayPoint(1.0, 2, POSITION, 1.0), True),
|
||||
(WayPoint(1.0, 2, POSITION, 1.0), WayPoint(1.0, 1, POSITION, 1.0), False),
|
||||
],
|
||||
)
|
||||
def test_waypoint_lessthan(self, wp1, wp2, expected):
|
||||
|
|
|
@ -1,30 +1,32 @@
|
|||
import pytest
|
||||
|
||||
from core.emulator.emudata import NodeOptions
|
||||
from core.emulator.enumerations import NodeTypes
|
||||
from core.emulator.emudata import InterfaceData, NodeOptions
|
||||
from core.emulator.session import Session
|
||||
from core.errors import CoreError
|
||||
from core.nodes.base import CoreNode
|
||||
from core.nodes.network import HubNode, SwitchNode, WlanNode
|
||||
|
||||
MODELS = ["router", "host", "PC", "mdr"]
|
||||
NET_TYPES = [NodeTypes.SWITCH, NodeTypes.HUB, NodeTypes.WIRELESS_LAN]
|
||||
NET_TYPES = [SwitchNode, HubNode, WlanNode]
|
||||
|
||||
|
||||
class TestNodes:
|
||||
@pytest.mark.parametrize("model", MODELS)
|
||||
def test_node_add(self, session, model):
|
||||
def test_node_add(self, session: Session, model: str):
|
||||
# given
|
||||
options = NodeOptions(model=model)
|
||||
|
||||
# when
|
||||
node = session.add_node(options=options)
|
||||
node = session.add_node(CoreNode, options=options)
|
||||
|
||||
# then
|
||||
assert node
|
||||
assert node.alive()
|
||||
assert node.up
|
||||
|
||||
def test_node_update(self, session):
|
||||
def test_node_update(self, session: Session):
|
||||
# given
|
||||
node = session.add_node()
|
||||
node = session.add_node(CoreNode)
|
||||
position_value = 100
|
||||
update_options = NodeOptions()
|
||||
update_options.set_position(x=position_value, y=position_value)
|
||||
|
@ -36,21 +38,23 @@ class TestNodes:
|
|||
assert node.position.x == position_value
|
||||
assert node.position.y == position_value
|
||||
|
||||
def test_node_delete(self, session):
|
||||
def test_node_delete(self, session: Session):
|
||||
# given
|
||||
node = session.add_node()
|
||||
node = session.add_node(CoreNode)
|
||||
|
||||
# when
|
||||
session.delete_node(node.id)
|
||||
|
||||
# then
|
||||
with pytest.raises(CoreError):
|
||||
session.get_node(node.id)
|
||||
session.get_node(node.id, CoreNode)
|
||||
|
||||
def test_node_sethwaddr(self, session):
|
||||
def test_node_sethwaddr(self, session: Session):
|
||||
# given
|
||||
node = session.add_node()
|
||||
index = node.newnetif()
|
||||
node = session.add_node(CoreNode)
|
||||
switch = session.add_node(SwitchNode)
|
||||
interface_data = InterfaceData()
|
||||
index = node.newnetif(switch, interface_data)
|
||||
interface = node.netif(index)
|
||||
mac = "aa:aa:aa:ff:ff:ff"
|
||||
|
||||
|
@ -60,10 +64,12 @@ class TestNodes:
|
|||
# then
|
||||
assert interface.hwaddr == mac
|
||||
|
||||
def test_node_sethwaddr_exception(self, session):
|
||||
def test_node_sethwaddr_exception(self, session: Session):
|
||||
# given
|
||||
node = session.add_node()
|
||||
index = node.newnetif()
|
||||
node = session.add_node(CoreNode)
|
||||
switch = session.add_node(SwitchNode)
|
||||
interface_data = InterfaceData()
|
||||
index = node.newnetif(switch, interface_data)
|
||||
node.netif(index)
|
||||
mac = "aa:aa:aa:ff:ff:fff"
|
||||
|
||||
|
@ -71,10 +77,12 @@ class TestNodes:
|
|||
with pytest.raises(CoreError):
|
||||
node.sethwaddr(index, mac)
|
||||
|
||||
def test_node_addaddr(self, session):
|
||||
def test_node_addaddr(self, session: Session):
|
||||
# given
|
||||
node = session.add_node()
|
||||
index = node.newnetif()
|
||||
node = session.add_node(CoreNode)
|
||||
switch = session.add_node(SwitchNode)
|
||||
interface_data = InterfaceData()
|
||||
index = node.newnetif(switch, interface_data)
|
||||
interface = node.netif(index)
|
||||
addr = "192.168.0.1/24"
|
||||
|
||||
|
@ -86,8 +94,10 @@ class TestNodes:
|
|||
|
||||
def test_node_addaddr_exception(self, session):
|
||||
# given
|
||||
node = session.add_node()
|
||||
index = node.newnetif()
|
||||
node = session.add_node(CoreNode)
|
||||
switch = session.add_node(SwitchNode)
|
||||
interface_data = InterfaceData()
|
||||
index = node.newnetif(switch, interface_data)
|
||||
node.netif(index)
|
||||
addr = "256.168.0.1/24"
|
||||
|
||||
|
@ -100,7 +110,7 @@ class TestNodes:
|
|||
# given
|
||||
|
||||
# when
|
||||
node = session.add_node(_type=net_type)
|
||||
node = session.add_node(net_type)
|
||||
|
||||
# then
|
||||
assert node
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue