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
* Enhancements
* 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.
# this defines the CORE version number, must be static for AC_INIT
AC_INIT(core, 6.4.0)
AC_INIT(core, 6.5.0)
# autoconf and automake initialization
AC_CONFIG_SRCDIR([netns/version.h.in])

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 threading
from contextlib import contextmanager
from typing import Any, Callable, Dict, Generator, List
from typing import Any, Callable, Dict, Generator, Iterable, List
import grpc
import netaddr
from core import utils
from core.api.grpc import configservices_pb2, core_pb2, core_pb2_grpc
from core.api.grpc.configservices_pb2 import (
GetConfigServiceDefaultsRequest,
@ -31,6 +29,8 @@ from core.api.grpc.emane_pb2 import (
EmaneLinkRequest,
EmaneLinkResponse,
EmaneModelConfig,
EmanePathlossesRequest,
EmanePathlossesResponse,
GetEmaneConfigRequest,
GetEmaneConfigResponse,
GetEmaneEventChannelRequest,
@ -89,7 +89,10 @@ from core.api.grpc.wlan_pb2 import (
SetWlanConfigRequest,
SetWlanConfigResponse,
WlanConfig,
WlanLinkRequest,
WlanLinkResponse,
)
from core.emulator.emudata import IpPrefixes
class InterfaceHelper:
@ -105,78 +108,29 @@ class InterfaceHelper:
:param ip6_prefix: ip6 prefix to use for generation
:raises ValueError: when both ip4 and ip6 prefixes have not been provided
"""
if not ip4_prefix and not ip6_prefix:
raise ValueError("ip4 or ip6 must be provided")
self.ip4 = None
if ip4_prefix:
self.ip4 = netaddr.IPNetwork(ip4_prefix)
self.ip6 = None
if ip6_prefix:
self.ip6 = netaddr.IPNetwork(ip6_prefix)
def ip4_address(self, node_id: int) -> str:
"""
Convenience method to return the IP4 address for a node.
:param node_id: node id to get IP4 address for
:return: IP4 address or None
"""
if not self.ip4:
raise ValueError("ip4 prefixes have not been set")
return str(self.ip4[node_id])
def ip6_address(self, node_id: int) -> str:
"""
Convenience method to return the IP6 address for a node.
:param node_id: node id to get IP6 address for
:return: IP4 address or None
"""
if not self.ip6:
raise ValueError("ip6 prefixes have not been set")
return str(self.ip6[node_id])
self.prefixes = IpPrefixes(ip4_prefix, ip6_prefix)
def create_interface(
self, node_id: int, interface_id: int, name: str = None, mac: str = None
) -> core_pb2.Interface:
"""
Creates interface data for linking nodes, using the nodes unique id for
generation, along with a random mac address, unless provided.
Create an interface protobuf object.
:param node_id: node id to create interface for
:param interface_id: interface id for interface
:param name: name to set for interface, default is eth{id}
:param mac: mac address to use for this interface, default is random
generation
:return: new interface data for the provided node
:param interface_id: interface id
:param name: name of interface
:param mac: mac address for interface
:return: interface protobuf
"""
# generate ip4 data
ip4 = None
ip4_mask = None
if self.ip4:
ip4 = self.ip4_address(node_id)
ip4_mask = self.ip4.prefixlen
# generate ip6 data
ip6 = None
ip6_mask = None
if self.ip6:
ip6 = self.ip6_address(node_id)
ip6_mask = self.ip6.prefixlen
# random mac
if not mac:
mac = utils.random_mac()
interface_data = self.prefixes.gen_interface(node_id, name, mac)
return core_pb2.Interface(
id=interface_id,
name=name,
ip4=ip4,
ip4mask=ip4_mask,
ip6=ip6,
ip6mask=ip6_mask,
mac=str(mac),
name=interface_data.name,
ip4=interface_data.ip4,
ip4mask=interface_data.ip4_mask,
ip6=interface_data.ip6,
ip6mask=interface_data.ip6_mask,
mac=interface_data.mac,
)
@ -285,6 +239,7 @@ class CoreGrpcClient:
:param session_id: id of session
:return: stop session response
:raises grpc.RpcError: when session doesn't exist
"""
request = core_pb2.StopSessionRequest(session_id=session_id)
return self.stub.StopSession(request)
@ -569,6 +524,18 @@ class CoreGrpcClient:
)
return self.stub.EditNode(request)
def move_nodes(
self, move_iterator: Iterable[core_pb2.MoveNodesRequest]
) -> core_pb2.MoveNodesResponse:
"""
Stream node movements using the provided iterator.
:param move_iterator: iterator for generating node movements
:return: move nodes response
:raises grpc.RpcError: when session or nodes do not exist
"""
return self.stub.MoveNodes(move_iterator)
def delete_node(self, session_id: int, node_id: int) -> core_pb2.DeleteNodeResponse:
"""
Delete node from session.
@ -582,7 +549,12 @@ class CoreGrpcClient:
return self.stub.DeleteNode(request)
def node_command(
self, session_id: int, node_id: int, command: str
self,
session_id: int,
node_id: int,
command: str,
wait: bool = True,
shell: bool = False,
) -> core_pb2.NodeCommandResponse:
"""
Send command to a node and get the output.
@ -590,11 +562,17 @@ class CoreGrpcClient:
:param session_id: session id
:param node_id: node id
:param command: command to run on node
:param wait: wait for command to complete
:param shell: send shell command
:return: response with command combined stdout/stderr
:raises grpc.RpcError: when session or node doesn't exist
"""
request = core_pb2.NodeCommandRequest(
session_id=session_id, node_id=node_id, command=command
session_id=session_id,
node_id=node_id,
command=command,
wait=wait,
shell=shell,
)
return self.stub.NodeCommand(request)
@ -1072,7 +1050,7 @@ class CoreGrpcClient:
session_id: int,
node_id: int,
model: str,
config: Dict[str, str],
config: Dict[str, str] = None,
interface_id: int = -1,
) -> SetEmaneModelConfigResponse:
"""
@ -1096,9 +1074,9 @@ class CoreGrpcClient:
def get_emane_model_configs(self, session_id: int) -> GetEmaneModelConfigsResponse:
"""
Get all emane model configurations for a session.
Get all EMANE model configurations for a session.
:param session_id: session id
:param session_id: session to get emane model configs
:return: response with a dictionary of node/interface ids to configurations
:raises grpc.RpcError: when session doesn't exist
"""
@ -1109,9 +1087,10 @@ class CoreGrpcClient:
"""
Save the current scenario to an XML file.
:param session_id: session id
:param session_id: session to save xml file for
:param file_path: local path to save scenario XML file to
:return: nothing
:raises grpc.RpcError: when session doesn't exist
"""
request = core_pb2.SaveXmlRequest(session_id=session_id)
response = self.stub.SaveXml(request)
@ -1137,11 +1116,12 @@ class CoreGrpcClient:
"""
Helps broadcast wireless link/unlink between EMANE nodes.
:param session_id: session id
:param nem_one:
:param nem_two:
:param session_id: session to emane link
:param nem_one: first nem for emane link
:param nem_two: second nem for emane link
:param linked: True to link, False to unlink
:return: core_pb2.EmaneLinkResponse
:return: get emane link response
:raises grpc.RpcError: when session or nodes related to nems do not exist
"""
request = EmaneLinkRequest(
session_id=session_id, nem_one=nem_one, nem_two=nem_two, linked=linked
@ -1153,30 +1133,57 @@ class CoreGrpcClient:
Retrieves a list of interfaces available on the host machine that are not
a part of a CORE session.
:return: core_pb2.GetInterfacesResponse
:return: get interfaces response
"""
request = core_pb2.GetInterfacesRequest()
return self.stub.GetInterfaces(request)
def get_config_services(self) -> GetConfigServicesResponse:
"""
Retrieve all known config services.
:return: get config services response
"""
request = GetConfigServicesRequest()
return self.stub.GetConfigServices(request)
def get_config_service_defaults(
self, name: str
) -> GetConfigServiceDefaultsResponse:
"""
Retrieves config service default values.
:param name: name of service to get defaults for
:return: get config service defaults
"""
request = GetConfigServiceDefaultsRequest(name=name)
return self.stub.GetConfigServiceDefaults(request)
def get_node_config_service_configs(
self, session_id: int
) -> GetNodeConfigServiceConfigsResponse:
"""
Retrieves all node config service configurations for a session.
:param session_id: session to get config service configurations for
:return: get node config service configs response
:raises grpc.RpcError: when session doesn't exist
"""
request = GetNodeConfigServiceConfigsRequest(session_id=session_id)
return self.stub.GetNodeConfigServiceConfigs(request)
def get_node_config_service(
self, session_id: int, node_id: int, name: str
) -> GetNodeConfigServiceResponse:
"""
Retrieves information for a specific config service on a node.
:param session_id: session node belongs to
:param node_id: id of node to get service information from
:param name: name of service
:return: get node config service response
:raises grpc.RpcError: when session or node doesn't exist
"""
request = GetNodeConfigServiceRequest(
session_id=session_id, node_id=node_id, name=name
)
@ -1185,25 +1192,92 @@ class CoreGrpcClient:
def get_node_config_services(
self, session_id: int, node_id: int
) -> GetNodeConfigServicesResponse:
"""
Retrieves the config services currently assigned to a node.
:param session_id: session node belongs to
:param node_id: id of node to get config services for
:return: get node config services response
:raises grpc.RpcError: when session or node doesn't exist
"""
request = GetNodeConfigServicesRequest(session_id=session_id, node_id=node_id)
return self.stub.GetNodeConfigServices(request)
def set_node_config_service(
self, session_id: int, node_id: int, name: str, config: Dict[str, str]
) -> SetNodeConfigServiceResponse:
"""
Assigns a config service to a node with the provided configuration.
:param session_id: session node belongs to
:param node_id: id of node to assign config service to
:param name: name of service
:param config: service configuration
:return: set node config service response
:raises grpc.RpcError: when session or node doesn't exist
"""
request = SetNodeConfigServiceRequest(
session_id=session_id, node_id=node_id, name=name, config=config
)
return self.stub.SetNodeConfigService(request)
def get_emane_event_channel(self, session_id: int) -> GetEmaneEventChannelResponse:
"""
Retrieves the current emane event channel being used for a session.
:param session_id: session to get emane event channel for
:return: emane event channel response
:raises grpc.RpcError: when session doesn't exist
"""
request = GetEmaneEventChannelRequest(session_id=session_id)
return self.stub.GetEmaneEventChannel(request)
def execute_script(self, script: str) -> ExecuteScriptResponse:
"""
Executes a python script given context of the current CoreEmu object.
:param script: script to execute
:return: execute script response
"""
request = ExecuteScriptRequest(script=script)
return self.stub.ExecuteScript(request)
def wlan_link(
self, session_id: int, wlan: int, node_one: int, node_two: int, linked: bool
) -> WlanLinkResponse:
"""
Links/unlinks nodes on the same WLAN.
:param session_id: session id containing wlan and nodes
:param wlan: wlan nodes must belong to
:param node_one: first node of pair to link/unlink
:param node_two: second node of pair to link/unlin
:param linked: True to link, False to unlink
:return: wlan link response
:raises grpc.RpcError: when session or one of the nodes do not exist
"""
request = WlanLinkRequest(
session_id=session_id,
wlan=wlan,
node_one=node_one,
node_two=node_two,
linked=linked,
)
return self.stub.WlanLink(request)
def emane_pathlosses(
self, pathloss_iterator: Iterable[EmanePathlossesRequest]
) -> EmanePathlossesResponse:
"""
Stream EMANE pathloss events.
:param pathloss_iterator: iterator for sending emane pathloss events
:return: emane pathloss response
:raises grpc.RpcError: when a pathloss event session or one of the nodes do not
exist
"""
return self.stub.EmanePathlosses(pathloss_iterator)
def connect(self) -> None:
"""
Open connection to server, must be closed manually.

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
"""
position = core_pb2.Position(x=event.x_position, y=event.y_position)
geo = core_pb2.Geo(lat=event.latitude, lon=event.longitude, alt=event.altitude)
node_proto = core_pb2.Node(
id=event.id,
name=event.name,
model=event.model,
position=position,
geo=geo,
services=event.services,
)
return core_pb2.NodeEvent(node=node_proto, source=event.source)

View file

@ -2,7 +2,9 @@ import logging
import time
from typing import Any, Dict, List, Tuple, Type
import grpc
import netaddr
from grpc import ServicerContext
from core import utils
from core.api.grpc import common_pb2, core_pb2
@ -13,7 +15,7 @@ from core.emulator.data import LinkData
from core.emulator.emudata import InterfaceData, LinkOptions, NodeOptions
from core.emulator.enumerations import LinkTypes, NodeTypes
from core.emulator.session import Session
from core.nodes.base import NodeBase
from core.nodes.base import CoreNode, NodeBase
from core.nodes.interface import CoreInterface
from core.services.coreservices import CoreService
@ -29,17 +31,19 @@ def add_node_data(node_proto: core_pb2.Node) -> Tuple[NodeTypes, int, NodeOption
"""
_id = node_proto.id
_type = NodeTypes(node_proto.type)
options = NodeOptions(name=node_proto.name, model=node_proto.model)
options.icon = node_proto.icon
options.opaque = node_proto.opaque
options.image = node_proto.image
options.services = node_proto.services
options.config_services = node_proto.config_services
options = NodeOptions(
name=node_proto.name,
model=node_proto.model,
icon=node_proto.icon,
opaque=node_proto.opaque,
image=node_proto.image,
services=node_proto.services,
config_services=node_proto.config_services,
)
if node_proto.emane:
options.emane = node_proto.emane
if node_proto.server:
options.server = node_proto.server
position = node_proto.position
options.set_position(position.x, position.y)
if node_proto.HasField("geo"):
@ -57,19 +61,17 @@ def link_interface(interface_proto: core_pb2.Interface) -> InterfaceData:
"""
interface = None
if interface_proto:
name = interface_proto.name
if name == "":
name = None
mac = interface_proto.mac
if mac == "":
mac = None
name = interface_proto.name if interface_proto.name else None
mac = interface_proto.mac if interface_proto.mac else None
ip4 = interface_proto.ip4 if interface_proto.ip4 else None
ip6 = interface_proto.ip6 if interface_proto.ip6 else None
interface = InterfaceData(
_id=interface_proto.id,
id=interface_proto.id,
name=name,
mac=mac,
ip4=interface_proto.ip4,
ip4=ip4,
ip4_mask=interface_proto.ip4mask,
ip6=interface_proto.ip6,
ip6=ip6,
ip6_mask=interface_proto.ip6mask,
)
return interface
@ -86,13 +88,8 @@ def add_link_data(
"""
interface_one = link_interface(link_proto.interface_one)
interface_two = link_interface(link_proto.interface_two)
link_type = None
link_type_value = link_proto.type
if link_type_value is not None:
link_type = LinkTypes(link_type_value)
options = LinkOptions(_type=link_type)
link_type = LinkTypes(link_proto.type)
options = LinkOptions(type=link_type)
options_data = link_proto.options
if options_data:
options.delay = options_data.delay
@ -106,7 +103,6 @@ def add_link_data(
options.unidirectional = options_data.unidirectional
options.key = options_data.key
options.opaque = options_data.opaque
return interface_one, interface_two, options
@ -123,7 +119,8 @@ def create_nodes(
funcs = []
for node_proto in node_protos:
_type, _id, options = add_node_data(node_proto)
args = (_type, _id, options)
_class = session.get_node_class(_type)
args = (_class, _id, options)
funcs.append((session.add_node, args, {}))
start = time.monotonic()
results, exceptions = utils.threadpool(funcs)
@ -234,6 +231,9 @@ def get_node_proto(session: Session, node: NodeBase) -> core_pb2.Node:
position = core_pb2.Position(
x=node.position.x, y=node.position.y, z=node.position.z
)
geo = core_pb2.Geo(
lat=node.position.lat, lon=node.position.lon, alt=node.position.alt
)
services = getattr(node, "services", [])
if services is None:
services = []
@ -254,6 +254,7 @@ def get_node_proto(session: Session, node: NodeBase) -> core_pb2.Node:
model=model,
type=node_type.value,
position=position,
geo=geo,
services=services,
icon=node.icon,
image=image,
@ -473,3 +474,23 @@ def interface_to_proto(interface: CoreInterface) -> core_pb2.Interface:
ip6=ip6,
ip6mask=ip6mask,
)
def get_nem_id(node: CoreNode, netif_id: int, context: ServicerContext) -> int:
"""
Get nem id for a given node and interface id.
:param node: node to get nem id for
:param netif_id: id of interface on node to get nem id for
:param context: request context
:return: nem id
"""
netif = node.netif(netif_id)
if not netif:
message = f"{node.name} missing interface {netif_id}"
context.abort(grpc.StatusCode.NOT_FOUND, message)
net = netif.net
if not isinstance(net, EmaneNet):
message = f"{node.name} interface {netif_id} is not an EMANE network"
context.abort(grpc.StatusCode.INVALID_ARGUMENT, message)
return net.getnemid(netif)

View file

@ -6,7 +6,7 @@ import tempfile
import threading
import time
from concurrent import futures
from typing import Type
from typing import Iterable, Type
import grpc
from grpc import ServicerContext
@ -39,6 +39,8 @@ from core.api.grpc.core_pb2 import ExecuteScriptResponse
from core.api.grpc.emane_pb2 import (
EmaneLinkRequest,
EmaneLinkResponse,
EmanePathlossesRequest,
EmanePathlossesResponse,
GetEmaneConfigRequest,
GetEmaneConfigResponse,
GetEmaneEventChannelRequest,
@ -102,15 +104,18 @@ from core.api.grpc.wlan_pb2 import (
GetWlanConfigsResponse,
SetWlanConfigRequest,
SetWlanConfigResponse,
WlanLinkRequest,
WlanLinkResponse,
)
from core.emulator.coreemu import CoreEmu
from core.emulator.data import LinkData
from core.emulator.emudata import LinkOptions, NodeOptions
from core.emulator.enumerations import EventTypes, LinkTypes, MessageFlags
from core.emulator.session import Session
from core.emulator.session import NT, Session
from core.errors import CoreCommandError, CoreError
from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility
from core.nodes.base import CoreNodeBase, NodeBase
from core.nodes.base import CoreNode, CoreNodeBase, NodeBase
from core.nodes.network import WlanNode
from core.services.coreservices import ServiceManager
_ONE_DAY_IN_SECONDS = 60 * 60 * 24
@ -170,21 +175,22 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
return session
def get_node(
self, session: Session, node_id: int, context: ServicerContext
) -> NodeBase:
self, session: Session, node_id: int, context: ServicerContext, _class: Type[NT]
) -> NT:
"""
Retrieve node given session and node id
:param session: session that has the node
:param node_id: node id
:param context:
:param context: request
:param _class: type of node we are expecting
:return: node object that satisfies. If node not found then raise an exception.
:raises Exception: raises grpc exception when node does not exist
"""
try:
return session.get_node(node_id)
except CoreError:
context.abort(grpc.StatusCode.NOT_FOUND, f"node {node_id} not found")
return session.get_node(node_id, _class)
except CoreError as e:
context.abort(grpc.StatusCode.NOT_FOUND, str(e))
def validate_service(
self, name: str, context: ServicerContext
@ -228,7 +234,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
# add all hooks
for hook in request.hooks:
state = EventTypes(hook.state)
session.add_hook(state, hook.file, None, hook.data)
session.add_hook(state, hook.file, hook.data)
# create nodes
_, exceptions = grpcutils.create_nodes(session, request.nodes)
@ -261,7 +267,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
# config service configs
for config in request.config_service_configs:
node = self.get_node(session, config.node_id, context)
node = self.get_node(session, config.node_id, context, CoreNode)
service = node.config_services[config.name]
if config.config:
service.set_config(config.config)
@ -663,7 +669,8 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
logging.debug("add node: %s", request)
session = self.get_session(request.session_id, context)
_type, _id, options = grpcutils.add_node_data(request.node)
node = session.add_node(_type=_type, _id=_id, options=options)
_class = session.get_node_class(_type)
node = session.add_node(_class, _id, options)
return core_pb2.AddNodeResponse(node_id=node.id)
def GetNode(
@ -678,7 +685,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
"""
logging.debug("get node: %s", request)
session = self.get_session(request.session_id, context)
node = self.get_node(session, request.node_id, context)
node = self.get_node(session, request.node_id, context, NodeBase)
interfaces = []
for interface_id in node._netif:
interface = node._netif[interface_id]
@ -687,6 +694,42 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
node_proto = grpcutils.get_node_proto(session, node)
return core_pb2.GetNodeResponse(node=node_proto, interfaces=interfaces)
def MoveNodes(
self,
request_iterator: Iterable[core_pb2.MoveNodesRequest],
context: ServicerContext,
) -> core_pb2.MoveNodesResponse:
"""
Stream node movements
:param request_iterator: move nodes request iterator
:param context: context object
:return: move nodes response
"""
for request in request_iterator:
if not request.WhichOneof("move_type"):
raise CoreError("move nodes must provide a move type")
session = self.get_session(request.session_id, context)
node = self.get_node(session, request.node_id, context, NodeBase)
options = NodeOptions()
has_geo = request.HasField("geo")
if has_geo:
logging.info("has geo")
lat = request.geo.lat
lon = request.geo.lon
alt = request.geo.alt
options.set_location(lat, lon, alt)
else:
x = request.position.x
y = request.position.y
logging.info("has pos: %s,%s", x, y)
options.set_position(x, y)
session.edit_node(node.id, options)
source = request.source if request.source else None
if not has_geo:
session.broadcast_node(node, source=source)
return core_pb2.MoveNodesResponse()
def EditNode(
self, request: core_pb2.EditNodeRequest, context: ServicerContext
) -> core_pb2.EditNodeResponse:
@ -699,9 +742,8 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
"""
logging.debug("edit node: %s", request)
session = self.get_session(request.session_id, context)
node = self.get_node(session, request.node_id, context)
options = NodeOptions()
options.icon = request.icon
node = self.get_node(session, request.node_id, context, NodeBase)
options = NodeOptions(icon=request.icon)
if request.HasField("position"):
x = request.position.x
y = request.position.y
@ -751,12 +793,14 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
"""
logging.debug("sending node command: %s", request)
session = self.get_session(request.session_id, context)
node = self.get_node(session, request.node_id, context)
node = self.get_node(session, request.node_id, context, CoreNode)
try:
output = node.cmd(request.command)
output = node.cmd(request.command, request.wait, request.shell)
return_code = 0
except CoreCommandError as e:
output = e.stderr
return core_pb2.NodeCommandResponse(output=output)
return_code = e.returncode
return core_pb2.NodeCommandResponse(output=output, return_code=return_code)
def GetNodeTerminal(
self, request: core_pb2.GetNodeTerminalRequest, context: ServicerContext
@ -770,7 +814,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
"""
logging.debug("getting node terminal: %s", request)
session = self.get_session(request.session_id, context)
node = self.get_node(session, request.node_id, context)
node = self.get_node(session, request.node_id, context, CoreNode)
terminal = node.termcmdstring("/bin/bash")
return core_pb2.GetNodeTerminalResponse(terminal=terminal)
@ -786,7 +830,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
"""
logging.debug("get node links: %s", request)
session = self.get_session(request.session_id, context)
node = self.get_node(session, request.node_id, context)
node = self.get_node(session, request.node_id, context, NodeBase)
links = get_links(node)
return core_pb2.GetNodeLinksResponse(links=links)
@ -803,14 +847,14 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
logging.debug("add link: %s", request)
# validate session and nodes
session = self.get_session(request.session_id, context)
self.get_node(session, request.link.node_one_id, context)
self.get_node(session, request.link.node_two_id, context)
self.get_node(session, request.link.node_one_id, context, NodeBase)
self.get_node(session, request.link.node_two_id, context, NodeBase)
node_one_id = request.link.node_one_id
node_two_id = request.link.node_two_id
interface_one, interface_two, options = grpcutils.add_link_data(request.link)
node_one_interface, node_two_interface = session.add_link(
node_one_id, node_two_id, interface_one, interface_two, link_options=options
node_one_id, node_two_id, interface_one, interface_two, options=options
)
interface_one_proto = None
interface_two_proto = None
@ -913,7 +957,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
session = self.get_session(request.session_id, context)
hook = request.hook
state = EventTypes(hook.state)
session.add_hook(state, hook.file, None, hook.data)
session.add_hook(state, hook.file, hook.data)
return core_pb2.AddHookResponse(result=True)
def GetMobilityConfigs(
@ -994,7 +1038,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
"""
logging.debug("mobility action: %s", request)
session = self.get_session(request.session_id, context)
node = self.get_node(session, request.node_id, context)
node = self.get_node(session, request.node_id, context, WlanNode)
result = True
if request.action == MobilityAction.START:
node.mobility.start()
@ -1121,7 +1165,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
"""
logging.debug("get node service file: %s", request)
session = self.get_session(request.session_id, context)
node = self.get_node(session, request.node_id, context)
node = self.get_node(session, request.node_id, context, CoreNode)
file_data = session.services.get_service_file(
node, request.service, request.file
)
@ -1176,7 +1220,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
"""
logging.debug("service action: %s", request)
session = self.get_session(request.session_id, context)
node = self.get_node(session, request.node_id, context)
node = self.get_node(session, request.node_id, context, CoreNode)
service = None
for current_service in node.services:
if current_service.name == request.service:
@ -1265,7 +1309,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
wlan_config.node_id, BasicRangeModel.name, wlan_config.config
)
if session.state == EventTypes.RUNTIME_STATE:
node = self.get_node(session, wlan_config.node_id, context)
node = self.get_node(session, wlan_config.node_id, context, WlanNode)
node.updatemodel(wlan_config.config)
return SetWlanConfigResponse(result=True)
@ -1546,7 +1590,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
:return: get node config service response
"""
session = self.get_session(request.session_id, context)
node = self.get_node(session, request.node_id, context)
node = self.get_node(session, request.node_id, context, CoreNode)
self.validate_service(request.name, context)
service = node.config_services.get(request.name)
if service:
@ -1628,7 +1672,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
:return: get node config services response
"""
session = self.get_session(request.session_id, context)
node = self.get_node(session, request.node_id, context)
node = self.get_node(session, request.node_id, context, CoreNode)
services = node.config_services.keys()
return GetNodeConfigServicesResponse(services=services)
@ -1643,7 +1687,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
:return: set node config service response
"""
session = self.get_session(request.session_id, context)
node = self.get_node(session, request.node_id, context)
node = self.get_node(session, request.node_id, context, CoreNode)
self.validate_service(request.name, context)
service = node.config_services.get(request.name)
if service:
@ -1684,3 +1728,45 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
if new_sessions:
new_session = new_sessions[0]
return ExecuteScriptResponse(session_id=new_session)
def WlanLink(
self, request: WlanLinkRequest, context: ServicerContext
) -> WlanLinkResponse:
session = self.get_session(request.session_id, context)
wlan = self.get_node(session, request.wlan, context, WlanNode)
if not isinstance(wlan.model, BasicRangeModel):
context.abort(
grpc.StatusCode.NOT_FOUND,
f"wlan node {request.wlan} does not using BasicRangeModel",
)
n1 = self.get_node(session, request.node_one, context, CoreNode)
n2 = self.get_node(session, request.node_two, context, CoreNode)
n1_netif, n2_netif = None, None
for net, netif1, netif2 in n1.commonnets(n2):
if net == wlan:
n1_netif = netif1
n2_netif = netif2
break
result = False
if n1_netif and n2_netif:
if request.linked:
wlan.link(n1_netif, n2_netif)
else:
wlan.unlink(n1_netif, n2_netif)
wlan.model.sendlinkmsg(n1_netif, n2_netif, unlink=not request.linked)
result = True
return WlanLinkResponse(result=result)
def EmanePathlosses(
self,
request_iterator: Iterable[EmanePathlossesRequest],
context: ServicerContext,
) -> EmanePathlossesResponse:
for request in request_iterator:
session = self.get_session(request.session_id, context)
n1 = self.get_node(session, request.node_one, context, CoreNode)
nem1 = grpcutils.get_nem_id(n1, request.interface_one_id, context)
n2 = self.get_node(session, request.node_two, context, CoreNode)
nem2 = grpcutils.get_nem_id(n2, request.interface_two_id, context)
session.emane.publish_pathloss(nem1, nem2, request.rx_one, request.rx_two)
return EmanePathlossesResponse()

View file

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

View file

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

View file

@ -6,7 +6,7 @@ import logging
import os
import threading
from collections import OrderedDict
from typing import TYPE_CHECKING, Dict, List, Set, Tuple, Type
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type
from core import utils
from core.config import ConfigGroup, Configuration, ModelManager
@ -19,18 +19,25 @@ from core.emane.linkmonitor import EmaneLinkMonitor
from core.emane.nodes import EmaneNet
from core.emane.rfpipe import EmaneRfPipeModel
from core.emane.tdma import EmaneTdmaModel
from core.emulator.enumerations import ConfigDataTypes, RegisterTlvs
from core.emulator.data import LinkData
from core.emulator.enumerations import (
ConfigDataTypes,
LinkTypes,
MessageFlags,
RegisterTlvs,
)
from core.errors import CoreCommandError, CoreError
from core.nodes.base import CoreNode
from core.nodes.base import CoreNode, NodeBase
from core.nodes.interface import CoreInterface
from core.nodes.network import CtrlNet
from core.nodes.physical import Rj45Node
from core.xml import emanexml
if TYPE_CHECKING:
from core.emulator.session import Session
try:
from emane.events import EventService
from emane.events import EventService, PathlossEvent
from emane.events import LocationEvent
from emane.events.eventserviceexception import EventServiceException
except ImportError:
@ -41,6 +48,7 @@ except ImportError:
except ImportError:
EventService = None
LocationEvent = None
PathlossEvent = None
EventServiceException = None
logging.debug("compatible emane python bindings not installed")
@ -458,7 +466,7 @@ class EmaneManager(ModelManager):
model_class = self.models[model_name]
emane_node.setmodel(model_class, config)
def nemlookup(self, nemid) -> Tuple[EmaneNet, CoreInterface]:
def nemlookup(self, nemid) -> Tuple[Optional[EmaneNet], Optional[CoreInterface]]:
"""
Look for the given numerical NEM ID and return the first matching
EMANE network and NEM interface.
@ -476,6 +484,29 @@ class EmaneManager(ModelManager):
return emane_node, netif
def get_nem_link(
self, nem1: int, nem2: int, flags: MessageFlags = MessageFlags.NONE
) -> Optional[LinkData]:
emane1, netif = self.nemlookup(nem1)
if not emane1 or not netif:
logging.error("invalid nem: %s", nem1)
return None
node1 = netif.node
emane2, netif = self.nemlookup(nem2)
if not emane2 or not netif:
logging.error("invalid nem: %s", nem2)
return None
node2 = netif.node
color = self.session.get_link_color(emane1.id)
return LinkData(
message_type=flags,
node1_id=node1.id,
node2_id=node2.id,
network_id=emane1.id,
link_type=LinkTypes.WIRELESS,
color=color,
)
def numnems(self) -> int:
"""
Return the number of NEMs emulated locally.
@ -567,7 +598,7 @@ class EmaneManager(ModelManager):
run_emane_on_host = False
for node in self.getnodes():
if hasattr(node, "transport_type") and node.transport_type == "raw":
if isinstance(node, Rj45Node):
run_emane_on_host = True
continue
path = self.session.session_dir
@ -626,7 +657,7 @@ class EmaneManager(ModelManager):
kill_transortd = "killall -q emanetransportd"
stop_emane_on_host = False
for node in self.getnodes():
if hasattr(node, "transport_type") and node.transport_type == "raw":
if isinstance(node, Rj45Node):
stop_emane_on_host = True
continue
@ -801,8 +832,8 @@ class EmaneManager(ModelManager):
zbit_check = z.bit_length() > 16 or z < 0
if any([xbit_check, ybit_check, zbit_check]):
logging.error(
"Unable to build node location message, received lat/long/alt exceeds coordinate "
"space: NEM %s (%d, %d, %d)",
"Unable to build node location message, received lat/long/alt "
"exceeds coordinate space: NEM %s (%d, %d, %d)",
nemid,
x,
y,
@ -812,7 +843,7 @@ class EmaneManager(ModelManager):
# generate a node message for this location update
try:
node = self.session.get_node(n)
node = self.session.get_node(n, NodeBase)
except CoreError:
logging.exception(
"location event NEM %s has no corresponding node %s", nemid, n
@ -836,9 +867,23 @@ class EmaneManager(ModelManager):
result = True
except CoreCommandError:
result = False
return result
def publish_pathloss(self, nem1: int, nem2: int, rx1: float, rx2: float) -> None:
"""
Publish pathloss events between provided nems, using provided rx power.
:param nem1: interface one for pathloss
:param nem2: interface two for pathloss
:param rx1: received power from nem2 to nem1
:param rx2: received power from nem1 to nem2
:return: nothing
"""
event = PathlossEvent()
event.append(nem1, forward=rx1)
event.append(nem2, forward=rx2)
self.service.publish(nem1, event)
self.service.publish(nem2, event)
class EmaneGlobalModel:
"""

View file

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

View file

@ -285,26 +285,11 @@ class EmaneLinkMonitor:
def send_link(self, message_type: MessageFlags, link_id: Tuple[int, int]) -> None:
nem_one, nem_two = link_id
emane_one, netif = self.emane_manager.nemlookup(nem_one)
if not emane_one or not netif:
logging.error("invalid nem: %s", nem_one)
return
node_one = netif.node
emane_two, netif = self.emane_manager.nemlookup(nem_two)
if not emane_two or not netif:
logging.error("invalid nem: %s", nem_two)
return
node_two = netif.node
logging.debug(
"%s emane link from %s(%s) to %s(%s)",
message_type.name,
node_one.name,
nem_one,
node_two.name,
nem_two,
)
label = self.get_link_label(link_id)
self.send_message(message_type, label, node_one.id, node_two.id, emane_one.id)
link = self.emane_manager.get_nem_link(nem_one, nem_two, message_type)
if link:
label = self.get_link_label(link_id)
link.label = label
self.emane_manager.session.broadcast_link(link)
def send_message(
self,

View file

@ -6,8 +6,16 @@ share the same MAC+PHY model.
import logging
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Type
from core.emulator.data import LinkData
from core.emulator.distributed import DistributedServer
from core.emulator.enumerations import LinkTypes, NodeTypes, RegisterTlvs
from core.emulator.emudata import LinkOptions
from core.emulator.enumerations import (
LinkTypes,
MessageFlags,
NodeTypes,
RegisterTlvs,
TransportType,
)
from core.nodes.base import CoreNetworkBase
from core.nodes.interface import CoreInterface
@ -48,35 +56,19 @@ class EmaneNet(CoreNetworkBase):
) -> None:
super().__init__(session, _id, name, start, server)
self.conf = ""
self.up = False
self.nemidmap = {}
self.model = None
self.mobility = None
def linkconfig(
self,
netif: CoreInterface,
bw: float = None,
delay: float = None,
loss: float = None,
duplicate: float = None,
jitter: float = None,
netif2: CoreInterface = None,
self, netif: CoreInterface, options: LinkOptions, netif2: CoreInterface = None
) -> None:
"""
The CommEffect model supports link configuration.
"""
if not self.model:
return
self.model.linkconfig(
netif=netif,
bw=bw,
delay=delay,
loss=loss,
duplicate=duplicate,
jitter=jitter,
netif2=netif2,
)
self.model.linkconfig(netif, options, netif2)
def config(self, conf: str) -> None:
self.conf = conf
@ -181,7 +173,7 @@ class EmaneNet(CoreNetworkBase):
emanetransportd terminates.
"""
for netif in self.netifs():
if "virtual" in netif.transport_type.lower():
if netif.transport_type == TransportType.VIRTUAL:
netif.shutdown()
netif.poshook = None
@ -204,6 +196,7 @@ class EmaneNet(CoreNetworkBase):
lat, lon, alt = self.session.location.getgeo(x, y, z)
if node.position.alt is not None:
alt = node.position.alt
node.position.set_geo(lon, lat, alt)
# altitude must be an integer or warning is printed
alt = int(round(alt))
return nemid, lon, lat, alt
@ -245,3 +238,27 @@ class EmaneNet(CoreNetworkBase):
nemid, lon, lat, alt = position
event.append(nemid, latitude=lat, longitude=lon, altitude=alt)
self.session.emane.service.publish(0, event)
def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]:
links = super().all_link_data(flags)
# gather current emane links
nem_ids = set(self.nemidmap.values())
emane_manager = self.session.emane
emane_links = emane_manager.link_monitor.links
considered = set()
for link_key in emane_links:
considered_key = tuple(sorted(link_key))
if considered_key in considered:
continue
considered.add(considered_key)
nem1, nem2 = considered_key
# ignore links not related to this node
if nem1 not in nem_ids and nem2 not in nem_ids:
continue
# ignore incomplete links
if (nem2, nem1) not in emane_links:
continue
link = emane_manager.get_nem_link(nem1, nem2)
if link:
links.append(link)
return links

View file

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

View file

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

View file

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

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
from core import utils
from core.api.grpc.core_pb2 import LinkOptions
from core.emane.nodes import EmaneNet
from core.emulator.enumerations import LinkTypes
from core.nodes.base import CoreNetworkBase, CoreNode
from core.nodes.interface import CoreInterface
from core.nodes.physical import PhysicalNode
class IdGen:
def __init__(self, _id: int = 0) -> None:
self.id = _id
def next(self) -> int:
self.id += 1
return self.id
def link_config(
network: CoreNetworkBase,
interface: CoreInterface,
link_options: LinkOptions,
devname: str = None,
interface_two: CoreInterface = None,
) -> None:
"""
Convenience method for configuring a link,
:param network: network to configure link for
:param interface: interface to configure
:param link_options: data to configure link with
:param devname: device name, default is None
:param interface_two: other interface associated, default is None
:return: nothing
"""
config = {
"netif": interface,
"bw": link_options.bandwidth,
"delay": link_options.delay,
"loss": link_options.per,
"duplicate": link_options.dup,
"jitter": link_options.jitter,
"netif2": interface_two,
}
# hacky check here, because physical and emane nodes do not conform to the same
# linkconfig interface
if not isinstance(network, (EmaneNet, PhysicalNode)):
config["devname"] = devname
network.linkconfig(**config)
if TYPE_CHECKING:
from core.nodes.base import CoreNode
@dataclass
class NodeOptions:
"""
Options for creating and updating nodes within core.
"""
def __init__(self, name: str = None, model: str = "PC", image: str = None) -> None:
"""
Create a NodeOptions object.
:param name: name of node, defaults to node class name postfix with its id
:param model: defines services for default and physical nodes, defaults to
"router"
:param image: image to use for docker nodes
"""
self.name = name
self.model = model
self.canvas = None
self.icon = None
self.opaque = None
self.services = []
self.config_services = []
self.x = None
self.y = None
self.lat = None
self.lon = None
self.alt = None
self.emulation_id = None
self.server = None
self.image = image
self.emane = None
name: str = None
model: Optional[str] = "PC"
canvas: int = None
icon: str = None
opaque: str = None
services: List[str] = field(default_factory=list)
config_services: List[str] = field(default_factory=list)
x: float = None
y: float = None
lat: float = None
lon: float = None
alt: float = None
emulation_id: int = None
server: str = None
image: str = None
emane: str = None
def set_position(self, x: float, y: float) -> None:
"""
@ -111,117 +58,56 @@ class NodeOptions:
self.alt = alt
@dataclass
class LinkOptions:
"""
Options for creating and updating links within core.
"""
def __init__(self, _type: LinkTypes = LinkTypes.WIRED) -> None:
"""
Create a LinkOptions object.
:param _type: type of link, defaults to
wired
"""
self.type = _type
self.session = None
self.delay = None
self.bandwidth = None
self.per = None
self.dup = None
self.jitter = None
self.mer = None
self.burst = None
self.mburst = None
self.gui_attributes = None
self.unidirectional = None
self.emulation_id = None
self.network_id = None
self.key = None
self.opaque = None
type: LinkTypes = LinkTypes.WIRED
session: int = None
delay: int = None
bandwidth: int = None
per: float = None
dup: int = None
jitter: int = None
mer: int = None
burst: int = None
mburst: int = None
gui_attributes: str = None
unidirectional: bool = None
emulation_id: int = None
network_id: int = None
key: int = None
opaque: str = None
@dataclass
class InterfaceData:
"""
Convenience class for storing interface data.
"""
def __init__(
self,
_id: int,
name: str,
mac: str,
ip4: str,
ip4_mask: int,
ip6: str,
ip6_mask: int,
) -> None:
"""
Creates an InterfaceData object.
:param _id: interface id
:param name: name for interface
:param mac: mac address
:param ip4: ipv4 address
:param ip4_mask: ipv4 bit mask
:param ip6: ipv6 address
:param ip6_mask: ipv6 bit mask
"""
self.id = _id
self.name = name
self.mac = mac
self.ip4 = ip4
self.ip4_mask = ip4_mask
self.ip6 = ip6
self.ip6_mask = ip6_mask
def has_ip4(self) -> bool:
"""
Determines if interface has an ip4 address.
:return: True if has ip4, False otherwise
"""
return all([self.ip4, self.ip4_mask])
def has_ip6(self) -> bool:
"""
Determines if interface has an ip6 address.
:return: True if has ip6, False otherwise
"""
return all([self.ip6, self.ip6_mask])
def ip4_address(self) -> Optional[str]:
"""
Retrieve a string representation of the ip4 address and netmask.
:return: ip4 string or None
"""
if self.has_ip4():
return f"{self.ip4}/{self.ip4_mask}"
else:
return None
def ip6_address(self) -> Optional[str]:
"""
Retrieve a string representation of the ip6 address and netmask.
:return: ip4 string or None
"""
if self.has_ip6():
return f"{self.ip6}/{self.ip6_mask}"
else:
return None
id: int = None
name: str = None
mac: str = None
ip4: str = None
ip4_mask: int = None
ip6: str = None
ip6_mask: int = None
def get_addresses(self) -> List[str]:
"""
Returns a list of ip4 and ip6 address when present.
Returns a list of ip4 and ip6 addresses when present.
:return: list of addresses
"""
ip4 = self.ip4_address()
ip6 = self.ip6_address()
return [i for i in [ip4, ip6] if i]
addresses = []
if self.ip4 and self.ip4_mask:
addresses.append(f"{self.ip4}/{self.ip4_mask}")
if self.ip6 and self.ip6_mask:
addresses.append(f"{self.ip6}/{self.ip6_mask}")
return addresses
class IpPrefixes:
@ -247,30 +133,63 @@ class IpPrefixes:
if ip6_prefix:
self.ip6 = netaddr.IPNetwork(ip6_prefix)
def ip4_address(self, node: CoreNode) -> str:
def ip4_address(self, node_id: int) -> str:
"""
Convenience method to return the IP4 address for a node.
:param node: node to get IP4 address for
:param node_id: node id to get IP4 address for
:return: IP4 address or None
"""
if not self.ip4:
raise ValueError("ip4 prefixes have not been set")
return str(self.ip4[node.id])
return str(self.ip4[node_id])
def ip6_address(self, node: CoreNode) -> str:
def ip6_address(self, node_id: int) -> str:
"""
Convenience method to return the IP6 address for a node.
:param node: node to get IP6 address for
:param node_id: node id to get IP6 address for
:return: IP4 address or None
"""
if not self.ip6:
raise ValueError("ip6 prefixes have not been set")
return str(self.ip6[node.id])
return str(self.ip6[node_id])
def gen_interface(self, node_id: int, name: str = None, mac: str = None):
"""
Creates interface data for linking nodes, using the nodes unique id for
generation, along with a random mac address, unless provided.
:param node_id: node id to create an interface for
:param name: name to set for interface, default is eth{id}
:param mac: mac address to use for this interface, default is random
generation
:return: new interface data for the provided node
"""
# generate ip4 data
ip4 = None
ip4_mask = None
if self.ip4:
ip4 = self.ip4_address(node_id)
ip4_mask = self.ip4.prefixlen
# generate ip6 data
ip6 = None
ip6_mask = None
if self.ip6:
ip6 = self.ip6_address(node_id)
ip6_mask = self.ip6.prefixlen
# random mac
if not mac:
mac = utils.random_mac()
return InterfaceData(
name=name, ip4=ip4, ip4_mask=ip4_mask, ip6=ip6, ip6_mask=ip6_mask, mac=mac
)
def create_interface(
self, node: CoreNode, name: str = None, mac: str = None
self, node: "CoreNode", name: str = None, mac: str = None
) -> InterfaceData:
"""
Creates interface data for linking nodes, using the nodes unique id for
@ -282,54 +201,6 @@ class IpPrefixes:
generation
:return: new interface data for the provided node
"""
# interface id
inteface_id = node.newifindex()
# generate ip4 data
ip4 = None
ip4_mask = None
if self.ip4:
ip4 = self.ip4_address(node)
ip4_mask = self.ip4.prefixlen
# generate ip6 data
ip6 = None
ip6_mask = None
if self.ip6:
ip6 = self.ip6_address(node)
ip6_mask = self.ip6.prefixlen
# random mac
if not mac:
mac = utils.random_mac()
return InterfaceData(
_id=inteface_id,
name=name,
ip4=ip4,
ip4_mask=ip4_mask,
ip6=ip6,
ip6_mask=ip6_mask,
mac=mac,
)
def create_interface(
node: CoreNode, network: CoreNetworkBase, interface_data: InterfaceData
):
"""
Create an interface for a node on a network using provided interface data.
:param node: node to create interface for
:param network: network to associate interface with
:param interface_data: interface data
:return: created interface
"""
node.newnetif(
network,
addrlist=interface_data.get_addresses(),
hwaddr=interface_data.mac,
ifindex=interface_data.id,
ifname=interface_data.name,
)
return node.netif(interface_data.id)
interface = self.gen_interface(node.id, name, mac)
interface.id = node.newifindex()
return interface

View file

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

View file

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

View file

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

View file

@ -1,7 +1,7 @@
import logging
import math
import tkinter as tk
from tkinter import font, ttk
from tkinter import PhotoImage, font, ttk
from tkinter.ttk import Progressbar
import grpc
@ -104,7 +104,7 @@ class Application(ttk.Frame):
self.rowconfigure(0, weight=1)
self.columnconfigure(1, weight=1)
self.grid(sticky="nsew")
self.toolbar = Toolbar(self, self)
self.toolbar = Toolbar(self)
self.toolbar.grid(sticky="ns")
self.right_frame = ttk.Frame(self)
self.right_frame.columnconfigure(0, weight=1)
@ -113,16 +113,15 @@ class Application(ttk.Frame):
self.draw_canvas()
self.draw_status()
self.progress = Progressbar(self.right_frame, mode="indeterminate")
self.menubar = Menubar(self.master, self)
self.menubar = Menubar(self)
self.master.config(menu=self.menubar)
def draw_canvas(self) -> None:
width = self.guiconfig.preferences.width
height = self.guiconfig.preferences.height
canvas_frame = ttk.Frame(self.right_frame)
canvas_frame.rowconfigure(0, weight=1)
canvas_frame.columnconfigure(0, weight=1)
canvas_frame.grid(sticky="nsew", pady=1)
self.canvas = CanvasGraph(canvas_frame, self, self.core, width, height)
self.canvas = CanvasGraph(canvas_frame, self, self.core)
self.canvas.grid(sticky="nsew")
scroll_y = ttk.Scrollbar(canvas_frame, command=self.canvas.yview)
scroll_y.grid(row=0, column=1, sticky="ns")
@ -150,6 +149,8 @@ class Application(ttk.Frame):
self.after(0, lambda: ErrorDialog(self, title, message).show())
def on_closing(self) -> None:
if self.toolbar.picker:
self.toolbar.picker.destroy()
self.menubar.prompt_save_running_session(True)
def save_config(self) -> None:
@ -161,5 +162,11 @@ class Application(ttk.Frame):
else:
self.toolbar.set_design()
def get_icon(self, image_enum: ImageEnum, width: int) -> PhotoImage:
return Images.get(image_enum, int(width * self.app_scale))
def get_custom_icon(self, image_file: str, width: int) -> PhotoImage:
return Images.get_custom(image_file, int(width * self.app_scale))
def close(self) -> None:
self.master.destroy()

View file

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

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

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

View file

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

View file

@ -104,7 +104,8 @@ class ObserverDialog(Dialog):
self.observers.insert(tk.END, name)
self.name.set("")
self.cmd.set("")
self.app.menubar.draw_custom_observers()
self.app.menubar.observers_menu.draw_custom()
self.app.toolbar.observers_menu.draw_custom()
else:
messagebox.showerror("Observer Error", f"{name} already exists")
@ -132,7 +133,8 @@ class ObserverDialog(Dialog):
self.observers.selection_clear(0, tk.END)
self.save_button.config(state=tk.DISABLED)
self.delete_button.config(state=tk.DISABLED)
self.app.menubar.draw_custom_observers()
self.app.menubar.observers_menu.draw_custom()
self.app.toolbar.observers_menu.draw_custom()
def handle_observer_change(self, event: tk.Event):
selection = self.observers.curselection()

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

@ -23,23 +23,13 @@ from core.gui.dialogs.sessionoptions import SessionOptionsDialog
from core.gui.dialogs.sessions import SessionsDialog
from core.gui.dialogs.throughput import ThroughputDialog
from core.gui.nodeutils import ICON_SIZE
from core.gui.observers import ObserversMenu
from core.gui.task import ProgressTask
if TYPE_CHECKING:
from core.gui.app import Application
MAX_FILES = 3
OBSERVERS = {
"List Processes": "ps",
"Show Interfaces": "ip address",
"IPV4 Routes": "ip -4 route",
"IPV6 Routes": "ip -6 route",
"Listening Sockets": "ss -tuwnl",
"IPv4 MFC Entries": "ip -4 mroute show",
"IPv6 MFC Entries": "ip -6 mroute show",
"Firewall Rules": "iptables -L",
"IPSec Policies": "setkey -DP",
}
class Menubar(tk.Menu):
@ -47,20 +37,17 @@ class Menubar(tk.Menu):
Core menubar
"""
def __init__(self, master: tk.Tk, app: "Application", **kwargs) -> None:
def __init__(self, app: "Application") -> None:
"""
Create a CoreMenubar instance
"""
super().__init__(master, **kwargs)
self.master.config(menu=self)
super().__init__(app)
self.app = app
self.core = app.core
self.canvas = app.canvas
self.recent_menu = None
self.edit_menu = None
self.observers_menu = None
self.observers_var = tk.StringVar(value=tk.NONE)
self.observers_custom_index = None
self.draw()
def draw(self) -> None:
@ -202,42 +189,9 @@ class Menubar(tk.Menu):
"""
Create observer widget menu item and create the sub menu items inside
"""
self.observers_menu = tk.Menu(widget_menu)
self.observers_menu.add_command(
label="Edit Observers", command=self.click_edit_observer_widgets
)
self.observers_menu.add_separator()
self.observers_menu.add_radiobutton(
label="None",
variable=self.observers_var,
value="none",
command=lambda: self.core.set_observer(None),
)
for name in sorted(OBSERVERS):
cmd = OBSERVERS[name]
self.observers_menu.add_radiobutton(
label=name,
variable=self.observers_var,
value=name,
command=partial(self.core.set_observer, cmd),
)
self.observers_custom_index = self.observers_menu.index(tk.END) + 1
self.draw_custom_observers()
self.observers_menu = ObserversMenu(widget_menu, self.app)
widget_menu.add_cascade(label="Observer Widgets", menu=self.observers_menu)
def draw_custom_observers(self) -> None:
current_observers_index = self.observers_menu.index(tk.END) + 1
if self.observers_custom_index < current_observers_index:
self.observers_menu.delete(self.observers_custom_index, tk.END)
for name in sorted(self.core.custom_observers):
observer = self.core.custom_observers[name]
self.observers_menu.add_radiobutton(
label=name,
variable=self.observers_var,
value=name,
command=partial(self.core.set_observer, observer.cmd),
)
def create_adjacency_menu(self, widget_menu: tk.Menu) -> None:
"""
Create adjacency menu item and the sub menu items inside

View file

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

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):
def __init__(self, master: tk.Widget, app: "Application", **kwargs):
super().__init__(master, **kwargs)
def __init__(self, master: tk.Widget, app: "Application"):
super().__init__(master)
self.app = app
self.status = None
self.statusvar = tk.StringVar()

View file

@ -5,20 +5,23 @@ from functools import partial
from tkinter import ttk
from typing import TYPE_CHECKING, Callable
from PIL.ImageTk import PhotoImage
from core.api.grpc import core_pb2
from core.gui.dialogs.marker import MarkerDialog
from core.gui.dialogs.colorpicker import ColorPickerDialog
from core.gui.dialogs.runtool import RunToolDialog
from core.gui.graph import tags
from core.gui.graph.enums import GraphMode
from core.gui.graph.shapeutils import ShapeType, is_marker
from core.gui.images import ImageEnum, Images
from core.gui.images import ImageEnum
from core.gui.nodeutils import NodeDraw, NodeUtils
from core.gui.observers import ObserversMenu
from core.gui.task import ProgressTask
from core.gui.themes import Styles
from core.gui.tooltip import Tooltip
if TYPE_CHECKING:
from core.gui.app import Application
from PIL import ImageTk
TOOLBAR_SIZE = 32
PICKER_SIZE = 24
@ -30,8 +33,124 @@ class NodeTypeEnum(Enum):
OTHER = 2
def icon(image_enum, width=TOOLBAR_SIZE):
return Images.get(image_enum, width)
def enable_buttons(frame: ttk.Frame, enabled: bool) -> None:
state = tk.NORMAL if enabled else tk.DISABLED
for child in frame.winfo_children():
child.configure(state=state)
class PickerFrame(ttk.Frame):
def __init__(self, app: "Application", button: ttk.Button) -> None:
super().__init__(app)
self.app = app
self.button = button
def create_node_button(self, node_draw: NodeDraw, func: Callable) -> None:
self.create_button(
node_draw.label, func, node_draw.image_enum, node_draw.image_file
)
def create_button(
self,
label: str,
func: Callable,
image_enum: ImageEnum = None,
image_file: str = None,
) -> None:
if image_enum:
bar_image = self.app.get_icon(image_enum, TOOLBAR_SIZE)
image = self.app.get_icon(image_enum, PICKER_SIZE)
else:
bar_image = self.app.get_custom_icon(image_file, TOOLBAR_SIZE)
image = self.app.get_custom_icon(image_file, PICKER_SIZE)
button = ttk.Button(
self, image=image, text=label, compound=tk.TOP, style=Styles.picker_button
)
button.image = image
button.bind("<ButtonRelease-1>", lambda e: func(bar_image))
button.grid(pady=1)
def show(self) -> None:
self.button.after(0, self._show)
def _show(self) -> None:
x = self.button.winfo_width() + 1
y = self.button.winfo_rooty() - self.app.winfo_rooty() - 1
self.place(x=x, y=y)
self.app.bind_all("<ButtonRelease-1>", lambda e: self.destroy())
self.wait_visibility()
self.grab_set()
self.wait_window()
self.app.unbind_all("<ButtonRelease-1>")
class ButtonBar(ttk.Frame):
def __init__(self, master: tk.Widget, app: "Application"):
super().__init__(master)
self.app = app
self.radio_buttons = []
def create_button(
self, image_enum: ImageEnum, func: Callable, tooltip: str, radio: bool = False
) -> ttk.Button:
image = self.app.get_icon(image_enum, TOOLBAR_SIZE)
button = ttk.Button(self, image=image, command=func)
button.image = image
button.grid(sticky="ew")
Tooltip(button, tooltip)
if radio:
self.radio_buttons.append(button)
return button
def select_radio(self, selected: ttk.Button) -> None:
for button in self.radio_buttons:
button.state(["!pressed"])
selected.state(["pressed"])
class MarkerFrame(ttk.Frame):
PAD = 3
def __init__(self, master: tk.BaseWidget, app: "Application") -> None:
super().__init__(master, padding=self.PAD)
self.app = app
self.color = "#000000"
self.size = tk.DoubleVar()
self.color_frame = None
self.draw()
def draw(self) -> None:
self.columnconfigure(0, weight=1)
image = self.app.get_icon(ImageEnum.DELETE, 16)
button = ttk.Button(self, image=image, width=2, command=self.click_clear)
button.image = image
button.grid(sticky="ew", pady=self.PAD)
Tooltip(button, "Delete Marker")
sizes = [1, 3, 8, 10]
self.size.set(sizes[0])
sizes = ttk.Combobox(
self, state="readonly", textvariable=self.size, value=sizes, width=2
)
sizes.grid(sticky="ew", pady=self.PAD)
Tooltip(sizes, "Marker Size")
frame_size = TOOLBAR_SIZE
self.color_frame = tk.Frame(
self, background=self.color, height=frame_size, width=frame_size
)
self.color_frame.grid(sticky="ew")
self.color_frame.bind("<Button-1>", self.click_color)
Tooltip(self.color_frame, "Marker Color")
def click_clear(self):
self.app.canvas.delete(tags.MARKER)
def click_color(self, _event: tk.Event) -> None:
dialog = ColorPickerDialog(self.app, self.app, self.color)
self.color = dialog.askcolor()
self.color_frame.config(background=self.color)
class Toolbar(ttk.Frame):
@ -39,11 +158,11 @@ class Toolbar(ttk.Frame):
Core toolbar class
"""
def __init__(self, master: tk.Widget, app: "Application", **kwargs):
def __init__(self, app: "Application") -> None:
"""
Create a CoreToolbar instance
"""
super().__init__(master, **kwargs)
super().__init__(app)
self.app = app
# design buttons
@ -63,316 +182,193 @@ class Toolbar(ttk.Frame):
# frames
self.design_frame = None
self.runtime_frame = None
self.node_picker = None
self.network_picker = None
self.annotation_picker = None
self.marker_frame = None
self.picker = None
# dialog
self.marker_tool = None
# observers
self.observers_menu = None
# these variables help keep track of what images being drawn so that scaling
# is possible since ImageTk.PhotoImage does not have resize method
self.node_enum = None
self.network_enum = None
self.annotation_enum = None
# is possible since PhotoImage does not have resize method
self.current_node = NodeUtils.NODES[0]
self.current_network = NodeUtils.NETWORK_NODES[0]
self.current_annotation = ShapeType.MARKER
self.annotation_enum = ImageEnum.MARKER
# draw components
self.draw()
def get_icon(self, image_enum, width=TOOLBAR_SIZE):
return Images.get(image_enum, int(width * self.app.app_scale))
def draw(self):
def draw(self) -> None:
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
self.draw_design_frame()
self.draw_runtime_frame()
self.design_frame.tkraise()
self.marker_frame = MarkerFrame(self, self.app)
def draw_design_frame(self):
self.design_frame = ttk.Frame(self)
def draw_design_frame(self) -> None:
self.design_frame = ButtonBar(self, self.app)
self.design_frame.grid(row=0, column=0, sticky="nsew")
self.design_frame.columnconfigure(0, weight=1)
self.play_button = self.create_button(
self.design_frame,
self.get_icon(ImageEnum.START),
self.click_start,
"start the session",
self.play_button = self.design_frame.create_button(
ImageEnum.START, self.click_start, "Start Session"
)
self.select_button = self.create_button(
self.design_frame,
self.get_icon(ImageEnum.SELECT),
self.click_selection,
"selection tool",
self.select_button = self.design_frame.create_button(
ImageEnum.SELECT, self.click_selection, "Selection Tool", radio=True
)
self.link_button = self.create_button(
self.design_frame,
self.get_icon(ImageEnum.LINK),
self.click_link,
"link tool",
self.link_button = self.design_frame.create_button(
ImageEnum.LINK, self.click_link, "Link Tool", radio=True
)
self.node_button = self.design_frame.create_button(
self.current_node.image_enum,
self.draw_node_picker,
"Container Nodes",
radio=True,
)
self.network_button = self.design_frame.create_button(
self.current_network.image_enum,
self.draw_network_picker,
"Link Layer Nodes",
radio=True,
)
self.annotation_button = self.design_frame.create_button(
self.annotation_enum,
self.draw_annotation_picker,
"Annotation Tools",
radio=True,
)
self.create_node_button()
self.create_network_button()
self.create_annotation_button()
def design_select(self, button: ttk.Button):
logging.debug("selecting design button: %s", button)
self.select_button.state(["!pressed"])
self.link_button.state(["!pressed"])
self.node_button.state(["!pressed"])
self.network_button.state(["!pressed"])
self.annotation_button.state(["!pressed"])
button.state(["pressed"])
def runtime_select(self, button: ttk.Button):
logging.debug("selecting runtime button: %s", button)
self.runtime_select_button.state(["!pressed"])
self.stop_button.state(["!pressed"])
self.runtime_marker_button.state(["!pressed"])
self.run_command_button.state(["!pressed"])
button.state(["pressed"])
def draw_runtime_frame(self):
self.runtime_frame = ttk.Frame(self)
def draw_runtime_frame(self) -> None:
self.runtime_frame = ButtonBar(self, self.app)
self.runtime_frame.grid(row=0, column=0, sticky="nsew")
self.runtime_frame.columnconfigure(0, weight=1)
self.stop_button = self.create_button(
self.runtime_frame,
self.get_icon(ImageEnum.STOP),
self.click_stop,
"stop the session",
self.stop_button = self.runtime_frame.create_button(
ImageEnum.STOP, self.click_stop, "Stop Session"
)
self.runtime_select_button = self.create_button(
self.runtime_frame,
self.get_icon(ImageEnum.SELECT),
self.click_runtime_selection,
"selection tool",
self.runtime_select_button = self.runtime_frame.create_button(
ImageEnum.SELECT, self.click_runtime_selection, "Selection Tool", radio=True
)
self.runtime_marker_button = self.create_button(
self.runtime_frame,
icon(ImageEnum.MARKER),
self.click_marker_button,
"marker",
self.create_observe_button()
self.runtime_marker_button = self.runtime_frame.create_button(
ImageEnum.MARKER, self.click_marker_button, "Marker Tool", radio=True
)
self.run_command_button = self.create_button(
self.runtime_frame, icon(ImageEnum.RUN), self.click_run_button, "run"
self.run_command_button = self.runtime_frame.create_button(
ImageEnum.RUN, self.click_run_button, "Run Tool"
)
def draw_node_picker(self):
self.hide_pickers()
self.node_picker = ttk.Frame(self.master)
def draw_node_picker(self) -> None:
self.hide_marker()
self.app.canvas.mode = GraphMode.NODE
self.app.canvas.node_draw = self.current_node
self.design_frame.select_radio(self.node_button)
self.picker = PickerFrame(self.app, self.node_button)
# draw default nodes
for node_draw in NodeUtils.NODES:
toolbar_image = self.get_icon(node_draw.image_enum, TOOLBAR_SIZE)
image = self.get_icon(node_draw.image_enum, PICKER_SIZE)
func = partial(
self.update_button,
self.node_button,
toolbar_image,
node_draw,
NodeTypeEnum.NODE,
node_draw.image_enum,
self.update_button, self.node_button, node_draw, NodeTypeEnum.NODE
)
self.create_picker_button(image, func, self.node_picker, node_draw.label)
self.picker.create_node_button(node_draw, func)
# draw custom nodes
for name in sorted(self.app.core.custom_nodes):
node_draw = self.app.core.custom_nodes[name]
toolbar_image = Images.get_custom(
node_draw.image_file, int(TOOLBAR_SIZE * self.app.app_scale)
)
image = Images.get_custom(
node_draw.image_file, int(PICKER_SIZE * self.app.app_scale)
)
func = partial(
self.update_button,
self.node_button,
toolbar_image,
node_draw,
NodeTypeEnum,
node_draw.image_file,
self.update_button, self.node_button, node_draw, NodeTypeEnum.NODE
)
self.create_picker_button(image, func, self.node_picker, name)
self.design_select(self.node_button)
self.node_button.after(
0, lambda: self.show_picker(self.node_button, self.node_picker)
)
self.picker.create_node_button(node_draw, func)
self.picker.show()
def show_picker(self, button: ttk.Button, picker: ttk.Frame):
x = self.winfo_width() + 1
y = button.winfo_rooty() - picker.master.winfo_rooty() - 1
picker.place(x=x, y=y)
self.app.bind_all("<ButtonRelease-1>", lambda e: self.hide_pickers())
picker.wait_visibility()
picker.grab_set()
self.wait_window(picker)
self.app.unbind_all("<ButtonRelease-1>")
def create_picker_button(
self, image: "ImageTk.PhotoImage", func: Callable, frame: ttk.Frame, label: str
):
"""
Create button and put it on the frame
:param image: button image
:param func: the command that is executed when button is clicked
:param frame: frame that contains the button
:param label: button label
"""
button = ttk.Button(
frame, image=image, text=label, compound=tk.TOP, style=Styles.picker_button
)
button.image = image
button.bind("<ButtonRelease-1>", lambda e: func())
button.grid(pady=1)
def create_button(
self,
frame: ttk.Frame,
image: "ImageTk.PhotoImage",
func: Callable,
tooltip: str,
):
button = ttk.Button(frame, image=image, command=func)
button.image = image
button.grid(sticky="ew")
Tooltip(button, tooltip)
return button
def click_selection(self):
logging.debug("clicked selection tool")
self.design_select(self.select_button)
def click_selection(self) -> None:
self.design_frame.select_radio(self.select_button)
self.app.canvas.mode = GraphMode.SELECT
self.hide_marker()
def click_runtime_selection(self):
logging.debug("clicked selection tool")
self.runtime_select(self.runtime_select_button)
def click_runtime_selection(self) -> None:
self.runtime_frame.select_radio(self.runtime_select_button)
self.app.canvas.mode = GraphMode.SELECT
self.hide_marker()
def click_start(self):
def click_start(self) -> None:
"""
Start session handler redraw buttons, send node and link messages to grpc
server.
"""
self.app.menubar.change_menubar_item_state(is_runtime=True)
self.app.canvas.mode = GraphMode.SELECT
enable_buttons(self.design_frame, enabled=False)
task = ProgressTask(
self.app, "Start", self.app.core.start_session, self.start_callback
)
task.start()
def start_callback(self, response: core_pb2.StartSessionResponse):
def start_callback(self, response: core_pb2.StartSessionResponse) -> None:
if response.result:
self.set_runtime()
self.app.core.set_metadata()
self.app.core.show_mobility_players()
else:
enable_buttons(self.design_frame, enabled=True)
message = "\n".join(response.exceptions)
self.app.show_error("Start Session Error", message)
def set_runtime(self):
def set_runtime(self) -> None:
enable_buttons(self.runtime_frame, enabled=True)
self.runtime_frame.tkraise()
self.click_runtime_selection()
self.hide_marker()
def set_design(self):
def set_design(self) -> None:
enable_buttons(self.design_frame, enabled=True)
self.design_frame.tkraise()
self.click_selection()
self.hide_marker()
def click_link(self):
logging.debug("Click LINK button")
self.design_select(self.link_button)
def click_link(self) -> None:
self.design_frame.select_radio(self.link_button)
self.app.canvas.mode = GraphMode.EDGE
self.hide_marker()
def update_button(
self,
button: ttk.Button,
image: "ImageTk",
node_draw: NodeDraw,
type_enum,
image_enum,
):
type_enum: NodeTypeEnum,
image: PhotoImage,
) -> None:
logging.debug("update button(%s): %s", button, node_draw)
self.hide_pickers()
button.configure(image=image)
button.image = image
self.app.canvas.mode = GraphMode.NODE
self.app.canvas.node_draw = node_draw
if type_enum == NodeTypeEnum.NODE:
self.node_enum = image_enum
self.current_node = node_draw
elif type_enum == NodeTypeEnum.NETWORK:
self.network_enum = image_enum
self.current_network = node_draw
def hide_pickers(self):
logging.debug("hiding pickers")
if self.node_picker:
self.node_picker.destroy()
self.node_picker = None
if self.network_picker:
self.network_picker.destroy()
self.network_picker = None
if self.annotation_picker:
self.annotation_picker.destroy()
self.annotation_picker = None
def create_node_button(self):
"""
Create network layer button
"""
image = self.get_icon(ImageEnum.ROUTER, TOOLBAR_SIZE)
self.node_button = ttk.Button(
self.design_frame, image=image, command=self.draw_node_picker
)
self.node_button.image = image
self.node_button.grid(sticky="ew")
Tooltip(self.node_button, "Network-layer virtual nodes")
self.node_enum = ImageEnum.ROUTER
def draw_network_picker(self):
def draw_network_picker(self) -> None:
"""
Draw the options for link-layer button.
"""
self.hide_pickers()
self.network_picker = ttk.Frame(self.master)
self.hide_marker()
self.app.canvas.mode = GraphMode.NODE
self.app.canvas.node_draw = self.current_network
self.design_frame.select_radio(self.network_button)
self.picker = PickerFrame(self.app, self.network_button)
for node_draw in NodeUtils.NETWORK_NODES:
toolbar_image = self.get_icon(node_draw.image_enum, TOOLBAR_SIZE)
image = self.get_icon(node_draw.image_enum, PICKER_SIZE)
self.create_picker_button(
image,
partial(
self.update_button,
self.network_button,
toolbar_image,
node_draw,
NodeTypeEnum.NETWORK,
node_draw.image_enum,
),
self.network_picker,
node_draw.label,
func = partial(
self.update_button, self.network_button, node_draw, NodeTypeEnum.NETWORK
)
self.design_select(self.network_button)
self.network_button.after(
0, lambda: self.show_picker(self.network_button, self.network_picker)
)
self.picker.create_node_button(node_draw, func)
self.picker.show()
def create_network_button(self):
"""
Create link-layer node button and the options that represent different
link-layer node types.
"""
image = self.get_icon(ImageEnum.HUB, TOOLBAR_SIZE)
self.network_button = ttk.Button(
self.design_frame, image=image, command=self.draw_network_picker
)
self.network_button.image = image
self.network_button.grid(sticky="ew")
Tooltip(self.network_button, "link-layer nodes")
self.network_enum = ImageEnum.HUB
def draw_annotation_picker(self):
def draw_annotation_picker(self) -> None:
"""
Draw the options for marker button.
"""
self.hide_pickers()
self.annotation_picker = ttk.Frame(self.master)
self.design_frame.select_radio(self.annotation_button)
self.app.canvas.mode = GraphMode.ANNOTATION
self.app.canvas.annotation_type = self.current_annotation
if is_marker(self.current_annotation):
self.show_marker()
self.picker = PickerFrame(self.app, self.annotation_button)
nodes = [
(ImageEnum.MARKER, ShapeType.MARKER),
(ImageEnum.OVAL, ShapeType.OVAL),
@ -380,114 +376,90 @@ class Toolbar(ttk.Frame):
(ImageEnum.TEXT, ShapeType.TEXT),
]
for image_enum, shape_type in nodes:
toolbar_image = self.get_icon(image_enum, TOOLBAR_SIZE)
image = self.get_icon(image_enum, PICKER_SIZE)
self.create_picker_button(
image,
partial(self.update_annotation, toolbar_image, shape_type, image_enum),
self.annotation_picker,
shape_type.value,
)
self.design_select(self.annotation_button)
self.annotation_button.after(
0, lambda: self.show_picker(self.annotation_button, self.annotation_picker)
)
label = shape_type.value
func = partial(self.update_annotation, shape_type, image_enum)
self.picker.create_button(label, func, image_enum)
self.picker.show()
def create_annotation_button(self):
"""
Create marker button and options that represent different marker types
"""
image = self.get_icon(ImageEnum.MARKER, TOOLBAR_SIZE)
self.annotation_button = ttk.Button(
self.design_frame, image=image, command=self.draw_annotation_picker
)
self.annotation_button.image = image
self.annotation_button.grid(sticky="ew")
Tooltip(self.annotation_button, "background annotation tools")
self.annotation_enum = ImageEnum.MARKER
def create_observe_button(self):
def create_observe_button(self) -> None:
image = self.app.get_icon(ImageEnum.OBSERVE, TOOLBAR_SIZE)
menu_button = ttk.Menubutton(
self.runtime_frame, image=icon(ImageEnum.OBSERVE), direction=tk.RIGHT
self.runtime_frame, image=image, direction=tk.RIGHT
)
menu_button.image = image
menu_button.grid(sticky="ew")
menu = tk.Menu(menu_button, tearoff=0)
menu_button["menu"] = menu
menu.add_command(label="None")
menu.add_command(label="processes")
menu.add_command(label="ifconfig")
menu.add_command(label="IPv4 routes")
menu.add_command(label="IPv6 routes")
menu.add_command(label="OSPFv2 neighbors")
menu.add_command(label="OSPFv3 neighbors")
menu.add_command(label="Listening sockets")
menu.add_command(label="IPv4 MFC entries")
menu.add_command(label="IPv6 MFC entries")
menu.add_command(label="firewall rules")
menu.add_command(label="IPSec policies")
menu.add_command(label="docker logs")
menu.add_command(label="OSPFv3 MDR level")
menu.add_command(label="PIM neighbors")
menu.add_command(label="Edit...")
self.observers_menu = ObserversMenu(menu_button, self.app)
menu_button["menu"] = self.observers_menu
def click_stop(self):
def click_stop(self) -> None:
"""
redraw buttons on the toolbar, send node and link messages to grpc server
"""
logging.info("clicked stop button")
self.app.menubar.change_menubar_item_state(is_runtime=False)
self.app.core.close_mobility_players()
enable_buttons(self.runtime_frame, enabled=False)
task = ProgressTask(
self.app, "Stop", self.app.core.stop_session, self.stop_callback
)
task.start()
def stop_callback(self, response: core_pb2.StopSessionResponse):
def stop_callback(self, response: core_pb2.StopSessionResponse) -> None:
self.set_design()
self.app.canvas.stopped_session()
def update_annotation(
self, image: "ImageTk.PhotoImage", shape_type: ShapeType, image_enum
):
logging.debug("clicked annotation: ")
self.hide_pickers()
self, shape_type: ShapeType, image_enum: ImageEnum, image: PhotoImage
) -> None:
logging.debug("clicked annotation")
self.annotation_button.configure(image=image)
self.annotation_button.image = image
self.app.canvas.mode = GraphMode.ANNOTATION
self.app.canvas.annotation_type = shape_type
self.current_annotation = shape_type
self.annotation_enum = image_enum
if is_marker(shape_type):
if self.marker_tool:
self.marker_tool.destroy()
self.marker_tool = MarkerDialog(self.app)
self.marker_tool.show()
self.show_marker()
else:
self.hide_marker()
def click_run_button(self):
def hide_marker(self) -> None:
self.marker_frame.grid_forget()
def show_marker(self) -> None:
self.marker_frame.grid()
def click_run_button(self) -> None:
logging.debug("Click on RUN button")
dialog = RunToolDialog(self.app)
dialog.show()
def click_marker_button(self):
logging.debug("Click on marker button")
self.runtime_select(self.runtime_marker_button)
def click_marker_button(self) -> None:
self.runtime_frame.select_radio(self.runtime_marker_button)
self.app.canvas.mode = GraphMode.ANNOTATION
self.app.canvas.annotation_type = ShapeType.MARKER
if self.marker_tool:
self.marker_tool.destroy()
self.marker_tool = MarkerDialog(self.app)
self.marker_tool.show()
self.show_marker()
def scale_button(self, button, image_enum):
image = icon(image_enum, int(TOOLBAR_SIZE * self.app.app_scale))
button.config(image=image)
button.image = image
def scale_button(
self, button: ttk.Button, image_enum: ImageEnum = None, image_file: str = None
) -> None:
image = None
if image_enum:
image = self.app.get_icon(image_enum, TOOLBAR_SIZE)
elif image_file:
image = self.app.get_custom_icon(image_file, TOOLBAR_SIZE)
if image:
button.config(image=image)
button.image = image
def scale(self):
def scale(self) -> None:
self.scale_button(self.play_button, ImageEnum.START)
self.scale_button(self.select_button, ImageEnum.SELECT)
self.scale_button(self.link_button, ImageEnum.LINK)
self.scale_button(self.node_button, self.node_enum)
self.scale_button(self.network_button, self.network_enum)
if self.current_node.image_enum:
self.scale_button(self.node_button, self.current_node.image_enum)
else:
self.scale_button(self.node_button, image_file=self.current_node.image_file)
self.scale_button(self.network_button, self.current_network.image_enum)
self.scale_button(self.annotation_button, self.annotation_enum)
self.scale_button(self.runtime_select_button, ImageEnum.SELECT)
self.scale_button(self.stop_button, ImageEnum.STOP)

View file

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

View file

@ -6,6 +6,7 @@ import logging
from typing import Tuple
import pyproj
from pyproj import Transformer
from core.emulator.enumerations import RegisterTlvs
@ -20,21 +21,23 @@ class GeoLocation:
defined projections.
"""
name = "location"
config_type = RegisterTlvs.UTILITY
name: str = "location"
config_type: RegisterTlvs = RegisterTlvs.UTILITY
def __init__(self) -> None:
"""
Creates a GeoLocation instance.
"""
self.to_pixels = pyproj.Transformer.from_crs(
self.to_pixels: Transformer = pyproj.Transformer.from_crs(
CRS_WGS84, CRS_PROJ, always_xy=True
)
self.to_geo = pyproj.Transformer.from_crs(CRS_PROJ, CRS_WGS84, always_xy=True)
self.refproj = (0.0, 0.0)
self.refgeo = (0.0, 0.0, 0.0)
self.refxyz = (0.0, 0.0, 0.0)
self.refscale = 1.0
self.to_geo: Transformer = pyproj.Transformer.from_crs(
CRS_PROJ, CRS_WGS84, always_xy=True
)
self.refproj: Tuple[float, float, float] = (0.0, 0.0, 0.0)
self.refgeo: Tuple[float, float, float] = (0.0, 0.0, 0.0)
self.refxyz: Tuple[float, float, float] = (0.0, 0.0, 0.0)
self.refscale: float = 1.0
def setrefgeo(self, lat: float, lon: float, alt: float) -> None:
"""
@ -58,7 +61,7 @@ class GeoLocation:
self.refxyz = (0.0, 0.0, 0.0)
self.refgeo = (0.0, 0.0, 0.0)
self.refscale = 1.0
self.refproj = self.to_pixels.transform(self.refgeo[0], self.refgeo[1])
self.refproj = self.to_pixels.transform(*self.refgeo)
def pixels2meters(self, value: float) -> float:
"""

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -5,7 +5,7 @@ sdt.py: Scripted Display Tool (SDT3D) helper
import logging
import socket
import threading
from typing import TYPE_CHECKING, Optional
from typing import IO, TYPE_CHECKING, Dict, Optional, Set, Tuple
from urllib.parse import urlparse
from core import constants
@ -42,11 +42,11 @@ class Sdt:
when a node position or link has changed.
"""
DEFAULT_SDT_URL = "tcp://127.0.0.1:50000/"
DEFAULT_SDT_URL: str = "tcp://127.0.0.1:50000/"
# default altitude (in meters) for flyto view
DEFAULT_ALT = 2500
DEFAULT_ALT: int = 2500
# TODO: read in user"s nodes.conf here; below are default node types from the GUI
DEFAULT_SPRITES = [
DEFAULT_SPRITES: Dict[str, str] = [
("router", "router.gif"),
("host", "host.gif"),
("PC", "pc.gif"),
@ -65,14 +65,14 @@ class Sdt:
:param session: session this manager is tied to
"""
self.session = session
self.lock = threading.Lock()
self.sock = None
self.connected = False
self.url = self.DEFAULT_SDT_URL
self.address = None
self.protocol = None
self.network_layers = set()
self.session: "Session" = session
self.lock: threading.Lock = threading.Lock()
self.sock: Optional[IO] = None
self.connected: bool = False
self.url: str = self.DEFAULT_SDT_URL
self.address: Optional[Tuple[Optional[str], Optional[int]]] = None
self.protocol: Optional[str] = None
self.network_layers: Set[str] = set()
self.session.node_handlers.append(self.handle_node_update)
self.session.link_handlers.append(self.handle_link_update)
@ -344,7 +344,7 @@ class Sdt:
"""
result = False
try:
node = self.session.get_node(node_id)
node = self.session.get_node(node_id, NodeBase)
result = isinstance(node, (WlanNode, EmaneNet))
except CoreError:
pass

View file

@ -39,7 +39,7 @@ class ServiceDependencies:
that all services will be booted and that all dependencies exist within the services provided.
"""
def __init__(self, services: List["CoreService"]) -> None:
def __init__(self, services: List[Type["CoreService"]]) -> None:
# helpers to check validity
self.dependents = {}
self.booted = set()

View file

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

View file

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

View file

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

View file

@ -158,19 +158,6 @@ def which(command: str, required: bool) -> str:
return found_path
def make_tuple(obj: Generic[T]) -> Tuple[T]:
"""
Create a tuple from an object, or return the object itself.
:param obj: object to convert to a tuple
:return: converted tuple or the object itself
"""
if hasattr(obj, "__iter__"):
return tuple(obj)
else:
return (obj,)
def make_tuple_fromstr(s: str, value_type: Callable[[str], T]) -> Tuple[T]:
"""
Create a tuple from a string.
@ -228,17 +215,21 @@ def cmd(
if shell is False:
args = shlex.split(args)
try:
p = Popen(args, stdout=PIPE, stderr=PIPE, env=env, cwd=cwd, shell=shell)
output = PIPE if wait else DEVNULL
p = Popen(args, stdout=output, stderr=output, env=env, cwd=cwd, shell=shell)
if wait:
stdout, stderr = p.communicate()
stdout = stdout.decode("utf-8").strip()
stderr = stderr.decode("utf-8").strip()
status = p.wait()
if status != 0:
raise CoreCommandError(status, args, stdout, stderr)
return stdout.decode("utf-8").strip()
return stdout
else:
return ""
except OSError:
raise CoreCommandError(-1, args)
except OSError as e:
logging.error("cmd error: %s", e.strerror)
raise CoreCommandError(1, args, "", e.strerror)
def file_munge(pathname: str, header: str, text: str) -> None:

View file

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

View file

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

View file

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

View file

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

View file

@ -2,7 +2,9 @@ import logging
from core.emulator.coreemu import CoreEmu
from core.emulator.emudata import IpPrefixes, NodeOptions
from core.emulator.enumerations import EventTypes, NodeTypes
from core.emulator.enumerations import EventTypes
from core.nodes.base import CoreNode
from core.nodes.docker import DockerNode
if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG)
@ -15,11 +17,11 @@ if __name__ == "__main__":
options = NodeOptions(model=None, image="ubuntu")
# create node one
node_one = session.add_node(_type=NodeTypes.DOCKER, options=options)
node_one = session.add_node(DockerNode, options=options)
interface_one = prefixes.create_interface(node_one)
# create node two
node_two = session.add_node()
node_two = session.add_node(CoreNode)
interface_two = prefixes.create_interface(node_two)
# add link

View file

@ -2,7 +2,8 @@ import logging
from core.emulator.coreemu import CoreEmu
from core.emulator.emudata import IpPrefixes, NodeOptions
from core.emulator.enumerations import EventTypes, NodeTypes
from core.emulator.enumerations import EventTypes
from core.nodes.docker import DockerNode
if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG)
@ -17,11 +18,11 @@ if __name__ == "__main__":
options = NodeOptions(model=None, image="ubuntu")
# create node one
node_one = session.add_node(_type=NodeTypes.DOCKER, options=options)
node_one = session.add_node(DockerNode, options=options)
interface_one = prefixes.create_interface(node_one)
# create node two
node_two = session.add_node(_type=NodeTypes.DOCKER, options=options)
node_two = session.add_node(DockerNode, options=options)
interface_two = prefixes.create_interface(node_two)
# add link

View file

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

View file

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

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
from core.api.grpc import client, core_pb2
from core.api.grpc import client
from core.api.grpc.core_pb2 import Node, NodeType, Position, SessionState
def log_event(event):
@ -8,8 +13,11 @@ def log_event(event):
def main():
core = client.CoreGrpcClient()
# helper to create interface addresses
interface_helper = client.InterfaceHelper(ip4_prefix="10.83.0.0/24")
# create grpc client and start connection context, which auto closes connection
core = client.CoreGrpcClient()
with core.context_connect():
# create session
response = core.create_session()
@ -19,38 +27,41 @@ def main():
session_id = response.session_id
core.events(session_id, log_event)
# change session state
response = core.set_session_state(
session_id, core_pb2.SessionState.CONFIGURATION
)
# change session state to configuration so that nodes get started when added
response = core.set_session_state(session_id, SessionState.CONFIGURATION)
logging.info("set session state: %s", response)
# create switch node
switch = core_pb2.Node(type=core_pb2.NodeType.SWITCH)
position = Position(x=200, y=200)
switch = Node(type=NodeType.SWITCH, position=position)
response = core.add_node(session_id, switch)
logging.info("created switch: %s", response)
switch_id = response.node_id
# helper to create interfaces
interface_helper = client.InterfaceHelper(ip4_prefix="10.83.0.0/16")
# create node one
position = Position(x=100, y=100)
node1 = Node(type=NodeType.DEFAULT, position=position)
response = core.add_node(session_id, node1)
logging.info("created node: %s", response)
node1_id = response.node_id
for i in range(2):
# create node
position = core_pb2.Position(x=50 + 50 * i, y=50)
node = core_pb2.Node(position=position)
response = core.add_node(session_id, node)
logging.info("created node: %s", response)
node_id = response.node_id
# create node two
position = Position(x=300, y=100)
node2 = Node(type=NodeType.DEFAULT, position=position)
response = core.add_node(session_id, node2)
logging.info("created node: %s", response)
node2_id = response.node_id
# create link
interface_one = interface_helper.create_interface(node_id, 0)
response = core.add_link(session_id, node_id, switch_id, interface_one)
logging.info("created link: %s", response)
# links nodes to switch
interface_one = interface_helper.create_interface(node1_id, 0)
response = core.add_link(session_id, node1_id, switch_id, interface_one)
logging.info("created link: %s", response)
interface_one = interface_helper.create_interface(node2_id, 0)
response = core.add_link(session_id, node2_id, switch_id, interface_one)
logging.info("created link: %s", response)
# change session state
response = core.set_session_state(
session_id, core_pb2.SessionState.INSTANTIATION
)
response = core.set_session_state(session_id, SessionState.INSTANTIATION)
logging.info("set session state: %s", response)

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.emudata import IpPrefixes, NodeOptions
from core.emulator.enumerations import EventTypes, NodeTypes
from core.emulator.enumerations import EventTypes
from core.nodes.base import CoreNode
from core.nodes.lxd import LxcNode
if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG)
@ -15,11 +17,11 @@ if __name__ == "__main__":
options = NodeOptions(image="ubuntu")
# create node one
node_one = session.add_node(_type=NodeTypes.LXC, options=options)
node_one = session.add_node(LxcNode, options=options)
interface_one = prefixes.create_interface(node_one)
# create node two
node_two = session.add_node()
node_two = session.add_node(CoreNode)
interface_two = prefixes.create_interface(node_two)
# add link

View file

@ -2,7 +2,8 @@ import logging
from core.emulator.coreemu import CoreEmu
from core.emulator.emudata import IpPrefixes, NodeOptions
from core.emulator.enumerations import EventTypes, NodeTypes
from core.emulator.enumerations import EventTypes
from core.nodes.lxd import LxcNode
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
@ -17,11 +18,11 @@ if __name__ == "__main__":
options = NodeOptions(image="ubuntu:18.04")
# create node one
node_one = session.add_node(_type=NodeTypes.LXC, options=options)
node_one = session.add_node(LxcNode, options=options)
interface_one = prefixes.create_interface(node_one)
# create node two
node_two = session.add_node(_type=NodeTypes.LXC, options=options)
node_two = session.add_node(LxcNode, options=options)
interface_two = prefixes.create_interface(node_two)
# add link

View file

@ -2,7 +2,10 @@ import logging
from core.emulator.coreemu import CoreEmu
from core.emulator.emudata import IpPrefixes, NodeOptions
from core.emulator.enumerations import EventTypes, NodeTypes
from core.emulator.enumerations import EventTypes
from core.nodes.base import CoreNode
from core.nodes.lxd import LxcNode
from core.nodes.network import SwitchNode
if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG)
@ -16,18 +19,18 @@ if __name__ == "__main__":
options = NodeOptions(image="ubuntu")
# create switch
switch = session.add_node(_type=NodeTypes.SWITCH)
switch = session.add_node(SwitchNode)
# node one
node_one = session.add_node(_type=NodeTypes.LXC, options=options)
node_one = session.add_node(LxcNode, options=options)
interface_one = prefixes.create_interface(node_one)
# node two
node_two = session.add_node(_type=NodeTypes.LXC, options=options)
node_two = session.add_node(LxcNode, options=options)
interface_two = prefixes.create_interface(node_two)
# node three
node_three = session.add_node()
node_three = session.add_node(CoreNode)
interface_three = prefixes.create_interface(node_three)
# add links

View file

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

View file

@ -8,7 +8,8 @@ import logging
from core.emulator.coreemu import CoreEmu
from core.emulator.emudata import IpPrefixes, NodeOptions
from core.emulator.enumerations import EventTypes, NodeTypes
from core.emulator.enumerations import EventTypes
from core.nodes.lxd import LxcNode
def parse(name):
@ -42,9 +43,9 @@ def main(args):
# create local node, switch, and remote nodes
options = NodeOptions(image="ubuntu:18.04")
node_one = session.add_node(_type=NodeTypes.LXC, options=options)
node_one = session.add_node(LxcNode, options=options)
options.server = server_name
node_two = session.add_node(_type=NodeTypes.LXC, options=options)
node_two = session.add_node(LxcNode, options=options)
# create node interfaces and link
interface_one = prefixes.create_interface(node_one)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -90,3 +90,16 @@ message EmaneModelConfig {
string model = 3;
map<string, string> config = 4;
}
message EmanePathlossesRequest {
int32 session_id = 1;
int32 node_one = 2;
float rx_one = 3;
int32 interface_one_id = 4;
int32 node_two = 5;
float rx_two = 6;
int32 interface_two_id = 7;
}
message EmanePathlossesResponse {
}

View file

@ -34,3 +34,15 @@ message SetWlanConfigRequest {
message SetWlanConfigResponse {
bool result = 1;
}
message WlanLinkRequest {
int32 session_id = 1;
int32 wlan = 2;
int32 node_one = 3;
int32 node_two = 4;
bool linked = 5;
}
message WlanLinkResponse {
bool result = 1;
}

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

View file

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
import argparse
import re
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
node types in a CORE installation.

View file

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
"""
coresendmsg: utility for generating CORE messages
"""
@ -19,7 +19,7 @@ def print_available_tlvs(t, tlv_class):
"""
print(f"TLVs available for {t} message:")
for tlv in sorted([tlv for tlv in tlv_class.tlv_type_map], key=lambda x: x.name):
print(f"{tlv.value}:{tlv.name}")
print(tlv.name.lower())
def print_examples(name):
@ -27,27 +27,26 @@ def print_examples(name):
Print example usage of this script.
"""
examples = [
("link n1number=2 n2number=3 delay=15000",
"set a 15ms delay on the link between n2 and n3"),
("link n1number=2 n2number=3 guiattr=\"color=blue\"",
"change the color of the link between n2 and n3"),
("node number=3 xpos=125 ypos=525",
("node number=3 x_position=125 y_position=525",
"move node number 3 to x,y=(125,525)"),
("node number=4 icon=/usr/local/share/core/icons/normal/router_red.gif",
"change node number 4\"s icon to red"),
("node flags=add number=5 type=0 name=\"n5\" xpos=500 ypos=500",
("node flags=add number=5 type=0 name=\"n5\" x_position=500 y_position=500",
"add a new router node n5"),
("link flags=add n1number=4 n2number=5 if1ip4=\"10.0.3.2\" " \
"if1ip4mask=24 if2ip4=\"10.0.3.1\" if2ip4mask=24",
("link n1_number=2 n2_number=3 delay=15000",
"set a 15ms delay on the link between n2 and n3"),
("link n1_number=2 n2_number=3 gui_attributes=\"color=blue\"",
"change the color of the link between n2 and n3"),
("link flags=add n1_number=4 n2_number=5 interface1_ip4=\"10.0.3.2\" "
"interface1_ip4_mask=24 interface2_ip4=\"10.0.3.1\" interface2_ip4_mask=24",
"link node n5 with n4 using the given interface addresses"),
("exec flags=str,txt node=1 num=1000 cmd=\"uname -a\" -l",
("execute flags=string,text node=1 number=1000 command=\"uname -a\" -l",
"run a command on node 1 and wait for the result"),
("exec node=2 num=1001 cmd=\"killall ospfd\"",
("execute node=2 number=1001 command=\"killall ospfd\"",
"run a command on node 2 and ignore the result"),
("file flags=add node=1 name=\"/var/log/test.log\" data=\"Hello World.\"",
("file flags=add node=1 name=\"/var/log/test.log\" data=\"hello world.\"",
"write a test.log file on node 1 with the given contents"),
("file flags=add node=2 name=\"test.log\" " \
"srcname=\"./test.log\"",
("file flags=add node=2 name=\"test.log\" source_name=\"./test.log\"",
"move a test.log file from host to node 2"),
]
print(f"Example {name} invocations:")
@ -152,12 +151,16 @@ def main():
"""
Parse command-line arguments to build and send a CORE message.
"""
types = [message_type.name for message_type in MessageTypes]
flags = [flag.name for flag in MessageFlags]
usagestr = "usage: %prog [-h|-H] [options] [message-type] [flags=flags] "
usagestr += "[message-TLVs]\n\n"
usagestr += f"Supported message types:\n {types}\n"
usagestr += f"Supported message flags (flags=f1,f2,...):\n {flags}"
types = [message_type.name.lower() for message_type in MessageTypes]
flags = [flag.name.lower() for flag in MessageFlags]
types_usage = " ".join(types)
flags_usage = " ".join(flags)
usagestr = (
"usage: %prog [-h|-H] [options] [message-type] [flags=flags] "
"[message-TLVs]\n\n"
f"Supported message types:\n {types_usage}\n"
f"Supported message flags (flags=f1,f2,...):\n {flags_usage}"
)
parser = optparse.OptionParser(usage=usagestr)
default_address = "localhost"
default_session = None
@ -171,7 +174,6 @@ def main():
tlvs=False,
tcp=default_tcp
)
parser.add_option("-H", dest="examples", action="store_true",
help="show example usage help message and exit")
parser.add_option("-p", "--port", dest="port", type=int,
@ -188,9 +190,9 @@ def main():
help=f"Use TCP instead of UDP and connect to a session default: {default_tcp}")
def usage(msg=None, err=0):
sys.stdout.write("\n")
print()
if msg:
sys.stdout.write(msg + "\n\n")
print(f"{msg}\n")
parser.print_help()
sys.exit(err)
@ -204,9 +206,10 @@ def main():
# given a message type t, determine the message and TLV classes
t = args.pop(0)
t = t.lower()
if t not in types:
usage(f"Unknown message type requested: {t}")
message_type = MessageTypes[t]
message_type = MessageTypes[t.upper()]
msg_cls = coreapi.CLASS_MAP[message_type.value]
tlv_cls = msg_cls.tlv_class
@ -222,26 +225,23 @@ def main():
typevalue = a.split("=")
if len(typevalue) < 2:
usage(f"Use \"type=value\" syntax instead of \"{a}\".")
tlv_typestr = typevalue[0]
tlv_typestr = typevalue[0].lower()
tlv_valstr = "=".join(typevalue[1:])
if tlv_typestr == "flags":
flagstr = tlv_valstr
continue
tlv_name = tlv_typestr
try:
tlv_type = tlv_cls.tlv_type_map[tlv_name]
tlv_type = tlv_cls.tlv_type_map[tlv_typestr.upper()]
tlvdata += tlv_cls.pack_string(tlv_type.value, tlv_valstr)
except KeyError:
usage(f"Unknown TLV: \"{tlv_name}\"")
usage(f"Unknown TLV: \"{tlv_typestr}\"")
flags = 0
for f in flagstr.split(","):
if f == "":
continue
try:
flag_enum = MessageFlags[f]
flag_enum = MessageFlags[f.upper()]
n = flag_enum.value
flags |= n
except KeyError:

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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