Merge pull request #467 from coreemu/develop

Develop
This commit is contained in:
bharnden 2020-06-11 15:43:10 -07:00 committed by GitHub
commit e0842197e3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
117 changed files with 2953 additions and 2749 deletions

View file

@ -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 ## 2020-05-11 CORE 6.4.0
* Enhancements * Enhancements
* updates to core-route-monitor, allow specific session, configurable settings, and properly * updates to core-route-monitor, allow specific session, configurable settings, and properly

View file

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

View file

@ -1,16 +1,14 @@
""" """
gRpc client for interfacing with CORE, when gRPC mode is enabled. gRpc client for interfacing with CORE.
""" """
import logging import logging
import threading import threading
from contextlib import contextmanager from contextlib import contextmanager
from typing import Any, Callable, Dict, Generator, List from typing import Any, Callable, Dict, Generator, Iterable, List
import grpc import grpc
import netaddr
from core import utils
from core.api.grpc import configservices_pb2, core_pb2, core_pb2_grpc from core.api.grpc import configservices_pb2, core_pb2, core_pb2_grpc
from core.api.grpc.configservices_pb2 import ( from core.api.grpc.configservices_pb2 import (
GetConfigServiceDefaultsRequest, GetConfigServiceDefaultsRequest,
@ -31,6 +29,8 @@ from core.api.grpc.emane_pb2 import (
EmaneLinkRequest, EmaneLinkRequest,
EmaneLinkResponse, EmaneLinkResponse,
EmaneModelConfig, EmaneModelConfig,
EmanePathlossesRequest,
EmanePathlossesResponse,
GetEmaneConfigRequest, GetEmaneConfigRequest,
GetEmaneConfigResponse, GetEmaneConfigResponse,
GetEmaneEventChannelRequest, GetEmaneEventChannelRequest,
@ -89,7 +89,10 @@ from core.api.grpc.wlan_pb2 import (
SetWlanConfigRequest, SetWlanConfigRequest,
SetWlanConfigResponse, SetWlanConfigResponse,
WlanConfig, WlanConfig,
WlanLinkRequest,
WlanLinkResponse,
) )
from core.emulator.emudata import IpPrefixes
class InterfaceHelper: class InterfaceHelper:
@ -105,78 +108,29 @@ class InterfaceHelper:
:param ip6_prefix: ip6 prefix to use for generation :param ip6_prefix: ip6 prefix to use for generation
:raises ValueError: when both ip4 and ip6 prefixes have not been provided :raises ValueError: when both ip4 and ip6 prefixes have not been provided
""" """
if not ip4_prefix and not ip6_prefix: self.prefixes = IpPrefixes(ip4_prefix, 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])
def create_interface( def create_interface(
self, node_id: int, interface_id: int, name: str = None, mac: str = None self, node_id: int, interface_id: int, name: str = None, mac: str = None
) -> core_pb2.Interface: ) -> core_pb2.Interface:
""" """
Creates interface data for linking nodes, using the nodes unique id for Create an interface protobuf object.
generation, along with a random mac address, unless provided.
:param node_id: node id to create interface for :param node_id: node id to create interface for
:param interface_id: interface id for interface :param interface_id: interface id
:param name: name to set for interface, default is eth{id} :param name: name of interface
:param mac: mac address to use for this interface, default is random :param mac: mac address for interface
generation :return: interface protobuf
:return: new interface data for the provided node
""" """
# generate ip4 data interface_data = self.prefixes.gen_interface(node_id, name, mac)
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 core_pb2.Interface( return core_pb2.Interface(
id=interface_id, id=interface_id,
name=name, name=interface_data.name,
ip4=ip4, ip4=interface_data.ip4,
ip4mask=ip4_mask, ip4mask=interface_data.ip4_mask,
ip6=ip6, ip6=interface_data.ip6,
ip6mask=ip6_mask, ip6mask=interface_data.ip6_mask,
mac=str(mac), mac=interface_data.mac,
) )
@ -285,6 +239,7 @@ class CoreGrpcClient:
:param session_id: id of session :param session_id: id of session
:return: stop session response :return: stop session response
:raises grpc.RpcError: when session doesn't exist
""" """
request = core_pb2.StopSessionRequest(session_id=session_id) request = core_pb2.StopSessionRequest(session_id=session_id)
return self.stub.StopSession(request) return self.stub.StopSession(request)
@ -569,6 +524,18 @@ class CoreGrpcClient:
) )
return self.stub.EditNode(request) 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: def delete_node(self, session_id: int, node_id: int) -> core_pb2.DeleteNodeResponse:
""" """
Delete node from session. Delete node from session.
@ -582,7 +549,12 @@ class CoreGrpcClient:
return self.stub.DeleteNode(request) return self.stub.DeleteNode(request)
def node_command( 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: ) -> core_pb2.NodeCommandResponse:
""" """
Send command to a node and get the output. Send command to a node and get the output.
@ -590,11 +562,17 @@ class CoreGrpcClient:
:param session_id: session id :param session_id: session id
:param node_id: node id :param node_id: node id
:param command: command to run on node :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 :return: response with command combined stdout/stderr
:raises grpc.RpcError: when session or node doesn't exist :raises grpc.RpcError: when session or node doesn't exist
""" """
request = core_pb2.NodeCommandRequest( 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) return self.stub.NodeCommand(request)
@ -1072,7 +1050,7 @@ class CoreGrpcClient:
session_id: int, session_id: int,
node_id: int, node_id: int,
model: str, model: str,
config: Dict[str, str], config: Dict[str, str] = None,
interface_id: int = -1, interface_id: int = -1,
) -> SetEmaneModelConfigResponse: ) -> SetEmaneModelConfigResponse:
""" """
@ -1096,9 +1074,9 @@ class CoreGrpcClient:
def get_emane_model_configs(self, session_id: int) -> GetEmaneModelConfigsResponse: 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 :return: response with a dictionary of node/interface ids to configurations
:raises grpc.RpcError: when session doesn't exist :raises grpc.RpcError: when session doesn't exist
""" """
@ -1109,9 +1087,10 @@ class CoreGrpcClient:
""" """
Save the current scenario to an XML file. 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 :param file_path: local path to save scenario XML file to
:return: nothing :return: nothing
:raises grpc.RpcError: when session doesn't exist
""" """
request = core_pb2.SaveXmlRequest(session_id=session_id) request = core_pb2.SaveXmlRequest(session_id=session_id)
response = self.stub.SaveXml(request) response = self.stub.SaveXml(request)
@ -1137,11 +1116,12 @@ class CoreGrpcClient:
""" """
Helps broadcast wireless link/unlink between EMANE nodes. Helps broadcast wireless link/unlink between EMANE nodes.
:param session_id: session id :param session_id: session to emane link
:param nem_one: :param nem_one: first nem for emane link
:param nem_two: :param nem_two: second nem for emane link
:param linked: True to link, False to unlink :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( request = EmaneLinkRequest(
session_id=session_id, nem_one=nem_one, nem_two=nem_two, linked=linked 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 Retrieves a list of interfaces available on the host machine that are not
a part of a CORE session. a part of a CORE session.
:return: core_pb2.GetInterfacesResponse :return: get interfaces response
""" """
request = core_pb2.GetInterfacesRequest() request = core_pb2.GetInterfacesRequest()
return self.stub.GetInterfaces(request) return self.stub.GetInterfaces(request)
def get_config_services(self) -> GetConfigServicesResponse: def get_config_services(self) -> GetConfigServicesResponse:
"""
Retrieve all known config services.
:return: get config services response
"""
request = GetConfigServicesRequest() request = GetConfigServicesRequest()
return self.stub.GetConfigServices(request) return self.stub.GetConfigServices(request)
def get_config_service_defaults( def get_config_service_defaults(
self, name: str self, name: str
) -> GetConfigServiceDefaultsResponse: ) -> GetConfigServiceDefaultsResponse:
"""
Retrieves config service default values.
:param name: name of service to get defaults for
:return: get config service defaults
"""
request = GetConfigServiceDefaultsRequest(name=name) request = GetConfigServiceDefaultsRequest(name=name)
return self.stub.GetConfigServiceDefaults(request) return self.stub.GetConfigServiceDefaults(request)
def get_node_config_service_configs( def get_node_config_service_configs(
self, session_id: int self, session_id: int
) -> GetNodeConfigServiceConfigsResponse: ) -> 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) request = GetNodeConfigServiceConfigsRequest(session_id=session_id)
return self.stub.GetNodeConfigServiceConfigs(request) return self.stub.GetNodeConfigServiceConfigs(request)
def get_node_config_service( def get_node_config_service(
self, session_id: int, node_id: int, name: str self, session_id: int, node_id: int, name: str
) -> GetNodeConfigServiceResponse: ) -> 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( request = GetNodeConfigServiceRequest(
session_id=session_id, node_id=node_id, name=name session_id=session_id, node_id=node_id, name=name
) )
@ -1185,25 +1192,92 @@ class CoreGrpcClient:
def get_node_config_services( def get_node_config_services(
self, session_id: int, node_id: int self, session_id: int, node_id: int
) -> GetNodeConfigServicesResponse: ) -> 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) request = GetNodeConfigServicesRequest(session_id=session_id, node_id=node_id)
return self.stub.GetNodeConfigServices(request) return self.stub.GetNodeConfigServices(request)
def set_node_config_service( def set_node_config_service(
self, session_id: int, node_id: int, name: str, config: Dict[str, str] self, session_id: int, node_id: int, name: str, config: Dict[str, str]
) -> SetNodeConfigServiceResponse: ) -> 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( request = SetNodeConfigServiceRequest(
session_id=session_id, node_id=node_id, name=name, config=config session_id=session_id, node_id=node_id, name=name, config=config
) )
return self.stub.SetNodeConfigService(request) return self.stub.SetNodeConfigService(request)
def get_emane_event_channel(self, session_id: int) -> GetEmaneEventChannelResponse: 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) request = GetEmaneEventChannelRequest(session_id=session_id)
return self.stub.GetEmaneEventChannel(request) return self.stub.GetEmaneEventChannel(request)
def execute_script(self, script: str) -> ExecuteScriptResponse: 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) request = ExecuteScriptRequest(script=script)
return self.stub.ExecuteScript(request) 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: def connect(self) -> None:
""" """
Open connection to server, must be closed manually. Open connection to server, must be closed manually.

View file

@ -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 :return: node event that contains node id, name, model, position, and services
""" """
position = core_pb2.Position(x=event.x_position, y=event.y_position) 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( node_proto = core_pb2.Node(
id=event.id, id=event.id,
name=event.name, name=event.name,
model=event.model, model=event.model,
position=position, position=position,
geo=geo,
services=event.services, services=event.services,
) )
return core_pb2.NodeEvent(node=node_proto, source=event.source) return core_pb2.NodeEvent(node=node_proto, source=event.source)

View file

@ -2,7 +2,9 @@ import logging
import time import time
from typing import Any, Dict, List, Tuple, Type from typing import Any, Dict, List, Tuple, Type
import grpc
import netaddr import netaddr
from grpc import ServicerContext
from core import utils from core import utils
from core.api.grpc import common_pb2, core_pb2 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.emudata import InterfaceData, LinkOptions, NodeOptions
from core.emulator.enumerations import LinkTypes, NodeTypes from core.emulator.enumerations import LinkTypes, NodeTypes
from core.emulator.session import Session from core.emulator.session import Session
from core.nodes.base import NodeBase from core.nodes.base import CoreNode, NodeBase
from core.nodes.interface import CoreInterface from core.nodes.interface import CoreInterface
from core.services.coreservices import CoreService 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 _id = node_proto.id
_type = NodeTypes(node_proto.type) _type = NodeTypes(node_proto.type)
options = NodeOptions(name=node_proto.name, model=node_proto.model) options = NodeOptions(
options.icon = node_proto.icon name=node_proto.name,
options.opaque = node_proto.opaque model=node_proto.model,
options.image = node_proto.image icon=node_proto.icon,
options.services = node_proto.services opaque=node_proto.opaque,
options.config_services = node_proto.config_services image=node_proto.image,
services=node_proto.services,
config_services=node_proto.config_services,
)
if node_proto.emane: if node_proto.emane:
options.emane = node_proto.emane options.emane = node_proto.emane
if node_proto.server: if node_proto.server:
options.server = node_proto.server options.server = node_proto.server
position = node_proto.position position = node_proto.position
options.set_position(position.x, position.y) options.set_position(position.x, position.y)
if node_proto.HasField("geo"): if node_proto.HasField("geo"):
@ -57,19 +61,17 @@ def link_interface(interface_proto: core_pb2.Interface) -> InterfaceData:
""" """
interface = None interface = None
if interface_proto: if interface_proto:
name = interface_proto.name name = interface_proto.name if interface_proto.name else None
if name == "": mac = interface_proto.mac if interface_proto.mac else None
name = None ip4 = interface_proto.ip4 if interface_proto.ip4 else None
mac = interface_proto.mac ip6 = interface_proto.ip6 if interface_proto.ip6 else None
if mac == "":
mac = None
interface = InterfaceData( interface = InterfaceData(
_id=interface_proto.id, id=interface_proto.id,
name=name, name=name,
mac=mac, mac=mac,
ip4=interface_proto.ip4, ip4=ip4,
ip4_mask=interface_proto.ip4mask, ip4_mask=interface_proto.ip4mask,
ip6=interface_proto.ip6, ip6=ip6,
ip6_mask=interface_proto.ip6mask, ip6_mask=interface_proto.ip6mask,
) )
return interface return interface
@ -86,13 +88,8 @@ def add_link_data(
""" """
interface_one = link_interface(link_proto.interface_one) interface_one = link_interface(link_proto.interface_one)
interface_two = link_interface(link_proto.interface_two) interface_two = link_interface(link_proto.interface_two)
link_type = LinkTypes(link_proto.type)
link_type = None options = LinkOptions(type=link_type)
link_type_value = link_proto.type
if link_type_value is not None:
link_type = LinkTypes(link_type_value)
options = LinkOptions(_type=link_type)
options_data = link_proto.options options_data = link_proto.options
if options_data: if options_data:
options.delay = options_data.delay options.delay = options_data.delay
@ -106,7 +103,6 @@ def add_link_data(
options.unidirectional = options_data.unidirectional options.unidirectional = options_data.unidirectional
options.key = options_data.key options.key = options_data.key
options.opaque = options_data.opaque options.opaque = options_data.opaque
return interface_one, interface_two, options return interface_one, interface_two, options
@ -123,7 +119,8 @@ def create_nodes(
funcs = [] funcs = []
for node_proto in node_protos: for node_proto in node_protos:
_type, _id, options = add_node_data(node_proto) _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, {})) funcs.append((session.add_node, args, {}))
start = time.monotonic() start = time.monotonic()
results, exceptions = utils.threadpool(funcs) results, exceptions = utils.threadpool(funcs)
@ -234,6 +231,9 @@ def get_node_proto(session: Session, node: NodeBase) -> core_pb2.Node:
position = core_pb2.Position( position = core_pb2.Position(
x=node.position.x, y=node.position.y, z=node.position.z 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", []) services = getattr(node, "services", [])
if services is None: if services is None:
services = [] services = []
@ -254,6 +254,7 @@ def get_node_proto(session: Session, node: NodeBase) -> core_pb2.Node:
model=model, model=model,
type=node_type.value, type=node_type.value,
position=position, position=position,
geo=geo,
services=services, services=services,
icon=node.icon, icon=node.icon,
image=image, image=image,
@ -473,3 +474,23 @@ def interface_to_proto(interface: CoreInterface) -> core_pb2.Interface:
ip6=ip6, ip6=ip6,
ip6mask=ip6mask, 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)

View file

@ -6,7 +6,7 @@ import tempfile
import threading import threading
import time import time
from concurrent import futures from concurrent import futures
from typing import Type from typing import Iterable, Type
import grpc import grpc
from grpc import ServicerContext from grpc import ServicerContext
@ -39,6 +39,8 @@ from core.api.grpc.core_pb2 import ExecuteScriptResponse
from core.api.grpc.emane_pb2 import ( from core.api.grpc.emane_pb2 import (
EmaneLinkRequest, EmaneLinkRequest,
EmaneLinkResponse, EmaneLinkResponse,
EmanePathlossesRequest,
EmanePathlossesResponse,
GetEmaneConfigRequest, GetEmaneConfigRequest,
GetEmaneConfigResponse, GetEmaneConfigResponse,
GetEmaneEventChannelRequest, GetEmaneEventChannelRequest,
@ -102,15 +104,18 @@ from core.api.grpc.wlan_pb2 import (
GetWlanConfigsResponse, GetWlanConfigsResponse,
SetWlanConfigRequest, SetWlanConfigRequest,
SetWlanConfigResponse, SetWlanConfigResponse,
WlanLinkRequest,
WlanLinkResponse,
) )
from core.emulator.coreemu import CoreEmu from core.emulator.coreemu import CoreEmu
from core.emulator.data import LinkData from core.emulator.data import LinkData
from core.emulator.emudata import LinkOptions, NodeOptions from core.emulator.emudata import LinkOptions, NodeOptions
from core.emulator.enumerations import EventTypes, LinkTypes, MessageFlags 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.errors import CoreCommandError, CoreError
from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility 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 from core.services.coreservices import ServiceManager
_ONE_DAY_IN_SECONDS = 60 * 60 * 24 _ONE_DAY_IN_SECONDS = 60 * 60 * 24
@ -170,21 +175,22 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
return session return session
def get_node( def get_node(
self, session: Session, node_id: int, context: ServicerContext self, session: Session, node_id: int, context: ServicerContext, _class: Type[NT]
) -> NodeBase: ) -> NT:
""" """
Retrieve node given session and node id Retrieve node given session and node id
:param session: session that has the node :param session: session that has the node
:param node_id: node id :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. :return: node object that satisfies. If node not found then raise an exception.
:raises Exception: raises grpc exception when node does not exist :raises Exception: raises grpc exception when node does not exist
""" """
try: try:
return session.get_node(node_id) return session.get_node(node_id, _class)
except CoreError: except CoreError as e:
context.abort(grpc.StatusCode.NOT_FOUND, f"node {node_id} not found") context.abort(grpc.StatusCode.NOT_FOUND, str(e))
def validate_service( def validate_service(
self, name: str, context: ServicerContext self, name: str, context: ServicerContext
@ -228,7 +234,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
# add all hooks # add all hooks
for hook in request.hooks: for hook in request.hooks:
state = EventTypes(hook.state) state = EventTypes(hook.state)
session.add_hook(state, hook.file, None, hook.data) session.add_hook(state, hook.file, hook.data)
# create nodes # create nodes
_, exceptions = grpcutils.create_nodes(session, request.nodes) _, exceptions = grpcutils.create_nodes(session, request.nodes)
@ -261,7 +267,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
# config service configs # config service configs
for config in request.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] service = node.config_services[config.name]
if config.config: if config.config:
service.set_config(config.config) service.set_config(config.config)
@ -663,7 +669,8 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
logging.debug("add node: %s", request) logging.debug("add node: %s", request)
session = self.get_session(request.session_id, context) session = self.get_session(request.session_id, context)
_type, _id, options = grpcutils.add_node_data(request.node) _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) return core_pb2.AddNodeResponse(node_id=node.id)
def GetNode( def GetNode(
@ -678,7 +685,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
""" """
logging.debug("get node: %s", request) logging.debug("get node: %s", request)
session = self.get_session(request.session_id, context) session = self.get_session(request.session_id, context)
node = self.get_node(session, request.node_id, context) node = self.get_node(session, request.node_id, context, NodeBase)
interfaces = [] interfaces = []
for interface_id in node._netif: for interface_id in node._netif:
interface = node._netif[interface_id] interface = node._netif[interface_id]
@ -687,6 +694,42 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
node_proto = grpcutils.get_node_proto(session, node) node_proto = grpcutils.get_node_proto(session, node)
return core_pb2.GetNodeResponse(node=node_proto, interfaces=interfaces) 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( def EditNode(
self, request: core_pb2.EditNodeRequest, context: ServicerContext self, request: core_pb2.EditNodeRequest, context: ServicerContext
) -> core_pb2.EditNodeResponse: ) -> core_pb2.EditNodeResponse:
@ -699,9 +742,8 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
""" """
logging.debug("edit node: %s", request) logging.debug("edit node: %s", request)
session = self.get_session(request.session_id, context) session = self.get_session(request.session_id, context)
node = self.get_node(session, request.node_id, context) node = self.get_node(session, request.node_id, context, NodeBase)
options = NodeOptions() options = NodeOptions(icon=request.icon)
options.icon = request.icon
if request.HasField("position"): if request.HasField("position"):
x = request.position.x x = request.position.x
y = request.position.y y = request.position.y
@ -751,12 +793,14 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
""" """
logging.debug("sending node command: %s", request) logging.debug("sending node command: %s", request)
session = self.get_session(request.session_id, context) session = self.get_session(request.session_id, context)
node = self.get_node(session, request.node_id, context) node = self.get_node(session, request.node_id, context, CoreNode)
try: try:
output = node.cmd(request.command) output = node.cmd(request.command, request.wait, request.shell)
return_code = 0
except CoreCommandError as e: except CoreCommandError as e:
output = e.stderr 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( def GetNodeTerminal(
self, request: core_pb2.GetNodeTerminalRequest, context: ServicerContext self, request: core_pb2.GetNodeTerminalRequest, context: ServicerContext
@ -770,7 +814,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
""" """
logging.debug("getting node terminal: %s", request) logging.debug("getting node terminal: %s", request)
session = self.get_session(request.session_id, context) session = self.get_session(request.session_id, context)
node = self.get_node(session, request.node_id, context) node = self.get_node(session, request.node_id, context, CoreNode)
terminal = node.termcmdstring("/bin/bash") terminal = node.termcmdstring("/bin/bash")
return core_pb2.GetNodeTerminalResponse(terminal=terminal) return core_pb2.GetNodeTerminalResponse(terminal=terminal)
@ -786,7 +830,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
""" """
logging.debug("get node links: %s", request) logging.debug("get node links: %s", request)
session = self.get_session(request.session_id, context) session = self.get_session(request.session_id, context)
node = self.get_node(session, request.node_id, context) node = self.get_node(session, request.node_id, context, NodeBase)
links = get_links(node) links = get_links(node)
return core_pb2.GetNodeLinksResponse(links=links) return core_pb2.GetNodeLinksResponse(links=links)
@ -803,14 +847,14 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
logging.debug("add link: %s", request) logging.debug("add link: %s", request)
# validate session and nodes # validate session and nodes
session = self.get_session(request.session_id, context) 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_one_id, context, NodeBase)
self.get_node(session, request.link.node_two_id, context) self.get_node(session, request.link.node_two_id, context, NodeBase)
node_one_id = request.link.node_one_id node_one_id = request.link.node_one_id
node_two_id = request.link.node_two_id node_two_id = request.link.node_two_id
interface_one, interface_two, options = grpcutils.add_link_data(request.link) interface_one, interface_two, options = grpcutils.add_link_data(request.link)
node_one_interface, node_two_interface = session.add_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_one_proto = None
interface_two_proto = None interface_two_proto = None
@ -913,7 +957,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
session = self.get_session(request.session_id, context) session = self.get_session(request.session_id, context)
hook = request.hook hook = request.hook
state = EventTypes(hook.state) 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) return core_pb2.AddHookResponse(result=True)
def GetMobilityConfigs( def GetMobilityConfigs(
@ -994,7 +1038,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
""" """
logging.debug("mobility action: %s", request) logging.debug("mobility action: %s", request)
session = self.get_session(request.session_id, context) session = self.get_session(request.session_id, context)
node = self.get_node(session, request.node_id, context) node = self.get_node(session, request.node_id, context, WlanNode)
result = True result = True
if request.action == MobilityAction.START: if request.action == MobilityAction.START:
node.mobility.start() node.mobility.start()
@ -1121,7 +1165,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
""" """
logging.debug("get node service file: %s", request) logging.debug("get node service file: %s", request)
session = self.get_session(request.session_id, context) session = self.get_session(request.session_id, context)
node = self.get_node(session, request.node_id, context) node = self.get_node(session, request.node_id, context, CoreNode)
file_data = session.services.get_service_file( file_data = session.services.get_service_file(
node, request.service, request.file node, request.service, request.file
) )
@ -1176,7 +1220,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
""" """
logging.debug("service action: %s", request) logging.debug("service action: %s", request)
session = self.get_session(request.session_id, context) session = self.get_session(request.session_id, context)
node = self.get_node(session, request.node_id, context) node = self.get_node(session, request.node_id, context, CoreNode)
service = None service = None
for current_service in node.services: for current_service in node.services:
if current_service.name == request.service: 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 wlan_config.node_id, BasicRangeModel.name, wlan_config.config
) )
if session.state == EventTypes.RUNTIME_STATE: 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) node.updatemodel(wlan_config.config)
return SetWlanConfigResponse(result=True) return SetWlanConfigResponse(result=True)
@ -1546,7 +1590,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
:return: get node config service response :return: get node config service response
""" """
session = self.get_session(request.session_id, context) session = self.get_session(request.session_id, context)
node = self.get_node(session, request.node_id, context) node = self.get_node(session, request.node_id, context, CoreNode)
self.validate_service(request.name, context) self.validate_service(request.name, context)
service = node.config_services.get(request.name) service = node.config_services.get(request.name)
if service: if service:
@ -1628,7 +1672,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
:return: get node config services response :return: get node config services response
""" """
session = self.get_session(request.session_id, context) session = self.get_session(request.session_id, context)
node = self.get_node(session, request.node_id, context) node = self.get_node(session, request.node_id, context, CoreNode)
services = node.config_services.keys() services = node.config_services.keys()
return GetNodeConfigServicesResponse(services=services) return GetNodeConfigServicesResponse(services=services)
@ -1643,7 +1687,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
:return: set node config service response :return: set node config service response
""" """
session = self.get_session(request.session_id, context) session = self.get_session(request.session_id, context)
node = self.get_node(session, request.node_id, context) node = self.get_node(session, request.node_id, context, CoreNode)
self.validate_service(request.name, context) self.validate_service(request.name, context)
service = node.config_services.get(request.name) service = node.config_services.get(request.name)
if service: if service:
@ -1684,3 +1728,45 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
if new_sessions: if new_sessions:
new_session = new_sessions[0] new_session = new_sessions[0]
return ExecuteScriptResponse(session_id=new_session) 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()

View file

@ -41,6 +41,7 @@ from core.emulator.enumerations import (
) )
from core.errors import CoreCommandError, CoreError from core.errors import CoreCommandError, CoreError
from core.location.mobility import BasicRangeModel from core.location.mobility import BasicRangeModel
from core.nodes.base import CoreNode, CoreNodeBase, NodeBase
from core.nodes.network import WlanNode from core.nodes.network import WlanNode
from core.services.coreservices import ServiceManager, ServiceShim from core.services.coreservices import ServiceManager, ServiceShim
@ -78,15 +79,9 @@ class CoreHandler(socketserver.BaseRequestHandler):
self._sessions_lock = threading.Lock() self._sessions_lock = threading.Lock()
self.handler_threads = [] self.handler_threads = []
num_threads = int(server.config["numthreads"]) thread = threading.Thread(target=self.handler_thread, daemon=True)
if num_threads < 1: thread.start()
raise ValueError(f"invalid number of threads: {num_threads}") self.handler_threads.append(thread)
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()
self.session = None self.session = None
self.coreemu = server.coreemu self.coreemu = server.coreemu
@ -681,10 +676,11 @@ class CoreHandler(socketserver.BaseRequestHandler):
logging.warning("ignoring invalid message: add and delete flag both set") logging.warning("ignoring invalid message: add and delete flag both set")
return () return ()
node_type = None _class = CoreNode
node_type_value = message.get_tlv(NodeTlvs.TYPE.value) node_type_value = message.get_tlv(NodeTlvs.TYPE.value)
if node_type_value is not None: if node_type_value is not None:
node_type = NodeTypes(node_type_value) node_type = NodeTypes(node_type_value)
_class = self.session.get_node_class(node_type)
node_id = message.get_tlv(NodeTlvs.NUMBER.value) node_id = message.get_tlv(NodeTlvs.NUMBER.value)
@ -719,7 +715,7 @@ class CoreHandler(socketserver.BaseRequestHandler):
options.services = services.split("|") options.services = services.split("|")
if message.flags & MessageFlags.ADD.value: 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 node:
if message.flags & MessageFlags.STRING.value: if message.flags & MessageFlags.STRING.value:
self.node_status_request[node.id] = True 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) node_two_id = message.get_tlv(LinkTlvs.N2_NUMBER.value)
interface_one = InterfaceData( 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), name=message.get_tlv(LinkTlvs.INTERFACE1_NAME.value),
mac=message.get_tlv(LinkTlvs.INTERFACE1_MAC.value), mac=message.get_tlv(LinkTlvs.INTERFACE1_MAC.value),
ip4=message.get_tlv(LinkTlvs.INTERFACE1_IP4.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), ip6_mask=message.get_tlv(LinkTlvs.INTERFACE1_IP6_MASK.value),
) )
interface_two = InterfaceData( 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), name=message.get_tlv(LinkTlvs.INTERFACE2_NAME.value),
mac=message.get_tlv(LinkTlvs.INTERFACE2_MAC.value), mac=message.get_tlv(LinkTlvs.INTERFACE2_MAC.value),
ip4=message.get_tlv(LinkTlvs.INTERFACE2_IP4.value), ip4=message.get_tlv(LinkTlvs.INTERFACE2_IP4.value),
@ -776,7 +772,7 @@ class CoreHandler(socketserver.BaseRequestHandler):
if link_type_value is not None: if link_type_value is not None:
link_type = LinkTypes(link_type_value) 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.delay = message.get_tlv(LinkTlvs.DELAY.value)
link_options.bandwidth = message.get_tlv(LinkTlvs.BANDWIDTH.value) link_options.bandwidth = message.get_tlv(LinkTlvs.BANDWIDTH.value)
link_options.session = message.get_tlv(LinkTlvs.SESSION.value) link_options.session = message.get_tlv(LinkTlvs.SESSION.value)
@ -836,7 +832,7 @@ class CoreHandler(socketserver.BaseRequestHandler):
return () return ()
try: try:
node = self.session.get_node(node_num) node = self.session.get_node(node_num, CoreNodeBase)
# build common TLV items for reply # build common TLV items for reply
tlv_data = b"" tlv_data = b""
@ -880,12 +876,7 @@ class CoreHandler(socketserver.BaseRequestHandler):
except CoreCommandError as e: except CoreCommandError as e:
res = e.stderr res = e.stderr
status = e.returncode status = e.returncode
logging.info( logging.info("done exec cmd=%s with status=%d", command, status)
"done exec cmd=%s with status=%d res=(%d bytes)",
command,
status,
len(res),
)
if message.flags & MessageFlags.TEXT.value: if message.flags & MessageFlags.TEXT.value:
tlv_data += coreapi.CoreExecuteTlv.pack( tlv_data += coreapi.CoreExecuteTlv.pack(
ExecuteTlvs.RESULT.value, res ExecuteTlvs.RESULT.value, res
@ -1233,7 +1224,7 @@ class CoreHandler(socketserver.BaseRequestHandler):
if not node_id: if not node_id:
return replies return replies
node = self.session.get_node(node_id) node = self.session.get_node(node_id, CoreNodeBase)
if node is None: if node is None:
logging.warning( logging.warning(
"request to configure service for unknown node %s", node_id "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) self.session.mobility.set_model_config(node_id, object_name, parsed_config)
if self.session.state == EventTypes.RUNTIME_STATE and parsed_config: if self.session.state == EventTypes.RUNTIME_STATE and parsed_config:
try: try:
node = self.session.get_node(node_id) node = self.session.get_node(node_id, WlanNode)
if object_name == BasicRangeModel.name: if object_name == BasicRangeModel.name:
node.updatemodel(parsed_config) node.updatemodel(parsed_config)
except CoreError: except CoreError:
@ -1504,7 +1495,7 @@ class CoreHandler(socketserver.BaseRequestHandler):
return () return ()
state = int(state) state = int(state)
state = EventTypes(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 () return ()
# writing a file to the host # 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()) logging.debug("handling event %s at %s", event_type.name, time.ctime())
if event_type.value <= EventTypes.SHUTDOWN_STATE.value: if event_type.value <= EventTypes.SHUTDOWN_STATE.value:
if node_id is not None: 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 # configure mobility models for WLAN added during runtime
if event_type == EventTypes.INSTANTIATION_STATE and isinstance( if event_type == EventTypes.INSTANTIATION_STATE and isinstance(
@ -1652,7 +1643,7 @@ class CoreHandler(socketserver.BaseRequestHandler):
name = event_data.name name = event_data.name
try: try:
node = self.session.get_node(node_id) node = self.session.get_node(node_id, CoreNodeBase)
except CoreError: except CoreError:
logging.warning( logging.warning(
"ignoring event for service '%s', unknown node '%s'", name, node_id "ignoring event for service '%s', unknown node '%s'", name, node_id
@ -1888,7 +1879,7 @@ class CoreHandler(socketserver.BaseRequestHandler):
data_types = tuple( data_types = tuple(
repeat(ConfigDataTypes.STRING.value, len(ServiceShim.keys)) 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) values = ServiceShim.tovaluelist(node, service)
config_data = ConfigData( config_data = ConfigData(
message_type=0, message_type=0,

View file

@ -10,6 +10,9 @@ from lxml import etree
from core.config import ConfigGroup, Configuration from core.config import ConfigGroup, Configuration
from core.emane import emanemanifest, emanemodel 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.nodes.interface import CoreInterface
from core.xml import emanexml from core.xml import emanexml
@ -78,9 +81,9 @@ class EmaneCommEffectModel(emanemodel.EmaneModel):
# create and write nem document # create and write nem document
nem_element = etree.Element("nem", name=f"{self.name} NEM", type="unstructured") nem_element = etree.Element("nem", name=f"{self.name} NEM", type="unstructured")
transport_type = "virtual" transport_type = TransportType.VIRTUAL
if interface and interface.transport_type == "raw": if interface and interface.transport_type == TransportType.RAW:
transport_type = "raw" transport_type = TransportType.RAW
transport_file = emanexml.transport_file_name(self.id, transport_type) transport_file = emanexml.transport_file_name(self.id, transport_type)
etree.SubElement(nem_element, "transport", definition=transport_file) etree.SubElement(nem_element, "transport", definition=transport_file)
@ -112,14 +115,7 @@ class EmaneCommEffectModel(emanemodel.EmaneModel):
emanexml.create_file(shim_element, "shim", shim_file) emanexml.create_file(shim_element, "shim", shim_file)
def linkconfig( def linkconfig(
self, self, netif: CoreInterface, options: LinkOptions, netif2: CoreInterface = None
netif: CoreInterface,
bw: float = None,
delay: float = None,
loss: float = None,
duplicate: float = None,
jitter: float = None,
netif2: CoreInterface = None,
) -> None: ) -> None:
""" """
Generate CommEffect events when a Link Message is received having 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: batch these into multiple events per transmission
# TODO: may want to split out seconds portion of delay and jitter # TODO: may want to split out seconds portion of delay and jitter
event = CommEffectEvent() event = CommEffectEvent()
emane_node = self.session.get_node(self.id) emane_node = self.session.get_node(self.id, EmaneNet)
nemid = emane_node.getnemid(netif) nemid = emane_node.getnemid(netif)
nemid2 = emane_node.getnemid(netif2) nemid2 = emane_node.getnemid(netif2)
mbw = bw
logging.info("sending comm effect event") logging.info("sending comm effect event")
event.append( event.append(
nemid, nemid,
latency=convert_none(delay), latency=convert_none(options.delay),
jitter=convert_none(jitter), jitter=convert_none(options.jitter),
loss=convert_none(loss), loss=convert_none(options.per),
duplicate=convert_none(duplicate), duplicate=convert_none(options.dup),
unicast=int(convert_none(bw)), unicast=int(convert_none(options.bandwidth)),
broadcast=int(convert_none(mbw)), broadcast=int(convert_none(options.bandwidth)),
) )
service.publish(nemid2, event) service.publish(nemid2, event)

View file

@ -6,7 +6,7 @@ import logging
import os import os
import threading import threading
from collections import OrderedDict 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 import utils
from core.config import ConfigGroup, Configuration, ModelManager 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.nodes import EmaneNet
from core.emane.rfpipe import EmaneRfPipeModel from core.emane.rfpipe import EmaneRfPipeModel
from core.emane.tdma import EmaneTdmaModel 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.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.interface import CoreInterface
from core.nodes.network import CtrlNet from core.nodes.network import CtrlNet
from core.nodes.physical import Rj45Node
from core.xml import emanexml from core.xml import emanexml
if TYPE_CHECKING: if TYPE_CHECKING:
from core.emulator.session import Session from core.emulator.session import Session
try: try:
from emane.events import EventService from emane.events import EventService, PathlossEvent
from emane.events import LocationEvent from emane.events import LocationEvent
from emane.events.eventserviceexception import EventServiceException from emane.events.eventserviceexception import EventServiceException
except ImportError: except ImportError:
@ -41,6 +48,7 @@ except ImportError:
except ImportError: except ImportError:
EventService = None EventService = None
LocationEvent = None LocationEvent = None
PathlossEvent = None
EventServiceException = None EventServiceException = None
logging.debug("compatible emane python bindings not installed") logging.debug("compatible emane python bindings not installed")
@ -458,7 +466,7 @@ class EmaneManager(ModelManager):
model_class = self.models[model_name] model_class = self.models[model_name]
emane_node.setmodel(model_class, config) 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 Look for the given numerical NEM ID and return the first matching
EMANE network and NEM interface. EMANE network and NEM interface.
@ -476,6 +484,29 @@ class EmaneManager(ModelManager):
return emane_node, netif 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: def numnems(self) -> int:
""" """
Return the number of NEMs emulated locally. Return the number of NEMs emulated locally.
@ -567,7 +598,7 @@ class EmaneManager(ModelManager):
run_emane_on_host = False run_emane_on_host = False
for node in self.getnodes(): for node in self.getnodes():
if hasattr(node, "transport_type") and node.transport_type == "raw": if isinstance(node, Rj45Node):
run_emane_on_host = True run_emane_on_host = True
continue continue
path = self.session.session_dir path = self.session.session_dir
@ -626,7 +657,7 @@ class EmaneManager(ModelManager):
kill_transortd = "killall -q emanetransportd" kill_transortd = "killall -q emanetransportd"
stop_emane_on_host = False stop_emane_on_host = False
for node in self.getnodes(): for node in self.getnodes():
if hasattr(node, "transport_type") and node.transport_type == "raw": if isinstance(node, Rj45Node):
stop_emane_on_host = True stop_emane_on_host = True
continue continue
@ -801,8 +832,8 @@ class EmaneManager(ModelManager):
zbit_check = z.bit_length() > 16 or z < 0 zbit_check = z.bit_length() > 16 or z < 0
if any([xbit_check, ybit_check, zbit_check]): if any([xbit_check, ybit_check, zbit_check]):
logging.error( logging.error(
"Unable to build node location message, received lat/long/alt exceeds coordinate " "Unable to build node location message, received lat/long/alt "
"space: NEM %s (%d, %d, %d)", "exceeds coordinate space: NEM %s (%d, %d, %d)",
nemid, nemid,
x, x,
y, y,
@ -812,7 +843,7 @@ class EmaneManager(ModelManager):
# generate a node message for this location update # generate a node message for this location update
try: try:
node = self.session.get_node(n) node = self.session.get_node(n, NodeBase)
except CoreError: except CoreError:
logging.exception( logging.exception(
"location event NEM %s has no corresponding node %s", nemid, n "location event NEM %s has no corresponding node %s", nemid, n
@ -836,9 +867,23 @@ class EmaneManager(ModelManager):
result = True result = True
except CoreCommandError: except CoreCommandError:
result = False result = False
return result 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: class EmaneGlobalModel:
""" """

View file

@ -7,9 +7,12 @@ from typing import Dict, List
from core.config import ConfigGroup, Configuration from core.config import ConfigGroup, Configuration
from core.emane import emanemanifest 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.errors import CoreError
from core.location.mobility import WirelessModel from core.location.mobility import WirelessModel
from core.nodes.base import CoreNode
from core.nodes.interface import CoreInterface from core.nodes.interface import CoreInterface
from core.xml import emanexml from core.xml import emanexml
@ -110,9 +113,9 @@ class EmaneModel(WirelessModel):
server = interface.node.server server = interface.node.server
# check if this is external # check if this is external
transport_type = "virtual" transport_type = TransportType.VIRTUAL
if interface and interface.transport_type == "raw": if interface and interface.transport_type == TransportType.RAW:
transport_type = "raw" transport_type = TransportType.RAW
transport_name = emanexml.transport_file_name(self.id, transport_type) transport_name = emanexml.transport_file_name(self.id, transport_type)
# create nem xml file # create nem xml file
@ -137,44 +140,31 @@ class EmaneModel(WirelessModel):
""" """
logging.debug("emane model(%s) has no post setup tasks", self.name) 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 Invoked from MobilityModel when nodes are moved; this causes
emane location events to be generated for the nodes in the moved emane location events to be generated for the nodes in the moved
list, making EmaneModels compatible with Ns2ScriptedMobility. list, making EmaneModels compatible with Ns2ScriptedMobility.
:param moved: were nodes moved :param moved: moved nodes
:param moved_netifs: interfaces that were moved :param moved_netifs: interfaces that were moved
:return: nothing :return: nothing
""" """
try: try:
wlan = self.session.get_node(self.id) wlan = self.session.get_node(self.id, EmaneNet)
wlan.setnempositions(moved_netifs) wlan.setnempositions(moved_netifs)
except CoreError: except CoreError:
logging.exception("error during update") logging.exception("error during update")
def linkconfig( def linkconfig(
self, self, netif: CoreInterface, options: LinkOptions, netif2: CoreInterface = None
netif: CoreInterface,
bw: float = None,
delay: float = None,
loss: float = None,
duplicate: float = None,
jitter: float = None,
netif2: CoreInterface = None,
) -> None: ) -> None:
""" """
Invoked when a Link Message is received. Default is unimplemented. Invoked when a Link Message is received. Default is unimplemented.
:param netif: interface one :param netif: interface one
:param bw: bandwidth to set to :param options: options for configuring link
: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 netif2: interface two :param netif2: interface two
:return: nothing :return: nothing
""" """
logging.warning( logging.warning("emane model(%s) does not support link config", self.name)
"emane model(%s) does not support link configuration", self.name
)

View file

@ -285,26 +285,11 @@ class EmaneLinkMonitor:
def send_link(self, message_type: MessageFlags, link_id: Tuple[int, int]) -> None: def send_link(self, message_type: MessageFlags, link_id: Tuple[int, int]) -> None:
nem_one, nem_two = link_id nem_one, nem_two = link_id
emane_one, netif = self.emane_manager.nemlookup(nem_one) link = self.emane_manager.get_nem_link(nem_one, nem_two, message_type)
if not emane_one or not netif: if link:
logging.error("invalid nem: %s", nem_one) label = self.get_link_label(link_id)
return link.label = label
node_one = netif.node self.emane_manager.session.broadcast_link(link)
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)
def send_message( def send_message(
self, self,

View file

@ -6,8 +6,16 @@ share the same MAC+PHY model.
import logging import logging
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Type 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.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.base import CoreNetworkBase
from core.nodes.interface import CoreInterface from core.nodes.interface import CoreInterface
@ -48,35 +56,19 @@ class EmaneNet(CoreNetworkBase):
) -> None: ) -> None:
super().__init__(session, _id, name, start, server) super().__init__(session, _id, name, start, server)
self.conf = "" self.conf = ""
self.up = False
self.nemidmap = {} self.nemidmap = {}
self.model = None self.model = None
self.mobility = None self.mobility = None
def linkconfig( def linkconfig(
self, self, netif: CoreInterface, options: LinkOptions, netif2: CoreInterface = None
netif: CoreInterface,
bw: float = None,
delay: float = None,
loss: float = None,
duplicate: float = None,
jitter: float = None,
netif2: CoreInterface = None,
) -> None: ) -> None:
""" """
The CommEffect model supports link configuration. The CommEffect model supports link configuration.
""" """
if not self.model: if not self.model:
return return
self.model.linkconfig( self.model.linkconfig(netif, options, netif2)
netif=netif,
bw=bw,
delay=delay,
loss=loss,
duplicate=duplicate,
jitter=jitter,
netif2=netif2,
)
def config(self, conf: str) -> None: def config(self, conf: str) -> None:
self.conf = conf self.conf = conf
@ -181,7 +173,7 @@ class EmaneNet(CoreNetworkBase):
emanetransportd terminates. emanetransportd terminates.
""" """
for netif in self.netifs(): for netif in self.netifs():
if "virtual" in netif.transport_type.lower(): if netif.transport_type == TransportType.VIRTUAL:
netif.shutdown() netif.shutdown()
netif.poshook = None netif.poshook = None
@ -204,6 +196,7 @@ class EmaneNet(CoreNetworkBase):
lat, lon, alt = self.session.location.getgeo(x, y, z) lat, lon, alt = self.session.location.getgeo(x, y, z)
if node.position.alt is not None: if node.position.alt is not None:
alt = node.position.alt alt = node.position.alt
node.position.set_geo(lon, lat, alt)
# altitude must be an integer or warning is printed # altitude must be an integer or warning is printed
alt = int(round(alt)) alt = int(round(alt))
return nemid, lon, lat, alt return nemid, lon, lat, alt
@ -245,3 +238,27 @@ class EmaneNet(CoreNetworkBase):
nemid, lon, lat, alt = position nemid, lon, lat, alt = position
event.append(nemid, latitude=lat, longitude=lon, altitude=alt) event.append(nemid, latitude=lat, longitude=lon, altitude=alt)
self.session.emane.service.publish(0, event) 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

View file

@ -3,7 +3,7 @@ import logging
import os import os
import signal import signal
import sys import sys
from typing import Mapping, Type from typing import Dict, List, Type
import core.services import core.services
from core import configservices from core import configservices
@ -36,7 +36,7 @@ class CoreEmu:
Provides logic for creating and configuring CORE sessions and the nodes within them. 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. Create a CoreEmu object.
@ -48,17 +48,17 @@ class CoreEmu:
# configuration # configuration
if config is None: if config is None:
config = {} config = {}
self.config = config self.config: Dict[str, str] = config
# session management # session management
self.sessions = {} self.sessions: Dict[int, Session] = {}
# load services # load services
self.service_errors = [] self.service_errors: List[str] = []
self.load_services() self.load_services()
# config services # config services
self.service_manager = ConfigServiceManager() self.service_manager: ConfigServiceManager = ConfigServiceManager()
config_services_path = os.path.abspath(os.path.dirname(configservices.__file__)) config_services_path = os.path.abspath(os.path.dirname(configservices.__file__))
self.service_manager.load(config_services_path) self.service_manager.load(config_services_path)
custom_dir = self.config.get("custom_config_services_dir") custom_dir = self.config.get("custom_config_services_dir")

View file

@ -38,7 +38,7 @@ class EventData:
event_type: EventTypes = None event_type: EventTypes = None
name: str = None name: str = None
data: str = None data: str = None
time: float = None time: str = None
session: int = None session: int = None

View file

@ -37,10 +37,10 @@ class DistributedServer:
:param name: convenience name to associate with host :param name: convenience name to associate with host
:param host: host to connect to :param host: host to connect to
""" """
self.name = name self.name: str = name
self.host = host self.host: str = host
self.conn = Connection(host, user="root") self.conn: Connection = Connection(host, user="root")
self.lock = threading.Lock() self.lock: threading.Lock = threading.Lock()
def remote_cmd( def remote_cmd(
self, cmd: str, env: Dict[str, str] = None, cwd: str = None, wait: bool = True self, cmd: str, env: Dict[str, str] = None, cwd: str = None, wait: bool = True
@ -117,10 +117,10 @@ class DistributedController:
:param session: session :param session: session
""" """
self.session = session self.session: "Session" = session
self.servers = OrderedDict() self.servers: Dict[str, DistributedServer] = OrderedDict()
self.tunnels = {} self.tunnels: Dict[int, Tuple[GreTap, GreTap]] = {}
self.address = self.session.options.get_config( self.address: str = self.session.options.get_config(
"distributed_address", default=None "distributed_address", default=None
) )
@ -178,13 +178,10 @@ class DistributedController:
""" """
for node_id in self.session.nodes: for node_id in self.session.nodes:
node = self.session.nodes[node_id] node = self.session.nodes[node_id]
if not isinstance(node, CoreNetwork): if not isinstance(node, CoreNetwork):
continue continue
if isinstance(node, CtrlNet) and node.serverintf is not None: if isinstance(node, CtrlNet) and node.serverintf is not None:
continue continue
for name in self.servers: for name in self.servers:
server = self.servers[name] server = self.servers[name]
self.create_gre_tunnel(node, server) 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. Create gre tunnel using a pair of gre taps between the local and remote server.
:param node: node to create gre tunnel for :param node: node to create gre tunnel for
:param server: server to create :param server: server to create
tunnel for tunnel for
@ -212,7 +208,7 @@ class DistributedController:
"local tunnel node(%s) to remote(%s) key(%s)", node.name, host, key "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 = 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 # server to local
logging.info( logging.info(
@ -221,7 +217,7 @@ class DistributedController:
remote_tap = GreTap( remote_tap = GreTap(
session=self.session, remoteip=self.address, key=key, server=server 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 # save tunnels for shutdown
tunnel = (local_tap, remote_tap) tunnel = (local_tap, remote_tap)
@ -243,15 +239,3 @@ class DistributedController:
(self.session.id << 16) ^ utils.hashkey(n1_id) ^ (utils.hashkey(n2_id) << 8) (self.session.id << 16) ^ utils.hashkey(n1_id) ^ (utils.hashkey(n2_id) << 8)
) )
return key & 0xFFFFFFFF 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)

View file

@ -1,90 +1,37 @@
from typing import List, Optional from dataclasses import dataclass, field
from typing import TYPE_CHECKING, List, Optional
import netaddr import netaddr
from core import utils 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.emulator.enumerations import LinkTypes
from core.nodes.base import CoreNetworkBase, CoreNode
from core.nodes.interface import CoreInterface if TYPE_CHECKING:
from core.nodes.physical import PhysicalNode from core.nodes.base import CoreNode
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)
@dataclass
class NodeOptions: class NodeOptions:
""" """
Options for creating and updating nodes within core. Options for creating and updating nodes within core.
""" """
def __init__(self, name: str = None, model: str = "PC", image: str = None) -> None: name: str = None
""" model: Optional[str] = "PC"
Create a NodeOptions object. canvas: int = None
icon: str = None
:param name: name of node, defaults to node class name postfix with its id opaque: str = None
:param model: defines services for default and physical nodes, defaults to services: List[str] = field(default_factory=list)
"router" config_services: List[str] = field(default_factory=list)
:param image: image to use for docker nodes x: float = None
""" y: float = None
self.name = name lat: float = None
self.model = model lon: float = None
self.canvas = None alt: float = None
self.icon = None emulation_id: int = None
self.opaque = None server: str = None
self.services = [] image: str = None
self.config_services = [] emane: str = None
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
def set_position(self, x: float, y: float) -> None: def set_position(self, x: float, y: float) -> None:
""" """
@ -111,117 +58,56 @@ class NodeOptions:
self.alt = alt self.alt = alt
@dataclass
class LinkOptions: class LinkOptions:
""" """
Options for creating and updating links within core. Options for creating and updating links within core.
""" """
def __init__(self, _type: LinkTypes = LinkTypes.WIRED) -> None: type: LinkTypes = LinkTypes.WIRED
""" session: int = None
Create a LinkOptions object. delay: int = None
bandwidth: int = None
:param _type: type of link, defaults to per: float = None
wired dup: int = None
""" jitter: int = None
self.type = _type mer: int = None
self.session = None burst: int = None
self.delay = None mburst: int = None
self.bandwidth = None gui_attributes: str = None
self.per = None unidirectional: bool = None
self.dup = None emulation_id: int = None
self.jitter = None network_id: int = None
self.mer = None key: int = None
self.burst = None opaque: str = 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
@dataclass
class InterfaceData: class InterfaceData:
""" """
Convenience class for storing interface data. Convenience class for storing interface data.
""" """
def __init__( id: int = None
self, name: str = None
_id: int, mac: str = None
name: str, ip4: str = None
mac: str, ip4_mask: int = None
ip4: str, ip6: str = None
ip4_mask: int, ip6_mask: int = None
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
def get_addresses(self) -> List[str]: 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 :return: list of addresses
""" """
ip4 = self.ip4_address() addresses = []
ip6 = self.ip6_address() if self.ip4 and self.ip4_mask:
return [i for i in [ip4, ip6] if i] 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: class IpPrefixes:
@ -247,30 +133,63 @@ class IpPrefixes:
if ip6_prefix: if ip6_prefix:
self.ip6 = netaddr.IPNetwork(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. 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 :return: IP4 address or None
""" """
if not self.ip4: if not self.ip4:
raise ValueError("ip4 prefixes have not been set") 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. 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 :return: IP4 address or None
""" """
if not self.ip6: if not self.ip6:
raise ValueError("ip6 prefixes have not been set") 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( def create_interface(
self, node: CoreNode, name: str = None, mac: str = None self, node: "CoreNode", name: str = None, mac: str = None
) -> InterfaceData: ) -> InterfaceData:
""" """
Creates interface data for linking nodes, using the nodes unique id for Creates interface data for linking nodes, using the nodes unique id for
@ -282,54 +201,6 @@ class IpPrefixes:
generation generation
:return: new interface data for the provided node :return: new interface data for the provided node
""" """
# interface id interface = self.gen_interface(node.id, name, mac)
inteface_id = node.newifindex() interface.id = node.newifindex()
return interface
# 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)

View file

@ -117,3 +117,13 @@ class ExceptionLevels(Enum):
ERROR = 2 ERROR = 2
WARNING = 3 WARNING = 3
NOTICE = 4 NOTICE = 4
class NetworkPolicy(Enum):
ACCEPT = "ACCEPT"
DROP = "DROP"
class TransportType(Enum):
RAW = "raw"
VIRTUAL = "virtual"

View file

@ -12,21 +12,22 @@ import subprocess
import tempfile import tempfile
import threading import threading
import time 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 import constants, utils
from core.configservice.manager import ConfigServiceManager
from core.emane.emanemanager import EmaneManager from core.emane.emanemanager import EmaneManager
from core.emane.nodes import EmaneNet from core.emane.nodes import EmaneNet
from core.emulator.data import ConfigData, EventData, ExceptionData, FileData, LinkData from core.emulator.data import (
from core.emulator.distributed import DistributedController ConfigData,
from core.emulator.emudata import ( EventData,
IdGen, ExceptionData,
InterfaceData, FileData,
LinkOptions, LinkData,
NodeOptions, NodeData,
create_interface,
link_config,
) )
from core.emulator.distributed import DistributedController
from core.emulator.emudata import InterfaceData, LinkOptions, NodeOptions
from core.emulator.enumerations import ( from core.emulator.enumerations import (
EventTypes, EventTypes,
ExceptionLevels, ExceptionLevels,
@ -41,7 +42,7 @@ from core.location.geo import GeoLocation
from core.location.mobility import BasicRangeModel, MobilityManager from core.location.mobility import BasicRangeModel, MobilityManager
from core.nodes.base import CoreNetworkBase, CoreNode, CoreNodeBase, NodeBase from core.nodes.base import CoreNetworkBase, CoreNode, CoreNodeBase, NodeBase
from core.nodes.docker import DockerNode 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.lxd import LxcNode
from core.nodes.network import ( from core.nodes.network import (
CtrlNet, CtrlNet,
@ -75,8 +76,10 @@ NODES = {
NodeTypes.LXC: LxcNode, NodeTypes.LXC: LxcNode,
} }
NODES_TYPE = {NODES[x]: x for x in NODES} NODES_TYPE = {NODES[x]: x for x in NODES}
CONTAINER_NODES = {DockerNode, LxcNode}
CTRL_NET_ID = 9001 CTRL_NET_ID = 9001
LINK_COLORS = ["green", "blue", "orange", "purple", "turquoise"] LINK_COLORS = ["green", "blue", "orange", "purple", "turquoise"]
NT = TypeVar("NT", bound=NodeBase)
class Session: class Session:
@ -94,63 +97,62 @@ class Session:
:param config: session configuration :param config: session configuration
:param mkdir: flag to determine if a directory should be made :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 # 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: if mkdir:
os.mkdir(self.session_dir) os.mkdir(self.session_dir)
self.name = None self.name: Optional[str] = None
self.file_name = None self.file_name: Optional[str] = None
self.thumbnail = None self.thumbnail: Optional[str] = None
self.user = None self.user: Optional[str] = None
self.event_loop = EventLoop() self.event_loop: EventLoop = EventLoop()
self.link_colors = {} self.link_colors: Dict[int, str] = {}
# dict of nodes: all nodes and nets # dict of nodes: all nodes and nets
self.node_id_gen = IdGen() self.nodes: Dict[int, NodeBase] = {}
self.nodes = {}
self._nodes_lock = threading.Lock() self._nodes_lock = threading.Lock()
self.state = EventTypes.DEFINITION_STATE self.state: EventTypes = EventTypes.DEFINITION_STATE
self._state_time = time.monotonic() self._state_time: float = time.monotonic()
self._state_file = os.path.join(self.session_dir, "state") self._state_file: str = os.path.join(self.session_dir, "state")
# hooks handlers # hooks handlers
self._hooks = {} self._hooks: Dict[EventTypes, Tuple[str, str]] = {}
self._state_hooks = {} self._state_hooks: Dict[EventTypes, Callable[[int], None]] = {}
self.add_state_hook( self.add_state_hook(
state=EventTypes.RUNTIME_STATE, hook=self.runtime_state_hook state=EventTypes.RUNTIME_STATE, hook=self.runtime_state_hook
) )
# handlers for broadcasting information # handlers for broadcasting information
self.event_handlers = [] self.event_handlers: List[Callable[[EventData], None]] = []
self.exception_handlers = [] self.exception_handlers: List[Callable[[ExceptionData], None]] = []
self.node_handlers = [] self.node_handlers: List[Callable[[NodeData], None]] = []
self.link_handlers = [] self.link_handlers: List[Callable[[LinkData], None]] = []
self.file_handlers = [] self.file_handlers: List[Callable[[FileData], None]] = []
self.config_handlers = [] self.config_handlers: List[Callable[[ConfigData], None]] = []
self.shutdown_handlers = [] self.shutdown_handlers: List[Callable[[Session], None]] = []
# session options/metadata # session options/metadata
self.options = SessionConfig() self.options: SessionConfig = SessionConfig()
if not config: if not config:
config = {} config = {}
for key in config: for key in config:
value = config[key] value = config[key]
self.options.set_config(key, value) self.options.set_config(key, value)
self.metadata = {} self.metadata: Dict[str, str] = {}
# distributed support and logic # distributed support and logic
self.distributed = DistributedController(self) self.distributed: DistributedController = DistributedController(self)
# initialize session feature helpers # initialize session feature helpers
self.location = GeoLocation() self.location: GeoLocation = GeoLocation()
self.mobility = MobilityManager(session=self) self.mobility: MobilityManager = MobilityManager(self)
self.services = CoreServices(session=self) self.services: CoreServices = CoreServices(self)
self.emane = EmaneManager(session=self) self.emane: EmaneManager = EmaneManager(self)
self.sdt = Sdt(session=self) self.sdt: Sdt = Sdt(self)
# initialize default node services # initialize default node services
self.services.default_services = { self.services.default_services = {
@ -162,7 +164,7 @@ class Session:
} }
# config services # config services
self.service_manager = None self.service_manager: Optional[ConfigServiceManager] = None
@classmethod @classmethod
def get_node_class(cls, _type: NodeTypes) -> Type[NodeBase]: def get_node_class(cls, _type: NodeTypes) -> Type[NodeBase]:
@ -194,7 +196,10 @@ class Session:
def _link_nodes( def _link_nodes(
self, node_one_id: int, node_two_id: int self, node_one_id: int, node_two_id: int
) -> Tuple[ ) -> Tuple[
CoreNode, CoreNode, CoreNetworkBase, CoreNetworkBase, Tuple[GreTap, GreTap] Optional[CoreNode],
Optional[CoreNode],
Optional[CoreNetworkBase],
Optional[CoreNetworkBase],
]: ]:
""" """
Convenience method for retrieving nodes within link data. Convenience method for retrieving nodes within link data.
@ -212,24 +217,8 @@ class Session:
net_two = None net_two = None
# retrieve node one # retrieve node one
node_one = self.get_node(node_one_id) node_one = self.get_node(node_one_id, NodeBase)
node_two = self.get_node(node_two_id) node_two = self.get_node(node_two_id, NodeBase)
# 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
if isinstance(node_one, CoreNetworkBase): if isinstance(node_one, CoreNetworkBase):
if not net_one: if not net_one:
@ -246,14 +235,13 @@ class Session:
node_two = None node_two = None
logging.debug( 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_one,
node_two, node_two,
net_one, net_one,
net_two, 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: def _link_wireless(self, objects: Iterable[CoreNodeBase], connect: bool) -> None:
""" """
@ -300,7 +288,7 @@ class Session:
node_two_id: int, node_two_id: int,
interface_one: InterfaceData = None, interface_one: InterfaceData = None,
interface_two: InterfaceData = None, interface_two: InterfaceData = None,
link_options: LinkOptions = None, options: LinkOptions = None,
) -> Tuple[CoreInterface, CoreInterface]: ) -> Tuple[CoreInterface, CoreInterface]:
""" """
Add a link between nodes. Add a link between nodes.
@ -311,15 +299,15 @@ class Session:
data, defaults to none data, defaults to none
:param interface_two: node two interface :param interface_two: node two interface
data, defaults to none data, defaults to none
:param link_options: data for creating link, :param options: data for creating link,
defaults to no options defaults to no options
:return: tuple of created core interfaces, depending on link :return: tuple of created core interfaces, depending on link
""" """
if not link_options: if not options:
link_options = LinkOptions() options = LinkOptions()
# get node objects identified by link data # 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 node_one_id, node_two_id
) )
@ -333,7 +321,7 @@ class Session:
try: try:
# wireless link # wireless link
if link_options.type == LinkTypes.WIRELESS: if options.type == LinkTypes.WIRELESS:
objects = [node_one, node_two, net_one, net_two] objects = [node_one, node_two, net_one, net_two]
self._link_wireless(objects, connect=True) self._link_wireless(objects, connect=True)
# wired link # wired link
@ -346,7 +334,7 @@ class Session:
node_two.name, node_two.name,
) )
start = self.state.should_start() 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 # node to network
if node_one and net_one: if node_one and net_one:
@ -355,11 +343,11 @@ class Session:
node_one.name, node_one.name,
net_one.name, net_one.name,
) )
interface = create_interface(node_one, net_one, interface_one) ifindex = node_one.newnetif(net_one, interface_one)
node_one_interface = interface node_one_interface = node_one.netif(ifindex)
wireless_net = isinstance(net_one, (EmaneNet, WlanNode)) wireless_net = isinstance(net_one, (EmaneNet, WlanNode))
if not wireless_net: if not wireless_net:
link_config(net_one, interface, link_options) net_one.linkconfig(node_one_interface, options)
# network to node # network to node
if node_two and net_one: if node_two and net_one:
@ -368,11 +356,11 @@ class Session:
node_two.name, node_two.name,
net_one.name, net_one.name,
) )
interface = create_interface(node_two, net_one, interface_two) ifindex = node_two.newnetif(net_one, interface_two)
node_two_interface = interface node_two_interface = node_two.netif(ifindex)
wireless_net = isinstance(net_one, (EmaneNet, WlanNode)) wireless_net = isinstance(net_one, (EmaneNet, WlanNode))
if not link_options.unidirectional and not wireless_net: if not options.unidirectional and not wireless_net:
link_config(net_one, interface, link_options) net_one.linkconfig(node_two_interface, options)
# network to network # network to network
if net_one and net_two: if net_one and net_two:
@ -383,25 +371,21 @@ class Session:
) )
interface = net_one.linknet(net_two) interface = net_one.linknet(net_two)
node_one_interface = interface node_one_interface = interface
link_config(net_one, interface, link_options) net_one.linkconfig(interface, options)
if not options.unidirectional:
if not link_options.unidirectional:
interface.swapparams("_params_up") interface.swapparams("_params_up")
link_config( net_two.linkconfig(interface, options)
net_two, interface, link_options, devname=interface.name
)
interface.swapparams("_params_up") interface.swapparams("_params_up")
# a tunnel node was found for the nodes # a tunnel node was found for the nodes
addresses = [] addresses = []
if not node_one and all([net_one, interface_one]): if not node_one and all([net_one, interface_one]):
addresses.extend(interface_one.get_addresses()) addresses.extend(interface_one.get_addresses())
if not node_two and all([net_two, interface_two]): if not node_two and all([net_two, interface_two]):
addresses.extend(interface_two.get_addresses()) addresses.extend(interface_two.get_addresses())
# tunnel node logic # tunnel node logic
key = link_options.key key = options.key
if key and isinstance(net_one, TunnelNode): if key and isinstance(net_one, TunnelNode):
logging.info("setting tunnel key for: %s", net_one.name) logging.info("setting tunnel key for: %s", net_one.name)
net_one.setkey(key) net_one.setkey(key)
@ -412,23 +396,6 @@ class Session:
net_two.setkey(key) net_two.setkey(key)
if addresses: if addresses:
net_two.addrconfig(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: finally:
if node_one: if node_one:
node_one.lock.release() node_one.lock.release()
@ -458,7 +425,7 @@ class Session:
:raises core.CoreError: when no common network is found for link being deleted :raises core.CoreError: when no common network is found for link being deleted
""" """
# get node objects identified by link data # 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 node_one_id, node_two_id
) )
@ -552,7 +519,7 @@ class Session:
node_two_id: int, node_two_id: int,
interface_one_id: int = None, interface_one_id: int = None,
interface_two_id: int = None, interface_two_id: int = None,
link_options: LinkOptions = None, options: LinkOptions = None,
) -> None: ) -> None:
""" """
Update link information between nodes. Update link information between nodes.
@ -561,16 +528,16 @@ class Session:
:param node_two_id: node two id :param node_two_id: node two id
:param interface_one_id: interface id for node one :param interface_one_id: interface id for node one
:param interface_two_id: interface id for node two :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 :return: nothing
:raises core.CoreError: when updating a wireless type link, when there is a unknown :raises core.CoreError: when updating a wireless type link, when there is a unknown
link between networks link between networks
""" """
if not link_options: if not options:
link_options = LinkOptions() options = LinkOptions()
# get node objects identified by link data # 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 node_one_id, node_two_id
) )
@ -581,7 +548,7 @@ class Session:
try: try:
# wireless link # wireless link
if link_options.type == LinkTypes.WIRELESS: if options.type == LinkTypes.WIRELESS:
raise CoreError("cannot update wireless link") raise CoreError("cannot update wireless link")
else: else:
if not node_one and not node_two: if not node_one and not node_two:
@ -599,35 +566,28 @@ class Session:
if upstream: if upstream:
interface.swapparams("_params_up") interface.swapparams("_params_up")
link_config( net_one.linkconfig(interface, options)
net_one, interface, link_options, devname=interface.name
)
interface.swapparams("_params_up") interface.swapparams("_params_up")
else: else:
link_config(net_one, interface, link_options) net_one.linkconfig(interface, options)
if not link_options.unidirectional: if not options.unidirectional:
if upstream: if upstream:
link_config(net_two, interface, link_options) net_two.linkconfig(interface, options)
else: else:
interface.swapparams("_params_up") interface.swapparams("_params_up")
link_config( net_two.linkconfig(interface, options)
net_two,
interface,
link_options,
devname=interface.name,
)
interface.swapparams("_params_up") interface.swapparams("_params_up")
else: else:
raise CoreError("modify link for unknown nodes") raise CoreError("modify link for unknown nodes")
elif not node_one: elif not node_one:
# node1 = layer 2node, node2 = layer3 node # node1 = layer 2node, node2 = layer3 node
interface = node_two.netif(interface_two_id) interface = node_two.netif(interface_two_id)
link_config(net_one, interface, link_options) net_one.linkconfig(interface, options)
elif not node_two: elif not node_two:
# node2 = layer 2node, node1 = layer3 node # node2 = layer 2node, node1 = layer3 node
interface = node_one.netif(interface_one_id) interface = node_one.netif(interface_one_id)
link_config(net_one, interface, link_options) net_one.linkconfig(interface, options)
else: else:
common_networks = node_one.commonnets(node_two) common_networks = node_one.commonnets(node_two)
if not common_networks: if not common_networks:
@ -640,60 +600,49 @@ class Session:
): ):
continue continue
link_config( net_one.linkconfig(interface_one, options, interface_two)
net_one, if not options.unidirectional:
interface_one, net_one.linkconfig(interface_two, options, 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,
)
finally: finally:
if node_one: if node_one:
node_one.lock.release() node_one.lock.release()
if node_two: if node_two:
node_two.lock.release() 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( def add_node(
self, self, _class: Type[NT], _id: int = None, options: NodeOptions = None
_type: NodeTypes = NodeTypes.DEFAULT, ) -> NT:
_id: int = None,
options: NodeOptions = None,
_cls: Type[NodeBase] = None,
) -> NodeBase:
""" """
Add a node to the session, based on the provided node data. 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 _id: id for node, defaults to None for generated id
:param options: data to create node with :param options: data to create node with
:param _cls: optional custom class to use for a created node
:return: created node :return: created node
:raises core.CoreError: when an invalid node type is given :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 # set node start based on current session state, override and check when rj45
start = self.state.should_start() start = self.state.should_start()
enable_rj45 = self.options.get_config("enablerj45") == "1" 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 start = False
# determine node id # determine node id
if not _id: if not _id:
while True: _id = self._next_node_id()
_id = self.node_id_gen.next()
if _id not in self.nodes:
break
# generate name if not provided # generate name if not provided
if not options: if not options:
@ -701,7 +650,7 @@ class Session:
options.set_position(0, 0) options.set_position(0, 0)
name = options.name name = options.name
if not name: if not name:
name = f"{node_class.__name__}{_id}" name = f"{_class.__name__}{_id}"
# verify distributed server # verify distributed server
server = self.distributed.servers.get(options.server) server = self.distributed.servers.get(options.server)
@ -711,24 +660,15 @@ class Session:
# create node # create node
logging.info( logging.info(
"creating node(%s) id(%s) name(%s) start(%s)", "creating node(%s) id(%s) name(%s) start(%s)",
node_class.__name__, _class.__name__,
_id, _id,
name, name,
start, start,
) )
if _type in [NodeTypes.DOCKER, NodeTypes.LXC]: kwargs = dict(_id=_id, name=name, start=start, server=server)
node = self.create_node( if _class in CONTAINER_NODES:
cls=node_class, kwargs["image"] = options.image
_id=_id, node = self.create_node(_class, **kwargs)
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
)
# set node attributes # set node attributes
node.icon = options.icon node.icon = options.icon
@ -777,7 +717,7 @@ class Session:
:raises core.CoreError: when node to update does not exist :raises core.CoreError: when node to update does not exist
""" """
# get node to update # get node to update
node = self.get_node(node_id) node = self.get_node(node_id, NodeBase)
# set node position and broadcast it # set node position and broadcast it
self.set_node_position(node, options) self.set_node_position(node, options)
@ -873,19 +813,19 @@ class Session:
CoreXmlWriter(self).write(file_name) CoreXmlWriter(self).write(file_name)
def add_hook( 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: ) -> None:
""" """
Store a hook from a received file message. Store a hook from a received file message.
:param state: when to run hook :param state: when to run hook
:param file_name: file name for hook :param file_name: file name for hook
:param source_name: source name
:param data: hook data :param data: hook data
:param source_name: source name
:return: nothing :return: nothing
""" """
logging.info( 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 hook = file_name, data
state_hooks = self._hooks.setdefault(state, []) state_hooks = self._hooks.setdefault(state, [])
@ -908,9 +848,7 @@ class Session:
:param data: file data :param data: file data
:return: nothing :return: nothing
""" """
node = self.get_node(node_id, CoreNodeBase)
node = self.get_node(node_id)
if source_name is not None: if source_name is not None:
node.addfile(source_name, file_name) node.addfile(source_name, file_name)
elif data is not None: elif data is not None:
@ -1363,17 +1301,17 @@ class Session:
break break
return node_id 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. 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 args: list of arguments for the class to create
:param kwargs: dictionary of arguments for the class to create :param kwargs: dictionary of arguments for the class to create
:return: the created node instance :return: the created node instance
:raises core.CoreError: when id of the node to create already exists :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: with self._nodes_lock:
if node.id in self.nodes: if node.id in self.nodes:
node.shutdown() node.shutdown()
@ -1381,17 +1319,23 @@ class Session:
self.nodes[node.id] = node self.nodes[node.id] = node
return node return node
def get_node(self, _id: int) -> NodeBase: def get_node(self, _id: int, _class: Type[NT]) -> NT:
""" """
Get a session node. Get a session node.
:param _id: node id to retrieve :param _id: node id to retrieve
:param _class: expected node class
:return: node for the given id :return: node for the given id
:raises core.CoreError: when node does not exist :raises core.CoreError: when node does not exist
""" """
if _id not in self.nodes: if _id not in self.nodes:
raise CoreError(f"unknown node id {_id}") 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: def delete_node(self, _id: int) -> bool:
""" """
@ -1425,7 +1369,6 @@ class Session:
self.sdt.delete_node(node.id) self.sdt.delete_node(node.id)
funcs.append((node.shutdown, [], {})) funcs.append((node.shutdown, [], {}))
utils.threadpool(funcs) utils.threadpool(funcs)
self.node_id_gen.id = 0
def write_nodes(self) -> None: def write_nodes(self) -> None:
""" """
@ -1709,10 +1652,7 @@ class Session:
:return: control net :return: control net
:raises CoreError: when control net is not found :raises CoreError: when control net is not found
""" """
node = self.get_node(CTRL_NET_ID + net_index) return self.get_node(CTRL_NET_ID + net_index, CtrlNet)
if not isinstance(node, CtrlNet):
raise CoreError("node is not a valid CtrlNet: %s", node.name)
return node
def add_remove_control_net( def add_remove_control_net(
self, net_index: int, remove: bool = False, conf_required: bool = True self, net_index: int, remove: bool = False, conf_required: bool = True
@ -1788,10 +1728,9 @@ class Session:
server_interface, server_interface,
) )
control_net = self.create_node( control_net = self.create_node(
cls=CtrlNet, CtrlNet,
prefix,
_id=_id, _id=_id,
prefix=prefix,
assign_address=True,
updown_script=updown_script, updown_script=updown_script,
serverintf=server_interface, serverintf=server_interface,
) )
@ -1820,35 +1759,28 @@ class Session:
control_net = self.add_remove_control_net(net_index, remove, conf_required) control_net = self.add_remove_control_net(net_index, remove, conf_required)
if not control_net: if not control_net:
return return
if not node: if not node:
return return
# ctrl# already exists # ctrl# already exists
if node.netif(control_net.CTRLIF_IDX_BASE + net_index): if node.netif(control_net.CTRLIF_IDX_BASE + net_index):
return return
control_ip = node.id
try: try:
address = control_net.prefix[control_ip] ip4 = control_net.prefix[node.id]
prefix = control_net.prefix.prefixlen ip4_mask = control_net.prefix.prefixlen
addrlist = [f"{address}/{prefix}"] 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: except ValueError:
msg = f"Control interface not added to node {node.id}. " msg = f"Control interface not added to node {node.id}. "
msg += f"Invalid control network prefix ({control_net.prefix}). " msg += f"Invalid control network prefix ({control_net.prefix}). "
msg += "A longer prefix length may be required for this many nodes." msg += "A longer prefix length may be required for this many nodes."
logging.exception(msg) 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( def update_control_interface_hosts(
self, net_index: int = 0, remove: bool = False self, net_index: int = 0, remove: bool = False
@ -1959,7 +1891,7 @@ class Session:
if not node_id: if not node_id:
utils.mute_detach(data) utils.mute_detach(data)
else: else:
node = self.get_node(node_id) node = self.get_node(node_id, CoreNodeBase)
node.cmd(data, wait=False) node.cmd(data, wait=False)
def get_link_color(self, network_id: int) -> str: def get_link_color(self, network_id: int) -> str:

View file

@ -1,4 +1,4 @@
from typing import Any from typing import Any, List
from core.config import ConfigurableManager, ConfigurableOptions, Configuration from core.config import ConfigurableManager, ConfigurableOptions, Configuration
from core.emulator.enumerations import ConfigDataTypes, RegisterTlvs from core.emulator.enumerations import ConfigDataTypes, RegisterTlvs
@ -10,8 +10,8 @@ class SessionConfig(ConfigurableManager, ConfigurableOptions):
Provides session configuration. Provides session configuration.
""" """
name = "session" name: str = "session"
options = [ options: List[Configuration] = [
Configuration( Configuration(
_id="controlnet", _type=ConfigDataTypes.STRING, label="Control Network" _id="controlnet", _type=ConfigDataTypes.STRING, label="Control Network"
), ),
@ -57,7 +57,7 @@ class SessionConfig(ConfigurableManager, ConfigurableOptions):
label="SDT3D URL", label="SDT3D URL",
), ),
] ]
config_type = RegisterTlvs.UTILITY config_type: RegisterTlvs = RegisterTlvs.UTILITY
def __init__(self) -> None: def __init__(self) -> None:
super().__init__() super().__init__()

View file

@ -1,7 +1,7 @@
import logging import logging
import math import math
import tkinter as tk import tkinter as tk
from tkinter import font, ttk from tkinter import PhotoImage, font, ttk
from tkinter.ttk import Progressbar from tkinter.ttk import Progressbar
import grpc import grpc
@ -104,7 +104,7 @@ class Application(ttk.Frame):
self.rowconfigure(0, weight=1) self.rowconfigure(0, weight=1)
self.columnconfigure(1, weight=1) self.columnconfigure(1, weight=1)
self.grid(sticky="nsew") self.grid(sticky="nsew")
self.toolbar = Toolbar(self, self) self.toolbar = Toolbar(self)
self.toolbar.grid(sticky="ns") self.toolbar.grid(sticky="ns")
self.right_frame = ttk.Frame(self) self.right_frame = ttk.Frame(self)
self.right_frame.columnconfigure(0, weight=1) self.right_frame.columnconfigure(0, weight=1)
@ -113,16 +113,15 @@ class Application(ttk.Frame):
self.draw_canvas() self.draw_canvas()
self.draw_status() self.draw_status()
self.progress = Progressbar(self.right_frame, mode="indeterminate") 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: def draw_canvas(self) -> None:
width = self.guiconfig.preferences.width
height = self.guiconfig.preferences.height
canvas_frame = ttk.Frame(self.right_frame) canvas_frame = ttk.Frame(self.right_frame)
canvas_frame.rowconfigure(0, weight=1) canvas_frame.rowconfigure(0, weight=1)
canvas_frame.columnconfigure(0, weight=1) canvas_frame.columnconfigure(0, weight=1)
canvas_frame.grid(sticky="nsew", pady=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") self.canvas.grid(sticky="nsew")
scroll_y = ttk.Scrollbar(canvas_frame, command=self.canvas.yview) scroll_y = ttk.Scrollbar(canvas_frame, command=self.canvas.yview)
scroll_y.grid(row=0, column=1, sticky="ns") 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()) self.after(0, lambda: ErrorDialog(self, title, message).show())
def on_closing(self) -> None: def on_closing(self) -> None:
if self.toolbar.picker:
self.toolbar.picker.destroy()
self.menubar.prompt_save_running_session(True) self.menubar.prompt_save_running_session(True)
def save_config(self) -> None: def save_config(self) -> None:
@ -161,5 +162,11 @@ class Application(ttk.Frame):
else: else:
self.toolbar.set_design() 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: def close(self) -> None:
self.master.destroy() self.master.destroy()

View file

@ -20,7 +20,6 @@ from core.gui.dialogs.emaneinstall import EmaneInstallDialog
from core.gui.dialogs.error import ErrorDialog from core.gui.dialogs.error import ErrorDialog
from core.gui.dialogs.mobilityplayer import MobilityPlayer from core.gui.dialogs.mobilityplayer import MobilityPlayer
from core.gui.dialogs.sessions import SessionsDialog from core.gui.dialogs.sessions import SessionsDialog
from core.gui.graph import tags
from core.gui.graph.edges import CanvasEdge from core.gui.graph.edges import CanvasEdge
from core.gui.graph.node import CanvasNode from core.gui.graph.node import CanvasNode
from core.gui.graph.shape import AnnotationData, Shape from core.gui.graph.shape import AnnotationData, Shape
@ -136,7 +135,7 @@ class CoreClient:
return return
if event.HasField("link_event"): 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"): elif event.HasField("session_event"):
logging.info("session event: %s", event) logging.info("session event: %s", event)
session_event = event.session_event session_event = event.session_event
@ -155,7 +154,7 @@ class CoreClient:
else: else:
logging.warning("unknown session event: %s", session_event) logging.warning("unknown session event: %s", session_event)
elif event.HasField("node_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"): elif event.HasField("config_event"):
logging.info("config event: %s", event) logging.info("config event: %s", event)
elif event.HasField("exception_event"): elif event.HasField("exception_event"):
@ -221,7 +220,7 @@ class CoreClient:
) )
return return
logging.debug("handling throughputs event: %s", event) 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): def handle_exception_event(self, event: core_pb2.ExceptionEvent):
logging.info("exception event: %s", event) logging.info("exception event: %s", event)
@ -331,6 +330,9 @@ class CoreClient:
except grpc.RpcError as e: except grpc.RpcError as e:
self.app.show_grpc_exception("Join Session Error", e) self.app.show_grpc_exception("Join Session Error", e)
# organize canvas
self.app.canvas.organize()
# update ui to represent current state # update ui to represent current state
self.app.after(0, self.app.joined_session_update) self.app.after(0, self.app.joined_session_update)
@ -390,9 +392,6 @@ class CoreClient:
except ValueError: except ValueError:
logging.exception("unknown shape: %s", shape_type) 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): def create_new_session(self):
""" """
Create a new session Create a new session
@ -838,7 +837,7 @@ class CoreClient:
ip4, ip6 = self.interfaces_manager.get_ips(node) ip4, ip6 = self.interfaces_manager.get_ips(node)
ip4_mask = self.interfaces_manager.ip4_mask ip4_mask = self.interfaces_manager.ip4_mask
ip6_mask = self.interfaces_manager.ip6_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}" name = f"eth{interface_id}"
interface = core_pb2.Interface( interface = core_pb2.Interface(
id=interface_id, id=interface_id,
@ -848,7 +847,7 @@ class CoreClient:
ip6=ip6, ip6=ip6,
ip6mask=ip6_mask, ip6mask=ip6_mask,
) )
logging.debug( logging.info(
"create node(%s) interface(%s) IPv4(%s) IPv6(%s)", "create node(%s) interface(%s) IPv4(%s) IPv6(%s)",
node.name, node.name,
interface.name, interface.name,
@ -887,12 +886,15 @@ class CoreClient:
interface_one=src_interface, interface_one=src_interface,
interface_two=dst_interface, interface_two=dst_interface,
) )
# assign after creating link proto, since interfaces are copied
if src_interface: if src_interface:
edge.src_interface = link.interface_one interface_one = link.interface_one
canvas_src_node.interfaces.append(link.interface_one) edge.src_interface = interface_one
canvas_src_node.interfaces[interface_one.id] = interface_one
if dst_interface: if dst_interface:
edge.dst_interface = link.interface_two interface_two = link.interface_two
canvas_dst_node.interfaces.append(link.interface_two) edge.dst_interface = interface_two
canvas_dst_node.interfaces[interface_two.id] = interface_two
edge.set_link(link) edge.set_link(link)
self.links[edge.token] = edge self.links[edge.token] = edge
logging.info("Add link between %s and %s", src_node.name, dst_node.name) 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View file

@ -7,6 +7,7 @@ from typing import TYPE_CHECKING
from core.gui import validation from core.gui import validation
from core.gui.dialogs.dialog import Dialog from core.gui.dialogs.dialog import Dialog
from core.gui.themes import PADX, PADY
if TYPE_CHECKING: if TYPE_CHECKING:
from core.gui.app import Application from core.gui.app import Application
@ -16,7 +17,7 @@ class ColorPickerDialog(Dialog):
def __init__( def __init__(
self, master: tk.BaseWidget, app: "Application", initcolor: str = "#000000" 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.red_entry = None
self.blue_entry = None self.blue_entry = None
self.green_entry = None self.green_entry = None
@ -43,42 +44,40 @@ class ColorPickerDialog(Dialog):
def draw(self): def draw(self):
self.top.columnconfigure(0, weight=1) self.top.columnconfigure(0, weight=1)
self.top.rowconfigure(3, weight=1)
# rgb frames # rgb frames
frame = ttk.Frame(self.top) frame = ttk.Frame(self.top)
frame.columnconfigure(0, weight=1) frame.grid(row=0, column=0, sticky="ew", pady=PADY)
frame.columnconfigure(1, weight=1) frame.columnconfigure(2, weight=3)
frame.columnconfigure(2, weight=6) frame.columnconfigure(3, weight=1)
frame.columnconfigure(3, weight=2) label = ttk.Label(frame, text="R")
label = ttk.Label(frame, text="R: ") label.grid(row=0, column=0, padx=PADX)
label.grid(row=0, column=0) self.red_entry = validation.RgbEntry(frame, width=3, textvariable=self.red)
self.red_entry = validation.RgbEntry(frame, width=4, textvariable=self.red) self.red_entry.grid(row=0, column=1, sticky="ew", padx=PADX)
self.red_entry.grid(row=0, column=1, sticky="nsew")
scale = ttk.Scale( scale = ttk.Scale(
frame, frame,
from_=0, from_=0,
to=255, to=255,
value=0, value=0,
# length=200,
orient=tk.HORIZONTAL, orient=tk.HORIZONTAL,
variable=self.red_scale, variable=self.red_scale,
command=lambda x: self.scale_callback(self.red_scale, self.red), 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( self.red_label = ttk.Label(
frame, background="#%02x%02x%02x" % (self.red.get(), 0, 0), width=5 frame, background="#%02x%02x%02x" % (self.red.get(), 0, 0), width=5
) )
self.red_label.grid(row=0, column=3, sticky="nsew") self.red_label.grid(row=0, column=3, sticky="ew")
frame.grid(row=0, column=0, sticky="nsew")
frame = ttk.Frame(self.top) frame = ttk.Frame(self.top)
frame.columnconfigure(0, weight=1) frame.grid(row=1, column=0, sticky="ew", pady=PADY)
frame.columnconfigure(1, weight=1) frame.columnconfigure(2, weight=3)
frame.columnconfigure(2, weight=6) frame.columnconfigure(3, weight=1)
frame.columnconfigure(3, weight=2) label = ttk.Label(frame, text="G")
label = ttk.Label(frame, text="G: ") label.grid(row=0, column=0, padx=PADX)
label.grid(row=0, column=0) self.green_entry = validation.RgbEntry(frame, width=3, textvariable=self.green)
self.green_entry = validation.RgbEntry(frame, width=4, textvariable=self.green) self.green_entry.grid(row=0, column=1, sticky="ew", padx=PADX)
self.green_entry.grid(row=0, column=1, sticky="nsew")
scale = ttk.Scale( scale = ttk.Scale(
frame, frame,
from_=0, from_=0,
@ -88,59 +87,54 @@ class ColorPickerDialog(Dialog):
variable=self.green_scale, variable=self.green_scale,
command=lambda x: self.scale_callback(self.green_scale, self.green), 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( self.green_label = ttk.Label(
frame, background="#%02x%02x%02x" % (0, self.green.get(), 0), width=5 frame, background="#%02x%02x%02x" % (0, self.green.get(), 0), width=5
) )
self.green_label.grid(row=0, column=3, sticky="nsew") self.green_label.grid(row=0, column=3, sticky="ew")
frame.grid(row=1, column=0, sticky="nsew")
frame = ttk.Frame(self.top) frame = ttk.Frame(self.top)
frame.columnconfigure(0, weight=1) frame.grid(row=2, column=0, sticky="ew", pady=PADY)
frame.columnconfigure(1, weight=1) frame.columnconfigure(2, weight=3)
frame.columnconfigure(2, weight=6) frame.columnconfigure(3, weight=1)
frame.columnconfigure(3, weight=2) label = ttk.Label(frame, text="B")
label = ttk.Label(frame, text="B: ") label.grid(row=0, column=0, padx=PADX)
label.grid(row=0, column=0) self.blue_entry = validation.RgbEntry(frame, width=3, textvariable=self.blue)
self.blue_entry = validation.RgbEntry(frame, width=4, textvariable=self.blue) self.blue_entry.grid(row=0, column=1, sticky="ew", padx=PADX)
self.blue_entry.grid(row=0, column=1, sticky="nsew")
scale = ttk.Scale( scale = ttk.Scale(
frame, frame,
from_=0, from_=0,
to=255, to=255,
value=0, value=0,
# length=200,
orient=tk.HORIZONTAL, orient=tk.HORIZONTAL,
variable=self.blue_scale, variable=self.blue_scale,
command=lambda x: self.scale_callback(self.blue_scale, self.blue), 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( self.blue_label = ttk.Label(
frame, background="#%02x%02x%02x" % (0, 0, self.blue.get()), width=5 frame, background="#%02x%02x%02x" % (0, 0, self.blue.get()), width=5
) )
self.blue_label.grid(row=0, column=3, sticky="nsew") self.blue_label.grid(row=0, column=3, sticky="ew")
frame.grid(row=2, column=0, sticky="nsew")
# hex code and color display # hex code and color display
frame = ttk.Frame(self.top) frame = ttk.Frame(self.top)
frame.columnconfigure(0, weight=1) frame.columnconfigure(0, weight=1)
label = ttk.Label(frame, text="Selection: ") frame.rowconfigure(1, weight=1)
label.grid(row=0, column=0, sticky="nsew")
self.hex_entry = validation.HexEntry(frame, textvariable=self.hex) 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 = tk.Frame(frame, background=self.color, width=100, height=100)
self.display.grid(row=2, column=0) self.display.grid(sticky="nsew")
frame.grid(row=3, column=0, sticky="nsew") frame.grid(row=3, column=0, sticky="nsew", pady=PADY)
# button frame # button frame
frame = ttk.Frame(self.top) frame = ttk.Frame(self.top)
frame.grid(row=4, column=0, sticky="ew")
frame.columnconfigure(0, weight=1) frame.columnconfigure(0, weight=1)
frame.columnconfigure(1, weight=1) frame.columnconfigure(1, weight=1)
button = ttk.Button(frame, text="OK", command=self.button_ok) 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 = ttk.Button(frame, text="Cancel", command=self.destroy)
button.grid(row=0, column=1, sticky="nsew") button.grid(row=0, column=1, sticky="ew")
frame.grid(row=4, column=0, sticky="nsew")
def set_bindings(self): def set_bindings(self):
self.red_entry.bind("<FocusIn>", lambda x: self.current_focus("rgb")) self.red_entry.bind("<FocusIn>", lambda x: self.current_focus("rgb"))

View file

@ -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()}"
)

View file

@ -6,7 +6,7 @@ import grpc
from core.api.grpc.mobility_pb2 import MobilityAction from core.api.grpc.mobility_pb2 import MobilityAction
from core.gui.dialogs.dialog import Dialog from core.gui.dialogs.dialog import Dialog
from core.gui.images import ImageEnum, Images from core.gui.images import ImageEnum
from core.gui.themes import PADX, PADY from core.gui.themes import PADX, PADY
if TYPE_CHECKING: if TYPE_CHECKING:
@ -89,17 +89,17 @@ class MobilityPlayerDialog(Dialog):
for i in range(3): for i in range(3):
frame.columnconfigure(i, weight=1) 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 = ttk.Button(frame, image=image, command=self.click_play)
self.play_button.image = image self.play_button.image = image
self.play_button.grid(row=0, column=0, sticky="ew", padx=PADX) 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 = ttk.Button(frame, image=image, command=self.click_pause)
self.pause_button.image = image self.pause_button.image = image
self.pause_button.grid(row=0, column=1, sticky="ew", padx=PADX) 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 = ttk.Button(frame, image=image, command=self.click_stop)
self.stop_button.image = image self.stop_button.image = image
self.stop_button.grid(row=0, column=2, sticky="ew", padx=PADX) self.stop_button.grid(row=0, column=2, sticky="ew", padx=PADX)

View file

@ -207,8 +207,8 @@ class NodeConfigDialog(Dialog):
notebook.grid(sticky="nsew", pady=PADY) notebook.grid(sticky="nsew", pady=PADY)
self.top.rowconfigure(notebook.grid_info()["row"], weight=1) self.top.rowconfigure(notebook.grid_info()["row"], weight=1)
state = tk.DISABLED if self.app.core.is_runtime() else tk.NORMAL state = tk.DISABLED if self.app.core.is_runtime() else tk.NORMAL
for interface in self.canvas_node.interfaces: for interface_id in sorted(self.canvas_node.interfaces):
logging.info("interface: %s", interface) interface = self.canvas_node.interfaces[interface_id]
tab = ttk.Frame(notebook, padding=FRAME_PAD) tab = ttk.Frame(notebook, padding=FRAME_PAD)
tab.grid(sticky="nsew", pady=PADY) tab.grid(sticky="nsew", pady=PADY)
tab.columnconfigure(1, weight=1) tab.columnconfigure(1, weight=1)
@ -309,7 +309,7 @@ class NodeConfigDialog(Dialog):
self.canvas_node.image = self.image self.canvas_node.image = self.image
# update node interface data # update node interface data
for interface in self.canvas_node.interfaces: for interface in self.canvas_node.interfaces.values():
data = self.interfaces[interface.id] data = self.interfaces[interface.id]
# validate ip4 # validate ip4

View file

@ -104,7 +104,8 @@ class ObserverDialog(Dialog):
self.observers.insert(tk.END, name) self.observers.insert(tk.END, name)
self.name.set("") self.name.set("")
self.cmd.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: else:
messagebox.showerror("Observer Error", f"{name} already exists") messagebox.showerror("Observer Error", f"{name} already exists")
@ -132,7 +133,8 @@ class ObserverDialog(Dialog):
self.observers.selection_clear(0, tk.END) self.observers.selection_clear(0, tk.END)
self.save_button.config(state=tk.DISABLED) self.save_button.config(state=tk.DISABLED)
self.delete_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): def handle_observer_change(self, event: tk.Event):
selection = self.observers.curselection() selection = self.observers.curselection()

View file

@ -17,6 +17,8 @@ if TYPE_CHECKING:
from core.gui.app import Application from core.gui.app import Application
from core.gui.graph.node import CanvasNode from core.gui.graph.node import CanvasNode
ICON_SIZE = 16
class ServiceConfigDialog(Dialog): class ServiceConfigDialog(Dialog):
def __init__( def __init__(
@ -51,10 +53,8 @@ class ServiceConfigDialog(Dialog):
self.directory_entry = None self.directory_entry = None
self.default_directories = [] self.default_directories = []
self.temp_directories = [] self.temp_directories = []
self.documentnew_img = Images.get( self.documentnew_img = self.app.get_icon(ImageEnum.DOCUMENTNEW, ICON_SIZE)
ImageEnum.DOCUMENTNEW, int(16 * app.app_scale) self.editdelete_img = self.app.get_icon(ImageEnum.EDITDELETE, ICON_SIZE)
)
self.editdelete_img = Images.get(ImageEnum.EDITDELETE, int(16 * app.app_scale))
self.notebook = None self.notebook = None
self.metadata_entry = None self.metadata_entry = None
self.filename_combobox = None self.filename_combobox = None

View file

@ -331,8 +331,6 @@ class CanvasEdge(Edge):
dst_pos = self.canvas.coords(self.dst) dst_pos = self.canvas.coords(self.dst)
self.move_dst(dst_pos) self.move_dst(dst_pos)
self.check_wireless() 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) logging.debug("Draw wired link from node %s to node %s", self.src, dst)
def is_wireless(self) -> bool: def is_wireless(self) -> bool:

View file

@ -20,7 +20,7 @@ from core.gui.graph.enums import GraphMode, ScaleOption
from core.gui.graph.node import CanvasNode from core.gui.graph.node import CanvasNode
from core.gui.graph.shape import Shape from core.gui.graph.shape import Shape
from core.gui.graph.shapeutils import ShapeType, is_draw_shape, is_marker from core.gui.graph.shapeutils import ShapeType, is_draw_shape, is_marker
from core.gui.images import ImageEnum, Images, TypeToImage from core.gui.images import ImageEnum, TypeToImage
from core.gui.nodeutils import NodeUtils from core.gui.nodeutils import NodeUtils
if TYPE_CHECKING: if TYPE_CHECKING:
@ -30,6 +30,8 @@ if TYPE_CHECKING:
ZOOM_IN = 1.1 ZOOM_IN = 1.1
ZOOM_OUT = 0.9 ZOOM_OUT = 0.9
ICON_SIZE = 48 ICON_SIZE = 48
MOVE_NODE_MODES = {GraphMode.NODE, GraphMode.SELECT}
MOVE_SHAPE_MODES = {GraphMode.ANNOTATION, GraphMode.SELECT}
class ShowVar(BooleanVar): class ShowVar(BooleanVar):
@ -41,19 +43,12 @@ class ShowVar(BooleanVar):
def state(self) -> str: def state(self) -> str:
return tk.NORMAL if self.get() else tk.HIDDEN 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()) self.canvas.itemconfigure(self.tag, state=self.state())
class CanvasGraph(tk.Canvas): class CanvasGraph(tk.Canvas):
def __init__( def __init__(self, master: tk.Widget, app: "Application", core: "CoreClient"):
self,
master: tk.Widget,
app: "Application",
core: "CoreClient",
width: int,
height: int,
):
super().__init__(master, highlightthickness=0, background="#cccccc") super().__init__(master, highlightthickness=0, background="#cccccc")
self.app = app self.app = app
self.core = core self.core = core
@ -74,6 +69,8 @@ class CanvasGraph(tk.Canvas):
self.drawing_edge = None self.drawing_edge = None
self.rect = None self.rect = None
self.shape_drawing = False self.shape_drawing = False
width = self.app.guiconfig.preferences.width
height = self.app.guiconfig.preferences.height
self.default_dimensions = (width, height) self.default_dimensions = (width, height)
self.current_dimensions = self.default_dimensions self.current_dimensions = self.default_dimensions
self.ratio = 1.0 self.ratio = 1.0
@ -144,7 +141,7 @@ class CanvasGraph(tk.Canvas):
self.show_ip6s.set(True) self.show_ip6s.set(True)
# delete any existing drawn items # delete any existing drawn items
for tag in tags.COMPONENT_TAGS: for tag in tags.RESET_TAGS:
self.delete(tag) self.delete(tag)
# set the private variables to default value # 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 the gui can't find node's image, default to the "edit-node" image
if not image: if not image:
image = Images.get( image = self.app.get_icon(ImageEnum.EDITNODE, ICON_SIZE)
ImageEnum.EDITNODE, int(ICON_SIZE * self.app.app_scale)
)
x = core_node.position.x x = core_node.position.x
y = core_node.position.y y = core_node.position.y
node = CanvasNode(self.app, x, y, core_node, image) node = CanvasNode(self.app, x, y, core_node, image)
@ -327,20 +322,25 @@ class CanvasGraph(tk.Canvas):
self.edges[edge.token] = edge self.edges[edge.token] = edge
self.core.links[edge.token] = edge self.core.links[edge.token] = edge
if link.HasField("interface_one"): if link.HasField("interface_one"):
canvas_node_one.interfaces.append(link.interface_one) interface_one = link.interface_one
edge.src_interface = 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"): if link.HasField("interface_two"):
canvas_node_two.interfaces.append(link.interface_two) interface_two = link.interface_two
edge.dst_interface = 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: elif link.options.unidirectional:
edge = self.edges[token] edge = self.edges[token]
edge.asymmetric_link = link edge.asymmetric_link = link
else: else:
logging.error("duplicate link received: %s", link) 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): def stopped_session(self):
# clear wireless edges # clear wireless edges
for edge in self.wireless_edges.values(): for edge in self.wireless_edges.values():
@ -521,8 +521,8 @@ class CanvasGraph(tk.Canvas):
other_interface = edge.dst_interface other_interface = edge.dst_interface
other_node = self.nodes[other_id] other_node = self.nodes[other_id]
other_node.edges.remove(edge) other_node.edges.remove(edge)
if other_interface in other_node.interfaces: if other_interface:
other_node.interfaces.remove(other_interface) del other_node.interfaces[other_interface.id]
if is_wireless: if is_wireless:
other_node.delete_antenna() other_node.delete_antenna()
@ -540,12 +540,12 @@ class CanvasGraph(tk.Canvas):
del self.edges[edge.token] del self.edges[edge.token]
src_node = self.nodes[edge.src] src_node = self.nodes[edge.src]
src_node.edges.discard(edge) src_node.edges.discard(edge)
if edge.src_interface in src_node.interfaces: if edge.src_interface:
src_node.interfaces.remove(edge.src_interface) del src_node.interfaces[edge.src_interface.id]
dst_node = self.nodes[edge.dst] dst_node = self.nodes[edge.dst]
dst_node.edges.discard(edge) dst_node.edges.discard(edge)
if edge.dst_interface in dst_node.interfaces: if edge.dst_interface:
dst_node.interfaces.remove(edge.dst_interface) del dst_node.interfaces[edge.dst_interface.id]
src_wireless = NodeUtils.is_wireless_node(src_node.core_node.type) src_wireless = NodeUtils.is_wireless_node(src_node.core_node.type)
if src_wireless: if src_wireless:
dst_node.delete_antenna() dst_node.delete_antenna()
@ -565,10 +565,10 @@ class CanvasGraph(tk.Canvas):
self.offset[0] * factor + event.x * (1 - factor), self.offset[0] * factor + event.x * (1 - factor),
self.offset[1] * factor + event.y * (1 - factor), self.offset[1] * factor + event.y * (1 - factor),
) )
logging.info("ratio: %s", self.ratio) logging.debug("ratio: %s", self.ratio)
logging.info("offset: %s", self.offset) logging.debug("offset: %s", self.offset)
self.app.statusbar.zoom.config(text="%s" % (int(self.ratio * 100)) + "%") zoom_label = f"{self.ratio * 100:.0f}%"
self.app.statusbar.zoom.config(text=zoom_label)
if self.wallpaper: if self.wallpaper:
self.redraw_wallpaper() self.redraw_wallpaper()
@ -590,16 +590,17 @@ class CanvasGraph(tk.Canvas):
if self.mode == GraphMode.EDGE and is_node: if self.mode == GraphMode.EDGE and is_node:
pos = self.coords(selected) pos = self.coords(selected)
self.drawing_edge = CanvasEdge(self, selected, pos, pos) self.drawing_edge = CanvasEdge(self, selected, pos, pos)
self.organize()
if self.mode == GraphMode.ANNOTATION: if self.mode == GraphMode.ANNOTATION:
if is_marker(self.annotation_type): if is_marker(self.annotation_type):
r = self.app.toolbar.marker_tool.radius r = self.app.toolbar.marker_frame.size.get()
self.create_oval( self.create_oval(
x - r, x - r,
y - r, y - r,
x + r, x + r,
y + r, y + r,
fill=self.app.toolbar.marker_tool.color, fill=self.app.toolbar.marker_frame.color,
outline="", outline="",
tags=(tags.MARKER, tags.ANNOTATION), tags=(tags.MARKER, tags.ANNOTATION),
state=self.show_annotations.state(), state=self.show_annotations.state(),
@ -652,9 +653,6 @@ class CanvasGraph(tk.Canvas):
self.select_object(selected, choose_multiple=True) self.select_object(selected, choose_multiple=True)
def click_motion(self, event: tk.Event): def click_motion(self, event: tk.Event):
"""
Redraw drawing edge according to the current position of the mouse
"""
x, y = self.canvas_xy(event) x, y = self.canvas_xy(event)
if not self.inside_canvas(x, y): if not self.inside_canvas(x, y):
if self.select_box: if self.select_box:
@ -676,18 +674,19 @@ class CanvasGraph(tk.Canvas):
if is_draw_shape(self.annotation_type) and self.shape_drawing: if is_draw_shape(self.annotation_type) and self.shape_drawing:
shape = self.shapes[self.selected] shape = self.shapes[self.selected]
shape.shape_motion(x, y) shape.shape_motion(x, y)
return
elif is_marker(self.annotation_type): elif is_marker(self.annotation_type):
r = self.app.toolbar.marker_tool.radius r = self.app.toolbar.marker_frame.size.get()
self.create_oval( self.create_oval(
x - r, x - r,
y - r, y - r,
x + r, x + r,
y + r, y + r,
fill=self.app.toolbar.marker_tool.color, fill=self.app.toolbar.marker_frame.color,
outline="", outline="",
tags=(tags.MARKER, tags.ANNOTATION), tags=(tags.MARKER, tags.ANNOTATION),
) )
return return
if self.mode == GraphMode.EDGE: if self.mode == GraphMode.EDGE:
return return
@ -695,11 +694,11 @@ class CanvasGraph(tk.Canvas):
# move selected objects # move selected objects
if self.selection: if self.selection:
for selected_id in 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 = self.shapes[selected_id]
shape.motion(x_offset, y_offset) 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 = self.nodes[selected_id]
node.motion(x_offset, y_offset, update=self.core.is_runtime()) node.motion(x_offset, y_offset, update=self.core.is_runtime())
else: else:
@ -714,7 +713,7 @@ class CanvasGraph(tk.Canvas):
if not self.app.core.is_runtime(): if not self.app.core.is_runtime():
self.delete_selected_objects() self.delete_selected_objects()
else: 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): def double_click(self, event: tk.Event):
selected = self.get_selected(event) selected = self.get_selected(event)
@ -733,13 +732,11 @@ class CanvasGraph(tk.Canvas):
if not core_node: if not core_node:
return return
try: try:
self.node_draw.image = Images.get( image_enum = self.node_draw.image_enum
self.node_draw.image_enum, int(ICON_SIZE * self.app.app_scale) self.node_draw.image = self.app.get_icon(image_enum, ICON_SIZE)
)
except AttributeError: except AttributeError:
self.node_draw.image = Images.get_custom( image_file = self.node_draw.image_file
self.node_draw.image_file, int(ICON_SIZE * self.app.app_scale) 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) node = CanvasNode(self.app, x, y, core_node, self.node_draw.image)
self.core.canvas_nodes[core_node.id] = node self.core.canvas_nodes[core_node.id] = node
self.nodes[node.id] = node self.nodes[node.id] = node
@ -830,10 +827,10 @@ class CanvasGraph(tk.Canvas):
self.draw_wallpaper(image) self.draw_wallpaper(image)
def redraw_canvas(self, dimensions: Tuple[int, int] = None): 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 # 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 factor = 1 / self.ratio
self.scale(tk.ALL, self.offset[0], self.offset[1], factor, factor) self.scale(tk.ALL, self.offset[0], self.offset[1], factor, factor)
self.move(tk.ALL, -self.offset[0], -self.offset[1]) self.move(tk.ALL, -self.offset[0], -self.offset[1])
@ -852,11 +849,11 @@ class CanvasGraph(tk.Canvas):
def redraw_wallpaper(self): def redraw_wallpaper(self):
if self.adjust_to_dim.get(): if self.adjust_to_dim.get():
logging.info("drawing wallpaper to canvas dimensions") logging.debug("drawing wallpaper to canvas dimensions")
self.resize_to_wallpaper() self.resize_to_wallpaper()
else: else:
option = ScaleOption(self.scale_option.get()) 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: if option == ScaleOption.UPPER_LEFT:
self.wallpaper_upper_left() self.wallpaper_upper_left()
elif option == ScaleOption.CENTERED: elif option == ScaleOption.CENTERED:
@ -865,10 +862,11 @@ class CanvasGraph(tk.Canvas):
self.wallpaper_scaled() self.wallpaper_scaled()
elif option == ScaleOption.TILED: elif option == ScaleOption.TILED:
logging.warning("tiled background not implemented yet") logging.warning("tiled background not implemented yet")
self.organize()
# raise items above wallpaper def organize(self) -> None:
for component in tags.ABOVE_WALLPAPER_TAGS: for tag in tags.ORGANIZE_TAGS:
self.tag_raise(component) self.tag_raise(tag)
def set_wallpaper(self, filename: str): def set_wallpaper(self, filename: str):
logging.debug("setting wallpaper: %s", filename) logging.debug("setting wallpaper: %s", filename)
@ -902,10 +900,10 @@ class CanvasGraph(tk.Canvas):
def copy(self): def copy(self):
if self.core.is_runtime(): if self.core.is_runtime():
logging.info("copy is disabled during runtime state") logging.debug("copy is disabled during runtime state")
return return
if self.selection: if self.selection:
logging.info("to copy nodes: %s", self.selection) logging.debug("to copy nodes: %s", self.selection)
self.to_copy.clear() self.to_copy.clear()
for node_id in self.selection.keys(): for node_id in self.selection.keys():
canvas_node = self.nodes[node_id] canvas_node = self.nodes[node_id]
@ -913,7 +911,7 @@ class CanvasGraph(tk.Canvas):
def paste(self): def paste(self):
if self.core.is_runtime(): if self.core.is_runtime():
logging.info("paste is disabled during runtime state") logging.debug("paste is disabled during runtime state")
return return
# maps original node canvas id to copy node canvas id # maps original node canvas id to copy node canvas id
copy_map = {} copy_map = {}
@ -1004,14 +1002,12 @@ class CanvasGraph(tk.Canvas):
): ):
for custom_node in self.app.guiconfig.nodes: for custom_node in self.app.guiconfig.nodes:
if custom_node.name == canvas_node.core_node.model: if custom_node.name == canvas_node.core_node.model:
img = Images.get_custom( img = self.app.get_custom_icon(custom_node.image, ICON_SIZE)
custom_node.image, int(ICON_SIZE * self.app.app_scale)
)
else: else:
image_enum = TypeToImage.get( image_enum = TypeToImage.get(
canvas_node.core_node.type, canvas_node.core_node.model 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) self.itemconfig(nid, image=img)
canvas_node.image = img canvas_node.image = img

View file

@ -17,7 +17,7 @@ from core.gui.dialogs.wlanconfig import WlanConfigDialog
from core.gui.graph import tags from core.gui.graph import tags
from core.gui.graph.edges import CanvasEdge from core.gui.graph.edges import CanvasEdge
from core.gui.graph.tooltip import CanvasTooltip from core.gui.graph.tooltip import CanvasTooltip
from core.gui.images import ImageEnum, Images from core.gui.images import ImageEnum
from core.gui.nodeutils import ANTENNA_SIZE, NodeUtils from core.gui.nodeutils import ANTENNA_SIZE, NodeUtils
if TYPE_CHECKING: if TYPE_CHECKING:
@ -55,7 +55,7 @@ class CanvasNode:
) )
self.tooltip = CanvasTooltip(self.canvas) self.tooltip = CanvasTooltip(self.canvas)
self.edges = set() self.edges = set()
self.interfaces = [] self.interfaces = {}
self.wireless_edges = set() self.wireless_edges = set()
self.antennas = [] self.antennas = []
self.antenna_images = {} self.antenna_images = {}
@ -70,6 +70,12 @@ class CanvasNode:
self.context = tk.Menu(self.canvas) self.context = tk.Menu(self.canvas)
themes.style_menu(self.context) 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): def setup_bindings(self):
self.canvas.tag_bind(self.id, "<Double-Button-1>", self.double_click) self.canvas.tag_bind(self.id, "<Double-Button-1>", self.double_click)
self.canvas.tag_bind(self.id, "<Enter>", self.on_enter) self.canvas.tag_bind(self.id, "<Enter>", self.on_enter)
@ -85,7 +91,7 @@ class CanvasNode:
def add_antenna(self): def add_antenna(self):
x, y = self.canvas.coords(self.id) x, y = self.canvas.coords(self.id)
offset = len(self.antennas) * 8 * self.app.app_scale 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( antenna_id = self.canvas.create_image(
x - 16 + offset, x - 16 + offset,
y - int(23 * self.app.app_scale), y - int(23 * self.app.app_scale),
@ -321,9 +327,7 @@ class CanvasNode:
def scale_antennas(self): def scale_antennas(self):
for i in range(len(self.antennas)): for i in range(len(self.antennas)):
antenna_id = self.antennas[i] antenna_id = self.antennas[i]
image = Images.get( image = self.app.get_icon(ImageEnum.ANTENNA, ANTENNA_SIZE)
ImageEnum.ANTENNA, int(ANTENNA_SIZE * self.app.app_scale)
)
self.canvas.itemconfig(antenna_id, image=image) self.canvas.itemconfig(antenna_id, image=image)
self.antenna_images[antenna_id] = image self.antenna_images[antenna_id] = image
node_x, node_y = self.canvas.coords(self.id) node_x, node_y = self.canvas.coords(self.id)

View file

@ -146,8 +146,7 @@ class Shape:
self.canvas.coords(self.id, self.x1, self.y1, x1, y1) self.canvas.coords(self.id, self.x1, self.y1, x1, y1)
def shape_complete(self, x: float, y: float): def shape_complete(self, x: float, y: float):
for component in tags.ABOVE_SHAPE: self.canvas.organize()
self.canvas.tag_raise(component)
s = ShapeDialog(self.app, self) s = ShapeDialog(self.app, self)
s.show() s.show()
@ -158,10 +157,11 @@ class Shape:
original_position = self.canvas.coords(self.id) original_position = self.canvas.coords(self.id)
self.canvas.move(self.id, x_offset, y_offset) self.canvas.move(self.id, x_offset, y_offset)
coords = self.canvas.coords(self.id) coords = self.canvas.coords(self.id)
if self.shape_type == ShapeType.TEXT:
coords = coords * 2
if not self.canvas.valid_position(*coords): if not self.canvas.valid_position(*coords):
self.canvas.coords(self.id, original_position) self.canvas.coords(self.id, original_position)
return return
self.canvas.move_selection(self.id, x_offset, y_offset) self.canvas.move_selection(self.id, x_offset, y_offset)
if self.text_id is not None: if self.text_id is not None:
self.canvas.move(self.text_id, x_offset, y_offset) self.canvas.move(self.text_id, x_offset, y_offset)

View file

@ -11,19 +11,21 @@ NODE = "node"
WALLPAPER = "wallpaper" WALLPAPER = "wallpaper"
SELECTION = "selectednodes" SELECTION = "selectednodes"
MARKER = "marker" MARKER = "marker"
ABOVE_WALLPAPER_TAGS = [ ORGANIZE_TAGS = [
WALLPAPER,
GRIDLINE, GRIDLINE,
SHAPE, SHAPE,
SHAPE_TEXT, SHAPE_TEXT,
EDGE, EDGE,
LINK_LABEL,
WIRELESS_EDGE, WIRELESS_EDGE,
LINK_LABEL,
ANTENNA, ANTENNA,
NODE, NODE,
NODE_LABEL, NODE_LABEL,
SELECTION,
MARKER,
] ]
ABOVE_SHAPE = [GRIDLINE, EDGE, LINK_LABEL, WIRELESS_EDGE, ANTENNA, NODE, NODE_LABEL] RESET_TAGS = [
COMPONENT_TAGS = [
EDGE, EDGE,
NODE, NODE,
NODE_LABEL, NODE_LABEL,

View file

@ -12,7 +12,9 @@ if TYPE_CHECKING:
from core.gui.graph.node import CanvasNode 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}") net = netaddr.IPNetwork(f"{interface.ip4}/{interface.ip4mask}")
ip_value = net.value ip_value = net.value
cidr_value = net.cidr.value cidr_value = net.cidr.value
@ -105,12 +107,12 @@ class InterfaceManager:
for interface in interfaces: for interface in interfaces:
subnets = self.get_subnets(interface) subnets = self.get_subnets(interface)
if subnets not in remaining_subnets: if subnets not in remaining_subnets:
if self.current_subnets == subnets:
self.current_subnets = None
self.used_subnets.pop(subnets.key(), None) self.used_subnets.pop(subnets.key(), None)
else: else:
index = get_index(interface) 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: def joined(self, links: List["core_pb2.Link"]) -> None:
interfaces = [] interfaces = []
@ -124,6 +126,8 @@ class InterfaceManager:
for interface in interfaces: for interface in interfaces:
subnets = self.get_subnets(interface) subnets = self.get_subnets(interface)
index = get_index(interface) index = get_index(interface)
if index is None:
continue
subnets.used_indexes.add(index) subnets.used_indexes.add(index)
if subnets.key() not in self.used_subnets: if subnets.key() not in self.used_subnets:
self.used_subnets[subnets.key()] = subnets self.used_subnets[subnets.key()] = subnets
@ -147,7 +151,6 @@ class InterfaceManager:
return str(ip4), str(ip6) return str(ip4), str(ip6)
def get_subnets(self, interface: "core_pb2.Interface") -> Subnets: def get_subnets(self, interface: "core_pb2.Interface") -> Subnets:
logging.info("get subnets for interface: %s", interface)
ip4_subnet = self.ip4_subnets ip4_subnet = self.ip4_subnets
if interface.ip4: if interface.ip4:
ip4_subnet = IPNetwork(f"{interface.ip4}/{interface.ip4mask}").cidr ip4_subnet = IPNetwork(f"{interface.ip4}/{interface.ip4mask}").cidr

View file

@ -23,23 +23,13 @@ from core.gui.dialogs.sessionoptions import SessionOptionsDialog
from core.gui.dialogs.sessions import SessionsDialog from core.gui.dialogs.sessions import SessionsDialog
from core.gui.dialogs.throughput import ThroughputDialog from core.gui.dialogs.throughput import ThroughputDialog
from core.gui.nodeutils import ICON_SIZE from core.gui.nodeutils import ICON_SIZE
from core.gui.observers import ObserversMenu
from core.gui.task import ProgressTask from core.gui.task import ProgressTask
if TYPE_CHECKING: if TYPE_CHECKING:
from core.gui.app import Application from core.gui.app import Application
MAX_FILES = 3 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): class Menubar(tk.Menu):
@ -47,20 +37,17 @@ class Menubar(tk.Menu):
Core menubar Core menubar
""" """
def __init__(self, master: tk.Tk, app: "Application", **kwargs) -> None: def __init__(self, app: "Application") -> None:
""" """
Create a CoreMenubar instance Create a CoreMenubar instance
""" """
super().__init__(master, **kwargs) super().__init__(app)
self.master.config(menu=self)
self.app = app self.app = app
self.core = app.core self.core = app.core
self.canvas = app.canvas self.canvas = app.canvas
self.recent_menu = None self.recent_menu = None
self.edit_menu = None self.edit_menu = None
self.observers_menu = None self.observers_menu = None
self.observers_var = tk.StringVar(value=tk.NONE)
self.observers_custom_index = None
self.draw() self.draw()
def draw(self) -> None: def draw(self) -> None:
@ -202,42 +189,9 @@ class Menubar(tk.Menu):
""" """
Create observer widget menu item and create the sub menu items inside Create observer widget menu item and create the sub menu items inside
""" """
self.observers_menu = tk.Menu(widget_menu) self.observers_menu = ObserversMenu(widget_menu, self.app)
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()
widget_menu.add_cascade(label="Observer Widgets", menu=self.observers_menu) 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: def create_adjacency_menu(self, widget_menu: tk.Menu) -> None:
""" """
Create adjacency menu item and the sub menu items inside Create adjacency menu item and the sub menu items inside

View file

@ -22,6 +22,7 @@ class NodeDraw:
self.node_type: core_pb2.NodeType = None self.node_type: core_pb2.NodeType = None
self.model: Optional[str] = None self.model: Optional[str] = None
self.services: Set[str] = set() self.services: Set[str] = set()
self.label = None
@classmethod @classmethod
def from_setup( def from_setup(

View 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()

View file

@ -13,8 +13,8 @@ if TYPE_CHECKING:
class StatusBar(ttk.Frame): class StatusBar(ttk.Frame):
def __init__(self, master: tk.Widget, app: "Application", **kwargs): def __init__(self, master: tk.Widget, app: "Application"):
super().__init__(master, **kwargs) super().__init__(master)
self.app = app self.app = app
self.status = None self.status = None
self.statusvar = tk.StringVar() self.statusvar = tk.StringVar()

View file

@ -5,20 +5,23 @@ from functools import partial
from tkinter import ttk from tkinter import ttk
from typing import TYPE_CHECKING, Callable from typing import TYPE_CHECKING, Callable
from PIL.ImageTk import PhotoImage
from core.api.grpc import core_pb2 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.dialogs.runtool import RunToolDialog
from core.gui.graph import tags
from core.gui.graph.enums import GraphMode from core.gui.graph.enums import GraphMode
from core.gui.graph.shapeutils import ShapeType, is_marker 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.nodeutils import NodeDraw, NodeUtils
from core.gui.observers import ObserversMenu
from core.gui.task import ProgressTask from core.gui.task import ProgressTask
from core.gui.themes import Styles from core.gui.themes import Styles
from core.gui.tooltip import Tooltip from core.gui.tooltip import Tooltip
if TYPE_CHECKING: if TYPE_CHECKING:
from core.gui.app import Application from core.gui.app import Application
from PIL import ImageTk
TOOLBAR_SIZE = 32 TOOLBAR_SIZE = 32
PICKER_SIZE = 24 PICKER_SIZE = 24
@ -30,8 +33,124 @@ class NodeTypeEnum(Enum):
OTHER = 2 OTHER = 2
def icon(image_enum, width=TOOLBAR_SIZE): def enable_buttons(frame: ttk.Frame, enabled: bool) -> None:
return Images.get(image_enum, width) 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): class Toolbar(ttk.Frame):
@ -39,11 +158,11 @@ class Toolbar(ttk.Frame):
Core toolbar class Core toolbar class
""" """
def __init__(self, master: tk.Widget, app: "Application", **kwargs): def __init__(self, app: "Application") -> None:
""" """
Create a CoreToolbar instance Create a CoreToolbar instance
""" """
super().__init__(master, **kwargs) super().__init__(app)
self.app = app self.app = app
# design buttons # design buttons
@ -63,316 +182,193 @@ class Toolbar(ttk.Frame):
# frames # frames
self.design_frame = None self.design_frame = None
self.runtime_frame = None self.runtime_frame = None
self.node_picker = None self.marker_frame = None
self.network_picker = None self.picker = None
self.annotation_picker = None
# dialog # observers
self.marker_tool = None self.observers_menu = None
# these variables help keep track of what images being drawn so that scaling # these variables help keep track of what images being drawn so that scaling
# is possible since ImageTk.PhotoImage does not have resize method # is possible since PhotoImage does not have resize method
self.node_enum = None self.current_node = NodeUtils.NODES[0]
self.network_enum = None self.current_network = NodeUtils.NETWORK_NODES[0]
self.annotation_enum = None self.current_annotation = ShapeType.MARKER
self.annotation_enum = ImageEnum.MARKER
# draw components # draw components
self.draw() self.draw()
def get_icon(self, image_enum, width=TOOLBAR_SIZE): def draw(self) -> None:
return Images.get(image_enum, int(width * self.app.app_scale))
def draw(self):
self.columnconfigure(0, weight=1) self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1) self.rowconfigure(0, weight=1)
self.draw_design_frame() self.draw_design_frame()
self.draw_runtime_frame() self.draw_runtime_frame()
self.design_frame.tkraise() self.design_frame.tkraise()
self.marker_frame = MarkerFrame(self, self.app)
def draw_design_frame(self): def draw_design_frame(self) -> None:
self.design_frame = ttk.Frame(self) self.design_frame = ButtonBar(self, self.app)
self.design_frame.grid(row=0, column=0, sticky="nsew") self.design_frame.grid(row=0, column=0, sticky="nsew")
self.design_frame.columnconfigure(0, weight=1) self.design_frame.columnconfigure(0, weight=1)
self.play_button = self.create_button( self.play_button = self.design_frame.create_button(
self.design_frame, ImageEnum.START, self.click_start, "Start Session"
self.get_icon(ImageEnum.START),
self.click_start,
"start the session",
) )
self.select_button = self.create_button( self.select_button = self.design_frame.create_button(
self.design_frame, ImageEnum.SELECT, self.click_selection, "Selection Tool", radio=True
self.get_icon(ImageEnum.SELECT),
self.click_selection,
"selection tool",
) )
self.link_button = self.create_button( self.link_button = self.design_frame.create_button(
self.design_frame, ImageEnum.LINK, self.click_link, "Link Tool", radio=True
self.get_icon(ImageEnum.LINK), )
self.click_link, self.node_button = self.design_frame.create_button(
"link tool", 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): def draw_runtime_frame(self) -> None:
logging.debug("selecting design button: %s", button) self.runtime_frame = ButtonBar(self, self.app)
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)
self.runtime_frame.grid(row=0, column=0, sticky="nsew") self.runtime_frame.grid(row=0, column=0, sticky="nsew")
self.runtime_frame.columnconfigure(0, weight=1) self.runtime_frame.columnconfigure(0, weight=1)
self.stop_button = self.create_button( self.stop_button = self.runtime_frame.create_button(
self.runtime_frame, ImageEnum.STOP, self.click_stop, "Stop Session"
self.get_icon(ImageEnum.STOP),
self.click_stop,
"stop the session",
) )
self.runtime_select_button = self.create_button( self.runtime_select_button = self.runtime_frame.create_button(
self.runtime_frame, ImageEnum.SELECT, self.click_runtime_selection, "Selection Tool", radio=True
self.get_icon(ImageEnum.SELECT),
self.click_runtime_selection,
"selection tool",
) )
self.runtime_marker_button = self.create_button( self.create_observe_button()
self.runtime_frame, self.runtime_marker_button = self.runtime_frame.create_button(
icon(ImageEnum.MARKER), ImageEnum.MARKER, self.click_marker_button, "Marker Tool", radio=True
self.click_marker_button,
"marker",
) )
self.run_command_button = self.create_button( self.run_command_button = self.runtime_frame.create_button(
self.runtime_frame, icon(ImageEnum.RUN), self.click_run_button, "run" ImageEnum.RUN, self.click_run_button, "Run Tool"
) )
def draw_node_picker(self): def draw_node_picker(self) -> None:
self.hide_pickers() self.hide_marker()
self.node_picker = ttk.Frame(self.master) 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 # draw default nodes
for node_draw in NodeUtils.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( func = partial(
self.update_button, self.update_button, self.node_button, node_draw, NodeTypeEnum.NODE
self.node_button,
toolbar_image,
node_draw,
NodeTypeEnum.NODE,
node_draw.image_enum,
) )
self.create_picker_button(image, func, self.node_picker, node_draw.label) self.picker.create_node_button(node_draw, func)
# draw custom nodes # draw custom nodes
for name in sorted(self.app.core.custom_nodes): for name in sorted(self.app.core.custom_nodes):
node_draw = self.app.core.custom_nodes[name] 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( func = partial(
self.update_button, self.update_button, self.node_button, node_draw, NodeTypeEnum.NODE
self.node_button,
toolbar_image,
node_draw,
NodeTypeEnum,
node_draw.image_file,
) )
self.create_picker_button(image, func, self.node_picker, name) self.picker.create_node_button(node_draw, func)
self.design_select(self.node_button) self.picker.show()
self.node_button.after(
0, lambda: self.show_picker(self.node_button, self.node_picker)
)
def show_picker(self, button: ttk.Button, picker: ttk.Frame): def click_selection(self) -> None:
x = self.winfo_width() + 1 self.design_frame.select_radio(self.select_button)
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)
self.app.canvas.mode = GraphMode.SELECT self.app.canvas.mode = GraphMode.SELECT
self.hide_marker()
def click_runtime_selection(self): def click_runtime_selection(self) -> None:
logging.debug("clicked selection tool") self.runtime_frame.select_radio(self.runtime_select_button)
self.runtime_select(self.runtime_select_button)
self.app.canvas.mode = GraphMode.SELECT 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 Start session handler redraw buttons, send node and link messages to grpc
server. server.
""" """
self.app.menubar.change_menubar_item_state(is_runtime=True) self.app.menubar.change_menubar_item_state(is_runtime=True)
self.app.canvas.mode = GraphMode.SELECT self.app.canvas.mode = GraphMode.SELECT
enable_buttons(self.design_frame, enabled=False)
task = ProgressTask( task = ProgressTask(
self.app, "Start", self.app.core.start_session, self.start_callback self.app, "Start", self.app.core.start_session, self.start_callback
) )
task.start() task.start()
def start_callback(self, response: core_pb2.StartSessionResponse): def start_callback(self, response: core_pb2.StartSessionResponse) -> None:
if response.result: if response.result:
self.set_runtime() self.set_runtime()
self.app.core.set_metadata() self.app.core.set_metadata()
self.app.core.show_mobility_players() self.app.core.show_mobility_players()
else: else:
enable_buttons(self.design_frame, enabled=True)
message = "\n".join(response.exceptions) message = "\n".join(response.exceptions)
self.app.show_error("Start Session Error", message) 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.runtime_frame.tkraise()
self.click_runtime_selection() 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.design_frame.tkraise()
self.click_selection() self.click_selection()
self.hide_marker()
def click_link(self): def click_link(self) -> None:
logging.debug("Click LINK button") self.design_frame.select_radio(self.link_button)
self.design_select(self.link_button)
self.app.canvas.mode = GraphMode.EDGE self.app.canvas.mode = GraphMode.EDGE
self.hide_marker()
def update_button( def update_button(
self, self,
button: ttk.Button, button: ttk.Button,
image: "ImageTk",
node_draw: NodeDraw, node_draw: NodeDraw,
type_enum, type_enum: NodeTypeEnum,
image_enum, image: PhotoImage,
): ) -> None:
logging.debug("update button(%s): %s", button, node_draw) logging.debug("update button(%s): %s", button, node_draw)
self.hide_pickers()
button.configure(image=image) button.configure(image=image)
button.image = image button.image = image
self.app.canvas.mode = GraphMode.NODE
self.app.canvas.node_draw = node_draw self.app.canvas.node_draw = node_draw
if type_enum == NodeTypeEnum.NODE: if type_enum == NodeTypeEnum.NODE:
self.node_enum = image_enum self.current_node = node_draw
elif type_enum == NodeTypeEnum.NETWORK: elif type_enum == NodeTypeEnum.NETWORK:
self.network_enum = image_enum self.current_network = node_draw
def hide_pickers(self): def draw_network_picker(self) -> None:
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):
""" """
Draw the options for link-layer button. Draw the options for link-layer button.
""" """
self.hide_pickers() self.hide_marker()
self.network_picker = ttk.Frame(self.master) 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: for node_draw in NodeUtils.NETWORK_NODES:
toolbar_image = self.get_icon(node_draw.image_enum, TOOLBAR_SIZE) func = partial(
image = self.get_icon(node_draw.image_enum, PICKER_SIZE) self.update_button, self.network_button, node_draw, NodeTypeEnum.NETWORK
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,
) )
self.design_select(self.network_button) self.picker.create_node_button(node_draw, func)
self.network_button.after( self.picker.show()
0, lambda: self.show_picker(self.network_button, self.network_picker)
)
def create_network_button(self): def draw_annotation_picker(self) -> None:
"""
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):
""" """
Draw the options for marker button. Draw the options for marker button.
""" """
self.hide_pickers() self.design_frame.select_radio(self.annotation_button)
self.annotation_picker = ttk.Frame(self.master) 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 = [ nodes = [
(ImageEnum.MARKER, ShapeType.MARKER), (ImageEnum.MARKER, ShapeType.MARKER),
(ImageEnum.OVAL, ShapeType.OVAL), (ImageEnum.OVAL, ShapeType.OVAL),
@ -380,114 +376,90 @@ class Toolbar(ttk.Frame):
(ImageEnum.TEXT, ShapeType.TEXT), (ImageEnum.TEXT, ShapeType.TEXT),
] ]
for image_enum, shape_type in nodes: for image_enum, shape_type in nodes:
toolbar_image = self.get_icon(image_enum, TOOLBAR_SIZE) label = shape_type.value
image = self.get_icon(image_enum, PICKER_SIZE) func = partial(self.update_annotation, shape_type, image_enum)
self.create_picker_button( self.picker.create_button(label, func, image_enum)
image, self.picker.show()
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)
)
def create_annotation_button(self): def create_observe_button(self) -> None:
""" image = self.app.get_icon(ImageEnum.OBSERVE, TOOLBAR_SIZE)
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):
menu_button = ttk.Menubutton( 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_button.grid(sticky="ew")
menu = tk.Menu(menu_button, tearoff=0) self.observers_menu = ObserversMenu(menu_button, self.app)
menu_button["menu"] = menu menu_button["menu"] = self.observers_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...")
def click_stop(self): def click_stop(self) -> None:
""" """
redraw buttons on the toolbar, send node and link messages to grpc server redraw buttons on the toolbar, send node and link messages to grpc server
""" """
logging.info("clicked stop button") logging.info("clicked stop button")
self.app.menubar.change_menubar_item_state(is_runtime=False) self.app.menubar.change_menubar_item_state(is_runtime=False)
self.app.core.close_mobility_players() self.app.core.close_mobility_players()
enable_buttons(self.runtime_frame, enabled=False)
task = ProgressTask( task = ProgressTask(
self.app, "Stop", self.app.core.stop_session, self.stop_callback self.app, "Stop", self.app.core.stop_session, self.stop_callback
) )
task.start() task.start()
def stop_callback(self, response: core_pb2.StopSessionResponse): def stop_callback(self, response: core_pb2.StopSessionResponse) -> None:
self.set_design() self.set_design()
self.app.canvas.stopped_session() self.app.canvas.stopped_session()
def update_annotation( def update_annotation(
self, image: "ImageTk.PhotoImage", shape_type: ShapeType, image_enum self, shape_type: ShapeType, image_enum: ImageEnum, image: PhotoImage
): ) -> None:
logging.debug("clicked annotation: ") logging.debug("clicked annotation")
self.hide_pickers()
self.annotation_button.configure(image=image) self.annotation_button.configure(image=image)
self.annotation_button.image = image self.annotation_button.image = image
self.app.canvas.mode = GraphMode.ANNOTATION
self.app.canvas.annotation_type = shape_type self.app.canvas.annotation_type = shape_type
self.current_annotation = shape_type
self.annotation_enum = image_enum self.annotation_enum = image_enum
if is_marker(shape_type): if is_marker(shape_type):
if self.marker_tool: self.show_marker()
self.marker_tool.destroy() else:
self.marker_tool = MarkerDialog(self.app) self.hide_marker()
self.marker_tool.show()
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") logging.debug("Click on RUN button")
dialog = RunToolDialog(self.app) dialog = RunToolDialog(self.app)
dialog.show() dialog.show()
def click_marker_button(self): def click_marker_button(self) -> None:
logging.debug("Click on marker button") self.runtime_frame.select_radio(self.runtime_marker_button)
self.runtime_select(self.runtime_marker_button)
self.app.canvas.mode = GraphMode.ANNOTATION self.app.canvas.mode = GraphMode.ANNOTATION
self.app.canvas.annotation_type = ShapeType.MARKER self.app.canvas.annotation_type = ShapeType.MARKER
if self.marker_tool: self.show_marker()
self.marker_tool.destroy()
self.marker_tool = MarkerDialog(self.app)
self.marker_tool.show()
def scale_button(self, button, image_enum): def scale_button(
image = icon(image_enum, int(TOOLBAR_SIZE * self.app.app_scale)) self, button: ttk.Button, image_enum: ImageEnum = None, image_file: str = None
button.config(image=image) ) -> None:
button.image = image 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.play_button, ImageEnum.START)
self.scale_button(self.select_button, ImageEnum.SELECT) self.scale_button(self.select_button, ImageEnum.SELECT)
self.scale_button(self.link_button, ImageEnum.LINK) self.scale_button(self.link_button, ImageEnum.LINK)
self.scale_button(self.node_button, self.node_enum) if self.current_node.image_enum:
self.scale_button(self.network_button, self.network_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.annotation_button, self.annotation_enum)
self.scale_button(self.runtime_select_button, ImageEnum.SELECT) self.scale_button(self.runtime_select_button, ImageEnum.SELECT)
self.scale_button(self.stop_button, ImageEnum.STOP) self.scale_button(self.stop_button, ImageEnum.STOP)

View file

@ -6,7 +6,7 @@ import heapq
import threading import threading
import time import time
from functools import total_ordering from functools import total_ordering
from typing import Any, Callable from typing import Any, Callable, Dict, List, Optional, Tuple
class Timer(threading.Thread): class Timer(threading.Thread):
@ -16,34 +16,33 @@ class Timer(threading.Thread):
""" """
def __init__( 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: ) -> None:
""" """
Create a Timer instance. Create a Timer instance.
:param interval: time interval :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 args: function arguments
:param kwargs: function keyword arguments :param kwargs: function keyword arguments
""" """
super().__init__() super().__init__()
self.interval = interval self.interval: float = interval
self.function = function self.func: Callable[..., None] = func
self.finished: threading.Event = threading.Event()
self.finished = threading.Event() self._running: threading.Lock = threading.Lock()
self._running = threading.Lock()
# validate arguments were provided # validate arguments were provided
if args: if args is None:
self.args = args args = ()
else: self.args: Tuple[Any] = args
self.args = []
# validate keyword arguments were provided # validate keyword arguments were provided
if kwargs: if kwargs is None:
self.kwargs = kwargs kwargs = {}
else: self.kwargs: Dict[Any, Any] = kwargs
self.kwargs = {}
def cancel(self) -> bool: def cancel(self) -> bool:
""" """
@ -67,7 +66,7 @@ class Timer(threading.Thread):
self.finished.wait(self.interval) self.finished.wait(self.interval)
with self._running: with self._running:
if not self.finished.is_set(): if not self.finished.is_set():
self.function(*self.args, **self.kwargs) self.func(*self.args, **self.kwargs)
self.finished.set() self.finished.set()
@ -78,7 +77,12 @@ class Event:
""" """
def __init__( 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: ) -> None:
""" """
Create an Event instance. Create an Event instance.
@ -89,12 +93,12 @@ class Event:
:param args: function arguments :param args: function arguments
:param kwds: function keyword arguments :param kwds: function keyword arguments
""" """
self.eventnum = eventnum self.eventnum: int = eventnum
self.time = event_time self.time: float = event_time
self.func = func self.func: Callable[..., None] = func
self.args = args self.args: Tuple[Any] = args
self.kwds = kwds self.kwds: Dict[Any, Any] = kwds
self.canceled = False self.canceled: bool = False
def __lt__(self, other: "Event") -> bool: def __lt__(self, other: "Event") -> bool:
result = self.time < other.time result = self.time < other.time
@ -118,7 +122,6 @@ class Event:
:return: nothing :return: nothing
""" """
# XXX not thread-safe
self.canceled = True self.canceled = True
@ -131,14 +134,14 @@ class EventLoop:
""" """
Creates a EventLoop instance. Creates a EventLoop instance.
""" """
self.lock = threading.RLock() self.lock: threading.RLock = threading.RLock()
self.queue = [] self.queue: List[Event] = []
self.eventnum = 0 self.eventnum: int = 0
self.timer = None self.timer: Optional[Timer] = None
self.running = False self.running: bool = False
self.start = None self.start: Optional[float] = None
def __run_events(self) -> None: def _run_events(self) -> None:
""" """
Run events. Run events.
@ -161,9 +164,9 @@ class EventLoop:
with self.lock: with self.lock:
self.timer = None self.timer = None
if schedule: if schedule:
self.__schedule_event() self._schedule_event()
def __schedule_event(self) -> None: def _schedule_event(self) -> None:
""" """
Schedule event. Schedule event.
@ -177,7 +180,7 @@ class EventLoop:
delay = self.queue[0].time - time.monotonic() delay = self.queue[0].time - time.monotonic()
if self.timer: if self.timer:
raise ValueError("timer was already set") 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.daemon = True
self.timer.start() self.timer.start()
@ -194,7 +197,7 @@ class EventLoop:
self.start = time.monotonic() self.start = time.monotonic()
for event in self.queue: for event in self.queue:
event.time += self.start event.time += self.start
self.__schedule_event() self._schedule_event()
def stop(self) -> None: def stop(self) -> None:
""" """
@ -242,5 +245,5 @@ class EventLoop:
if self.timer is not None and self.timer.cancel(): if self.timer is not None and self.timer.cancel():
self.timer = None self.timer = None
if self.running and self.timer is None: if self.running and self.timer is None:
self.__schedule_event() self._schedule_event()
return event return event

View file

@ -6,6 +6,7 @@ import logging
from typing import Tuple from typing import Tuple
import pyproj import pyproj
from pyproj import Transformer
from core.emulator.enumerations import RegisterTlvs from core.emulator.enumerations import RegisterTlvs
@ -20,21 +21,23 @@ class GeoLocation:
defined projections. defined projections.
""" """
name = "location" name: str = "location"
config_type = RegisterTlvs.UTILITY config_type: RegisterTlvs = RegisterTlvs.UTILITY
def __init__(self) -> None: def __init__(self) -> None:
""" """
Creates a GeoLocation instance. 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 CRS_WGS84, CRS_PROJ, always_xy=True
) )
self.to_geo = pyproj.Transformer.from_crs(CRS_PROJ, CRS_WGS84, always_xy=True) self.to_geo: Transformer = pyproj.Transformer.from_crs(
self.refproj = (0.0, 0.0) CRS_PROJ, CRS_WGS84, always_xy=True
self.refgeo = (0.0, 0.0, 0.0) )
self.refxyz = (0.0, 0.0, 0.0) self.refproj: Tuple[float, float, float] = (0.0, 0.0, 0.0)
self.refscale = 1.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: 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.refxyz = (0.0, 0.0, 0.0)
self.refgeo = (0.0, 0.0, 0.0) self.refgeo = (0.0, 0.0, 0.0)
self.refscale = 1.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: def pixels2meters(self, value: float) -> float:
""" """

View file

@ -9,11 +9,12 @@ import os
import threading import threading
import time import time
from functools import total_ordering 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 import utils
from core.config import ConfigGroup, ConfigurableOptions, Configuration, ModelManager from core.config import ConfigGroup, ConfigurableOptions, Configuration, ModelManager
from core.emulator.data import EventData, LinkData from core.emulator.data import EventData, LinkData
from core.emulator.emudata import LinkOptions
from core.emulator.enumerations import ( from core.emulator.enumerations import (
ConfigDataTypes, ConfigDataTypes,
EventTypes, EventTypes,
@ -22,8 +23,9 @@ from core.emulator.enumerations import (
RegisterTlvs, RegisterTlvs,
) )
from core.errors import CoreError 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.interface import CoreInterface
from core.nodes.network import WlanNode
if TYPE_CHECKING: if TYPE_CHECKING:
from core.emulator.session import Session from core.emulator.session import Session
@ -45,7 +47,7 @@ class MobilityManager(ModelManager):
:param session: session this manager is tied to :param session: session this manager is tied to
""" """
super().__init__() super().__init__()
self.session = session self.session: "Session" = session
self.models[BasicRangeModel.name] = BasicRangeModel self.models[BasicRangeModel.name] = BasicRangeModel
self.models[Ns2ScriptedMobility.name] = Ns2ScriptedMobility self.models[Ns2ScriptedMobility.name] = Ns2ScriptedMobility
@ -75,7 +77,7 @@ class MobilityManager(ModelManager):
) )
try: try:
node = self.session.get_node(node_id) node = self.session.get_node(node_id, WlanNode)
except CoreError: except CoreError:
logging.warning( logging.warning(
"skipping mobility configuration for unknown node: %s", node_id "skipping mobility configuration for unknown node: %s", node_id
@ -103,9 +105,8 @@ class MobilityManager(ModelManager):
event_type = event_data.event_type event_type = event_data.event_type
node_id = event_data.node node_id = event_data.node
name = event_data.name name = event_data.name
try: try:
node = self.session.get_node(node_id) node = self.session.get_node(node_id, WlanNode)
except CoreError: except CoreError:
logging.exception( logging.exception(
"Ignoring event for model '%s', unknown node '%s'", name, node_id "Ignoring event for model '%s', unknown node '%s'", name, node_id
@ -177,7 +178,7 @@ class MobilityManager(ModelManager):
self.session.broadcast_event(event_data) self.session.broadcast_event(event_data)
def updatewlans( def updatewlans(
self, moved: List[NodeBase], moved_netifs: List[CoreInterface] self, moved: List[CoreNode], moved_netifs: List[CoreInterface]
) -> None: ) -> None:
""" """
A mobility script has caused nodes in the 'moved' list to move. A mobility script has caused nodes in the 'moved' list to move.
@ -190,7 +191,7 @@ class MobilityManager(ModelManager):
""" """
for node_id in self.nodes(): for node_id in self.nodes():
try: try:
node = self.session.get_node(node_id) node = self.session.get_node(node_id, WlanNode)
except CoreError: except CoreError:
continue continue
if node.model: if node.model:
@ -203,21 +204,21 @@ class WirelessModel(ConfigurableOptions):
Used for managing arbitrary configuration parameters. Used for managing arbitrary configuration parameters.
""" """
config_type = RegisterTlvs.WIRELESS config_type: RegisterTlvs = RegisterTlvs.WIRELESS
bitmap = None bitmap: str = None
position_callback = 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. Create a WirelessModel instance.
:param session: core session we are tied to :param session: core session we are tied to
:param _id: object id :param _id: object id
""" """
self.session = session self.session: "Session" = session
self.id = _id 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) May be used if the model can populate the GUI with wireless (green)
link lines. link lines.
@ -227,11 +228,11 @@ class WirelessModel(ConfigurableOptions):
""" """
return [] 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. Update this wireless model.
:param moved: flag is it was moved :param moved: moved nodes
:param moved_netifs: moved network interfaces :param moved_netifs: moved network interfaces
:return: nothing :return: nothing
""" """
@ -255,8 +256,8 @@ class BasicRangeModel(WirelessModel):
the GUI. the GUI.
""" """
name = "basic_range" name: str = "basic_range"
options = [ options: List[Configuration] = [
Configuration( Configuration(
_id="range", _id="range",
_type=ConfigDataTypes.UINT32, _type=ConfigDataTypes.UINT32,
@ -298,15 +299,15 @@ class BasicRangeModel(WirelessModel):
:param _id: object id :param _id: object id
""" """
super().__init__(session, _id) super().__init__(session, _id)
self.session = session self.session: "Session" = session
self.wlan = session.get_node(_id) self.wlan: WlanNode = session.get_node(_id, WlanNode)
self._netifs = {} self._netifs: Dict[CoreInterface, Tuple[float, float, float]] = {}
self._netifslock = threading.Lock() self._netifslock: threading.Lock = threading.Lock()
self.range = 0 self.range: int = 0
self.bw = None self.bw: Optional[int] = None
self.delay = None self.delay: Optional[int] = None
self.loss = None self.loss: Optional[float] = None
self.jitter = None self.jitter: Optional[int] = None
def _get_config(self, current_value: int, config: Dict[str, str], name: str) -> int: def _get_config(self, current_value: int, config: Dict[str, str], name: str) -> int:
""" """
@ -334,14 +335,13 @@ class BasicRangeModel(WirelessModel):
""" """
with self._netifslock: with self._netifslock:
for netif in self._netifs: for netif in self._netifs:
self.wlan.linkconfig( options = LinkOptions(
netif, bandwidth=self.bw,
bw=self.bw,
delay=self.delay, delay=self.delay,
loss=self.loss, per=self.loss,
duplicate=None,
jitter=self.jitter, jitter=self.jitter,
) )
self.wlan.linkconfig(netif, options)
def get_position(self, netif: CoreInterface) -> Tuple[float, float, float]: def get_position(self, netif: CoreInterface) -> Tuple[float, float, float]:
""" """
@ -374,14 +374,14 @@ class BasicRangeModel(WirelessModel):
position_callback = set_position 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 positions have changed without recalc. Update positions from
node.position, then re-calculate links for those that have moved. node.position, then re-calculate links for those that have moved.
Assumes bidirectional links, with one calculation per node pair, where Assumes bidirectional links, with one calculation per node pair, where
one of the nodes has moved. one of the nodes has moved.
:param moved: flag is it was moved :param moved: moved nodes
:param moved_netifs: moved network interfaces :param moved_netifs: moved network interfaces
:return: nothing :return: nothing
""" """
@ -509,11 +509,7 @@ class BasicRangeModel(WirelessModel):
:param unlink: unlink or not :param unlink: unlink or not
:return: nothing :return: nothing
""" """
if unlink: message_type = MessageFlags.DELETE if unlink else MessageFlags.ADD
message_type = MessageFlags.DELETE
else:
message_type = MessageFlags.ADD
link_data = self.create_link_data(netif, netif2, message_type) link_data = self.create_link_data(netif, netif2, message_type)
self.session.broadcast_link(link_data) self.session.broadcast_link(link_data)
@ -539,29 +535,35 @@ class WayPoint:
Maintains information regarding waypoints. 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. Creates a WayPoint instance.
:param time: waypoint time :param _time: waypoint time
:param nodenum: node id :param node_id: node id
:param coords: waypoint coordinates :param coords: waypoint coordinates
:param speed: waypoint speed :param speed: waypoint speed
""" """
self.time = time self.time: float = _time
self.nodenum = nodenum self.node_id: int = node_id
self.coords = coords self.coords: Tuple[float, float, Optional[float]] = coords
self.speed = speed self.speed: float = speed
def __eq__(self, other: "WayPoint") -> bool: 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: def __ne__(self, other: "WayPoint") -> bool:
return not self == other return not self == other
def __lt__(self, other: "WayPoint") -> bool: def __lt__(self, other: "WayPoint") -> bool:
if self.time == other.time: if self.time == other.time:
return self.nodenum < other.nodenum return self.node_id < other.node_id
else: else:
return self.time < other.time return self.time < other.time
@ -571,12 +573,11 @@ class WayPointMobility(WirelessModel):
Abstract class for mobility models that set node waypoints. Abstract class for mobility models that set node waypoints.
""" """
name = "waypoint" name: str = "waypoint"
config_type = RegisterTlvs.MOBILITY config_type: RegisterTlvs = RegisterTlvs.MOBILITY
STATE_STOPPED: int = 0
STATE_STOPPED = 0 STATE_RUNNING: int = 1
STATE_RUNNING = 1 STATE_PAUSED: int = 2
STATE_PAUSED = 2
def __init__(self, session: "Session", _id: int) -> None: def __init__(self, session: "Session", _id: int) -> None:
""" """
@ -587,20 +588,21 @@ class WayPointMobility(WirelessModel):
:return: :return:
""" """
super().__init__(session=session, _id=_id) super().__init__(session=session, _id=_id)
self.state = self.STATE_STOPPED self.state: int = self.STATE_STOPPED
self.queue = [] self.queue: List[WayPoint] = []
self.queue_copy = [] self.queue_copy: List[WayPoint] = []
self.points = {} self.points: Dict[int, WayPoint] = {}
self.initial = {} self.initial: Dict[int, WayPoint] = {}
self.lasttime = None self.lasttime: Optional[float] = None
self.endtime = None self.endtime: Optional[int] = None
self.wlan = session.get_node(_id) self.timezero: float = 0.0
self.wlan: WlanNode = session.get_node(_id, WlanNode)
# these are really set in child class via confmatrix # these are really set in child class via confmatrix
self.loop = False self.loop: bool = False
self.refresh_ms = 50 self.refresh_ms: int = 50
# flag whether to stop scheduling when queue is empty # flag whether to stop scheduling when queue is empty
# (ns-3 sets this to False as new waypoints may be added from trace) # (ns-3 sets this to False as new waypoints may be added from trace)
self.empty_queue_stop = True self.empty_queue_stop: bool = True
def runround(self) -> None: def runround(self) -> None:
""" """
@ -688,16 +690,11 @@ class WayPointMobility(WirelessModel):
self.setnodeposition(node, x2, y2, z2) self.setnodeposition(node, x2, y2, z2)
del self.points[node.id] del self.points[node.id]
return True return True
# speed can be a velocity vector or speed value
if isinstance(speed, (float, int)): # linear speed value
# linear speed value alpha = math.atan2(y2 - y1, x2 - x1)
alpha = math.atan2(y2 - y1, x2 - x1) sx = speed * math.cos(alpha)
sx = speed * math.cos(alpha) sy = speed * math.sin(alpha)
sy = speed * math.sin(alpha)
else:
# velocity vector
sx = speed[0]
sy = speed[1]
# calculate dt * speed = distance moved # calculate dt * speed = distance moved
dx = sx * dt dx = sx * dt
@ -740,7 +737,13 @@ class WayPointMobility(WirelessModel):
self.session.mobility.updatewlans(moved, moved_netifs) self.session.mobility.updatewlans(moved, moved_netifs)
def addwaypoint( 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: ) -> None:
""" """
Waypoints are pushed to a heapq, sorted by time. Waypoints are pushed to a heapq, sorted by time.
@ -780,7 +783,7 @@ class WayPointMobility(WirelessModel):
if self.queue[0].time > now: if self.queue[0].time > now:
break break
wp = heapq.heappop(self.queue) wp = heapq.heappop(self.queue)
self.points[wp.nodenum] = wp self.points[wp.node_id] = wp
def copywaypoints(self) -> None: def copywaypoints(self) -> None:
""" """
@ -880,8 +883,8 @@ class Ns2ScriptedMobility(WayPointMobility):
BonnMotion. BonnMotion.
""" """
name = "ns2script" name: str = "ns2script"
options = [ options: List[Configuration] = [
Configuration( Configuration(
_id="file", _type=ConfigDataTypes.STRING, label="mobility script file" _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())) 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. Creates a Ns2ScriptedMobility instance.
@ -935,17 +938,14 @@ class Ns2ScriptedMobility(WayPointMobility):
:param _id: object id :param _id: object id
""" """
super().__init__(session, _id) super().__init__(session, _id)
self._netifs = {} self.file: Optional[str] = None
self._netifslock = threading.Lock() self.refresh_ms: Optional[int] = None
self.loop: Optional[bool] = None
self.file = None self.autostart: Optional[str] = None
self.refresh_ms = None self.nodemap: Dict[int, int] = {}
self.loop = None self.script_start: Optional[str] = None
self.autostart = None self.script_pause: Optional[str] = None
self.nodemap = {} self.script_stop: Optional[str] = None
self.script_start = None
self.script_pause = None
self.script_stop = None
def update_config(self, config: Dict[str, str]) -> None: def update_config(self, config: Dict[str, str]) -> None:
self.file = config["file"] self.file = config["file"]

View file

@ -6,6 +6,7 @@ import logging
import os import os
import shutil import shutil
import threading import threading
from threading import RLock
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Type from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Type
import netaddr import netaddr
@ -14,9 +15,10 @@ from core import utils
from core.configservice.dependencies import ConfigServiceDependencies from core.configservice.dependencies import ConfigServiceDependencies
from core.constants import MOUNT_BIN, VNODED_BIN from core.constants import MOUNT_BIN, VNODED_BIN
from core.emulator.data import LinkData, NodeData from core.emulator.data import LinkData, NodeData
from core.emulator.emudata import InterfaceData, LinkOptions
from core.emulator.enumerations import LinkTypes, MessageFlags, NodeTypes from core.emulator.enumerations import LinkTypes, MessageFlags, NodeTypes
from core.errors import CoreCommandError, CoreError from core.errors import CoreCommandError, CoreError
from core.nodes import client from core.nodes.client import VnodeClient
from core.nodes.interface import CoreInterface, TunTap, Veth from core.nodes.interface import CoreInterface, TunTap, Veth
from core.nodes.netclient import LinuxNetClient, get_net_client from core.nodes.netclient import LinuxNetClient, get_net_client
@ -24,7 +26,9 @@ if TYPE_CHECKING:
from core.emulator.distributed import DistributedServer from core.emulator.distributed import DistributedServer
from core.emulator.session import Session from core.emulator.session import Session
from core.configservice.base import ConfigService from core.configservice.base import ConfigService
from core.services.coreservices import CoreService
CoreServices = List[CoreService]
ConfigServiceType = Type[ConfigService] ConfigServiceType = Type[ConfigService]
_DEFAULT_MTU = 1500 _DEFAULT_MTU = 1500
@ -35,7 +39,7 @@ class NodeBase:
Base class for CORE nodes (nodes and networks) Base class for CORE nodes (nodes and networks)
""" """
apitype = None apitype: Optional[NodeTypes] = None
# TODO: appears start has no usage, verify and remove # TODO: appears start has no usage, verify and remove
def __init__( def __init__(
@ -57,27 +61,25 @@ class NodeBase:
will run on, default is None for localhost will run on, default is None for localhost
""" """
self.session = session self.session: "Session" = session
if _id is None: if _id is None:
_id = session.get_node_id() _id = session.get_node_id()
self.id = _id self.id: int = _id
if name is None: if name is None:
name = f"o{self.id}" name = f"o{self.id}"
self.name = name self.name: str = name
self.server = server self.server: "DistributedServer" = server
self.type: Optional[str] = None
self.type = None self.services: CoreServices = []
self.services = None self._netif: Dict[int, CoreInterface] = {}
# ifindex is key, CoreInterface instance is value self.ifindex: int = 0
self._netif = {} self.canvas: Optional[int] = None
self.ifindex = 0 self.icon: Optional[str] = None
self.canvas = None self.opaque: Optional[str] = None
self.icon = None self.position: Position = Position()
self.opaque = None self.up: bool = False
self.position = Position()
use_ovs = session.options.get_config("ovs") == "True" 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: def startup(self) -> None:
""" """
@ -209,9 +211,7 @@ class NodeBase:
server = None server = None
if self.server is not None: if self.server is not None:
server = self.server.name server = self.server.name
services = None services = [service.name for service in self.services]
if self.services is not None:
services = [service.name for service in self.services]
return NodeData( return NodeData(
message_type=message_type, message_type=message_type,
id=self.id, id=self.id,
@ -268,11 +268,9 @@ class CoreNodeBase(NodeBase):
will run on, default is None for localhost will run on, default is None for localhost
""" """
super().__init__(session, _id, name, start, server) super().__init__(session, _id, name, start, server)
self.services = [] self.config_services: Dict[str, "ConfigService"] = {}
self.config_services = {} self.nodedir: Optional[str] = None
self.nodedir = None self.tmpnodedir: bool = False
self.tmpnodedir = False
self.up = False
def add_config_service(self, service_class: "ConfigServiceType") -> None: def add_config_service(self, service_class: "ConfigServiceType") -> None:
""" """
@ -301,7 +299,7 @@ class CoreNodeBase(NodeBase):
def start_config_services(self) -> None: 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. dependency chains.
:return: nothing :return: nothing
@ -333,7 +331,6 @@ class CoreNodeBase(NodeBase):
preserve = self.session.options.get_config("preservedir") == "1" preserve = self.session.options.get_config("preservedir") == "1"
if preserve: if preserve:
return return
if self.tmpnodedir: if self.tmpnodedir:
self.host_cmd(f"rm -rf {self.nodedir}") self.host_cmd(f"rm -rf {self.nodedir}")
@ -413,14 +410,14 @@ class CoreNodeBase(NodeBase):
netif.setposition() netif.setposition()
def commonnets( def commonnets(
self, obj: "CoreNodeBase", want_ctrl: bool = False self, node: "CoreNodeBase", want_ctrl: bool = False
) -> List[Tuple[NodeBase, CoreInterface, CoreInterface]]: ) -> List[Tuple["CoreNetworkBase", CoreInterface, CoreInterface]]:
""" """
Given another node or net object, return common networks between Given another node or net object, return common networks between
this node and that object. A list of tuples is returned, with each tuple this node and that object. A list of tuples is returned, with each tuple
consisting of (network, interface1, interface2). 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 :param want_ctrl: flag set to determine if control network are wanted
:return: tuples of common networks :return: tuples of common networks
""" """
@ -428,11 +425,33 @@ class CoreNodeBase(NodeBase):
for netif1 in self.netifs(): for netif1 in self.netifs():
if not want_ctrl and hasattr(netif1, "control"): if not want_ctrl and hasattr(netif1, "control"):
continue continue
for netif2 in obj.netifs(): for netif2 in node.netifs():
if netif1.net == netif2.net: if netif1.net == netif2.net:
common.append((netif1.net, netif1, netif2)) common.append((netif1.net, netif1, netif2))
return common 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: def cmd(self, args: str, wait: bool = True, shell: bool = False) -> str:
""" """
Runs a command within a node container. Runs a command within a node container.
@ -469,7 +488,6 @@ class CoreNode(CoreNodeBase):
_id: int = None, _id: int = None,
name: str = None, name: str = None,
nodedir: str = None, nodedir: str = None,
bootsh: str = "boot.sh",
start: bool = True, start: bool = True,
server: "DistributedServer" = None, server: "DistributedServer" = None,
) -> None: ) -> None:
@ -480,25 +498,21 @@ class CoreNode(CoreNodeBase):
:param _id: object id :param _id: object id
:param name: object name :param name: object name
:param nodedir: node directory :param nodedir: node directory
:param bootsh: boot shell to use
:param start: start flag :param start: start flag
:param server: remote server node :param server: remote server node
will run on, default is None for localhost will run on, default is None for localhost
""" """
super().__init__(session, _id, name, start, server) super().__init__(session, _id, name, start, server)
self.nodedir = nodedir self.nodedir: Optional[str] = nodedir
self.ctrlchnlname = os.path.abspath( self.ctrlchnlname: str = os.path.abspath(
os.path.join(self.session.session_dir, self.name) os.path.join(self.session.session_dir, self.name)
) )
self.client = None self.client: Optional[VnodeClient] = None
self.pid = None self.pid: Optional[int] = None
self.lock = threading.RLock() self.lock: RLock = RLock()
self._mounts = [] self._mounts: List[Tuple[str, str]] = []
self.bootsh = bootsh
use_ovs = session.options.get_config("ovs") == "True" 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: if start:
self.startup() self.startup()
@ -522,7 +536,6 @@ class CoreNode(CoreNodeBase):
self.host_cmd(f"kill -0 {self.pid}") self.host_cmd(f"kill -0 {self.pid}")
except CoreCommandError: except CoreCommandError:
return False return False
return True return True
def startup(self) -> None: def startup(self) -> None:
@ -554,7 +567,7 @@ class CoreNode(CoreNodeBase):
logging.debug("node(%s) pid: %s", self.name, self.pid) logging.debug("node(%s) pid: %s", self.name, self.pid)
# create vnode client # create vnode client
self.client = client.VnodeClient(self.name, self.ctrlchnlname) self.client = VnodeClient(self.name, self.ctrlchnlname)
# bring up the loopback interface # bring up the loopback interface
logging.debug("bringing up loopback interface") logging.debug("bringing up loopback interface")
@ -833,53 +846,36 @@ class CoreNode(CoreNodeBase):
interface_name = self.ifname(ifindex) interface_name = self.ifname(ifindex)
self.node_net_client.device_up(interface_name) self.node_net_client.device_up(interface_name)
def newnetif( def newnetif(self, net: "CoreNetworkBase", interface: InterfaceData) -> int:
self,
net: "CoreNetworkBase" = None,
addrlist: List[str] = None,
hwaddr: str = None,
ifindex: int = None,
ifname: str = None,
) -> int:
""" """
Create a new network interface. Create a new network interface.
:param net: network to associate with :param net: network to associate with
:param addrlist: addresses to add on the interface :param interface: interface data for new interface
:param hwaddr: hardware address to set for interface
:param ifindex: index of interface to create
:param ifname: name for interface
:return: interface index :return: interface index
""" """
if not addrlist: addresses = interface.get_addresses()
addrlist = []
with self.lock: with self.lock:
# TODO: emane specific code # TODO: emane specific code
if net is not None and net.is_emane is True: if net.is_emane is True:
ifindex = self.newtuntap(ifindex, ifname) ifindex = self.newtuntap(interface.id, interface.name)
# TUN/TAP is not ready for addressing yet; the device may # TUN/TAP is not ready for addressing yet; the device may
# take some time to appear, and installing it into a # take some time to appear, and installing it into a
# namespace after it has been bound removes addressing; # namespace after it has been bound removes addressing;
# save addresses with the interface now # save addresses with the interface now
self.attachnet(ifindex, net) self.attachnet(ifindex, net)
netif = self.netif(ifindex) netif = self.netif(ifindex)
netif.sethwaddr(hwaddr) netif.sethwaddr(interface.mac)
for address in utils.make_tuple(addrlist): for address in addresses:
netif.addaddr(address) netif.addaddr(address)
return ifindex return ifindex
else: else:
ifindex = self.newveth(ifindex, ifname) ifindex = self.newveth(interface.id, interface.name)
self.attachnet(ifindex, net)
if net is not None: if interface.mac:
self.attachnet(ifindex, net) self.sethwaddr(ifindex, interface.mac)
for address in addresses:
if hwaddr:
self.sethwaddr(ifindex, hwaddr)
for address in utils.make_tuple(addrlist):
self.addaddr(ifindex, address) self.addaddr(ifindex, address)
self.ifup(ifindex) self.ifup(ifindex)
return ifindex return ifindex
@ -992,6 +988,7 @@ class CoreNetworkBase(NodeBase):
will run on, default is None for localhost will run on, default is None for localhost
""" """
super().__init__(session, _id, name, start, server) super().__init__(session, _id, name, start, server)
self.brname = None
self._linked = {} self._linked = {}
self._linked_lock = threading.Lock() self._linked_lock = threading.Lock()
@ -1020,7 +1017,7 @@ class CoreNetworkBase(NodeBase):
""" """
pass 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. 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 :return: interface the provided network is linked to
""" """
for netif in self.netifs(): for netif in self.netifs():
if hasattr(netif, "othernet") and netif.othernet == net: if getattr(netif, "othernet", None) == net:
return netif return netif
return None return None
@ -1072,11 +1069,11 @@ class CoreNetworkBase(NodeBase):
for netif in self.netifs(sort=True): for netif in self.netifs(sort=True):
if not hasattr(netif, "node"): if not hasattr(netif, "node"):
continue continue
linked_node = netif.node
uni = False uni = False
linked_node = netif.node
if linked_node is None: if linked_node is None:
# two layer-2 switches/hubs linked together via linknet() # two layer-2 switches/hubs linked together via linknet()
if not hasattr(netif, "othernet"): if not netif.othernet:
continue continue
linked_node = netif.othernet linked_node = netif.othernet
if linked_node.id == self.id: if linked_node.id == self.id:
@ -1149,6 +1146,19 @@ class CoreNetworkBase(NodeBase):
return all_links 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: class Position:
""" """
@ -1163,12 +1173,12 @@ class Position:
:param y: y position :param y: y position
:param z: z position :param z: z position
""" """
self.x = x self.x: float = x
self.y = y self.y: float = y
self.z = z self.z: float = z
self.lon = None self.lon: Optional[float] = None
self.lat = None self.lat: Optional[float] = None
self.alt = None self.alt: Optional[float] = None
def set(self, x: float = None, y: float = None, z: float = None) -> bool: def set(self, x: float = None, y: float = None, z: float = None) -> bool:
""" """

View file

@ -20,8 +20,8 @@ class VnodeClient:
:param name: name for client :param name: name for client
:param ctrlchnlname: control channel name :param ctrlchnlname: control channel name
""" """
self.name = name self.name: str = name
self.ctrlchnlname = ctrlchnlname self.ctrlchnlname: str = ctrlchnlname
def _verify_connection(self) -> None: def _verify_connection(self) -> None:
""" """

View file

@ -2,7 +2,7 @@ import json
import logging import logging
import os import os
from tempfile import NamedTemporaryFile 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 import utils
from core.emulator.distributed import DistributedServer from core.emulator.distributed import DistributedServer
@ -17,15 +17,16 @@ if TYPE_CHECKING:
class DockerClient: class DockerClient:
def __init__(self, name: str, image: str, run: Callable[..., str]) -> None: def __init__(self, name: str, image: str, run: Callable[..., str]) -> None:
self.name = name self.name: str = name
self.image = image self.image: str = image
self.run = run self.run: Callable[..., str] = run
self.pid = None self.pid: Optional[str] = None
def create_container(self) -> str: def create_container(self) -> str:
self.run( self.run(
f"docker run -td --init --net=none --hostname {self.name} --name {self.name} " f"docker run -td --init --net=none --hostname {self.name} "
f"--sysctl net.ipv6.conf.all.disable_ipv6=0 {self.image} /bin/bash" f"--name {self.name} --sysctl net.ipv6.conf.all.disable_ipv6=0 "
f"--privileged {self.image} /bin/bash"
) )
self.pid = self.get_pid() self.pid = self.get_pid()
return self.pid return self.pid
@ -35,7 +36,7 @@ class DockerClient:
output = self.run(args) output = self.run(args)
data = json.loads(output) data = json.loads(output)
if not data: 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] return data[0]
def is_alive(self) -> bool: def is_alive(self) -> bool:
@ -53,11 +54,7 @@ class DockerClient:
return utils.cmd(f"docker exec {self.name} {cmd}", wait=wait, shell=shell) return utils.cmd(f"docker exec {self.name} {cmd}", wait=wait, shell=shell)
def create_ns_cmd(self, cmd: str) -> str: def create_ns_cmd(self, cmd: str) -> str:
return f"nsenter -t {self.pid} -u -i -p -n {cmd}" return f"nsenter -t {self.pid} -a {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)
def get_pid(self) -> str: def get_pid(self) -> str:
args = f"docker inspect -f '{{{{.State.Pid}}}}' {self.name}" args = f"docker inspect -f '{{{{.State.Pid}}}}' {self.name}"
@ -80,7 +77,6 @@ class DockerNode(CoreNode):
_id: int = None, _id: int = None,
name: str = None, name: str = None,
nodedir: str = None, nodedir: str = None,
bootsh: str = "boot.sh",
start: bool = True, start: bool = True,
server: DistributedServer = None, server: DistributedServer = None,
image: str = None image: str = None
@ -92,7 +88,6 @@ class DockerNode(CoreNode):
:param _id: object id :param _id: object id
:param name: object name :param name: object name
:param nodedir: node directory :param nodedir: node directory
:param bootsh: boot shell to use
:param start: start flag :param start: start flag
:param server: remote server node :param server: remote server node
will run on, default is None for localhost will run on, default is None for localhost
@ -100,8 +95,8 @@ class DockerNode(CoreNode):
""" """
if image is None: if image is None:
image = "ubuntu" image = "ubuntu"
self.image = image self.image: str = image
super().__init__(session, _id, name, nodedir, bootsh, start, server) super().__init__(session, _id, name, nodedir, start, server)
def create_node_net_client(self, use_ovs: bool) -> LinuxNetClient: def create_node_net_client(self, use_ovs: bool) -> LinuxNetClient:
""" """

View file

@ -4,12 +4,12 @@ virtual ethernet classes that implement the interfaces available under Linux.
import logging import logging
import time 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 import utils
from core.emulator.enumerations import MessageFlags from core.emulator.enumerations import MessageFlags, TransportType
from core.errors import CoreCommandError 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: if TYPE_CHECKING:
from core.emulator.distributed import DistributedServer from core.emulator.distributed import DistributedServer
@ -27,6 +27,7 @@ class CoreInterface:
session: "Session", session: "Session",
node: "CoreNode", node: "CoreNode",
name: str, name: str,
localname: str,
mtu: int, mtu: int,
server: "DistributedServer" = None, server: "DistributedServer" = None,
) -> None: ) -> None:
@ -36,33 +37,35 @@ class CoreInterface:
:param session: core session instance :param session: core session instance
:param node: node for interface :param node: node for interface
:param name: interface name :param name: interface name
:param localname: interface local name
:param mtu: mtu value :param mtu: mtu value
:param server: remote server node :param server: remote server node
will run on, default is None for localhost will run on, default is None for localhost
""" """
self.session = session self.session: "Session" = session
self.node = node self.node: "CoreNode" = node
self.name = name self.name: str = name
if not isinstance(mtu, int): self.localname: str = localname
raise ValueError self.up: bool = False
self.mtu = mtu self.mtu: int = mtu
self.net = None self.net: Optional[CoreNetworkBase] = None
self._params = {} self.othernet: Optional[CoreNetworkBase] = None
self.addrlist = [] self._params: Dict[str, float] = {}
self.hwaddr = None self.addrlist: List[str] = []
self.hwaddr: Optional[str] = None
# placeholder position hook # placeholder position hook
self.poshook = lambda x: None self.poshook: Callable[[CoreInterface], None] = lambda x: None
# used with EMANE # used with EMANE
self.transport_type = None self.transport_type: Optional[TransportType] = None
# node interface index # node interface index
self.netindex = None self.netindex: Optional[int] = None
# net interface index # net interface index
self.netifi = None self.netifi: Optional[int] = None
# index used to find flow data # index used to find flow data
self.flow_id = None self.flow_id: Optional[int] = None
self.server = server self.server: Optional["DistributedServer"] = server
use_ovs = session.options.get_config("ovs") == "True" 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( def host_cmd(
self, self,
@ -258,9 +261,7 @@ class Veth(CoreInterface):
:raises CoreCommandError: when there is a command exception :raises CoreCommandError: when there is a command exception
""" """
# note that net arg is ignored # note that net arg is ignored
super().__init__(session, node, name, mtu, server) super().__init__(session, node, name, localname, mtu, server)
self.localname = localname
self.up = False
if start: if start:
self.startup() self.startup()
@ -326,10 +327,8 @@ class TunTap(CoreInterface):
will run on, default is None for localhost will run on, default is None for localhost
:param start: start flag :param start: start flag
""" """
super().__init__(session, node, name, mtu, server) super().__init__(session, node, name, localname, mtu, server)
self.localname = localname self.transport_type = TransportType.VIRTUAL
self.up = False
self.transport_type = "virtual"
if start: if start:
self.startup() self.startup()
@ -509,22 +508,17 @@ class GreTap(CoreInterface):
will run on, default is None for localhost will run on, default is None for localhost
:raises CoreCommandError: when there is a command exception :raises CoreCommandError: when there is a command exception
""" """
super().__init__(session, node, name, mtu, server)
if _id is None: if _id is None:
# from PyCoreObj
_id = ((id(self) >> 16) ^ (id(self) & 0xFFFF)) & 0xFFFF _id = ((id(self) >> 16) ^ (id(self) & 0xFFFF)) & 0xFFFF
self.id = _id self.id = _id
sessionid = self.session.short_session_id() sessionid = session.short_session_id()
# interface name on the local host machine localname = f"gt.{self.id}.{sessionid}"
self.localname = f"gt.{self.id}.{sessionid}" super().__init__(session, node, name, localname, mtu, server)
self.transport_type = "raw" self.transport_type = TransportType.RAW
if not start: if not start:
self.up = False
return return
if remoteip is None: if remoteip is None:
raise ValueError("missing remote IP required for GRE TAP device") raise ValueError("missing remote IP required for GRE TAP device")
self.net_client.create_gretap(self.localname, remoteip, localip, ttl, key) self.net_client.create_gretap(self.localname, remoteip, localip, ttl, key)
self.net_client.device_up(self.localname) self.net_client.device_up(self.localname)
self.up = True self.up = True

View file

@ -3,7 +3,7 @@ import logging
import os import os
import time import time
from tempfile import NamedTemporaryFile 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 import utils
from core.emulator.distributed import DistributedServer from core.emulator.distributed import DistributedServer
@ -18,10 +18,10 @@ if TYPE_CHECKING:
class LxdClient: class LxdClient:
def __init__(self, name: str, image: str, run: Callable[..., str]) -> None: def __init__(self, name: str, image: str, run: Callable[..., str]) -> None:
self.name = name self.name: str = name
self.image = image self.image: str = image
self.run = run self.run: Callable[..., str] = run
self.pid = None self.pid: Optional[int] = None
def create_container(self) -> int: def create_container(self) -> int:
self.run(f"lxc launch {self.image} {self.name}") self.run(f"lxc launch {self.image} {self.name}")
@ -34,7 +34,7 @@ class LxdClient:
output = self.run(args) output = self.run(args)
data = json.loads(output) data = json.loads(output)
if not data: 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] return data[0]
def is_alive(self) -> bool: def is_alive(self) -> bool:
@ -74,7 +74,6 @@ class LxcNode(CoreNode):
_id: int = None, _id: int = None,
name: str = None, name: str = None,
nodedir: str = None, nodedir: str = None,
bootsh: str = "boot.sh",
start: bool = True, start: bool = True,
server: DistributedServer = None, server: DistributedServer = None,
image: str = None, image: str = None,
@ -86,7 +85,6 @@ class LxcNode(CoreNode):
:param _id: object id :param _id: object id
:param name: object name :param name: object name
:param nodedir: node directory :param nodedir: node directory
:param bootsh: boot shell to use
:param start: start flag :param start: start flag
:param server: remote server node :param server: remote server node
will run on, default is None for localhost will run on, default is None for localhost
@ -94,8 +92,8 @@ class LxcNode(CoreNode):
""" """
if image is None: if image is None:
image = "ubuntu" image = "ubuntu"
self.image = image self.image: str = image
super().__init__(session, _id, name, nodedir, bootsh, start, server) super().__init__(session, _id, name, nodedir, start, server)
def alive(self) -> bool: def alive(self) -> bool:
""" """

View file

@ -1,7 +1,6 @@
""" """
Clients for dealing with bridge/interface commands. Clients for dealing with bridge/interface commands.
""" """
import json
from typing import Callable from typing import Callable
import netaddr import netaddr
@ -20,7 +19,7 @@ class LinuxNetClient:
:param run: function to run commands with :param run: function to run commands with
""" """
self.run = run self.run: Callable[..., str] = run
def set_hostname(self, name: str) -> None: def set_hostname(self, name: str) -> None:
""" """
@ -71,13 +70,22 @@ class LinuxNetClient:
def device_show(self, device: str) -> str: 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 :param device: device to get information for
:return: device information :return: device information
""" """
return self.run(f"{IP_BIN} link show {device}") 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: def get_mac(self, device: str) -> str:
""" """
Retrieve MAC address for a given device. Retrieve MAC address for a given device.
@ -114,7 +122,8 @@ class LinuxNetClient:
:return: nothing :return: nothing
""" """
self.run( 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, shell=True,
) )
@ -241,9 +250,9 @@ class LinuxNetClient:
self.device_down(name) self.device_down(name)
self.run(f"{IP_BIN} link delete {name} type bridge") 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 bridge_name: bridge name
:param interface_name: interface name :param interface_name: interface name
@ -269,12 +278,13 @@ class LinuxNetClient:
:param _id: node id to check bridges for :param _id: node id to check bridges for
:return: True if there are existing bridges, False otherwise :return: True if there are existing bridges, False otherwise
""" """
output = self.run(f"{IP_BIN} -j link show type bridge") output = self.run(f"{IP_BIN} -o link show type bridge")
bridges = json.loads(output) lines = output.split("\n")
for bridge in bridges: for line in lines:
name = bridge.get("ifname") values = line.split(":")
if not name: if not len(values) >= 2:
continue continue
name = values[1]
fields = name.split(".") fields = name.split(".")
if len(fields) != 3: if len(fields) != 3:
continue continue
@ -320,7 +330,7 @@ class OvsNetClient(LinuxNetClient):
self.device_down(name) self.device_down(name)
self.run(f"{OVS_BIN} del-br {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. Create an interface associated with a network bridge.

View file

@ -12,7 +12,14 @@ import netaddr
from core import utils from core import utils
from core.constants import EBTABLES_BIN, TC_BIN from core.constants import EBTABLES_BIN, TC_BIN
from core.emulator.data import LinkData, NodeData 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.errors import CoreCommandError, CoreError
from core.nodes.base import CoreNetworkBase from core.nodes.base import CoreNetworkBase
from core.nodes.interface import CoreInterface, GreTap, Veth from core.nodes.interface import CoreInterface, GreTap, Veth
@ -21,7 +28,7 @@ from core.nodes.netclient import get_net_client
if TYPE_CHECKING: if TYPE_CHECKING:
from core.emulator.distributed import DistributedServer from core.emulator.distributed import DistributedServer
from core.emulator.session import Session from core.emulator.session import Session
from core.location.mobility import WirelessModel from core.location.mobility import WirelessModel, WayPointMobility
WirelessModelType = Type[WirelessModel] WirelessModelType = Type[WirelessModel]
@ -36,26 +43,26 @@ class EbtablesQueue:
""" """
# update rate is every 300ms # update rate is every 300ms
rate = 0.3 rate: float = 0.3
# ebtables # ebtables
atomic_file = "/tmp/pycore.ebtables.atomic" atomic_file: str = "/tmp/pycore.ebtables.atomic"
def __init__(self) -> None: def __init__(self) -> None:
""" """
Initialize the helper class, but don't start the update thread Initialize the helper class, but don't start the update thread
until a WLAN is instantiated. until a WLAN is instantiated.
""" """
self.doupdateloop = False self.doupdateloop: bool = False
self.updatethread = None self.updatethread: Optional[threading.Thread] = None
# this lock protects cmds and updates lists # this lock protects cmds and updates lists
self.updatelock = threading.Lock() self.updatelock: threading.Lock = threading.Lock()
# list of pending ebtables commands # list of pending ebtables commands
self.cmds = [] self.cmds: List[str] = []
# list of WLANs requiring update # list of WLANs requiring update
self.updates = [] self.updates: List["CoreNetwork"] = []
# timestamps of last WLAN update; this keeps track of WLANs that are # timestamps of last WLAN update; this keeps track of WLANs that are
# using this queue # using this queue
self.last_update_time = {} self.last_update_time: Dict["CoreNetwork", float] = {}
def startupdateloop(self, wlan: "CoreNetwork") -> None: def startupdateloop(self, wlan: "CoreNetwork") -> None:
""" """
@ -65,10 +72,8 @@ class EbtablesQueue:
""" """
with self.updatelock: with self.updatelock:
self.last_update_time[wlan] = time.monotonic() self.last_update_time[wlan] = time.monotonic()
if self.doupdateloop: if self.doupdateloop:
return return
self.doupdateloop = True self.doupdateloop = True
self.updatethread = threading.Thread(target=self.updateloop, daemon=True) self.updatethread = threading.Thread(target=self.updateloop, daemon=True)
self.updatethread.start() self.updatethread.start()
@ -86,10 +91,8 @@ class EbtablesQueue:
logging.exception( logging.exception(
"error deleting last update time for wlan, ignored before: %s", wlan "error deleting last update time for wlan, ignored before: %s", wlan
) )
if len(self.last_update_time) > 0: if len(self.last_update_time) > 0:
return return
self.doupdateloop = False self.doupdateloop = False
if self.updatethread: if self.updatethread:
self.updatethread.join() self.updatethread.join()
@ -208,21 +211,21 @@ class EbtablesQueue:
wlan.has_ebtables_chain = True wlan.has_ebtables_chain = True
self.cmds.extend( 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}", f"-A FORWARD --logical-in {wlan.brname} -j {wlan.brname}",
] ]
) )
# rebuild the chain # rebuild the chain
for netif1, v in wlan._linked.items(): for netif1, v in wlan._linked.items():
for netif2, linked in v.items(): for netif2, linked in v.items():
if wlan.policy == "DROP" and linked: if wlan.policy == NetworkPolicy.DROP and linked:
self.cmds.extend( self.cmds.extend(
[ [
f"-A {wlan.brname} -i {netif1.localname} -o {netif2.localname} -j ACCEPT", f"-A {wlan.brname} -i {netif1.localname} -o {netif2.localname} -j ACCEPT",
f"-A {wlan.brname} -o {netif1.localname} -i {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( self.cmds.extend(
[ [
f"-A {wlan.brname} -i {netif1.localname} -o {netif2.localname} -j DROP", 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 # a global object because all WLANs share the same queue
# cannot have multiple threads invoking the ebtables commnd # cannot have multiple threads invoking the ebtables commnd
ebq = EbtablesQueue() ebq: EbtablesQueue = EbtablesQueue()
def ebtablescmds(call: Callable[..., str], cmds: List[str]) -> None: def ebtablescmds(call: Callable[..., str], cmds: List[str]) -> None:
@ -254,7 +257,7 @@ class CoreNetwork(CoreNetworkBase):
Provides linux bridge network functionality for core nodes. Provides linux bridge network functionality for core nodes.
""" """
policy = "DROP" policy: NetworkPolicy = NetworkPolicy.DROP
def __init__( def __init__(
self, self,
@ -263,7 +266,7 @@ class CoreNetwork(CoreNetworkBase):
name: str = None, name: str = None,
start: bool = True, start: bool = True,
server: "DistributedServer" = None, server: "DistributedServer" = None,
policy: str = None, policy: NetworkPolicy = None,
) -> None: ) -> None:
""" """
Creates a LxBrNet instance. Creates a LxBrNet instance.
@ -281,11 +284,10 @@ class CoreNetwork(CoreNetworkBase):
name = str(self.id) name = str(self.id)
if policy is not None: if policy is not None:
self.policy = policy self.policy = policy
self.name = name self.name: Optional[str] = name
sessionid = self.session.short_session_id() sessionid = self.session.short_session_id()
self.brname = f"b.{self.id}.{sessionid}" self.brname: str = f"b.{self.id}.{sessionid}"
self.up = False self.has_ebtables_chain: bool = False
self.has_ebtables_chain = False
if start: if start:
self.startup() self.startup()
ebq.startupdateloop(self) ebq.startupdateloop(self)
@ -365,7 +367,7 @@ class CoreNetwork(CoreNetworkBase):
:return: nothing :return: nothing
""" """
if self.up: 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) super().attach(netif)
def detach(self, netif: CoreInterface) -> None: def detach(self, netif: CoreInterface) -> None:
@ -397,12 +399,12 @@ class CoreNetwork(CoreNetworkBase):
try: try:
linked = self._linked[netif1][netif2] linked = self._linked[netif1][netif2]
except KeyError: except KeyError:
if self.policy == "ACCEPT": if self.policy == NetworkPolicy.ACCEPT:
linked = True linked = True
elif self.policy == "DROP": elif self.policy == NetworkPolicy.DROP:
linked = False linked = False
else: else:
raise Exception(f"unknown policy: {self.policy}") raise Exception(f"unknown policy: {self.policy.value}")
self._linked[netif1][netif2] = linked self._linked[netif1][netif2] = linked
return linked return linked
@ -440,41 +442,27 @@ class CoreNetwork(CoreNetworkBase):
ebq.ebchange(self) ebq.ebchange(self)
def linkconfig( def linkconfig(
self, self, netif: CoreInterface, options: LinkOptions, netif2: CoreInterface = None
netif: CoreInterface,
bw: float = None,
delay: float = None,
loss: float = None,
duplicate: float = None,
jitter: float = None,
netif2: float = None,
devname: str = None,
) -> None: ) -> None:
""" """
Configure link parameters by applying tc queuing disciplines on the interface. Configure link parameters by applying tc queuing disciplines on the interface.
:param netif: interface one :param netif: interface one
:param bw: bandwidth to set to :param options: options for configuring link
: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 netif2: interface two :param netif2: interface two
:param devname: device name
:return: nothing :return: nothing
""" """
if devname is None: devname = netif.localname
devname = netif.localname
tc = f"{TC_BIN} qdisc replace dev {devname}" tc = f"{TC_BIN} qdisc replace dev {devname}"
parent = "root" parent = "root"
changed = False changed = False
bw = options.bandwidth
if netif.setparam("bw", bw): if netif.setparam("bw", bw):
# from tc-tbf(8): minimum value for burst is rate / kernel_hz # from tc-tbf(8): minimum value for burst is rate / kernel_hz
if bw is not None: burst = max(2 * netif.mtu, int(bw / 1000))
burst = max(2 * netif.mtu, bw / 1000) # max IP payload
# max IP payload limit = 0xFFFF
limit = 0xFFFF tbf = f"tbf rate {bw} burst {burst} limit {limit}"
tbf = f"tbf rate {bw} burst {burst} limit {limit}"
if bw > 0: if bw > 0:
if self.up: if self.up:
cmd = f"{tc} {parent} handle 1: {tbf}" cmd = f"{tc} {parent} handle 1: {tbf}"
@ -492,13 +480,17 @@ class CoreNetwork(CoreNetworkBase):
if netif.getparam("has_tbf"): if netif.getparam("has_tbf"):
parent = "parent 1:1" parent = "parent 1:1"
netem = "netem" netem = "netem"
delay = options.delay
changed = max(changed, netif.setparam("delay", delay)) changed = max(changed, netif.setparam("delay", delay))
loss = options.per
if loss is not None: if loss is not None:
loss = float(loss) loss = float(loss)
changed = max(changed, netif.setparam("loss", loss)) changed = max(changed, netif.setparam("loss", loss))
duplicate = options.dup
if duplicate is not None: if duplicate is not None:
duplicate = int(duplicate) duplicate = int(duplicate)
changed = max(changed, netif.setparam("duplicate", duplicate)) changed = max(changed, netif.setparam("duplicate", duplicate))
jitter = options.jitter
changed = max(changed, netif.setparam("jitter", jitter)) changed = max(changed, netif.setparam("jitter", jitter))
if not changed: if not changed:
return return
@ -565,9 +557,8 @@ class CoreNetwork(CoreNetworkBase):
netif = Veth(self.session, None, name, localname, start=self.up) netif = Veth(self.session, None, name, localname, start=self.up)
self.attach(netif) self.attach(netif)
if net.up: if net.up and net.brname:
# this is similar to net.attach() but uses netif.name instead of localname netif.net_client.set_interface_master(net.brname, netif.name)
netif.net_client.create_interface(net.brname, netif.name)
i = net.newifindex() i = net.newifindex()
net._netif[i] = netif net._netif[i] = netif
with net._linked_lock: with net._linked_lock:
@ -585,7 +576,7 @@ class CoreNetwork(CoreNetworkBase):
:return: interface the provided network is linked to :return: interface the provided network is linked to
""" """
for netif in self.netifs(): for netif in self.netifs():
if hasattr(netif, "othernet") and netif.othernet == net: if netif.othernet == net:
return netif return netif
return None return None
@ -615,7 +606,7 @@ class GreTapBridge(CoreNetwork):
remoteip: str = None, remoteip: str = None,
_id: int = None, _id: int = None,
name: str = None, name: str = None,
policy: str = "ACCEPT", policy: NetworkPolicy = NetworkPolicy.ACCEPT,
localip: str = None, localip: str = None,
ttl: int = 255, ttl: int = 255,
key: int = None, key: int = None,
@ -638,17 +629,16 @@ class GreTapBridge(CoreNetwork):
will run on, default is None for localhost will run on, default is None for localhost
""" """
CoreNetwork.__init__(self, session, _id, name, False, server, policy) CoreNetwork.__init__(self, session, _id, name, False, server, policy)
self.grekey = key if key is None:
if self.grekey is None: key = self.session.id ^ self.id
self.grekey = self.session.id ^ self.id self.grekey: int = key
self.localnum = None self.localnum: Optional[int] = None
self.remotenum = None self.remotenum: Optional[int] = None
self.remoteip = remoteip self.remoteip: Optional[str] = remoteip
self.localip = localip self.localip: Optional[str] = localip
self.ttl = ttl self.ttl: int = ttl
if remoteip is None: self.gretap: Optional[GreTap] = None
self.gretap = None if remoteip is not None:
else:
self.gretap = GreTap( self.gretap = GreTap(
node=self, node=self,
session=session, session=session,
@ -723,10 +713,10 @@ class CtrlNet(CoreNetwork):
Control network functionality. Control network functionality.
""" """
policy = "ACCEPT" policy: NetworkPolicy = NetworkPolicy.ACCEPT
# base control interface index # base control interface index
CTRLIF_IDX_BASE = 99 CTRLIF_IDX_BASE: int = 99
DEFAULT_PREFIX_LIST = [ 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.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.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", "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__( def __init__(
self, self,
session: "Session", session: "Session",
prefix: str,
_id: int = None, _id: int = None,
name: str = None, name: str = None,
prefix: str = None,
hostid: int = None, hostid: int = None,
start: bool = True, start: bool = True,
server: "DistributedServer" = None, server: "DistributedServer" = None,
assign_address: bool = True, assign_address: bool = True,
updown_script: str = None, updown_script: str = None,
serverintf: CoreInterface = None, serverintf: str = None,
) -> None: ) -> None:
""" """
Creates a CtrlNet instance. Creates a CtrlNet instance.
@ -762,11 +752,11 @@ class CtrlNet(CoreNetwork):
:param serverintf: server interface :param serverintf: server interface
:return: :return:
""" """
self.prefix = netaddr.IPNetwork(prefix).cidr self.prefix: netaddr.IPNetwork = netaddr.IPNetwork(prefix).cidr
self.hostid = hostid self.hostid: Optional[int] = hostid
self.assign_address = assign_address self.assign_address: bool = assign_address
self.updown_script = updown_script self.updown_script: Optional[str] = updown_script
self.serverintf = serverintf self.serverintf: Optional[str] = serverintf
super().__init__(session, _id, name, start, server) super().__init__(session, _id, name, start, server)
def add_addresses(self, index: int) -> None: def add_addresses(self, index: int) -> None:
@ -817,7 +807,7 @@ class CtrlNet(CoreNetwork):
self.host_cmd(f"{self.updown_script} {self.brname} startup") self.host_cmd(f"{self.updown_script} {self.brname} startup")
if self.serverintf: 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: def shutdown(self) -> None:
""" """
@ -863,7 +853,7 @@ class PtpNet(CoreNetwork):
Peer to peer network node. Peer to peer network node.
""" """
policy = "ACCEPT" policy: NetworkPolicy = NetworkPolicy.ACCEPT
def attach(self, netif: CoreInterface) -> None: def attach(self, netif: CoreInterface) -> None:
""" """
@ -993,9 +983,9 @@ class SwitchNode(CoreNetwork):
Provides switch functionality within a core node. Provides switch functionality within a core node.
""" """
apitype = NodeTypes.SWITCH apitype: NodeTypes = NodeTypes.SWITCH
policy = "ACCEPT" policy: NetworkPolicy = NetworkPolicy.ACCEPT
type = "lanswitch" type: str = "lanswitch"
class HubNode(CoreNetwork): class HubNode(CoreNetwork):
@ -1004,9 +994,9 @@ class HubNode(CoreNetwork):
ports by turning off MAC address learning. ports by turning off MAC address learning.
""" """
apitype = NodeTypes.HUB apitype: NodeTypes = NodeTypes.HUB
policy = "ACCEPT" policy: NetworkPolicy = NetworkPolicy.ACCEPT
type = "hub" type: str = "hub"
def startup(self) -> None: def startup(self) -> None:
""" """
@ -1023,10 +1013,10 @@ class WlanNode(CoreNetwork):
Provides wireless lan functionality within a core node. Provides wireless lan functionality within a core node.
""" """
apitype = NodeTypes.WIRELESS_LAN apitype: NodeTypes = NodeTypes.WIRELESS_LAN
linktype = LinkTypes.WIRED linktype: LinkTypes = LinkTypes.WIRED
policy = "DROP" policy: NetworkPolicy = NetworkPolicy.DROP
type = "wlan" type: str = "wlan"
def __init__( def __init__(
self, self,
@ -1035,7 +1025,7 @@ class WlanNode(CoreNetwork):
name: str = None, name: str = None,
start: bool = True, start: bool = True,
server: "DistributedServer" = None, server: "DistributedServer" = None,
policy: str = None, policy: NetworkPolicy = None,
) -> None: ) -> None:
""" """
Create a WlanNode instance. Create a WlanNode instance.
@ -1050,8 +1040,8 @@ class WlanNode(CoreNetwork):
""" """
super().__init__(session, _id, name, start, server, policy) super().__init__(session, _id, name, start, server, policy)
# wireless and mobility models (BasicRangeModel, Ns2WaypointMobility) # wireless and mobility models (BasicRangeModel, Ns2WaypointMobility)
self.model = None self.model: Optional[WirelessModel] = None
self.mobility = None self.mobility: Optional[WayPointMobility] = None
def startup(self) -> None: def startup(self) -> None:
""" """
@ -1127,6 +1117,6 @@ class TunnelNode(GreTapBridge):
Provides tunnel functionality in a core node. Provides tunnel functionality in a core node.
""" """
apitype = NodeTypes.TUNNEL apitype: NodeTypes = NodeTypes.TUNNEL
policy = "ACCEPT" policy: NetworkPolicy = NetworkPolicy.ACCEPT
type = "tunnel" type: str = "tunnel"

View file

@ -5,15 +5,16 @@ PhysicalNode class for including real systems in the emulated network.
import logging import logging
import os import os
import threading 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 import utils
from core.constants import MOUNT_BIN, UMOUNT_BIN from core.constants import MOUNT_BIN, UMOUNT_BIN
from core.emulator.distributed import DistributedServer 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.errors import CoreCommandError, CoreError
from core.nodes.base import CoreNetworkBase, CoreNodeBase 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 from core.nodes.network import CoreNetwork, GreTap
if TYPE_CHECKING: if TYPE_CHECKING:
@ -23,7 +24,7 @@ if TYPE_CHECKING:
class PhysicalNode(CoreNodeBase): class PhysicalNode(CoreNodeBase):
def __init__( def __init__(
self, self,
session, session: "Session",
_id: int = None, _id: int = None,
name: str = None, name: str = None,
nodedir: str = None, nodedir: str = None,
@ -33,10 +34,10 @@ class PhysicalNode(CoreNodeBase):
super().__init__(session, _id, name, start, server) super().__init__(session, _id, name, start, server)
if not self.server: if not self.server:
raise CoreError("physical nodes must be assigned to a remote server") raise CoreError("physical nodes must be assigned to a remote server")
self.nodedir = nodedir self.nodedir: Optional[str] = nodedir
self.up = start self.up: bool = start
self.lock = threading.RLock() self.lock: threading.RLock = threading.RLock()
self._mounts = [] self._mounts: List[Tuple[str, str]] = []
if start: if start:
self.startup() self.startup()
@ -112,7 +113,7 @@ class PhysicalNode(CoreNodeBase):
logging.exception("trying to delete unknown address: %s", addr) logging.exception("trying to delete unknown address: %s", addr)
if self.up: if self.up:
self.net_client.delete_address(interface.name, str(addr)) self.net_client.delete_address(interface.name, addr)
def adoptnetif( def adoptnetif(
self, netif: CoreInterface, ifindex: int, hwaddr: str, addrlist: List[str] self, netif: CoreInterface, ifindex: int, hwaddr: str, addrlist: List[str]
@ -125,47 +126,27 @@ class PhysicalNode(CoreNodeBase):
netif.name = f"gt{ifindex}" netif.name = f"gt{ifindex}"
netif.node = self netif.node = self
self.addnetif(netif, ifindex) self.addnetif(netif, ifindex)
# use a more reasonable name, e.g. "gt0" instead of "gt.56286.150" # use a more reasonable name, e.g. "gt0" instead of "gt.56286.150"
if self.up: if self.up:
self.net_client.device_down(netif.localname) self.net_client.device_down(netif.localname)
self.net_client.device_name(netif.localname, netif.name) self.net_client.device_name(netif.localname, netif.name)
netif.localname = netif.name netif.localname = netif.name
if hwaddr: if hwaddr:
self.sethwaddr(ifindex, hwaddr) self.sethwaddr(ifindex, hwaddr)
for addr in addrlist:
for addr in utils.make_tuple(addrlist):
self.addaddr(ifindex, addr) self.addaddr(ifindex, addr)
if self.up: if self.up:
self.net_client.device_up(netif.localname) self.net_client.device_up(netif.localname)
def linkconfig( def linkconfig(
self, self, netif: CoreInterface, options: LinkOptions, netif2: CoreInterface = None
netif: CoreInterface,
bw: float = None,
delay: float = None,
loss: float = None,
duplicate: float = None,
jitter: float = None,
netif2: CoreInterface = None,
) -> None: ) -> None:
""" """
Apply tc queing disciplines using linkconfig. Apply tc queing disciplines using linkconfig.
""" """
linux_bridge = CoreNetwork(session=self.session, start=False) linux_bridge = CoreNetwork(session=self.session, start=False)
linux_bridge.up = True linux_bridge.up = True
linux_bridge.linkconfig( linux_bridge.linkconfig(netif, options, netif2)
netif,
bw=bw,
delay=delay,
loss=loss,
duplicate=duplicate,
jitter=jitter,
netif2=netif2,
)
del linux_bridge del linux_bridge
def newifindex(self) -> int: def newifindex(self) -> int:
@ -176,37 +157,25 @@ class PhysicalNode(CoreNodeBase):
self.ifindex += 1 self.ifindex += 1
return ifindex return ifindex
def newnetif( def newnetif(self, net: CoreNetworkBase, interface: InterfaceData) -> int:
self,
net: Veth = None,
addrlist: List[str] = None,
hwaddr: str = None,
ifindex: int = None,
ifname: str = None,
) -> int:
logging.info("creating interface") logging.info("creating interface")
if not addrlist: addresses = interface.get_addresses()
addrlist = [] ifindex = interface.id
if self.up and net is None:
raise NotImplementedError
if ifindex is None: if ifindex is None:
ifindex = self.newifindex() ifindex = self.newifindex()
name = interface.name
if ifname is None: if name is None:
ifname = f"gt{ifindex}" name = f"gt{ifindex}"
if self.up: if self.up:
# this is reached when this node is linked to a network node # 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 # tunnel to net not built yet, so build it now and adopt it
_, remote_tap = self.session.distributed.create_gre_tunnel(net, self.server) _, 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 return ifindex
else: else:
# this is reached when configuring services (self.up=False) # this is reached when configuring services (self.up=False)
netif = GreTap(node=self, name=ifname, session=self.session, start=False) netif = GreTap(node=self, name=name, session=self.session, start=False)
self.adoptnetif(netif, ifindex, hwaddr, addrlist) self.adoptnetif(netif, ifindex, interface.mac, addresses)
return ifindex return ifindex
def privatedir(self, path: str) -> None: def privatedir(self, path: str) -> None:
@ -258,14 +227,14 @@ class PhysicalNode(CoreNodeBase):
return self.host_cmd(args, wait=wait) 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 RJ45Node is a physical interface on the host linked to the emulated
network. network.
""" """
apitype = NodeTypes.RJ45 apitype: NodeTypes = NodeTypes.RJ45
type = "rj45" type: str = "rj45"
def __init__( def __init__(
self, self,
@ -287,16 +256,13 @@ class Rj45Node(CoreNodeBase, CoreInterface):
:param server: remote server node :param server: remote server node
will run on, default is None for localhost will run on, default is None for localhost
""" """
CoreNodeBase.__init__(self, session, _id, name, start, server) super().__init__(session, _id, name, start, server)
CoreInterface.__init__(self, session, self, name, mtu, server) self.interface = CoreInterface(session, self, name, name, mtu, server)
self.lock = threading.RLock() self.interface.transport_type = TransportType.RAW
self.ifindex = None self.lock: threading.RLock = threading.RLock()
# the following are PyCoreNetIf attributes self.ifindex: Optional[int] = None
self.transport_type = "raw" self.old_up: bool = False
self.localname = name self.old_addrs: List[Tuple[str, Optional[str]]] = []
self.old_up = False
self.old_addrs = []
if start: if start:
self.startup() self.startup()
@ -309,7 +275,7 @@ class Rj45Node(CoreNodeBase, CoreInterface):
""" """
# interface will also be marked up during net.attach() # interface will also be marked up during net.attach()
self.savestate() self.savestate()
self.net_client.device_up(self.localname) self.net_client.device_up(self.interface.localname)
self.up = True self.up = True
def shutdown(self) -> None: def shutdown(self) -> None:
@ -321,78 +287,39 @@ class Rj45Node(CoreNodeBase, CoreInterface):
""" """
if not self.up: if not self.up:
return return
localname = self.interface.localname
self.net_client.device_down(localname)
self.net_client.device_flush(localname)
try: try:
self.net_client.device_down(self.localname) self.net_client.delete_tc(localname)
self.net_client.device_flush(self.localname)
self.net_client.delete_tc(self.localname)
except CoreCommandError: except CoreCommandError:
logging.exception("error shutting down") pass
self.up = False self.up = False
self.restorestate() self.restorestate()
# TODO: issue in that both classes inherited from provide the same method with def newnetif(self, net: CoreNetworkBase, interface: InterfaceData) -> int:
# 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:
""" """
This is called when linking with another node. Since this node This is called when linking with another node. Since this node
represents an interface, we do not create another object here, represents an interface, we do not create another object here,
but attach ourselves to the given network. but attach ourselves to the given network.
:param net: new network instance :param net: new network instance
:param addrlist: address list :param interface: interface data for new interface
:param hwaddr: hardware address
:param ifindex: interface index
:param ifname: interface name
:return: interface index :return: interface index
:raises ValueError: when an interface has already been created, one max :raises ValueError: when an interface has already been created, one max
""" """
with self.lock: with self.lock:
ifindex = interface.id
if ifindex is None: if ifindex is None:
ifindex = 0 ifindex = 0
if self.interface.net is not None:
if self.net is not None:
raise ValueError("RJ45 nodes support at most 1 network interface") raise ValueError("RJ45 nodes support at most 1 network interface")
self._netif[ifindex] = self.interface
self._netif[ifindex] = self
# PyCoreNetIf.node is self
self.node = self
self.ifindex = ifindex self.ifindex = ifindex
if net is not None: if net is not None:
self.attachnet(net) self.interface.attachnet(net)
for addr in interface.get_addresses():
if addrlist: self.addaddr(addr)
for addr in utils.make_tuple(addrlist):
self.addaddr(addr)
return ifindex return ifindex
def delnetif(self, ifindex: int) -> None: def delnetif(self, ifindex: int) -> None:
@ -404,9 +331,7 @@ class Rj45Node(CoreNodeBase, CoreInterface):
""" """
if ifindex is None: if ifindex is None:
ifindex = 0 ifindex = 0
self._netif.pop(ifindex) self._netif.pop(ifindex)
if ifindex == self.ifindex: if ifindex == self.ifindex:
self.shutdown() self.shutdown()
else: else:
@ -424,15 +349,12 @@ class Rj45Node(CoreNodeBase, CoreInterface):
:param net: network to retrieve :param net: network to retrieve
:return: a network interface :return: a network interface
""" """
if net is not None and net == self.net: if net is not None and net == self.interface.net:
return self return self.interface
if ifindex is None: if ifindex is None:
ifindex = 0 ifindex = 0
if ifindex == self.ifindex: if ifindex == self.ifindex:
return self return self.interface
return None return None
def getifindex(self, netif: CoreInterface) -> Optional[int]: def getifindex(self, netif: CoreInterface) -> Optional[int]:
@ -443,7 +365,7 @@ class Rj45Node(CoreNodeBase, CoreInterface):
index for index for
:return: interface index, None otherwise :return: interface index, None otherwise
""" """
if netif != self: if netif != self.interface:
return None return None
return self.ifindex return self.ifindex
@ -458,7 +380,7 @@ class Rj45Node(CoreNodeBase, CoreInterface):
addr = utils.validate_ip(addr) addr = utils.validate_ip(addr)
if self.up: if self.up:
self.net_client.create_address(self.name, addr) self.net_client.create_address(self.name, addr)
CoreInterface.addaddr(self, addr) self.interface.addaddr(addr)
def deladdr(self, addr: str) -> None: def deladdr(self, addr: str) -> None:
""" """
@ -469,8 +391,8 @@ class Rj45Node(CoreNodeBase, CoreInterface):
:raises CoreCommandError: when there is a command exception :raises CoreCommandError: when there is a command exception
""" """
if self.up: if self.up:
self.net_client.delete_address(self.name, str(addr)) self.net_client.delete_address(self.name, addr)
CoreInterface.deladdr(self, addr) self.interface.deladdr(addr)
def savestate(self) -> None: def savestate(self) -> None:
""" """
@ -481,14 +403,14 @@ class Rj45Node(CoreNodeBase, CoreInterface):
:raises CoreCommandError: when there is a command exception :raises CoreCommandError: when there is a command exception
""" """
self.old_up = False self.old_up = False
self.old_addrs = [] self.old_addrs: List[Tuple[str, Optional[str]]] = []
output = self.net_client.device_show(self.localname) localname = self.interface.localname
output = self.net_client.address_show(localname)
for line in output.split("\n"): for line in output.split("\n"):
items = line.split() items = line.split()
if len(items) < 2: if len(items) < 2:
continue continue
if items[1] == f"{localname}:":
if items[1] == f"{self.localname}:":
flags = items[2][1:-1].split(",") flags = items[2][1:-1].split(",")
if "UP" in flags: if "UP" in flags:
self.old_up = True self.old_up = True
@ -498,6 +420,7 @@ class Rj45Node(CoreNodeBase, CoreInterface):
if items[1][:4] == "fe80": if items[1][:4] == "fe80":
continue continue
self.old_addrs.append((items[1], None)) 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: def restorestate(self) -> None:
""" """
@ -506,16 +429,12 @@ class Rj45Node(CoreNodeBase, CoreInterface):
:return: nothing :return: nothing
:raises CoreCommandError: when there is a command exception :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: for addr in self.old_addrs:
if addr[1] is None: self.net_client.create_address(localname, addr[0], addr[1])
self.net_client.create_address(self.localname, addr[0])
else:
self.net_client.create_address(
self.localname, addr[0], broadcast=addr[1]
)
if self.old_up: 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: 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 :param z: z position
:return: True if position changed, False otherwise :return: True if position changed, False otherwise
""" """
CoreNodeBase.setposition(self, x, y, z) super().setposition(x, y, z)
CoreInterface.setposition(self) self.interface.setposition()
def termcmdstring(self, sh: str) -> str: def termcmdstring(self, sh: str) -> str:
""" """

View file

@ -5,7 +5,7 @@ sdt.py: Scripted Display Tool (SDT3D) helper
import logging import logging
import socket import socket
import threading import threading
from typing import TYPE_CHECKING, Optional from typing import IO, TYPE_CHECKING, Dict, Optional, Set, Tuple
from urllib.parse import urlparse from urllib.parse import urlparse
from core import constants from core import constants
@ -42,11 +42,11 @@ class Sdt:
when a node position or link has changed. 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 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 # 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"), ("router", "router.gif"),
("host", "host.gif"), ("host", "host.gif"),
("PC", "pc.gif"), ("PC", "pc.gif"),
@ -65,14 +65,14 @@ class Sdt:
:param session: session this manager is tied to :param session: session this manager is tied to
""" """
self.session = session self.session: "Session" = session
self.lock = threading.Lock() self.lock: threading.Lock = threading.Lock()
self.sock = None self.sock: Optional[IO] = None
self.connected = False self.connected: bool = False
self.url = self.DEFAULT_SDT_URL self.url: str = self.DEFAULT_SDT_URL
self.address = None self.address: Optional[Tuple[Optional[str], Optional[int]]] = None
self.protocol = None self.protocol: Optional[str] = None
self.network_layers = set() self.network_layers: Set[str] = set()
self.session.node_handlers.append(self.handle_node_update) self.session.node_handlers.append(self.handle_node_update)
self.session.link_handlers.append(self.handle_link_update) self.session.link_handlers.append(self.handle_link_update)
@ -344,7 +344,7 @@ class Sdt:
""" """
result = False result = False
try: try:
node = self.session.get_node(node_id) node = self.session.get_node(node_id, NodeBase)
result = isinstance(node, (WlanNode, EmaneNet)) result = isinstance(node, (WlanNode, EmaneNet))
except CoreError: except CoreError:
pass pass

View file

@ -39,7 +39,7 @@ class ServiceDependencies:
that all services will be booted and that all dependencies exist within the services provided. 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 # helpers to check validity
self.dependents = {} self.dependents = {}
self.booted = set() self.booted = set()

View file

@ -1,4 +1,5 @@
from core.emane.nodes import EmaneNet from core.emane.nodes import EmaneNet
from core.errors import CoreError
from core.services.coreservices import CoreService from core.services.coreservices import CoreService
from core.xml import emanexml from core.xml import emanexml
@ -20,8 +21,8 @@ class EmaneTransportService(CoreService):
if filename == cls.configs[0]: if filename == cls.configs[0]:
transport_commands = [] transport_commands = []
for interface in node.netifs(sort=True): for interface in node.netifs(sort=True):
network_node = node.session.get_node(interface.net.id) try:
if isinstance(network_node, EmaneNet): network_node = node.session.get_node(interface.net.id, EmaneNet)
config = node.session.emane.get_configs( config = node.session.emane.get_configs(
network_node.id, network_node.model.name network_node.id, network_node.model.name
) )
@ -32,6 +33,8 @@ class EmaneTransportService(CoreService):
% nem_id % nem_id
) )
transport_commands.append(command) transport_commands.append(command)
except CoreError:
pass
transport_commands = "\n".join(transport_commands) transport_commands = "\n".join(transport_commands)
return """ return """
emanegentransportxml -o ../ ../platform%s.xml emanegentransportxml -o ../ ../platform%s.xml

View file

@ -354,7 +354,7 @@ class FrrService(CoreService):
for peerifc in ifc.net.netifs(): for peerifc in ifc.net.netifs():
if peerifc == ifc: if peerifc == ifc:
continue continue
if isinstance(peerifc, Rj45Node): if isinstance(peerifc.node, Rj45Node):
return True return True
return False return False

View file

@ -272,7 +272,7 @@ class QuaggaService(CoreService):
for peerifc in ifc.net.netifs(): for peerifc in ifc.net.netifs():
if peerifc == ifc: if peerifc == ifc:
continue continue
if isinstance(peerifc, Rj45Node): if isinstance(peerifc.node, Rj45Node):
return True return True
return False return False

View file

@ -158,19 +158,6 @@ def which(command: str, required: bool) -> str:
return found_path 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]: def make_tuple_fromstr(s: str, value_type: Callable[[str], T]) -> Tuple[T]:
""" """
Create a tuple from a string. Create a tuple from a string.
@ -228,17 +215,21 @@ def cmd(
if shell is False: if shell is False:
args = shlex.split(args) args = shlex.split(args)
try: 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: if wait:
stdout, stderr = p.communicate() stdout, stderr = p.communicate()
stdout = stdout.decode("utf-8").strip()
stderr = stderr.decode("utf-8").strip()
status = p.wait() status = p.wait()
if status != 0: if status != 0:
raise CoreCommandError(status, args, stdout, stderr) raise CoreCommandError(status, args, stdout, stderr)
return stdout.decode("utf-8").strip() return stdout
else: else:
return "" return ""
except OSError: except OSError as e:
raise CoreCommandError(-1, args) logging.error("cmd error: %s", e.strerror)
raise CoreCommandError(1, args, "", e.strerror)
def file_munge(pathname: str, header: str, text: str) -> None: def file_munge(pathname: str, header: str, text: str) -> None:

View file

@ -10,7 +10,7 @@ from core.emulator.data import LinkData
from core.emulator.emudata import InterfaceData, LinkOptions, NodeOptions from core.emulator.emudata import InterfaceData, LinkOptions, NodeOptions
from core.emulator.enumerations import EventTypes, NodeTypes from core.emulator.enumerations import EventTypes, NodeTypes
from core.errors import CoreXmlError 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.docker import DockerNode
from core.nodes.lxd import LxcNode from core.nodes.lxd import LxcNode
from core.nodes.network import CtrlNet, WlanNode 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") ip4_mask = get_int(interface_element, "ip4_mask")
ip6 = interface_element.get("ip6") ip6 = interface_element.get("ip6")
ip6_mask = get_int(interface_element, "ip6_mask") 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: def create_emane_config(session: "Session") -> etree.Element:
@ -505,9 +513,9 @@ class CoreXmlWriter:
ip6_mask: int, ip6_mask: int,
) -> etree.Element: ) -> etree.Element:
interface = etree.Element(element_name) interface = etree.Element(element_name)
node = self.session.get_node(node_id) node = self.session.get_node(node_id, NodeBase)
interface_name = None interface_name = None
if not isinstance(node, CoreNetworkBase): if isinstance(node, CoreNodeBase):
node_interface = node.netif(interface_id) node_interface = node.netif(interface_id)
interface_name = node_interface.name interface_name = node_interface.name
@ -523,7 +531,6 @@ class CoreXmlWriter:
add_attribute(interface, "ip4_mask", ip4_mask) add_attribute(interface, "ip4_mask", ip4_mask)
add_attribute(interface, "ip6", ip6) add_attribute(interface, "ip6", ip6)
add_attribute(interface, "ip6_mask", ip6_mask) add_attribute(interface, "ip6_mask", ip6_mask)
return interface return interface
def create_link_element(self, link_data: LinkData) -> etree.Element: def create_link_element(self, link_data: LinkData) -> etree.Element:
@ -560,8 +567,8 @@ class CoreXmlWriter:
link_element.append(interface_two) link_element.append(interface_two)
# check for options, don't write for emane/wlan links # check for options, don't write for emane/wlan links
node_one = self.session.get_node(link_data.node1_id) node_one = self.session.get_node(link_data.node1_id, NodeBase)
node_two = self.session.get_node(link_data.node2_id) node_two = self.session.get_node(link_data.node2_id, NodeBase)
is_node_one_wireless = isinstance(node_one, (WlanNode, EmaneNet)) is_node_one_wireless = isinstance(node_one, (WlanNode, EmaneNet))
is_node_two_wireless = isinstance(node_two, (WlanNode, EmaneNet)) is_node_two_wireless = isinstance(node_two, (WlanNode, EmaneNet))
if not any([is_node_one_wireless, is_node_two_wireless]): if not any([is_node_one_wireless, is_node_two_wireless]):
@ -663,7 +670,7 @@ class CoreXmlReader:
state = EventTypes(state) state = EventTypes(state)
data = hook.text data = hook.text
logging.info("reading hook: state(%s) name(%s)", state, name) 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: def read_session_origin(self) -> None:
session_origin = self.scenario.find("session_origin") session_origin = self.scenario.find("session_origin")
@ -833,14 +840,14 @@ class CoreXmlReader:
icon = device_element.get("icon") icon = device_element.get("icon")
clazz = device_element.get("class") clazz = device_element.get("class")
image = device_element.get("image") image = device_element.get("image")
options = NodeOptions(name, model, image) options = NodeOptions(name=name, model=model, image=image, icon=icon)
options.icon = icon
node_type = NodeTypes.DEFAULT node_type = NodeTypes.DEFAULT
if clazz == "docker": if clazz == "docker":
node_type = NodeTypes.DOCKER node_type = NodeTypes.DOCKER
elif clazz == "lxc": elif clazz == "lxc":
node_type = NodeTypes.LXC node_type = NodeTypes.LXC
_class = self.session.get_node_class(node_type)
service_elements = device_element.find("services") service_elements = device_element.find("services")
if service_elements is not None: if service_elements is not None:
@ -866,15 +873,15 @@ class CoreXmlReader:
options.set_location(lat, lon, alt) options.set_location(lat, lon, alt)
logging.info("reading node id(%s) model(%s) name(%s)", node_id, model, name) 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: def read_network(self, network_element: etree.Element) -> None:
node_id = get_int(network_element, "id") node_id = get_int(network_element, "id")
name = network_element.get("name") name = network_element.get("name")
node_type = NodeTypes[network_element.get("type")] node_type = NodeTypes[network_element.get("type")]
_class = self.session.get_node_class(node_type)
icon = network_element.get("icon") icon = network_element.get("icon")
options = NodeOptions(name) options = NodeOptions(name=name, icon=icon)
options.icon = icon
position_element = network_element.find("position") position_element = network_element.find("position")
if position_element is not None: if position_element is not None:
@ -892,7 +899,7 @@ class CoreXmlReader:
logging.info( logging.info(
"reading node id(%s) node_type(%s) name(%s)", node_id, node_type, name "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: def read_configservice_configs(self) -> None:
configservice_configs = self.scenario.find("configservice_configurations") configservice_configs = self.scenario.find("configservice_configurations")
@ -902,7 +909,7 @@ class CoreXmlReader:
for configservice_element in configservice_configs.iterchildren(): for configservice_element in configservice_configs.iterchildren():
name = configservice_element.get("name") name = configservice_element.get("name")
node_id = get_int(configservice_element, "node") 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] service = node.config_services[name]
configs_element = configservice_element.find("configs") configs_element = configservice_element.find("configs")

View file

@ -9,6 +9,7 @@ from core import utils
from core.config import Configuration from core.config import Configuration
from core.emane.nodes import EmaneNet from core.emane.nodes import EmaneNet
from core.emulator.distributed import DistributedServer from core.emulator.distributed import DistributedServer
from core.emulator.enumerations import TransportType
from core.nodes.interface import CoreInterface from core.nodes.interface import CoreInterface
from core.nodes.network import CtrlNet from core.nodes.network import CtrlNet
from core.xml import corexml from core.xml import corexml
@ -182,7 +183,7 @@ def build_node_platform_xml(
transport_type = netif.transport_type transport_type = netif.transport_type
if not transport_type: if not transport_type:
logging.info("warning: %s interface type unsupported!", netif.name) 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_file = transport_file_name(node.id, transport_type)
transport_element = etree.SubElement( transport_element = etree.SubElement(
nem_element, "transport", definition=transport_file nem_element, "transport", definition=transport_file
@ -196,7 +197,7 @@ def build_node_platform_xml(
# merging code # merging code
key = netif.node.id key = netif.node.id
if netif.transport_type == "raw": if netif.transport_type == TransportType.RAW:
key = "host" key = "host"
otadev = control_net.brname otadev = control_net.brname
eventdev = 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 # build XML for specific interface (NEM) configs
need_virtual = False need_virtual = False
need_raw = False need_raw = False
vtype = "virtual" vtype = TransportType.VIRTUAL
rtype = "raw" rtype = TransportType.RAW
for netif in node.netifs(): for netif in node.netifs():
# check for interface specific emane configuration and write xml files # 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) node.model.build_xml_files(config, netif)
# check transport type needed for interface # check transport type needed for interface
if "virtual" in netif.transport_type: if netif.transport_type == TransportType.VIRTUAL:
need_virtual = True need_virtual = True
vtype = netif.transport_type vtype = netif.transport_type
else: else:
@ -301,7 +302,7 @@ def build_xml_files(emane_manager: "EmaneManager", node: EmaneNet) -> None:
def build_transport_xml( def build_transport_xml(
emane_manager: "EmaneManager", node: EmaneNet, transport_type: str emane_manager: "EmaneManager", node: EmaneNet, transport_type: TransportType
) -> None: ) -> None:
""" """
Build transport xml file for node and transport type. Build transport xml file for node and transport type.
@ -314,8 +315,8 @@ def build_transport_xml(
""" """
transport_element = etree.Element( transport_element = etree.Element(
"transport", "transport",
name=f"{transport_type.capitalize()} Transport", name=f"{transport_type.value.capitalize()} Transport",
library=f"trans{transport_type.lower()}", library=f"trans{transport_type.value.lower()}",
) )
# add bitrate # add bitrate
@ -325,7 +326,7 @@ def build_transport_xml(
config = emane_manager.get_configs(node.id, node.model.name) config = emane_manager.get_configs(node.id, node.model.name)
flowcontrol = config.get("flowcontrolenable", "0") == "1" flowcontrol = config.get("flowcontrolenable", "0") == "1"
if "virtual" in transport_type.lower(): if transport_type == TransportType.VIRTUAL:
device_path = "/dev/net/tun_flowctl" device_path = "/dev/net/tun_flowctl"
if not os.path.exists(device_path): if not os.path.exists(device_path):
device_path = "/dev/net/tun" device_path = "/dev/net/tun"
@ -482,7 +483,7 @@ def create_event_service_xml(
create_file(event_element, "emaneeventmsgsvc", file_path, server) 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. 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 :param transport_type: transport type to generate transport file
:return: :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: 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) basename = _basename(emane_model, interface)
append = "" append = ""
if interface and interface.transport_type == "raw": if interface and interface.transport_type == TransportType.RAW:
append = "_raw" append = "_raw"
return f"{basename}nem{append}.xml" return f"{basename}nem{append}.xml"

View file

@ -4,7 +4,6 @@ listenaddr = localhost
port = 4038 port = 4038
grpcaddress = localhost grpcaddress = localhost
grpcport = 50051 grpcport = 50051
numthreads = 1
quagga_bin_search = "/usr/local/bin /usr/bin /usr/lib/quagga" quagga_bin_search = "/usr/local/bin /usr/bin /usr/lib/quagga"
quagga_sbin_search = "/usr/local/sbin /usr/sbin /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" frr_bin_search = "/usr/local/bin /usr/bin /usr/lib/frr"

View file

@ -2,7 +2,9 @@ import logging
from core.emulator.coreemu import CoreEmu from core.emulator.coreemu import CoreEmu
from core.emulator.emudata import IpPrefixes, NodeOptions 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__": if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
@ -13,16 +15,16 @@ if __name__ == "__main__":
coreemu = CoreEmu() coreemu = CoreEmu()
session = coreemu.create_session() session = coreemu.create_session()
session.set_state(EventTypes.CONFIGURATION_STATE) session.set_state(EventTypes.CONFIGURATION_STATE)
switch = session.add_node(_type=NodeTypes.SWITCH) switch = session.add_node(SwitchNode)
# node one # node one
options.config_services = ["DefaultRoute", "IPForward"] 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) interface = prefixes.create_interface(node_one)
session.add_link(node_one.id, switch.id, interface_one=interface) session.add_link(node_one.id, switch.id, interface_one=interface)
# node two # node two
node_two = session.add_node(options=options) node_two = session.add_node(CoreNode, options=options)
interface = prefixes.create_interface(node_two) interface = prefixes.create_interface(node_two)
session.add_link(node_two.id, switch.id, interface_one=interface) session.add_link(node_two.id, switch.id, interface_one=interface)

View file

@ -2,7 +2,9 @@ import logging
from core.emulator.coreemu import CoreEmu from core.emulator.coreemu import CoreEmu
from core.emulator.emudata import IpPrefixes, NodeOptions 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__": if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
@ -15,11 +17,11 @@ if __name__ == "__main__":
options = NodeOptions(model=None, image="ubuntu") options = NodeOptions(model=None, image="ubuntu")
# create node one # 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) interface_one = prefixes.create_interface(node_one)
# create node two # create node two
node_two = session.add_node() node_two = session.add_node(CoreNode)
interface_two = prefixes.create_interface(node_two) interface_two = prefixes.create_interface(node_two)
# add link # add link

View file

@ -2,7 +2,8 @@ import logging
from core.emulator.coreemu import CoreEmu from core.emulator.coreemu import CoreEmu
from core.emulator.emudata import IpPrefixes, NodeOptions 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__": if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
@ -17,11 +18,11 @@ if __name__ == "__main__":
options = NodeOptions(model=None, image="ubuntu") options = NodeOptions(model=None, image="ubuntu")
# create node one # 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) interface_one = prefixes.create_interface(node_one)
# create node two # 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) interface_two = prefixes.create_interface(node_two)
# add link # add link

View file

@ -2,7 +2,10 @@ import logging
from core.emulator.coreemu import CoreEmu from core.emulator.coreemu import CoreEmu
from core.emulator.emudata import IpPrefixes, NodeOptions 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__": if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
@ -16,18 +19,18 @@ if __name__ == "__main__":
options = NodeOptions(model=None, image="ubuntu") options = NodeOptions(model=None, image="ubuntu")
# create switch # create switch
switch = session.add_node(_type=NodeTypes.SWITCH) switch = session.add_node(SwitchNode)
# node one # 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) interface_one = prefixes.create_interface(node_one)
# node two # 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) interface_two = prefixes.create_interface(node_two)
# node three # node three
node_three = session.add_node() node_three = session.add_node(CoreNode)
interface_three = prefixes.create_interface(node_three) interface_three = prefixes.create_interface(node_three)
# add links # add links

View file

@ -1,7 +1,8 @@
import argparse import argparse
import logging 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): def log_event(event):
@ -26,13 +27,11 @@ def main(args):
core.events(session_id, log_event) core.events(session_id, log_event)
# change session state # change session state
response = core.set_session_state( response = core.set_session_state(session_id, SessionState.CONFIGURATION)
session_id, core_pb2.SessionState.CONFIGURATION
)
logging.info("set session state: %s", response) logging.info("set session state: %s", response)
# create switch node # create switch node
switch = core_pb2.Node(type=core_pb2.NodeType.SWITCH) switch = Node(type=NodeType.SWITCH)
response = core.add_node(session_id, switch) response = core.add_node(session_id, switch)
logging.info("created switch: %s", response) logging.info("created switch: %s", response)
switch_id = response.node_id switch_id = response.node_id
@ -41,8 +40,8 @@ def main(args):
interface_helper = client.InterfaceHelper(ip4_prefix="10.83.0.0/16") interface_helper = client.InterfaceHelper(ip4_prefix="10.83.0.0/16")
# create node one # create node one
position = core_pb2.Position(x=100, y=50) position = Position(x=100, y=50)
node = core_pb2.Node(position=position) node = Node(position=position)
response = core.add_node(session_id, node) response = core.add_node(session_id, node)
logging.info("created node one: %s", response) logging.info("created node one: %s", response)
node_one_id = response.node_id node_one_id = response.node_id
@ -53,8 +52,8 @@ def main(args):
logging.info("created link from node one to switch: %s", response) logging.info("created link from node one to switch: %s", response)
# create node two # create node two
position = core_pb2.Position(x=200, y=50) position = Position(x=200, y=50)
node = core_pb2.Node(position=position, server=server_name) node = Node(position=position, server=server_name)
response = core.add_node(session_id, node) response = core.add_node(session_id, node)
logging.info("created node two: %s", response) logging.info("created node two: %s", response)
node_two_id = response.node_id node_two_id = response.node_id
@ -65,9 +64,7 @@ def main(args):
logging.info("created link from node two to switch: %s", response) logging.info("created link from node two to switch: %s", response)
# change session state # change session state
response = core.set_session_state( response = core.set_session_state(session_id, SessionState.INSTANTIATION)
session_id, core_pb2.SessionState.INSTANTIATION
)
logging.info("set session state: %s", response) logging.info("set session state: %s", response)

View 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()

View file

@ -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()

View file

@ -1,6 +1,11 @@
"""
Example using gRPC API to create a simple switch network.
"""
import logging 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): def log_event(event):
@ -8,8 +13,11 @@ def log_event(event):
def main(): 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(): with core.context_connect():
# create session # create session
response = core.create_session() response = core.create_session()
@ -19,38 +27,41 @@ def main():
session_id = response.session_id session_id = response.session_id
core.events(session_id, log_event) core.events(session_id, log_event)
# change session state # change session state to configuration so that nodes get started when added
response = core.set_session_state( response = core.set_session_state(session_id, SessionState.CONFIGURATION)
session_id, core_pb2.SessionState.CONFIGURATION
)
logging.info("set session state: %s", response) logging.info("set session state: %s", response)
# create switch node # 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) response = core.add_node(session_id, switch)
logging.info("created switch: %s", response) logging.info("created switch: %s", response)
switch_id = response.node_id switch_id = response.node_id
# helper to create interfaces # create node one
interface_helper = client.InterfaceHelper(ip4_prefix="10.83.0.0/16") 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 two
# create node position = Position(x=300, y=100)
position = core_pb2.Position(x=50 + 50 * i, y=50) node2 = Node(type=NodeType.DEFAULT, position=position)
node = core_pb2.Node(position=position) response = core.add_node(session_id, node2)
response = core.add_node(session_id, node) logging.info("created node: %s", response)
logging.info("created node: %s", response) node2_id = response.node_id
node_id = response.node_id
# create link # links nodes to switch
interface_one = interface_helper.create_interface(node_id, 0) interface_one = interface_helper.create_interface(node1_id, 0)
response = core.add_link(session_id, node_id, switch_id, interface_one) response = core.add_link(session_id, node1_id, switch_id, interface_one)
logging.info("created link: %s", response) 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 # change session state
response = core.set_session_state( response = core.set_session_state(session_id, SessionState.INSTANTIATION)
session_id, core_pb2.SessionState.INSTANTIATION
)
logging.info("set session state: %s", response) logging.info("set session state: %s", response)

View 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()

View file

@ -2,7 +2,9 @@ import logging
from core.emulator.coreemu import CoreEmu from core.emulator.coreemu import CoreEmu
from core.emulator.emudata import IpPrefixes, NodeOptions 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__": if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
@ -15,11 +17,11 @@ if __name__ == "__main__":
options = NodeOptions(image="ubuntu") options = NodeOptions(image="ubuntu")
# create node one # 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) interface_one = prefixes.create_interface(node_one)
# create node two # create node two
node_two = session.add_node() node_two = session.add_node(CoreNode)
interface_two = prefixes.create_interface(node_two) interface_two = prefixes.create_interface(node_two)
# add link # add link

View file

@ -2,7 +2,8 @@ import logging
from core.emulator.coreemu import CoreEmu from core.emulator.coreemu import CoreEmu
from core.emulator.emudata import IpPrefixes, NodeOptions 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__": if __name__ == "__main__":
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
@ -17,11 +18,11 @@ if __name__ == "__main__":
options = NodeOptions(image="ubuntu:18.04") options = NodeOptions(image="ubuntu:18.04")
# create node one # 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) interface_one = prefixes.create_interface(node_one)
# create node two # 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) interface_two = prefixes.create_interface(node_two)
# add link # add link

View file

@ -2,7 +2,10 @@ import logging
from core.emulator.coreemu import CoreEmu from core.emulator.coreemu import CoreEmu
from core.emulator.emudata import IpPrefixes, NodeOptions 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__": if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
@ -16,18 +19,18 @@ if __name__ == "__main__":
options = NodeOptions(image="ubuntu") options = NodeOptions(image="ubuntu")
# create switch # create switch
switch = session.add_node(_type=NodeTypes.SWITCH) switch = session.add_node(SwitchNode)
# node one # 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) interface_one = prefixes.create_interface(node_one)
# node two # 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) interface_two = prefixes.create_interface(node_two)
# node three # node three
node_three = session.add_node() node_three = session.add_node(CoreNode)
interface_three = prefixes.create_interface(node_three) interface_three = prefixes.create_interface(node_three)
# add links # add links

View file

@ -7,9 +7,11 @@ import argparse
import logging import logging
from core.emane.ieee80211abg import EmaneIeee80211abgModel from core.emane.ieee80211abg import EmaneIeee80211abgModel
from core.emane.nodes import EmaneNet
from core.emulator.coreemu import CoreEmu from core.emulator.coreemu import CoreEmu
from core.emulator.emudata import IpPrefixes, NodeOptions 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): def parse(name):
@ -50,11 +52,11 @@ def main(args):
# create local node, switch, and remote nodes # create local node, switch, and remote nodes
options = NodeOptions(model="mdr") options = NodeOptions(model="mdr")
options.set_position(0, 0) options.set_position(0, 0)
node_one = session.add_node(options=options) node_one = session.add_node(CoreNode, options=options)
emane_net = session.add_node(_type=NodeTypes.EMANE) emane_net = session.add_node(EmaneNet)
session.emane.set_model(emane_net, EmaneIeee80211abgModel) session.emane.set_model(emane_net, EmaneIeee80211abgModel)
options.server = server_name options.server = server_name
node_two = session.add_node(options=options) node_two = session.add_node(CoreNode, options=options)
# create node interfaces and link # create node interfaces and link
interface_one = prefixes.create_interface(node_one) interface_one = prefixes.create_interface(node_one)

View file

@ -8,7 +8,8 @@ import logging
from core.emulator.coreemu import CoreEmu from core.emulator.coreemu import CoreEmu
from core.emulator.emudata import IpPrefixes, NodeOptions 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): def parse(name):
@ -42,9 +43,9 @@ def main(args):
# create local node, switch, and remote nodes # create local node, switch, and remote nodes
options = NodeOptions(image="ubuntu:18.04") 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 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 # create node interfaces and link
interface_one = prefixes.create_interface(node_one) interface_one = prefixes.create_interface(node_one)

View file

@ -9,6 +9,7 @@ import logging
from core.emulator.coreemu import CoreEmu from core.emulator.coreemu import CoreEmu
from core.emulator.emudata import IpPrefixes, NodeOptions from core.emulator.emudata import IpPrefixes, NodeOptions
from core.emulator.enumerations import EventTypes from core.emulator.enumerations import EventTypes
from core.nodes.base import CoreNode
def parse(name): def parse(name):
@ -42,9 +43,9 @@ def main(args):
# create local node, switch, and remote nodes # create local node, switch, and remote nodes
options = NodeOptions() options = NodeOptions()
node_one = session.add_node(options=options) node_one = session.add_node(CoreNode, options=options)
options.server = server_name options.server = server_name
node_two = session.add_node(options=options) node_two = session.add_node(CoreNode, options=options)
# create node interfaces and link # create node interfaces and link
interface_one = prefixes.create_interface(node_one) interface_one = prefixes.create_interface(node_one)

View file

@ -8,7 +8,9 @@ import logging
from core.emulator.coreemu import CoreEmu from core.emulator.coreemu import CoreEmu
from core.emulator.emudata import IpPrefixes, NodeOptions 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): def parse(name):
@ -43,11 +45,11 @@ def main(args):
session.set_state(EventTypes.CONFIGURATION_STATE) session.set_state(EventTypes.CONFIGURATION_STATE)
# create local node, switch, and remote nodes # create local node, switch, and remote nodes
node_one = session.add_node() node_one = session.add_node(CoreNode)
switch = session.add_node(_type=NodeTypes.SWITCH) switch = session.add_node(SwitchNode)
options = NodeOptions() options = NodeOptions()
options.server = server_name options.server = server_name
node_two = session.add_node(options=options) node_two = session.add_node(CoreNode, options=options)
# create node interfaces and link # create node interfaces and link
interface_one = prefixes.create_interface(node_one) interface_one = prefixes.create_interface(node_one)

View file

@ -8,9 +8,11 @@ import logging
import time import time
from core.emane.ieee80211abg import EmaneIeee80211abgModel from core.emane.ieee80211abg import EmaneIeee80211abgModel
from core.emane.nodes import EmaneNet
from core.emulator.coreemu import CoreEmu from core.emulator.coreemu import CoreEmu
from core.emulator.emudata import IpPrefixes, NodeOptions 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 NODES = 2
EMANE_DELAY = 10 EMANE_DELAY = 10
@ -32,13 +34,13 @@ def main():
session.set_location(47.57917, -122.13232, 2.00000, 1.0) session.set_location(47.57917, -122.13232, 2.00000, 1.0)
options = NodeOptions() options = NodeOptions()
options.set_position(80, 50) 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) session.emane.set_model(emane_network, EmaneIeee80211abgModel)
# create nodes # create nodes
options = NodeOptions(model="mdr") options = NodeOptions(model="mdr")
for i in range(NODES): 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) node.setposition(x=150 * (i + 1), y=150)
interface = prefixes.create_interface(node) interface = prefixes.create_interface(node)
session.add_link(node.id, emane_network.id, interface_one=interface) session.add_link(node.id, emane_network.id, interface_one=interface)
@ -51,8 +53,8 @@ def main():
time.sleep(EMANE_DELAY) time.sleep(EMANE_DELAY)
# get nodes to run example # get nodes to run example
first_node = session.get_node(1) first_node = session.get_node(1, CoreNode)
last_node = session.get_node(NODES) last_node = session.get_node(NODES, CoreNode)
address = prefixes.ip4_address(first_node) address = prefixes.ip4_address(first_node)
logging.info("node %s pinging %s", last_node.name, address) logging.info("node %s pinging %s", last_node.name, address)
output = last_node.cmd(f"ping -c 3 {address}") output = last_node.cmd(f"ping -c 3 {address}")

View file

@ -7,7 +7,9 @@ import logging
from core.emulator.coreemu import CoreEmu from core.emulator.coreemu import CoreEmu
from core.emulator.emudata import IpPrefixes 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 NODES = 2
@ -24,11 +26,11 @@ def main():
session.set_state(EventTypes.CONFIGURATION_STATE) session.set_state(EventTypes.CONFIGURATION_STATE)
# create switch network node # create switch network node
switch = session.add_node(_type=NodeTypes.SWITCH, _id=100) switch = session.add_node(SwitchNode, _id=100)
# create nodes # create nodes
for _ in range(NODES): for _ in range(NODES):
node = session.add_node() node = session.add_node(CoreNode)
interface = prefixes.create_interface(node) interface = prefixes.create_interface(node)
session.add_link(node.id, switch.id, interface_one=interface) session.add_link(node.id, switch.id, interface_one=interface)
@ -36,8 +38,8 @@ def main():
session.instantiate() session.instantiate()
# get nodes to run example # get nodes to run example
first_node = session.get_node(1) first_node = session.get_node(1, CoreNode)
last_node = session.get_node(NODES) last_node = session.get_node(NODES, CoreNode)
address = prefixes.ip4_address(first_node) address = prefixes.ip4_address(first_node)
logging.info("node %s pinging %s", last_node.name, address) logging.info("node %s pinging %s", last_node.name, address)
output = last_node.cmd(f"ping -c 3 {address}") output = last_node.cmd(f"ping -c 3 {address}")

View file

@ -7,8 +7,11 @@ same CoreEmu instance the GUI is using.
import logging import logging
from core.emulator.coreemu import CoreEmu
from core.emulator.emudata import IpPrefixes 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 NODES = 2
@ -18,18 +21,18 @@ def main():
prefixes = IpPrefixes("10.83.0.0/16") prefixes = IpPrefixes("10.83.0.0/16")
# create emulator instance for creating sessions and utility methods # create emulator instance for creating sessions and utility methods
coreemu = globals()["coreemu"] coreemu: CoreEmu = globals()["coreemu"]
session = coreemu.create_session() session = coreemu.create_session()
# must be in configuration state for nodes to start, when using "node_add" below # must be in configuration state for nodes to start, when using "node_add" below
session.set_state(EventTypes.CONFIGURATION_STATE) session.set_state(EventTypes.CONFIGURATION_STATE)
# create switch network node # create switch network node
switch = session.add_node(_type=NodeTypes.SWITCH) switch = session.add_node(SwitchNode)
# create nodes # create nodes
for _ in range(NODES): for _ in range(NODES):
node = session.add_node() node = session.add_node(CoreNode)
interface = prefixes.create_interface(node) interface = prefixes.create_interface(node)
session.add_link(node.id, switch.id, interface_one=interface) session.add_link(node.id, switch.id, interface_one=interface)

View file

@ -7,8 +7,10 @@ import logging
from core.emulator.coreemu import CoreEmu from core.emulator.coreemu import CoreEmu
from core.emulator.emudata import IpPrefixes, NodeOptions 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.location.mobility import BasicRangeModel
from core.nodes.base import CoreNode
from core.nodes.network import WlanNode
NODES = 2 NODES = 2
@ -25,14 +27,14 @@ def main():
session.set_state(EventTypes.CONFIGURATION_STATE) session.set_state(EventTypes.CONFIGURATION_STATE)
# create wlan network node # 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) session.mobility.set_model(wlan, BasicRangeModel)
# create nodes, must set a position for wlan basic range model # create nodes, must set a position for wlan basic range model
options = NodeOptions(model="mdr") options = NodeOptions(model="mdr")
options.set_position(0, 0) options.set_position(0, 0)
for _ in range(NODES): for _ in range(NODES):
node = session.add_node(options=options) node = session.add_node(CoreNode, options=options)
interface = prefixes.create_interface(node) interface = prefixes.create_interface(node)
session.add_link(node.id, wlan.id, interface_one=interface) session.add_link(node.id, wlan.id, interface_one=interface)
@ -40,8 +42,8 @@ def main():
session.instantiate() session.instantiate()
# get nodes for example run # get nodes for example run
first_node = session.get_node(1) first_node = session.get_node(1, CoreNode)
last_node = session.get_node(NODES) last_node = session.get_node(NODES, CoreNode)
address = prefixes.ip4_address(first_node) address = prefixes.ip4_address(first_node)
logging.info("node %s pinging %s", last_node.name, address) logging.info("node %s pinging %s", last_node.name, address)
output = last_node.cmd(f"ping -c 3 {address}") output = last_node.cmd(f"ping -c 3 {address}")

View file

@ -61,6 +61,8 @@ service CoreApi {
} }
rpc GetNodeTerminal (GetNodeTerminalRequest) returns (GetNodeTerminalResponse) { rpc GetNodeTerminal (GetNodeTerminalRequest) returns (GetNodeTerminalResponse) {
} }
rpc MoveNodes (stream MoveNodesRequest) returns (MoveNodesResponse) {
}
// link rpc // link rpc
rpc GetNodeLinks (GetNodeLinksRequest) returns (GetNodeLinksResponse) { rpc GetNodeLinks (GetNodeLinksRequest) returns (GetNodeLinksResponse) {
@ -129,6 +131,8 @@ service CoreApi {
} }
rpc SetWlanConfig (wlan.SetWlanConfigRequest) returns (wlan.SetWlanConfigResponse) { rpc SetWlanConfig (wlan.SetWlanConfigRequest) returns (wlan.SetWlanConfigResponse) {
} }
rpc WlanLink (wlan.WlanLinkRequest) returns (wlan.WlanLinkResponse) {
}
// emane rpc // emane rpc
rpc GetEmaneConfig (emane.GetEmaneConfigRequest) returns (emane.GetEmaneConfigResponse) { rpc GetEmaneConfig (emane.GetEmaneConfigRequest) returns (emane.GetEmaneConfigResponse) {
@ -145,6 +149,8 @@ service CoreApi {
} }
rpc GetEmaneEventChannel (emane.GetEmaneEventChannelRequest) returns (emane.GetEmaneEventChannelResponse) { rpc GetEmaneEventChannel (emane.GetEmaneEventChannelRequest) returns (emane.GetEmaneEventChannelResponse) {
} }
rpc EmanePathlosses (stream emane.EmanePathlossesRequest) returns (emane.EmanePathlossesResponse) {
}
// xml rpc // xml rpc
rpc SaveXml (SaveXmlRequest) returns (SaveXmlResponse) { rpc SaveXml (SaveXmlRequest) returns (SaveXmlResponse) {
@ -444,14 +450,30 @@ message GetNodeTerminalResponse {
string terminal = 1; 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 { message NodeCommandRequest {
int32 session_id = 1; int32 session_id = 1;
int32 node_id = 2; int32 node_id = 2;
string command = 3; string command = 3;
bool wait = 4;
bool shell = 5;
} }
message NodeCommandResponse { message NodeCommandResponse {
string output = 1; string output = 1;
int32 return_code = 2;
} }
message GetNodeLinksRequest { message GetNodeLinksRequest {

View file

@ -90,3 +90,16 @@ message EmaneModelConfig {
string model = 3; string model = 3;
map<string, string> config = 4; 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 {
}

View file

@ -34,3 +34,15 @@ message SetWlanConfigRequest {
message SetWlanConfigResponse { message SetWlanConfigResponse {
bool result = 1; 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;
}

View file

@ -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 core-daemon: the CORE daemon is a server process that receives CORE API
messages and instantiates emulated nodes and networks within the kernel. Various 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 # these are the defaults used in the config file
default_log = os.path.join(constants.CORE_CONF_DIR, "logging.conf") default_log = os.path.join(constants.CORE_CONF_DIR, "logging.conf")
default_grpc_port = "50051" default_grpc_port = "50051"
default_threads = "1"
default_address = "localhost" default_address = "localhost"
defaults = { defaults = {
"port": str(CORE_API_PORT), "port": str(CORE_API_PORT),
"listenaddr": default_address, "listenaddr": default_address,
"numthreads": default_threads,
"grpcport": default_grpc_port, "grpcport": default_grpc_port,
"grpcaddress": default_address, "grpcaddress": default_address,
"logfile": default_log "logfile": default_log
@ -110,8 +108,6 @@ def get_merged_config(filename):
help=f"read config from specified file; default = {filename}") help=f"read config from specified file; default = {filename}")
parser.add_argument("-p", "--port", dest="port", type=int, parser.add_argument("-p", "--port", dest="port", type=int,
help=f"port number to listen on; default = {CORE_API_PORT}") 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("--ovs", action="store_true", help="enable experimental ovs mode, default is false")
parser.add_argument("--grpc-port", dest="grpcport", parser.add_argument("--grpc-port", dest="grpcport",
help=f"grpc port to listen on; default {default_grpc_port}") help=f"grpc port to listen on; default {default_grpc_port}")
@ -148,14 +144,9 @@ def main():
:return: nothing :return: nothing
""" """
# get a configuration merged from config file and command-line arguments
cfg = get_merged_config(f"{CORE_CONF_DIR}/core.conf") cfg = get_merged_config(f"{CORE_CONF_DIR}/core.conf")
# load logging configuration
load_logging_config(cfg["logfile"]) load_logging_config(cfg["logfile"])
banner() banner()
try: try:
cored(cfg) cored(cfg)
except KeyboardInterrupt: except KeyboardInterrupt:

View file

@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
import argparse import argparse
import re import re
import sys import sys

View file

@ -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 core-manage: Helper tool to add, remove, or check for services, models, and
node types in a CORE installation. node types in a CORE installation.

View file

@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
import argparse import argparse
import logging import logging
from logging.handlers import TimedRotatingFileHandler from logging.handlers import TimedRotatingFileHandler

View file

@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
import argparse import argparse
import enum import enum
import select import select

View file

@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
import argparse import argparse
import re import re
from io import TextIOWrapper from io import TextIOWrapper

View file

@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
""" """
coresendmsg: utility for generating CORE messages coresendmsg: utility for generating CORE messages
""" """
@ -19,7 +19,7 @@ def print_available_tlvs(t, tlv_class):
""" """
print(f"TLVs available for {t} message:") 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): 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): def print_examples(name):
@ -27,27 +27,26 @@ def print_examples(name):
Print example usage of this script. Print example usage of this script.
""" """
examples = [ examples = [
("link n1number=2 n2number=3 delay=15000", ("node number=3 x_position=125 y_position=525",
"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",
"move node number 3 to x,y=(125,525)"), "move node number 3 to x,y=(125,525)"),
("node number=4 icon=/usr/local/share/core/icons/normal/router_red.gif", ("node number=4 icon=/usr/local/share/core/icons/normal/router_red.gif",
"change node number 4\"s icon to red"), "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"), "add a new router node n5"),
("link flags=add n1number=4 n2number=5 if1ip4=\"10.0.3.2\" " \ ("link n1_number=2 n2_number=3 delay=15000",
"if1ip4mask=24 if2ip4=\"10.0.3.1\" if2ip4mask=24", "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"), "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"), "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"), "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"), "write a test.log file on node 1 with the given contents"),
("file flags=add node=2 name=\"test.log\" " \ ("file flags=add node=2 name=\"test.log\" source_name=\"./test.log\"",
"srcname=\"./test.log\"",
"move a test.log file from host to node 2"), "move a test.log file from host to node 2"),
] ]
print(f"Example {name} invocations:") print(f"Example {name} invocations:")
@ -152,12 +151,16 @@ def main():
""" """
Parse command-line arguments to build and send a CORE message. Parse command-line arguments to build and send a CORE message.
""" """
types = [message_type.name for message_type in MessageTypes] types = [message_type.name.lower() for message_type in MessageTypes]
flags = [flag.name for flag in MessageFlags] flags = [flag.name.lower() for flag in MessageFlags]
usagestr = "usage: %prog [-h|-H] [options] [message-type] [flags=flags] " types_usage = " ".join(types)
usagestr += "[message-TLVs]\n\n" flags_usage = " ".join(flags)
usagestr += f"Supported message types:\n {types}\n" usagestr = (
usagestr += f"Supported message flags (flags=f1,f2,...):\n {flags}" "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) parser = optparse.OptionParser(usage=usagestr)
default_address = "localhost" default_address = "localhost"
default_session = None default_session = None
@ -171,7 +174,6 @@ def main():
tlvs=False, tlvs=False,
tcp=default_tcp tcp=default_tcp
) )
parser.add_option("-H", dest="examples", action="store_true", parser.add_option("-H", dest="examples", action="store_true",
help="show example usage help message and exit") help="show example usage help message and exit")
parser.add_option("-p", "--port", dest="port", type=int, 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}") help=f"Use TCP instead of UDP and connect to a session default: {default_tcp}")
def usage(msg=None, err=0): def usage(msg=None, err=0):
sys.stdout.write("\n") print()
if msg: if msg:
sys.stdout.write(msg + "\n\n") print(f"{msg}\n")
parser.print_help() parser.print_help()
sys.exit(err) sys.exit(err)
@ -204,9 +206,10 @@ def main():
# given a message type t, determine the message and TLV classes # given a message type t, determine the message and TLV classes
t = args.pop(0) t = args.pop(0)
t = t.lower()
if t not in types: if t not in types:
usage(f"Unknown message type requested: {t}") usage(f"Unknown message type requested: {t}")
message_type = MessageTypes[t] message_type = MessageTypes[t.upper()]
msg_cls = coreapi.CLASS_MAP[message_type.value] msg_cls = coreapi.CLASS_MAP[message_type.value]
tlv_cls = msg_cls.tlv_class tlv_cls = msg_cls.tlv_class
@ -222,26 +225,23 @@ def main():
typevalue = a.split("=") typevalue = a.split("=")
if len(typevalue) < 2: if len(typevalue) < 2:
usage(f"Use \"type=value\" syntax instead of \"{a}\".") usage(f"Use \"type=value\" syntax instead of \"{a}\".")
tlv_typestr = typevalue[0] tlv_typestr = typevalue[0].lower()
tlv_valstr = "=".join(typevalue[1:]) tlv_valstr = "=".join(typevalue[1:])
if tlv_typestr == "flags": if tlv_typestr == "flags":
flagstr = tlv_valstr flagstr = tlv_valstr
continue continue
tlv_name = tlv_typestr
try: 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) tlvdata += tlv_cls.pack_string(tlv_type.value, tlv_valstr)
except KeyError: except KeyError:
usage(f"Unknown TLV: \"{tlv_name}\"") usage(f"Unknown TLV: \"{tlv_typestr}\"")
flags = 0 flags = 0
for f in flagstr.split(","): for f in flagstr.split(","):
if f == "": if f == "":
continue continue
try: try:
flag_enum = MessageFlags[f] flag_enum = MessageFlags[f.upper()]
n = flag_enum.value n = flag_enum.value
flags |= n flags |= n
except KeyError: except KeyError:

View file

@ -44,8 +44,8 @@ class PatchManager:
class MockServer: class MockServer:
def __init__(self, config, coreemu): def __init__(self, coreemu):
self.config = config self.config = {}
self.coreemu = coreemu self.coreemu = coreemu
@ -108,7 +108,7 @@ def module_grpc(global_coreemu):
def module_coretlv(patcher, global_coreemu, global_session): def module_coretlv(patcher, global_coreemu, global_session):
request_mock = MagicMock() request_mock = MagicMock()
request_mock.fileno = MagicMock(return_value=1) 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 = CoreHandler(request_mock, "", server)
request_handler.session = global_session request_handler.session = global_session
request_handler.add_session_handlers() request_handler.add_session_handlers()

View file

@ -2,18 +2,22 @@
Unit tests for testing CORE EMANE networks. Unit tests for testing CORE EMANE networks.
""" """
import os import os
from tempfile import TemporaryFile
from xml.etree import ElementTree from xml.etree import ElementTree
import pytest import pytest
from core.emane.bypass import EmaneBypassModel from core.emane.bypass import EmaneBypassModel
from core.emane.commeffect import EmaneCommEffectModel from core.emane.commeffect import EmaneCommEffectModel
from core.emane.emanemodel import EmaneModel
from core.emane.ieee80211abg import EmaneIeee80211abgModel from core.emane.ieee80211abg import EmaneIeee80211abgModel
from core.emane.nodes import EmaneNet
from core.emane.rfpipe import EmaneRfPipeModel from core.emane.rfpipe import EmaneRfPipeModel
from core.emane.tdma import EmaneTdmaModel from core.emane.tdma import EmaneTdmaModel
from core.emulator.emudata import NodeOptions from core.emulator.emudata import IpPrefixes, NodeOptions
from core.emulator.enumerations import NodeTypes from core.emulator.session import Session
from core.errors import CoreCommandError, CoreError from core.errors import CoreCommandError, CoreError
from core.nodes.base import CoreNode
_EMANE_MODELS = [ _EMANE_MODELS = [
EmaneIeee80211abgModel, EmaneIeee80211abgModel,
@ -25,8 +29,10 @@ _EMANE_MODELS = [
_DIR = os.path.dirname(os.path.abspath(__file__)) _DIR = os.path.dirname(os.path.abspath(__file__))
def ping(from_node, to_node, ip_prefixes, count=3): def ping(
address = ip_prefixes.ip4_address(to_node) from_node: CoreNode, to_node: CoreNode, ip_prefixes: IpPrefixes, count: int = 3
):
address = ip_prefixes.ip4_address(to_node.id)
try: try:
from_node.cmd(f"ping -c {count} {address}") from_node.cmd(f"ping -c {count} {address}")
status = 0 status = 0
@ -37,7 +43,7 @@ def ping(from_node, to_node, ip_prefixes, count=3):
class TestEmane: class TestEmane:
@pytest.mark.parametrize("model", _EMANE_MODELS) @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. Test emane models within a basic network.
@ -50,7 +56,7 @@ class TestEmane:
session.set_location(47.57917, -122.13232, 2.00000, 1.0) session.set_location(47.57917, -122.13232, 2.00000, 1.0)
options = NodeOptions() options = NodeOptions()
options.set_position(80, 50) 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) session.emane.set_model(emane_network, model)
# configure tdma # configure tdma
@ -64,9 +70,9 @@ class TestEmane:
# create nodes # create nodes
options = NodeOptions(model="mdr") options = NodeOptions(model="mdr")
options.set_position(150, 150) 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) 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]): for i, node in enumerate([node_one, node_two]):
node.setposition(x=150 * (i + 1), y=150) node.setposition(x=150 * (i + 1), y=150)
@ -80,7 +86,9 @@ class TestEmane:
status = ping(node_one, node_two, ip_prefixes, count=5) status = ping(node_one, node_two, ip_prefixes, count=5)
assert not status 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. Test xml client methods for emane.
@ -92,7 +100,7 @@ class TestEmane:
session.set_location(47.57917, -122.13232, 2.00000, 1.0) session.set_location(47.57917, -122.13232, 2.00000, 1.0)
options = NodeOptions() options = NodeOptions()
options.set_position(80, 50) 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_key = "txpower"
config_value = "10" config_value = "10"
session.emane.set_model( session.emane.set_model(
@ -102,9 +110,9 @@ class TestEmane:
# create nodes # create nodes
options = NodeOptions(model="mdr") options = NodeOptions(model="mdr")
options.set_position(150, 150) 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) 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]): for i, node in enumerate([node_one, node_two]):
node.setposition(x=150 * (i + 1), y=150) node.setposition(x=150 * (i + 1), y=150)
@ -133,9 +141,9 @@ class TestEmane:
# verify nodes have been removed from session # verify nodes have been removed from session
with pytest.raises(CoreError): with pytest.raises(CoreError):
assert not session.get_node(n1_id) assert not session.get_node(n1_id, CoreNode)
with pytest.raises(CoreError): with pytest.raises(CoreError):
assert not session.get_node(n2_id) assert not session.get_node(n2_id, CoreNode)
# load saved xml # load saved xml
session.open_xml(file_path, start=True) session.open_xml(file_path, start=True)
@ -146,7 +154,7 @@ class TestEmane:
) )
# verify nodes and configuration were restored # verify nodes and configuration were restored
assert session.get_node(n1_id) assert session.get_node(n1_id, CoreNode)
assert session.get_node(n2_id) assert session.get_node(n2_id, CoreNode)
assert session.get_node(emane_id) assert session.get_node(emane_id, EmaneNet)
assert value == config_value assert value == config_value

View file

@ -7,8 +7,10 @@ from core.config import (
ModelManager, ModelManager,
) )
from core.emane.ieee80211abg import EmaneIeee80211abgModel 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.location.mobility import BasicRangeModel
from core.nodes.network import WlanNode
class TestConfigurableOptions(ConfigurableOptions): class TestConfigurableOptions(ConfigurableOptions):
@ -40,7 +42,7 @@ class TestConf:
def test_nodes(self): def test_nodes(self):
# given # given
config_manager = ConfigurableManager() config_manager = ConfigurableManager()
test_config = {1: 2} test_config = {"1": "2"}
node_id = 1 node_id = 1
config_manager.set_configs(test_config) config_manager.set_configs(test_config)
config_manager.set_configs(test_config, node_id=node_id) config_manager.set_configs(test_config, node_id=node_id)
@ -55,7 +57,7 @@ class TestConf:
def test_config_reset_all(self): def test_config_reset_all(self):
# given # given
config_manager = ConfigurableManager() config_manager = ConfigurableManager()
test_config = {1: 2} test_config = {"1": "2"}
node_id = 1 node_id = 1
config_manager.set_configs(test_config) config_manager.set_configs(test_config)
config_manager.set_configs(test_config, node_id=node_id) config_manager.set_configs(test_config, node_id=node_id)
@ -69,7 +71,7 @@ class TestConf:
def test_config_reset_node(self): def test_config_reset_node(self):
# given # given
config_manager = ConfigurableManager() config_manager = ConfigurableManager()
test_config = {1: 2} test_config = {"1": "2"}
node_id = 1 node_id = 1
config_manager.set_configs(test_config) config_manager.set_configs(test_config)
config_manager.set_configs(test_config, node_id=node_id) config_manager.set_configs(test_config, node_id=node_id)
@ -84,7 +86,7 @@ class TestConf:
def test_configs_setget(self): def test_configs_setget(self):
# given # given
config_manager = ConfigurableManager() config_manager = ConfigurableManager()
test_config = {1: 2} test_config = {"1": "2"}
node_id = 1 node_id = 1
config_manager.set_configs(test_config) config_manager.set_configs(test_config)
config_manager.set_configs(test_config, node_id=node_id) config_manager.set_configs(test_config, node_id=node_id)
@ -145,9 +147,9 @@ class TestConf:
with pytest.raises(ValueError): with pytest.raises(ValueError):
manager.get_model_config(1, bad_name) manager.get_model_config(1, bad_name)
def test_model_set(self, session): def test_model_set(self, session: Session):
# given # given
wlan_node = session.add_node(_type=NodeTypes.WIRELESS_LAN) wlan_node = session.add_node(WlanNode)
# when # when
session.mobility.set_model(wlan_node, BasicRangeModel) session.mobility.set_model(wlan_node, BasicRangeModel)
@ -155,17 +157,17 @@ class TestConf:
# then # then
assert session.mobility.get_model_config(wlan_node.id, BasicRangeModel.name) 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 # given
wlan_node = session.add_node(_type=NodeTypes.WIRELESS_LAN) wlan_node = session.add_node(WlanNode)
# when / then # when / then
with pytest.raises(ValueError): with pytest.raises(ValueError):
session.mobility.set_model(wlan_node, EmaneIeee80211abgModel) session.mobility.set_model(wlan_node, EmaneIeee80211abgModel)
def test_get_models(self, session): def test_get_models(self, session: Session):
# given # given
wlan_node = session.add_node(_type=NodeTypes.WIRELESS_LAN) wlan_node = session.add_node(WlanNode)
session.mobility.set_model(wlan_node, BasicRangeModel) session.mobility.set_model(wlan_node, BasicRangeModel)
# when # when

View file

@ -4,21 +4,25 @@ Unit tests for testing basic CORE networks.
import os import os
import threading import threading
from typing import Type
import pytest import pytest
from core.emulator.emudata import NodeOptions from core.emulator.emudata import IpPrefixes, NodeOptions
from core.emulator.enumerations import MessageFlags, NodeTypes from core.emulator.enumerations import MessageFlags
from core.emulator.session import Session
from core.errors import CoreCommandError from core.errors import CoreCommandError
from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility 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__)) _PATH = os.path.abspath(os.path.dirname(__file__))
_MOBILITY_FILE = os.path.join(_PATH, "mobility.scen") _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): def ping(from_node: CoreNode, to_node: CoreNode, ip_prefixes: IpPrefixes):
address = ip_prefixes.ip4_address(to_node) address = ip_prefixes.ip4_address(to_node.id)
try: try:
from_node.cmd(f"ping -c 1 {address}") from_node.cmd(f"ping -c 1 {address}")
status = 0 status = 0
@ -29,7 +33,9 @@ def ping(from_node, to_node, ip_prefixes):
class TestCore: class TestCore:
@pytest.mark.parametrize("net_type", _WIRED) @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. Test ptp node network.
@ -39,11 +45,11 @@ class TestCore:
""" """
# create net node # create net node
net_node = session.add_node(_type=net_type) net_node = session.add_node(net_type)
# create nodes # create nodes
node_one = session.add_node() node_one = session.add_node(CoreNode)
node_two = session.add_node() node_two = session.add_node(CoreNode)
# link nodes to net node # link nodes to net node
for node in [node_one, node_two]: for node in [node_one, node_two]:
@ -57,7 +63,7 @@ class TestCore:
status = ping(node_one, node_two, ip_prefixes) status = ping(node_one, node_two, ip_prefixes)
assert not status 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. Test vnode client methods.
@ -66,11 +72,11 @@ class TestCore:
:param ip_prefixes: generates ip addresses for nodes :param ip_prefixes: generates ip addresses for nodes
""" """
# create ptp # create ptp
ptp_node = session.add_node(_type=NodeTypes.PEER_TO_PEER) ptp_node = session.add_node(PtpNet)
# create nodes # create nodes
node_one = session.add_node() node_one = session.add_node(CoreNode)
node_two = session.add_node() node_two = session.add_node(CoreNode)
# link nodes to ptp net # link nodes to ptp net
for node in [node_one, node_two]: for node in [node_one, node_two]:
@ -90,7 +96,7 @@ class TestCore:
if not request.config.getoption("mock"): if not request.config.getoption("mock"):
assert client.check_cmd("echo hello") == "hello" 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. Test netif methods.
@ -99,11 +105,11 @@ class TestCore:
""" """
# create ptp # create ptp
ptp_node = session.add_node(_type=NodeTypes.PEER_TO_PEER) ptp_node = session.add_node(PtpNet)
# create nodes # create nodes
node_one = session.add_node() node_one = session.add_node(CoreNode)
node_two = session.add_node() node_two = session.add_node(CoreNode)
# link nodes to ptp net # link nodes to ptp net
for node in [node_one, node_two]: for node in [node_one, node_two]:
@ -121,8 +127,8 @@ class TestCore:
assert node_two.commonnets(node_one) assert node_two.commonnets(node_one)
# check we can retrieve netif index # check we can retrieve netif index
assert node_one.getifindex(0) assert node_one.ifname(0)
assert node_two.getifindex(0) assert node_two.ifname(0)
# check interface parameters # check interface parameters
interface = node_one.netif(0) interface = node_one.netif(0)
@ -134,7 +140,7 @@ class TestCore:
node_one.delnetif(0) node_one.delnetif(0)
assert not node_one.netif(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. Test basic wlan network.
@ -143,14 +149,14 @@ class TestCore:
""" """
# create wlan # create wlan
wlan_node = session.add_node(_type=NodeTypes.WIRELESS_LAN) wlan_node = session.add_node(WlanNode)
session.mobility.set_model(wlan_node, BasicRangeModel) session.mobility.set_model(wlan_node, BasicRangeModel)
# create nodes # create nodes
options = NodeOptions(model="mdr") options = NodeOptions(model="mdr")
options.set_position(0, 0) options.set_position(0, 0)
node_one = session.add_node(options=options) node_one = session.add_node(CoreNode, options=options)
node_two = session.add_node(options=options) node_two = session.add_node(CoreNode, options=options)
# link nodes # link nodes
for node in [node_one, node_two]: for node in [node_one, node_two]:
@ -164,7 +170,7 @@ class TestCore:
status = ping(node_one, node_two, ip_prefixes) status = ping(node_one, node_two, ip_prefixes)
assert not status assert not status
def test_mobility(self, session, ip_prefixes): def test_mobility(self, session: Session, ip_prefixes: IpPrefixes):
""" """
Test basic wlan network. Test basic wlan network.
@ -173,14 +179,14 @@ class TestCore:
""" """
# create wlan # create wlan
wlan_node = session.add_node(_type=NodeTypes.WIRELESS_LAN) wlan_node = session.add_node(WlanNode)
session.mobility.set_model(wlan_node, BasicRangeModel) session.mobility.set_model(wlan_node, BasicRangeModel)
# create nodes # create nodes
options = NodeOptions(model="mdr") options = NodeOptions(model="mdr")
options.set_position(0, 0) options.set_position(0, 0)
node_one = session.add_node(options=options) node_one = session.add_node(CoreNode, options=options)
node_two = session.add_node(options=options) node_two = session.add_node(CoreNode, options=options)
# link nodes # link nodes
for node in [node_one, node_two]: for node in [node_one, node_two]:

View file

@ -1,18 +1,19 @@
from core.emulator.emudata import NodeOptions 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: class TestDistributed:
def test_remote_node(self, session): def test_remote_node(self, session: Session):
# given # given
server_name = "core2" server_name = "core2"
host = "127.0.0.1" host = "127.0.0.1"
# when # when
session.distributed.add_server(server_name, host) session.distributed.add_server(server_name, host)
options = NodeOptions() options = NodeOptions(server=server_name)
options.server = server_name node = session.add_node(CoreNode, options=options)
node = session.add_node(options=options)
session.instantiate() session.instantiate()
# then # then
@ -20,7 +21,7 @@ class TestDistributed:
assert node.server.name == server_name assert node.server.name == server_name
assert node.server.host == host assert node.server.host == host
def test_remote_bridge(self, session): def test_remote_bridge(self, session: Session):
# given # given
server_name = "core2" server_name = "core2"
host = "127.0.0.1" host = "127.0.0.1"
@ -28,9 +29,8 @@ class TestDistributed:
# when # when
session.distributed.add_server(server_name, host) session.distributed.add_server(server_name, host)
options = NodeOptions() options = NodeOptions(server=server_name)
options.server = server_name node = session.add_node(HubNode, options=options)
node = session.add_node(_type=NodeTypes.HUB, options=options)
session.instantiate() session.instantiate()
# then # then

View file

@ -1,5 +1,7 @@
import time import time
from queue import Queue from queue import Queue
from tempfile import TemporaryFile
from typing import Optional
import grpc import grpc
import pytest 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.client import CoreGrpcClient, InterfaceHelper
from core.api.grpc.emane_pb2 import EmaneModelConfig from core.api.grpc.emane_pb2 import EmaneModelConfig
from core.api.grpc.mobility_pb2 import MobilityAction, MobilityConfig 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.services_pb2 import ServiceAction, ServiceConfig, ServiceFileConfig
from core.api.grpc.wlan_pb2 import WlanConfig from core.api.grpc.wlan_pb2 import WlanConfig
from core.api.tlv.dataconversion import ConfigShim from core.api.tlv.dataconversion import ConfigShim
from core.api.tlv.enumerations import ConfigFlags from core.api.tlv.enumerations import ConfigFlags
from core.emane.ieee80211abg import EmaneIeee80211abgModel from core.emane.ieee80211abg import EmaneIeee80211abgModel
from core.emulator.data import EventData from core.emane.nodes import EmaneNet
from core.emulator.emudata import NodeOptions from core.emulator.data import EventData, NodeData
from core.emulator.emudata import IpPrefixes, NodeOptions
from core.emulator.enumerations import EventTypes, ExceptionLevels, NodeTypes from core.emulator.enumerations import EventTypes, ExceptionLevels, NodeTypes
from core.errors import CoreError from core.errors import CoreError
from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility
from core.nodes.base import CoreNode
from core.nodes.network import SwitchNode, WlanNode
from core.xml.corexml import CoreXmlWriter from core.xml.corexml import CoreXmlWriter
class TestGrpc: class TestGrpc:
def test_start_session(self, grpc_server): def test_start_session(self, grpc_server: CoreGrpcServer):
# given # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
@ -156,7 +162,9 @@ class TestGrpc:
assert service_file.data == service_file_config.data assert service_file.data == service_file_config.data
@pytest.mark.parametrize("session_id", [None, 6013]) @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 # given
client = CoreGrpcClient() client = CoreGrpcClient()
@ -175,7 +183,9 @@ class TestGrpc:
assert session.id == session_id assert session.id == session_id
@pytest.mark.parametrize("session_id, expected", [(None, True), (6013, False)]) @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 # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
@ -190,11 +200,11 @@ class TestGrpc:
assert response.result is expected assert response.result is expected
assert grpc_server.coreemu.sessions.get(session_id) is None 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 # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
session.add_node() session.add_node(CoreNode)
session.set_state(EventTypes.DEFINITION_STATE) session.set_state(EventTypes.DEFINITION_STATE)
# then # then
@ -206,7 +216,7 @@ class TestGrpc:
assert len(response.session.nodes) == 1 assert len(response.session.nodes) == 1
assert len(response.session.links) == 0 assert len(response.session.links) == 0
def test_get_sessions(self, grpc_server): def test_get_sessions(self, grpc_server: CoreGrpcServer):
# given # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
@ -224,7 +234,7 @@ class TestGrpc:
assert len(response.sessions) == 1 assert len(response.sessions) == 1
assert found_session is not None assert found_session is not None
def test_get_session_options(self, grpc_server): def test_get_session_options(self, grpc_server: CoreGrpcServer):
# given # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
@ -236,7 +246,7 @@ class TestGrpc:
# then # then
assert len(response.config) > 0 assert len(response.config) > 0
def test_get_session_location(self, grpc_server): def test_get_session_location(self, grpc_server: CoreGrpcServer):
# given # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
@ -254,7 +264,7 @@ class TestGrpc:
assert response.location.lon == 0 assert response.location.lon == 0
assert response.location.alt == 0 assert response.location.alt == 0
def test_set_session_location(self, grpc_server): def test_set_session_location(self, grpc_server: CoreGrpcServer):
# given # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
@ -281,7 +291,7 @@ class TestGrpc:
assert session.location.refscale == scale assert session.location.refscale == scale
assert session.location.refgeo == lat_lon_alt 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 # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
@ -298,7 +308,7 @@ class TestGrpc:
config = session.options.get_configs() config = session.options.get_configs()
assert len(config) > 0 assert len(config) > 0
def test_set_session_metadata(self, grpc_server): def test_set_session_metadata(self, grpc_server: CoreGrpcServer):
# given # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
@ -313,7 +323,7 @@ class TestGrpc:
assert response.result is True assert response.result is True
assert session.metadata[key] == value assert session.metadata[key] == value
def test_get_session_metadata(self, grpc_server): def test_get_session_metadata(self, grpc_server: CoreGrpcServer):
# given # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
@ -328,7 +338,7 @@ class TestGrpc:
# then # then
assert response.config[key] == value assert response.config[key] == value
def test_set_session_state(self, grpc_server): def test_set_session_state(self, grpc_server: CoreGrpcServer):
# given # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
@ -343,7 +353,7 @@ class TestGrpc:
assert response.result is True assert response.result is True
assert session.state == EventTypes.DEFINITION_STATE assert session.state == EventTypes.DEFINITION_STATE
def test_add_node(self, grpc_server): def test_add_node(self, grpc_server: CoreGrpcServer):
# given # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
@ -355,13 +365,13 @@ class TestGrpc:
# then # then
assert response.node_id is not None 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 # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
node = session.add_node() node = session.add_node(CoreNode)
# then # then
with client.context_connect(): with client.context_connect():
@ -370,11 +380,11 @@ class TestGrpc:
# then # then
assert response.node.id == node.id assert response.node.id == node.id
def test_edit_node(self, grpc_server): def test_edit_node(self, grpc_server: CoreGrpcServer):
# given # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
node = session.add_node() node = session.add_node(CoreNode)
# then # then
x, y = 10, 10 x, y = 10, 10
@ -388,11 +398,13 @@ class TestGrpc:
assert node.position.y == y assert node.position.y == y
@pytest.mark.parametrize("node_id, expected", [(1, True), (2, False)]) @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 # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
node = session.add_node() node = session.add_node(CoreNode)
# then # then
with client.context_connect(): with client.context_connect():
@ -402,9 +414,9 @@ class TestGrpc:
assert response.result is expected assert response.result is expected
if expected is True: if expected is True:
with pytest.raises(CoreError): 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"): if request.config.getoption("mock"):
pytest.skip("mocking calls") pytest.skip("mocking calls")
@ -413,7 +425,7 @@ class TestGrpc:
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
session.set_state(EventTypes.CONFIGURATION_STATE) session.set_state(EventTypes.CONFIGURATION_STATE)
options = NodeOptions(model="Host") options = NodeOptions(model="Host")
node = session.add_node(options=options) node = session.add_node(CoreNode, options=options)
session.instantiate() session.instantiate()
output = "hello world" output = "hello world"
@ -425,13 +437,13 @@ class TestGrpc:
# then # then
assert response.output == output assert response.output == output
def test_get_node_terminal(self, grpc_server): def test_get_node_terminal(self, grpc_server: CoreGrpcServer):
# given # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
session.set_state(EventTypes.CONFIGURATION_STATE) session.set_state(EventTypes.CONFIGURATION_STATE)
options = NodeOptions(model="Host") options = NodeOptions(model="Host")
node = session.add_node(options=options) node = session.add_node(CoreNode, options=options)
session.instantiate() session.instantiate()
# then # then
@ -441,13 +453,13 @@ class TestGrpc:
# then # then
assert response.terminal is not None assert response.terminal is not None
def test_get_hooks(self, grpc_server): def test_get_hooks(self, grpc_server: CoreGrpcServer):
# given # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
file_name = "test" file_name = "test"
file_data = "echo hello" 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 # then
with client.context_connect(): with client.context_connect():
@ -460,7 +472,7 @@ class TestGrpc:
assert hook.file == file_name assert hook.file == file_name
assert hook.data == file_data assert hook.data == file_data
def test_add_hook(self, grpc_server): def test_add_hook(self, grpc_server: CoreGrpcServer):
# given # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
@ -476,7 +488,7 @@ class TestGrpc:
# then # then
assert response.result is True assert response.result is True
def test_save_xml(self, grpc_server, tmpdir): def test_save_xml(self, grpc_server: CoreGrpcServer, tmpdir: TemporaryFile):
# given # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
@ -489,7 +501,7 @@ class TestGrpc:
# then # then
assert tmp.exists() assert tmp.exists()
def test_open_xml_hook(self, grpc_server, tmpdir): def test_open_xml_hook(self, grpc_server: CoreGrpcServer, tmpdir: TemporaryFile):
# given # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
@ -504,12 +516,12 @@ class TestGrpc:
assert response.result is True assert response.result is True
assert response.session_id is not None 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 # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
switch = session.add_node(_type=NodeTypes.SWITCH) switch = session.add_node(SwitchNode)
node = session.add_node() node = session.add_node(CoreNode)
interface = ip_prefixes.create_interface(node) interface = ip_prefixes.create_interface(node)
session.add_link(node.id, switch.id, interface) session.add_link(node.id, switch.id, interface)
@ -520,12 +532,14 @@ class TestGrpc:
# then # then
assert len(response.links) == 1 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 # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
switch = session.add_node(_type=NodeTypes.SWITCH) switch = session.add_node(SwitchNode)
node = session.add_node() node = session.add_node(CoreNode)
interface = ip_prefixes.create_interface(node) interface = ip_prefixes.create_interface(node)
session.add_link(node.id, switch.id, interface) session.add_link(node.id, switch.id, interface)
@ -534,12 +548,14 @@ class TestGrpc:
with client.context_connect(): with client.context_connect():
client.get_node_links(session.id, 3) 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 # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
switch = session.add_node(_type=NodeTypes.SWITCH) switch = session.add_node(SwitchNode)
node = session.add_node() node = session.add_node(CoreNode)
assert len(switch.all_link_data()) == 0 assert len(switch.all_link_data()) == 0
# then # then
@ -551,11 +567,13 @@ class TestGrpc:
assert response.result is True assert response.result is True
assert len(switch.all_link_data()) == 1 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 # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
node = session.add_node() node = session.add_node(CoreNode)
# then # then
interface = interface_helper.create_interface(node.id, 0) interface = interface_helper.create_interface(node.id, 0)
@ -563,12 +581,12 @@ class TestGrpc:
with client.context_connect(): with client.context_connect():
client.add_link(session.id, 1, 3, interface) 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 # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
switch = session.add_node(_type=NodeTypes.SWITCH) switch = session.add_node(SwitchNode)
node = session.add_node() node = session.add_node(CoreNode)
interface = ip_prefixes.create_interface(node) interface = ip_prefixes.create_interface(node)
session.add_link(node.id, switch.id, interface) session.add_link(node.id, switch.id, interface)
options = core_pb2.LinkOptions(bandwidth=30000) options = core_pb2.LinkOptions(bandwidth=30000)
@ -586,13 +604,13 @@ class TestGrpc:
link = switch.all_link_data()[0] link = switch.all_link_data()[0]
assert options.bandwidth == link.bandwidth 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 # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() 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) 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) interface_two = ip_prefixes.create_interface(node_two)
session.add_link(node_one.id, node_two.id, interface_one, interface_two) session.add_link(node_one.id, node_two.id, interface_one, interface_two)
link_node = None link_node = None
@ -613,11 +631,11 @@ class TestGrpc:
assert response.result is True assert response.result is True
assert len(link_node.all_link_data(0)) == 0 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 # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
wlan = session.add_node(_type=NodeTypes.WIRELESS_LAN) wlan = session.add_node(WlanNode)
# then # then
with client.context_connect(): with client.context_connect():
@ -626,12 +644,12 @@ class TestGrpc:
# then # then
assert len(response.config) > 0 assert len(response.config) > 0
def test_set_wlan_config(self, grpc_server): def test_set_wlan_config(self, grpc_server: CoreGrpcServer):
# given # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
session.set_state(EventTypes.CONFIGURATION_STATE) 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()) wlan.setmodel(BasicRangeModel, BasicRangeModel.default_values())
session.instantiate() session.instantiate()
range_key = "range" range_key = "range"
@ -658,7 +676,7 @@ class TestGrpc:
assert config[range_key] == range_value assert config[range_key] == range_value
assert wlan.model.range == int(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 # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
@ -670,7 +688,7 @@ class TestGrpc:
# then # then
assert len(response.config) > 0 assert len(response.config) > 0
def test_set_emane_config(self, grpc_server): def test_set_emane_config(self, grpc_server: CoreGrpcServer):
# given # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
@ -687,14 +705,13 @@ class TestGrpc:
assert len(config) > 1 assert len(config) > 1
assert config[config_key] == config_value 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 # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
session.set_location(47.57917, -122.13232, 2.00000, 1.0) session.set_location(47.57917, -122.13232, 2.00000, 1.0)
options = NodeOptions() options = NodeOptions(emane=EmaneIeee80211abgModel.name)
options.emane = EmaneIeee80211abgModel.name emane_network = session.add_node(EmaneNet, options=options)
emane_network = session.add_node(_type=NodeTypes.EMANE, options=options)
session.emane.set_model(emane_network, EmaneIeee80211abgModel) session.emane.set_model(emane_network, EmaneIeee80211abgModel)
config_key = "platform_id_start" config_key = "platform_id_start"
config_value = "2" config_value = "2"
@ -714,14 +731,13 @@ class TestGrpc:
assert len(model_config.config) > 0 assert len(model_config.config) > 0
assert model_config.interface == -1 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 # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
session.set_location(47.57917, -122.13232, 2.00000, 1.0) session.set_location(47.57917, -122.13232, 2.00000, 1.0)
options = NodeOptions() options = NodeOptions(emane=EmaneIeee80211abgModel.name)
options.emane = EmaneIeee80211abgModel.name emane_network = session.add_node(EmaneNet, options=options)
emane_network = session.add_node(_type=NodeTypes.EMANE, options=options)
session.emane.set_model(emane_network, EmaneIeee80211abgModel) session.emane.set_model(emane_network, EmaneIeee80211abgModel)
config_key = "bandwidth" config_key = "bandwidth"
config_value = "900000" config_value = "900000"
@ -742,14 +758,13 @@ class TestGrpc:
) )
assert config[config_key] == config_value 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 # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
session.set_location(47.57917, -122.13232, 2.00000, 1.0) session.set_location(47.57917, -122.13232, 2.00000, 1.0)
options = NodeOptions() options = NodeOptions(emane=EmaneIeee80211abgModel.name)
options.emane = EmaneIeee80211abgModel.name emane_network = session.add_node(EmaneNet, options=options)
emane_network = session.add_node(_type=NodeTypes.EMANE, options=options)
session.emane.set_model(emane_network, EmaneIeee80211abgModel) session.emane.set_model(emane_network, EmaneIeee80211abgModel)
# then # then
@ -761,7 +776,7 @@ class TestGrpc:
# then # then
assert len(response.config) > 0 assert len(response.config) > 0
def test_get_emane_models(self, grpc_server): def test_get_emane_models(self, grpc_server: CoreGrpcServer):
# given # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
@ -773,11 +788,11 @@ class TestGrpc:
# then # then
assert len(response.models) > 0 assert len(response.models) > 0
def test_get_mobility_configs(self, grpc_server): def test_get_mobility_configs(self, grpc_server: CoreGrpcServer):
# given # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() 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.mobility.set_model_config(wlan.id, Ns2ScriptedMobility.name, {})
# then # then
@ -790,11 +805,11 @@ class TestGrpc:
mapped_config = response.configs[wlan.id] mapped_config = response.configs[wlan.id]
assert len(mapped_config.config) > 0 assert len(mapped_config.config) > 0
def test_get_mobility_config(self, grpc_server): def test_get_mobility_config(self, grpc_server: CoreGrpcServer):
# given # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() 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.mobility.set_model_config(wlan.id, Ns2ScriptedMobility.name, {})
# then # then
@ -804,11 +819,11 @@ class TestGrpc:
# then # then
assert len(response.config) > 0 assert len(response.config) > 0
def test_set_mobility_config(self, grpc_server): def test_set_mobility_config(self, grpc_server: CoreGrpcServer):
# given # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
wlan = session.add_node(_type=NodeTypes.WIRELESS_LAN) wlan = session.add_node(WlanNode)
config_key = "refresh_ms" config_key = "refresh_ms"
config_value = "60" config_value = "60"
@ -823,11 +838,11 @@ class TestGrpc:
config = session.mobility.get_model_config(wlan.id, Ns2ScriptedMobility.name) config = session.mobility.get_model_config(wlan.id, Ns2ScriptedMobility.name)
assert config[config_key] == config_value assert config[config_key] == config_value
def test_mobility_action(self, grpc_server): def test_mobility_action(self, grpc_server: CoreGrpcServer):
# given # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() 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.mobility.set_model_config(wlan.id, Ns2ScriptedMobility.name, {})
session.instantiate() session.instantiate()
@ -838,7 +853,7 @@ class TestGrpc:
# then # then
assert response.result is True assert response.result is True
def test_get_services(self, grpc_server): def test_get_services(self, grpc_server: CoreGrpcServer):
# given # given
client = CoreGrpcClient() client = CoreGrpcClient()
@ -849,7 +864,7 @@ class TestGrpc:
# then # then
assert len(response.services) > 0 assert len(response.services) > 0
def test_get_service_defaults(self, grpc_server): def test_get_service_defaults(self, grpc_server: CoreGrpcServer):
# given # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
@ -861,7 +876,7 @@ class TestGrpc:
# then # then
assert len(response.defaults) > 0 assert len(response.defaults) > 0
def test_set_service_defaults(self, grpc_server): def test_set_service_defaults(self, grpc_server: CoreGrpcServer):
# given # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
@ -876,11 +891,11 @@ class TestGrpc:
assert response.result is True assert response.result is True
assert session.services.default_services[node_type] == services 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 # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
node = session.add_node() node = session.add_node(CoreNode)
service_name = "DefaultRoute" service_name = "DefaultRoute"
session.services.set_service(node.id, service_name) session.services.set_service(node.id, service_name)
@ -894,11 +909,11 @@ class TestGrpc:
assert service_config.node_id == node.id assert service_config.node_id == node.id
assert service_config.service == service_name assert service_config.service == service_name
def test_get_node_service(self, grpc_server): def test_get_node_service(self, grpc_server: CoreGrpcServer):
# given # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
node = session.add_node() node = session.add_node(CoreNode)
# then # then
with client.context_connect(): with client.context_connect():
@ -907,11 +922,11 @@ class TestGrpc:
# then # then
assert len(response.service.configs) > 0 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 # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
node = session.add_node() node = session.add_node(CoreNode)
# then # then
with client.context_connect(): with client.context_connect():
@ -922,11 +937,11 @@ class TestGrpc:
# then # then
assert response.data is not None assert response.data is not None
def test_set_node_service(self, grpc_server): def test_set_node_service(self, grpc_server: CoreGrpcServer):
# given # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
node = session.add_node() node = session.add_node(CoreNode)
service_name = "DefaultRoute" service_name = "DefaultRoute"
validate = ["echo hello"] validate = ["echo hello"]
@ -943,11 +958,11 @@ class TestGrpc:
) )
assert service.validate == tuple(validate) 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 # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
node = session.add_node() node = session.add_node(CoreNode)
service_name = "DefaultRoute" service_name = "DefaultRoute"
file_name = "defaultroute.sh" file_name = "defaultroute.sh"
file_data = "echo hello" file_data = "echo hello"
@ -963,11 +978,11 @@ class TestGrpc:
service_file = session.services.get_service_file(node, service_name, file_name) service_file = session.services.get_service_file(node, service_name, file_name)
assert service_file.data == file_data assert service_file.data == file_data
def test_service_action(self, grpc_server): def test_service_action(self, grpc_server: CoreGrpcServer):
# given # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
node = session.add_node() node = session.add_node(CoreNode)
service_name = "DefaultRoute" service_name = "DefaultRoute"
# then # then
@ -979,16 +994,23 @@ class TestGrpc:
# then # then
assert response.result is True assert response.result is True
def test_node_events(self, grpc_server): def test_node_events(self, grpc_server: CoreGrpcServer):
# given # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() 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() queue = Queue()
def handle_event(event_data): def handle_event(event_data):
assert event_data.session_id == session.id assert event_data.session_id == session.id
assert event_data.HasField("node_event") 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) queue.put(event_data)
# then # then
@ -1000,12 +1022,12 @@ class TestGrpc:
# then # then
queue.get(timeout=5) 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 # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
wlan = session.add_node(_type=NodeTypes.WIRELESS_LAN) wlan = session.add_node(WlanNode)
node = session.add_node() node = session.add_node(CoreNode)
interface = ip_prefixes.create_interface(node) interface = ip_prefixes.create_interface(node)
session.add_link(node.id, wlan.id, interface) session.add_link(node.id, wlan.id, interface)
link_data = wlan.all_link_data()[0] link_data = wlan.all_link_data()[0]
@ -1025,7 +1047,7 @@ class TestGrpc:
# then # then
queue.get(timeout=5) queue.get(timeout=5)
def test_throughputs(self, request, grpc_server): def test_throughputs(self, request, grpc_server: CoreGrpcServer):
if request.config.getoption("mock"): if request.config.getoption("mock"):
pytest.skip("mocking calls") pytest.skip("mocking calls")
@ -1046,7 +1068,7 @@ class TestGrpc:
# then # then
queue.get(timeout=5) queue.get(timeout=5)
def test_session_events(self, grpc_server): def test_session_events(self, grpc_server: CoreGrpcServer):
# given # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
@ -1069,7 +1091,7 @@ class TestGrpc:
# then # then
queue.get(timeout=5) queue.get(timeout=5)
def test_config_events(self, grpc_server): def test_config_events(self, grpc_server: CoreGrpcServer):
# given # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
@ -1093,7 +1115,7 @@ class TestGrpc:
# then # then
queue.get(timeout=5) queue.get(timeout=5)
def test_exception_events(self, grpc_server): def test_exception_events(self, grpc_server: CoreGrpcServer):
# given # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
@ -1122,11 +1144,11 @@ class TestGrpc:
# then # then
queue.get(timeout=5) queue.get(timeout=5)
def test_file_events(self, grpc_server): def test_file_events(self, grpc_server: CoreGrpcServer):
# given # given
client = CoreGrpcClient() client = CoreGrpcClient()
session = grpc_server.coreemu.create_session() session = grpc_server.coreemu.create_session()
node = session.add_node() node = session.add_node(CoreNode)
queue = Queue() queue = Queue()
def handle_event(event_data): def handle_event(event_data):
@ -1145,3 +1167,71 @@ class TestGrpc:
# then # then
queue.get(timeout=5) 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())

View file

@ -3,6 +3,7 @@ Tests for testing tlv message handling.
""" """
import os import os
import time import time
from typing import Optional
import mock import mock
import netaddr import netaddr
@ -10,6 +11,7 @@ import pytest
from mock import MagicMock from mock import MagicMock
from core.api.tlv import coreapi from core.api.tlv import coreapi
from core.api.tlv.corehandlers import CoreHandler
from core.api.tlv.enumerations import ( from core.api.tlv.enumerations import (
ConfigFlags, ConfigFlags,
ConfigTlvs, ConfigTlvs,
@ -24,9 +26,11 @@ from core.emane.ieee80211abg import EmaneIeee80211abgModel
from core.emulator.enumerations import EventTypes, MessageFlags, NodeTypes, RegisterTlvs from core.emulator.enumerations import EventTypes, MessageFlags, NodeTypes, RegisterTlvs
from core.errors import CoreError from core.errors import CoreError
from core.location.mobility import BasicRangeModel 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) return "|".join(f"{x}={values[x]}" for x in values)
@ -42,7 +46,9 @@ class TestGui:
(NodeTypes.TUNNEL, None), (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 node_id = 1
message = coreapi.CoreNodeMessage.create( message = coreapi.CoreNodeMessage.create(
MessageFlags.ADD.value, MessageFlags.ADD.value,
@ -57,12 +63,11 @@ class TestGui:
) )
coretlv.handle_message(message) 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: CoreHandler):
def test_node_update(self, coretlv):
node_id = 1 node_id = 1
coretlv.session.add_node(_id=node_id) coretlv.session.add_node(CoreNode, _id=node_id)
x = 50 x = 50
y = 100 y = 100
message = coreapi.CoreNodeMessage.create( message = coreapi.CoreNodeMessage.create(
@ -76,14 +81,14 @@ class TestGui:
coretlv.handle_message(message) 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 is not None
assert node.position.x == x assert node.position.x == x
assert node.position.y == y assert node.position.y == y
def test_node_delete(self, coretlv): def test_node_delete(self, coretlv: CoreHandler):
node_id = 1 node_id = 1
coretlv.session.add_node(_id=node_id) coretlv.session.add_node(CoreNode, _id=node_id)
message = coreapi.CoreNodeMessage.create( message = coreapi.CoreNodeMessage.create(
MessageFlags.DELETE.value, [(NodeTlvs.NUMBER, node_id)] MessageFlags.DELETE.value, [(NodeTlvs.NUMBER, node_id)]
) )
@ -91,13 +96,13 @@ class TestGui:
coretlv.handle_message(message) coretlv.handle_message(message)
with pytest.raises(CoreError): 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 node_one = 1
coretlv.session.add_node(_id=node_one) coretlv.session.add_node(CoreNode, _id=node_one)
switch = 2 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") ip_prefix = netaddr.IPNetwork("10.0.0.0/24")
interface_one = str(ip_prefix[node_one]) interface_one = str(ip_prefix[node_one])
message = coreapi.CoreLinkMessage.create( message = coreapi.CoreLinkMessage.create(
@ -113,15 +118,15 @@ class TestGui:
coretlv.handle_message(message) 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() all_links = switch_node.all_link_data()
assert len(all_links) == 1 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 node_one = 1
coretlv.session.add_node(_id=node_one) coretlv.session.add_node(CoreNode, _id=node_one)
switch = 2 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") ip_prefix = netaddr.IPNetwork("10.0.0.0/24")
interface_one = str(ip_prefix[node_one]) interface_one = str(ip_prefix[node_one])
message = coreapi.CoreLinkMessage.create( message = coreapi.CoreLinkMessage.create(
@ -137,15 +142,15 @@ class TestGui:
coretlv.handle_message(message) 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() all_links = switch_node.all_link_data()
assert len(all_links) == 1 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 node_one = 1
coretlv.session.add_node(_id=node_one) coretlv.session.add_node(CoreNode, _id=node_one)
node_two = 2 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") ip_prefix = netaddr.IPNetwork("10.0.0.0/24")
interface_one = str(ip_prefix[node_one]) interface_one = str(ip_prefix[node_one])
interface_two = str(ip_prefix[node_two]) interface_two = str(ip_prefix[node_two])
@ -171,11 +176,11 @@ class TestGui:
all_links += node.all_link_data() all_links += node.all_link_data()
assert len(all_links) == 1 assert len(all_links) == 1
def test_link_update(self, coretlv): def test_link_update(self, coretlv: CoreHandler):
node_one = 1 node_one = 1
coretlv.session.add_node(_id=node_one) coretlv.session.add_node(CoreNode, _id=node_one)
switch = 2 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") ip_prefix = netaddr.IPNetwork("10.0.0.0/24")
interface_one = str(ip_prefix[node_one]) interface_one = str(ip_prefix[node_one])
message = coreapi.CoreLinkMessage.create( message = coreapi.CoreLinkMessage.create(
@ -189,7 +194,7 @@ class TestGui:
], ],
) )
coretlv.handle_message(message) 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() all_links = switch_node.all_link_data()
assert len(all_links) == 1 assert len(all_links) == 1
link = all_links[0] link = all_links[0]
@ -207,17 +212,17 @@ class TestGui:
) )
coretlv.handle_message(message) 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() all_links = switch_node.all_link_data()
assert len(all_links) == 1 assert len(all_links) == 1
link = all_links[0] link = all_links[0]
assert link.bandwidth == bandwidth 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 node_one = 1
coretlv.session.add_node(_id=node_one) coretlv.session.add_node(CoreNode, _id=node_one)
node_two = 2 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") ip_prefix = netaddr.IPNetwork("10.0.0.0/24")
interface_one = str(ip_prefix[node_one]) interface_one = str(ip_prefix[node_one])
interface_two = str(ip_prefix[node_two]) interface_two = str(ip_prefix[node_two])
@ -257,11 +262,11 @@ class TestGui:
all_links += node.all_link_data() all_links += node.all_link_data()
assert len(all_links) == 0 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 node_one = 1
coretlv.session.add_node(_id=node_one) coretlv.session.add_node(CoreNode, _id=node_one)
switch = 2 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") ip_prefix = netaddr.IPNetwork("10.0.0.0/24")
interface_one = str(ip_prefix[node_one]) interface_one = str(ip_prefix[node_one])
message = coreapi.CoreLinkMessage.create( message = coreapi.CoreLinkMessage.create(
@ -275,7 +280,7 @@ class TestGui:
], ],
) )
coretlv.handle_message(message) 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() all_links = switch_node.all_link_data()
assert len(all_links) == 1 assert len(all_links) == 1
@ -289,15 +294,15 @@ class TestGui:
) )
coretlv.handle_message(message) 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() all_links = switch_node.all_link_data()
assert len(all_links) == 0 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 node_one = 1
coretlv.session.add_node(_id=node_one) coretlv.session.add_node(CoreNode, _id=node_one)
switch = 2 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") ip_prefix = netaddr.IPNetwork("10.0.0.0/24")
interface_one = str(ip_prefix[node_one]) interface_one = str(ip_prefix[node_one])
message = coreapi.CoreLinkMessage.create( message = coreapi.CoreLinkMessage.create(
@ -311,7 +316,7 @@ class TestGui:
], ],
) )
coretlv.handle_message(message) 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() all_links = switch_node.all_link_data()
assert len(all_links) == 1 assert len(all_links) == 1
@ -325,11 +330,11 @@ class TestGui:
) )
coretlv.handle_message(message) 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() all_links = switch_node.all_link_data()
assert len(all_links) == 0 assert len(all_links) == 0
def test_session_update(self, coretlv): def test_session_update(self, coretlv: CoreHandler):
session_id = coretlv.session.id session_id = coretlv.session.id
name = "test" name = "test"
message = coreapi.CoreSessionMessage.create( message = coreapi.CoreSessionMessage.create(
@ -340,7 +345,7 @@ class TestGui:
assert coretlv.session.name == name assert coretlv.session.name == name
def test_session_query(self, coretlv): def test_session_query(self, coretlv: CoreHandler):
coretlv.dispatch_replies = mock.MagicMock() coretlv.dispatch_replies = mock.MagicMock()
message = coreapi.CoreSessionMessage.create(MessageFlags.STRING.value, []) message = coreapi.CoreSessionMessage.create(MessageFlags.STRING.value, [])
@ -350,7 +355,7 @@ class TestGui:
replies = args[0] replies = args[0]
assert len(replies) == 1 assert len(replies) == 1
def test_session_join(self, coretlv): def test_session_join(self, coretlv: CoreHandler):
coretlv.dispatch_replies = mock.MagicMock() coretlv.dispatch_replies = mock.MagicMock()
session_id = coretlv.session.id session_id = coretlv.session.id
message = coreapi.CoreSessionMessage.create( message = coreapi.CoreSessionMessage.create(
@ -361,7 +366,7 @@ class TestGui:
assert coretlv.session.id == session_id 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 assert len(coretlv.coreemu.sessions) == 1
session_id = coretlv.session.id session_id = coretlv.session.id
message = coreapi.CoreSessionMessage.create( message = coreapi.CoreSessionMessage.create(
@ -372,7 +377,7 @@ class TestGui:
assert len(coretlv.coreemu.sessions) == 0 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 state = EventTypes.DATACOLLECT_STATE
assert coretlv.session._hooks.get(state) is None assert coretlv.session._hooks.get(state) is None
file_name = "test.sh" file_name = "test.sh"
@ -394,8 +399,8 @@ class TestGui:
assert file_name == name assert file_name == name
assert file_data == data assert file_data == data
def test_file_service_file_set(self, coretlv): def test_file_service_file_set(self, coretlv: CoreHandler):
node = coretlv.session.add_node() node = coretlv.session.add_node(CoreNode)
service = "DefaultRoute" service = "DefaultRoute"
file_name = "defaultroute.sh" file_name = "defaultroute.sh"
file_data = "echo hello" file_data = "echo hello"
@ -416,9 +421,9 @@ class TestGui:
) )
assert file_data == service_file.data 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" file_name = "/var/log/test/node.log"
node = coretlv.session.add_node() node = coretlv.session.add_node(CoreNode)
node.makenodedir() node.makenodedir()
file_data = "echo hello" file_data = "echo hello"
message = coreapi.CoreFileMessage.create( message = coreapi.CoreFileMessage.create(
@ -438,9 +443,9 @@ class TestGui:
create_path = os.path.join(node.nodedir, created_directory, basename) create_path = os.path.join(node.nodedir, created_directory, basename)
assert os.path.exists(create_path) 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() coretlv.dispatch_replies = mock.MagicMock()
node = coretlv.session.add_node() node = coretlv.session.add_node(CoreNode)
message = coreapi.CoreExecMessage.create( message = coreapi.CoreExecMessage.create(
MessageFlags.TTY.value, MessageFlags.TTY.value,
[ [
@ -456,12 +461,12 @@ class TestGui:
replies = args[0] replies = args[0]
assert len(replies) == 1 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"): if request.config.getoption("mock"):
pytest.skip("mocking calls") pytest.skip("mocking calls")
coretlv.dispatch_replies = mock.MagicMock() coretlv.dispatch_replies = mock.MagicMock()
node = coretlv.session.add_node() node = coretlv.session.add_node(CoreNode)
cmd = "echo hello" cmd = "echo hello"
message = coreapi.CoreExecMessage.create( message = coreapi.CoreExecMessage.create(
MessageFlags.TEXT.value | MessageFlags.LOCAL.value, MessageFlags.TEXT.value | MessageFlags.LOCAL.value,
@ -478,9 +483,9 @@ class TestGui:
replies = args[0] replies = args[0]
assert len(replies) == 1 assert len(replies) == 1
def test_exec_node_command(self, coretlv): def test_exec_node_command(self, coretlv: CoreHandler):
coretlv.dispatch_replies = mock.MagicMock() coretlv.dispatch_replies = mock.MagicMock()
node = coretlv.session.add_node() node = coretlv.session.add_node(CoreNode)
cmd = "echo hello" cmd = "echo hello"
message = coreapi.CoreExecMessage.create( message = coreapi.CoreExecMessage.create(
MessageFlags.TEXT.value, MessageFlags.TEXT.value,
@ -506,16 +511,16 @@ class TestGui:
EventTypes.DEFINITION_STATE, 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)]) message = coreapi.CoreEventMessage.create(0, [(EventTlvs.TYPE, state.value)])
coretlv.handle_message(message) coretlv.handle_message(message)
assert coretlv.session.state == state assert coretlv.session.state == state
def test_event_schedule(self, coretlv): def test_event_schedule(self, coretlv: CoreHandler):
coretlv.session.add_event = mock.MagicMock() coretlv.session.add_event = mock.MagicMock()
node = coretlv.session.add_node() node = coretlv.session.add_node(CoreNode)
message = coreapi.CoreEventMessage.create( message = coreapi.CoreEventMessage.create(
MessageFlags.ADD.value, MessageFlags.ADD.value,
[ [
@ -531,10 +536,10 @@ class TestGui:
coretlv.session.add_event.assert_called_once() 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") xml_file = tmpdir.join("coretlv.session.xml")
file_path = xml_file.strpath file_path = xml_file.strpath
coretlv.session.add_node() coretlv.session.add_node(CoreNode)
message = coreapi.CoreEventMessage.create( message = coreapi.CoreEventMessage.create(
0, 0,
[(EventTlvs.TYPE, EventTypes.FILE_SAVE.value), (EventTlvs.NAME, file_path)], [(EventTlvs.TYPE, EventTypes.FILE_SAVE.value), (EventTlvs.NAME, file_path)],
@ -544,10 +549,10 @@ class TestGui:
assert os.path.exists(file_path) 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") xml_file = tmpdir.join("coretlv.session.xml")
file_path = xml_file.strpath file_path = xml_file.strpath
node = coretlv.session.add_node() node = coretlv.session.add_node(CoreNode)
coretlv.session.save_xml(file_path) coretlv.session.save_xml(file_path)
coretlv.session.delete_node(node.id) coretlv.session.delete_node(node.id)
message = coreapi.CoreEventMessage.create( message = coreapi.CoreEventMessage.create(
@ -556,8 +561,7 @@ class TestGui:
) )
coretlv.handle_message(message) coretlv.handle_message(message)
assert coretlv.session.get_node(node.id, NodeBase)
assert coretlv.session.get_node(node.id)
@pytest.mark.parametrize( @pytest.mark.parametrize(
"state", "state",
@ -569,9 +573,9 @@ class TestGui:
EventTypes.RECONFIGURE, EventTypes.RECONFIGURE,
], ],
) )
def test_event_service(self, coretlv, state): def test_event_service(self, coretlv: CoreHandler, state: EventTypes):
coretlv.session.broadcast_event = mock.MagicMock() coretlv.session.broadcast_event = mock.MagicMock()
node = coretlv.session.add_node() node = coretlv.session.add_node(CoreNode)
message = coreapi.CoreEventMessage.create( message = coreapi.CoreEventMessage.create(
0, 0,
[ [
@ -595,21 +599,21 @@ class TestGui:
EventTypes.RECONFIGURE, EventTypes.RECONFIGURE,
], ],
) )
def test_event_mobility(self, coretlv, state): def test_event_mobility(self, coretlv: CoreHandler, state: EventTypes):
message = coreapi.CoreEventMessage.create( message = coreapi.CoreEventMessage.create(
0, [(EventTlvs.TYPE, state.value), (EventTlvs.NAME, "mobility:ns2script")] 0, [(EventTlvs.TYPE, state.value), (EventTlvs.NAME, "mobility:ns2script")]
) )
coretlv.handle_message(message) 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")]) message = coreapi.CoreRegMessage.create(0, [(RegisterTlvs.GUI, "gui")])
coretlv.handle_message(message) 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") xml_file = tmpdir.join("coretlv.session.xml")
file_path = xml_file.strpath file_path = xml_file.strpath
node = coretlv.session.add_node() node = coretlv.session.add_node(CoreNode)
coretlv.session.save_xml(file_path) coretlv.session.save_xml(file_path)
coretlv.session.delete_node(node.id) coretlv.session.delete_node(node.id)
message = coreapi.CoreRegMessage.create( message = coreapi.CoreRegMessage.create(
@ -619,15 +623,16 @@ class TestGui:
coretlv.handle_message(message) 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") xml_file = tmpdir.join("test.py")
file_path = xml_file.strpath file_path = xml_file.strpath
with open(file_path, "w") as f: with open(file_path, "w") as f:
f.write("from core.nodes.base import CoreNode\n")
f.write("coreemu = globals()['coreemu']\n") f.write("coreemu = globals()['coreemu']\n")
f.write(f"session = coreemu.sessions[{coretlv.session.id}]\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( message = coreapi.CoreRegMessage.create(
0, [(RegisterTlvs.EXECUTE_SERVER, file_path)] 0, [(RegisterTlvs.EXECUTE_SERVER, file_path)]
) )
@ -637,7 +642,7 @@ class TestGui:
assert len(coretlv.session.nodes) == 1 assert len(coretlv.session.nodes) == 1
def test_config_all(self, coretlv): def test_config_all(self, coretlv: CoreHandler):
message = coreapi.CoreConfMessage.create( message = coreapi.CoreConfMessage.create(
MessageFlags.ADD.value, MessageFlags.ADD.value,
[(ConfigTlvs.OBJECT, "all"), (ConfigTlvs.TYPE, ConfigFlags.RESET.value)], [(ConfigTlvs.OBJECT, "all"), (ConfigTlvs.TYPE, ConfigFlags.RESET.value)],
@ -648,7 +653,7 @@ class TestGui:
assert coretlv.session.location.refxyz == (0, 0, 0) 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( message = coreapi.CoreConfMessage.create(
0, 0,
[ [
@ -662,7 +667,7 @@ class TestGui:
coretlv.handle_broadcast_config.assert_called_once() 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_key = "test"
test_value = "test" test_value = "test"
values = {test_key: test_value} values = {test_key: test_value}
@ -679,7 +684,7 @@ class TestGui:
assert coretlv.session.options.get_config(test_key) == test_value 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( message = coreapi.CoreConfMessage.create(
0, 0,
[ [
@ -693,7 +698,7 @@ class TestGui:
assert coretlv.session.location.refxyz == (0, 0, 0) 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( message = coreapi.CoreConfMessage.create(
0, 0,
[ [
@ -709,7 +714,7 @@ class TestGui:
assert coretlv.session.location.refgeo == (70, 50, 0) assert coretlv.session.location.refgeo == (70, 50, 0)
assert coretlv.session.location.refscale == 0.5 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( message = coreapi.CoreConfMessage.create(
0, 0,
[ [
@ -723,7 +728,7 @@ class TestGui:
coretlv.handle_broadcast_config.assert_called_once() 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_key = "test"
test_value = "test" test_value = "test"
values = {test_key: test_value} values = {test_key: test_value}
@ -740,7 +745,7 @@ class TestGui:
assert coretlv.session.metadata[test_key] == test_value 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" server = "test"
host = "10.0.0.1" host = "10.0.0.1"
port = 50000 port = 50000
@ -758,7 +763,7 @@ class TestGui:
coretlv.session.distributed.add_server.assert_called_once_with(server, host) 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( message = coreapi.CoreConfMessage.create(
0, 0,
[ [
@ -772,8 +777,8 @@ class TestGui:
coretlv.handle_broadcast_config.assert_called_once() coretlv.handle_broadcast_config.assert_called_once()
def test_config_services_request_specific(self, coretlv): def test_config_services_request_specific(self, coretlv: CoreHandler):
node = coretlv.session.add_node() node = coretlv.session.add_node(CoreNode)
message = coreapi.CoreConfMessage.create( message = coreapi.CoreConfMessage.create(
0, 0,
[ [
@ -789,8 +794,8 @@ class TestGui:
coretlv.handle_broadcast_config.assert_called_once() coretlv.handle_broadcast_config.assert_called_once()
def test_config_services_request_specific_file(self, coretlv): def test_config_services_request_specific_file(self, coretlv: CoreHandler):
node = coretlv.session.add_node() node = coretlv.session.add_node(CoreNode)
message = coreapi.CoreConfMessage.create( message = coreapi.CoreConfMessage.create(
0, 0,
[ [
@ -806,8 +811,8 @@ class TestGui:
coretlv.session.broadcast_file.assert_called_once() coretlv.session.broadcast_file.assert_called_once()
def test_config_services_reset(self, coretlv): def test_config_services_reset(self, coretlv: CoreHandler):
node = coretlv.session.add_node() node = coretlv.session.add_node(CoreNode)
service = "DefaultRoute" service = "DefaultRoute"
coretlv.session.services.set_service(node.id, service) coretlv.session.services.set_service(node.id, service)
message = coreapi.CoreConfMessage.create( message = coreapi.CoreConfMessage.create(
@ -823,8 +828,8 @@ class TestGui:
assert coretlv.session.services.get_service(node.id, service) is None assert coretlv.session.services.get_service(node.id, service) is None
def test_config_services_set(self, coretlv): def test_config_services_set(self, coretlv: CoreHandler):
node = coretlv.session.add_node() node = coretlv.session.add_node(CoreNode)
service = "DefaultRoute" service = "DefaultRoute"
values = {"meta": "metadata"} values = {"meta": "metadata"}
message = coreapi.CoreConfMessage.create( message = coreapi.CoreConfMessage.create(
@ -843,8 +848,8 @@ class TestGui:
assert coretlv.session.services.get_service(node.id, service) is not None assert coretlv.session.services.get_service(node.id, service) is not None
def test_config_mobility_reset(self, coretlv): def test_config_mobility_reset(self, coretlv: CoreHandler):
wlan = coretlv.session.add_node(_type=NodeTypes.WIRELESS_LAN) wlan = coretlv.session.add_node(WlanNode)
message = coreapi.CoreConfMessage.create( message = coreapi.CoreConfMessage.create(
0, 0,
[ [
@ -859,8 +864,8 @@ class TestGui:
assert len(coretlv.session.mobility.node_configurations) == 0 assert len(coretlv.session.mobility.node_configurations) == 0
def test_config_mobility_model_request(self, coretlv): def test_config_mobility_model_request(self, coretlv: CoreHandler):
wlan = coretlv.session.add_node(_type=NodeTypes.WIRELESS_LAN) wlan = coretlv.session.add_node(WlanNode)
message = coreapi.CoreConfMessage.create( message = coreapi.CoreConfMessage.create(
0, 0,
[ [
@ -875,8 +880,8 @@ class TestGui:
coretlv.handle_broadcast_config.assert_called_once() coretlv.handle_broadcast_config.assert_called_once()
def test_config_mobility_model_update(self, coretlv): def test_config_mobility_model_update(self, coretlv: CoreHandler):
wlan = coretlv.session.add_node(_type=NodeTypes.WIRELESS_LAN) wlan = coretlv.session.add_node(WlanNode)
config_key = "range" config_key = "range"
config_value = "1000" config_value = "1000"
values = {config_key: config_value} values = {config_key: config_value}
@ -897,8 +902,8 @@ class TestGui:
) )
assert config[config_key] == config_value assert config[config_key] == config_value
def test_config_emane_model_request(self, coretlv): def test_config_emane_model_request(self, coretlv: CoreHandler):
wlan = coretlv.session.add_node(_type=NodeTypes.WIRELESS_LAN) wlan = coretlv.session.add_node(WlanNode)
message = coreapi.CoreConfMessage.create( message = coreapi.CoreConfMessage.create(
0, 0,
[ [
@ -913,8 +918,8 @@ class TestGui:
coretlv.handle_broadcast_config.assert_called_once() coretlv.handle_broadcast_config.assert_called_once()
def test_config_emane_model_update(self, coretlv): def test_config_emane_model_update(self, coretlv: CoreHandler):
wlan = coretlv.session.add_node(_type=NodeTypes.WIRELESS_LAN) wlan = coretlv.session.add_node(WlanNode)
config_key = "distance" config_key = "distance"
config_value = "50051" config_value = "50051"
values = {config_key: config_value} values = {config_key: config_value}
@ -935,7 +940,7 @@ class TestGui:
) )
assert config[config_key] == config_value 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( message = coreapi.CoreConfMessage.create(
0, 0,
[ [
@ -949,7 +954,7 @@ class TestGui:
coretlv.handle_broadcast_config.assert_called_once() 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_key = "eventservicedevice"
config_value = "eth4" config_value = "eth4"
values = {config_key: config_value} values = {config_key: config_value}

View file

@ -1,11 +1,17 @@
from core.emulator.emudata import LinkOptions from typing import Tuple
from core.emulator.enumerations import NodeTypes
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 # create nodes
node_one = session.add_node() node_one = session.add_node(CoreNode)
node_two = session.add_node() node_two = session.add_node(CoreNode)
# link nodes to net node # link nodes to net node
interface_one = ip_prefixes.create_interface(node_one) interface_one = ip_prefixes.create_interface(node_one)
@ -19,10 +25,10 @@ def create_ptp_network(session, ip_prefixes):
class TestLinks: class TestLinks:
def test_ptp(self, session, ip_prefixes): def test_ptp(self, session: Session, ip_prefixes: IpPrefixes):
# given # given
node_one = session.add_node() node_one = session.add_node(CoreNode)
node_two = session.add_node() node_two = session.add_node(CoreNode)
interface_one = ip_prefixes.create_interface(node_one) interface_one = ip_prefixes.create_interface(node_one)
interface_two = ip_prefixes.create_interface(node_two) interface_two = ip_prefixes.create_interface(node_two)
@ -33,10 +39,10 @@ class TestLinks:
assert node_one.netif(interface_one.id) assert node_one.netif(interface_one.id)
assert node_two.netif(interface_two.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 # given
node_one = session.add_node() node_one = session.add_node(CoreNode)
node_two = session.add_node(_type=NodeTypes.SWITCH) node_two = session.add_node(SwitchNode)
interface_one = ip_prefixes.create_interface(node_one) interface_one = ip_prefixes.create_interface(node_one)
# when # when
@ -46,10 +52,10 @@ class TestLinks:
assert node_two.all_link_data() assert node_two.all_link_data()
assert node_one.netif(interface_one.id) 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 # given
node_one = session.add_node(_type=NodeTypes.SWITCH) node_one = session.add_node(SwitchNode)
node_two = session.add_node() node_two = session.add_node(CoreNode)
interface_two = ip_prefixes.create_interface(node_two) interface_two = ip_prefixes.create_interface(node_two)
# when # when
@ -61,8 +67,8 @@ class TestLinks:
def test_net_to_net(self, session): def test_net_to_net(self, session):
# given # given
node_one = session.add_node(_type=NodeTypes.SWITCH) node_one = session.add_node(SwitchNode)
node_two = session.add_node(_type=NodeTypes.SWITCH) node_two = session.add_node(SwitchNode)
# when # when
session.add_link(node_one.id, node_two.id) session.add_link(node_one.id, node_two.id)
@ -70,15 +76,15 @@ class TestLinks:
# then # then
assert node_one.all_link_data() 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 # given
delay = 50 delay = 50
bandwidth = 5000000 bandwidth = 5000000
per = 25 per = 25
dup = 25 dup = 25
jitter = 10 jitter = 10
node_one = session.add_node() node_one = session.add_node(CoreNode)
node_two = session.add_node(_type=NodeTypes.SWITCH) node_two = session.add_node(SwitchNode)
interface_one_data = ip_prefixes.create_interface(node_one) interface_one_data = ip_prefixes.create_interface(node_one)
session.add_link(node_one.id, node_two.id, interface_one_data) session.add_link(node_one.id, node_two.id, interface_one_data)
interface_one = node_one.netif(interface_one_data.id) interface_one = node_one.netif(interface_one_data.id)
@ -99,7 +105,7 @@ class TestLinks:
node_one.id, node_one.id,
node_two.id, node_two.id,
interface_one_id=interface_one_data.id, interface_one_id=interface_one_data.id,
link_options=link_options, options=link_options,
) )
# then # then
@ -109,10 +115,10 @@ class TestLinks:
assert interface_one.getparam("duplicate") == dup assert interface_one.getparam("duplicate") == dup
assert interface_one.getparam("jitter") == jitter 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 # given
node_one = session.add_node() node_one = session.add_node(CoreNode)
node_two = session.add_node() node_two = session.add_node(CoreNode)
interface_one = ip_prefixes.create_interface(node_one) interface_one = ip_prefixes.create_interface(node_one)
interface_two = ip_prefixes.create_interface(node_two) interface_two = ip_prefixes.create_interface(node_two)
session.add_link(node_one.id, node_two.id, interface_one, interface_two) session.add_link(node_one.id, node_two.id, interface_one, interface_two)

View file

@ -2,15 +2,17 @@ import pytest
from core.location.mobility import WayPoint from core.location.mobility import WayPoint
POSITION = (0.0, 0.0, 0.0)
class TestMobility: class TestMobility:
@pytest.mark.parametrize( @pytest.mark.parametrize(
"wp1, wp2, expected", "wp1, wp2, expected",
[ [
(WayPoint(10.0, 1, [0, 0], 1.0), WayPoint(1.0, 2, [0, 0], 1.0), False), (WayPoint(10.0, 1, POSITION, 1.0), WayPoint(1.0, 2, POSITION, 1.0), False),
(WayPoint(1.0, 1, [0, 0], 1.0), WayPoint(10.0, 2, [0, 0], 1.0), True), (WayPoint(1.0, 1, POSITION, 1.0), WayPoint(10.0, 2, POSITION, 1.0), True),
(WayPoint(1.0, 1, [0, 0], 1.0), WayPoint(1.0, 2, [0, 0], 1.0), True), (WayPoint(1.0, 1, POSITION, 1.0), WayPoint(1.0, 2, POSITION, 1.0), True),
(WayPoint(1.0, 2, [0, 0], 1.0), WayPoint(1.0, 1, [0, 0], 1.0), False), (WayPoint(1.0, 2, POSITION, 1.0), WayPoint(1.0, 1, POSITION, 1.0), False),
], ],
) )
def test_waypoint_lessthan(self, wp1, wp2, expected): def test_waypoint_lessthan(self, wp1, wp2, expected):

View file

@ -1,30 +1,32 @@
import pytest import pytest
from core.emulator.emudata import NodeOptions from core.emulator.emudata import InterfaceData, NodeOptions
from core.emulator.enumerations import NodeTypes from core.emulator.session import Session
from core.errors import CoreError from core.errors import CoreError
from core.nodes.base import CoreNode
from core.nodes.network import HubNode, SwitchNode, WlanNode
MODELS = ["router", "host", "PC", "mdr"] MODELS = ["router", "host", "PC", "mdr"]
NET_TYPES = [NodeTypes.SWITCH, NodeTypes.HUB, NodeTypes.WIRELESS_LAN] NET_TYPES = [SwitchNode, HubNode, WlanNode]
class TestNodes: class TestNodes:
@pytest.mark.parametrize("model", MODELS) @pytest.mark.parametrize("model", MODELS)
def test_node_add(self, session, model): def test_node_add(self, session: Session, model: str):
# given # given
options = NodeOptions(model=model) options = NodeOptions(model=model)
# when # when
node = session.add_node(options=options) node = session.add_node(CoreNode, options=options)
# then # then
assert node assert node
assert node.alive() assert node.alive()
assert node.up assert node.up
def test_node_update(self, session): def test_node_update(self, session: Session):
# given # given
node = session.add_node() node = session.add_node(CoreNode)
position_value = 100 position_value = 100
update_options = NodeOptions() update_options = NodeOptions()
update_options.set_position(x=position_value, y=position_value) 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.x == position_value
assert node.position.y == position_value assert node.position.y == position_value
def test_node_delete(self, session): def test_node_delete(self, session: Session):
# given # given
node = session.add_node() node = session.add_node(CoreNode)
# when # when
session.delete_node(node.id) session.delete_node(node.id)
# then # then
with pytest.raises(CoreError): 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 # given
node = session.add_node() node = session.add_node(CoreNode)
index = node.newnetif() switch = session.add_node(SwitchNode)
interface_data = InterfaceData()
index = node.newnetif(switch, interface_data)
interface = node.netif(index) interface = node.netif(index)
mac = "aa:aa:aa:ff:ff:ff" mac = "aa:aa:aa:ff:ff:ff"
@ -60,10 +64,12 @@ class TestNodes:
# then # then
assert interface.hwaddr == mac assert interface.hwaddr == mac
def test_node_sethwaddr_exception(self, session): def test_node_sethwaddr_exception(self, session: Session):
# given # given
node = session.add_node() node = session.add_node(CoreNode)
index = node.newnetif() switch = session.add_node(SwitchNode)
interface_data = InterfaceData()
index = node.newnetif(switch, interface_data)
node.netif(index) node.netif(index)
mac = "aa:aa:aa:ff:ff:fff" mac = "aa:aa:aa:ff:ff:fff"
@ -71,10 +77,12 @@ class TestNodes:
with pytest.raises(CoreError): with pytest.raises(CoreError):
node.sethwaddr(index, mac) node.sethwaddr(index, mac)
def test_node_addaddr(self, session): def test_node_addaddr(self, session: Session):
# given # given
node = session.add_node() node = session.add_node(CoreNode)
index = node.newnetif() switch = session.add_node(SwitchNode)
interface_data = InterfaceData()
index = node.newnetif(switch, interface_data)
interface = node.netif(index) interface = node.netif(index)
addr = "192.168.0.1/24" addr = "192.168.0.1/24"
@ -86,8 +94,10 @@ class TestNodes:
def test_node_addaddr_exception(self, session): def test_node_addaddr_exception(self, session):
# given # given
node = session.add_node() node = session.add_node(CoreNode)
index = node.newnetif() switch = session.add_node(SwitchNode)
interface_data = InterfaceData()
index = node.newnetif(switch, interface_data)
node.netif(index) node.netif(index)
addr = "256.168.0.1/24" addr = "256.168.0.1/24"
@ -100,7 +110,7 @@ class TestNodes:
# given # given
# when # when
node = session.add_node(_type=net_type) node = session.add_node(net_type)
# then # then
assert node assert node

Some files were not shown because too many files have changed in this diff Show more