Merge pull request #344 from coreemu/enhancement/type-hinting

Enhancement/type hinting
This commit is contained in:
bharnden 2020-01-15 13:38:06 -08:00 committed by GitHub
commit e574968e38
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 2101 additions and 1299 deletions

View file

@ -5,6 +5,7 @@ gRpc client for interfacing with CORE, when gRPC mode is enabled.
import logging
import threading
from contextlib import contextmanager
from typing import Any, Callable, Dict, Generator, List
import grpc
import netaddr
@ -18,7 +19,7 @@ class InterfaceHelper:
Convenience class to help generate IP4 and IP6 addresses for gRPC clients.
"""
def __init__(self, ip4_prefix=None, ip6_prefix=None):
def __init__(self, ip4_prefix: str = None, ip6_prefix: str = None) -> None:
"""
Creates an InterfaceHelper object.
@ -36,7 +37,7 @@ class InterfaceHelper:
if ip6_prefix:
self.ip6 = netaddr.IPNetwork(ip6_prefix)
def ip4_address(self, node_id):
def ip4_address(self, node_id: int) -> str:
"""
Convenience method to return the IP4 address for a node.
@ -48,7 +49,7 @@ class InterfaceHelper:
raise ValueError("ip4 prefixes have not been set")
return str(self.ip4[node_id])
def ip6_address(self, node_id):
def ip6_address(self, node_id: int) -> str:
"""
Convenience method to return the IP6 address for a node.
@ -60,15 +61,18 @@ class InterfaceHelper:
raise ValueError("ip6 prefixes have not been set")
return str(self.ip6[node_id])
def create_interface(self, node_id, interface_id, name=None, mac=None):
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.
Creates interface data for linking nodes, using the nodes unique id for
generation, along with a random mac address, unless provided.
:param int node_id: node id to create interface for
:param int interface_id: interface id for interface
:param str name: name to set for interface, default is eth{id}
:param str mac: mac address to use for this interface, default is random generation
:param str mac: mac address to use for this interface, default is random
generation
:return: new interface data for the provided node
:rtype: core_pb2.Interface
"""
@ -101,7 +105,7 @@ class InterfaceHelper:
)
def stream_listener(stream, handler):
def stream_listener(stream: Any, handler: Callable[[core_pb2.Event], None]) -> None:
"""
Listen for stream events and provide them to the handler.
@ -119,7 +123,7 @@ def stream_listener(stream, handler):
logging.exception("stream error")
def start_streamer(stream, handler):
def start_streamer(stream: Any, handler: Callable[[core_pb2.Event], None]) -> None:
"""
Convenience method for starting a grpc stream thread for handling streamed events.
@ -137,7 +141,7 @@ class CoreGrpcClient:
Provides convenience methods for interfacing with the CORE grpc server.
"""
def __init__(self, address="localhost:50051", proxy=False):
def __init__(self, address: str = "localhost:50051", proxy: bool = False) -> None:
"""
Creates a CoreGrpcClient instance.
@ -150,19 +154,19 @@ class CoreGrpcClient:
def start_session(
self,
session_id,
nodes,
links,
location=None,
hooks=None,
emane_config=None,
emane_model_configs=None,
wlan_configs=None,
mobility_configs=None,
service_configs=None,
service_file_configs=None,
asymmetric_links=None,
):
session_id: int,
nodes: List[core_pb2.Node],
links: List[core_pb2.Link],
location: core_pb2.SessionLocation = None,
hooks: List[core_pb2.Hook] = None,
emane_config: Dict[str, str] = None,
emane_model_configs: List[core_pb2.EmaneModelConfig] = None,
wlan_configs: List[core_pb2.WlanConfig] = None,
mobility_configs: List[core_pb2.MobilityConfig] = None,
service_configs: List[core_pb2.ServiceConfig] = None,
service_file_configs: List[core_pb2.ServiceFileConfig] = None,
asymmetric_links: List[core_pb2.Link] = None,
) -> core_pb2.StartSessionResponse:
"""
Start a session.
@ -197,7 +201,7 @@ class CoreGrpcClient:
)
return self.stub.StartSession(request)
def stop_session(self, session_id):
def stop_session(self, session_id: int) -> core_pb2.StopSessionResponse:
"""
Stop a running session.
@ -208,18 +212,19 @@ class CoreGrpcClient:
request = core_pb2.StopSessionRequest(session_id=session_id)
return self.stub.StopSession(request)
def create_session(self, session_id=None):
def create_session(self, session_id: int = None) -> core_pb2.CreateSessionResponse:
"""
Create a session.
:param int session_id: id for session, default is None and one will be created for you
:param int session_id: id for session, default is None and one will be created
for you
:return: response with created session id
:rtype: core_pb2.CreateSessionResponse
"""
request = core_pb2.CreateSessionRequest(session_id=session_id)
return self.stub.CreateSession(request)
def delete_session(self, session_id):
def delete_session(self, session_id: int) -> core_pb2.DeleteSessionResponse:
"""
Delete a session.
@ -231,16 +236,17 @@ class CoreGrpcClient:
request = core_pb2.DeleteSessionRequest(session_id=session_id)
return self.stub.DeleteSession(request)
def get_sessions(self):
def get_sessions(self) -> core_pb2.GetSessionsResponse:
"""
Retrieves all currently known sessions.
:return: response with a list of currently known session, their state and number of nodes
:return: response with a list of currently known session, their state and
number of nodes
:rtype: core_pb2.GetSessionsResponse
"""
return self.stub.GetSessions(core_pb2.GetSessionsRequest())
def get_session(self, session_id):
def get_session(self, session_id: int) -> core_pb2.GetSessionResponse:
"""
Retrieve a session.
@ -252,7 +258,9 @@ class CoreGrpcClient:
request = core_pb2.GetSessionRequest(session_id=session_id)
return self.stub.GetSession(request)
def get_session_options(self, session_id):
def get_session_options(
self, session_id: int
) -> core_pb2.GetSessionOptionsResponse:
"""
Retrieve session options as a dict with id mapping.
@ -264,7 +272,9 @@ class CoreGrpcClient:
request = core_pb2.GetSessionOptionsRequest(session_id=session_id)
return self.stub.GetSessionOptions(request)
def set_session_options(self, session_id, config):
def set_session_options(
self, session_id: int, config: Dict[str, str]
) -> core_pb2.SetSessionOptionsResponse:
"""
Set options for a session.
@ -279,7 +289,9 @@ class CoreGrpcClient:
)
return self.stub.SetSessionOptions(request)
def get_session_metadata(self, session_id):
def get_session_metadata(
self, session_id: int
) -> core_pb2.GetSessionMetadataResponse:
"""
Retrieve session metadata as a dict with id mapping.
@ -291,7 +303,9 @@ class CoreGrpcClient:
request = core_pb2.GetSessionMetadataRequest(session_id=session_id)
return self.stub.GetSessionMetadata(request)
def set_session_metadata(self, session_id, config):
def set_session_metadata(
self, session_id: int, config: Dict[str, str]
) -> core_pb2.SetSessionMetadataResponse:
"""
Set metadata for a session.
@ -306,7 +320,9 @@ class CoreGrpcClient:
)
return self.stub.SetSessionMetadata(request)
def get_session_location(self, session_id):
def get_session_location(
self, session_id: int
) -> core_pb2.GetSessionLocationResponse:
"""
Get session location.
@ -320,15 +336,15 @@ class CoreGrpcClient:
def set_session_location(
self,
session_id,
x=None,
y=None,
z=None,
lat=None,
lon=None,
alt=None,
scale=None,
):
session_id: int,
x: float = None,
y: float = None,
z: float = None,
lat: float = None,
lon: float = None,
alt: float = None,
scale: float = None,
) -> core_pb2.SetSessionLocationResponse:
"""
Set session location.
@ -352,7 +368,9 @@ class CoreGrpcClient:
)
return self.stub.SetSessionLocation(request)
def set_session_state(self, session_id, state):
def set_session_state(
self, session_id: int, state: core_pb2.SessionState
) -> core_pb2.SetSessionStateResponse:
"""
Set session state.
@ -365,7 +383,9 @@ class CoreGrpcClient:
request = core_pb2.SetSessionStateRequest(session_id=session_id, state=state)
return self.stub.SetSessionState(request)
def add_session_server(self, session_id, name, host):
def add_session_server(
self, session_id: int, name: str, host: str
) -> core_pb2.AddSessionServerResponse:
"""
Add distributed session server.
@ -381,7 +401,12 @@ class CoreGrpcClient:
)
return self.stub.AddSessionServer(request)
def events(self, session_id, handler, events=None):
def events(
self,
session_id: int,
handler: Callable[[core_pb2.Event], None],
events: List[core_pb2.Event] = None,
) -> Any:
"""
Listen for session events.
@ -393,10 +418,13 @@ class CoreGrpcClient:
"""
request = core_pb2.EventsRequest(session_id=session_id, events=events)
stream = self.stub.Events(request)
logging.info("STREAM TYPE: %s", type(stream))
start_streamer(stream, handler)
return stream
def throughputs(self, session_id, handler):
def throughputs(
self, session_id: int, handler: Callable[[core_pb2.ThroughputsEvent], None]
) -> Any:
"""
Listen for throughput events with information for interfaces and bridges.
@ -410,7 +438,9 @@ class CoreGrpcClient:
start_streamer(stream, handler)
return stream
def add_node(self, session_id, node):
def add_node(
self, session_id: int, node: core_pb2.Node
) -> core_pb2.AddNodeResponse:
"""
Add node to session.
@ -423,7 +453,7 @@ class CoreGrpcClient:
request = core_pb2.AddNodeRequest(session_id=session_id, node=node)
return self.stub.AddNode(request)
def get_node(self, session_id, node_id):
def get_node(self, session_id: int, node_id: int) -> core_pb2.GetNodeResponse:
"""
Get node details.
@ -436,7 +466,14 @@ class CoreGrpcClient:
request = core_pb2.GetNodeRequest(session_id=session_id, node_id=node_id)
return self.stub.GetNode(request)
def edit_node(self, session_id, node_id, position, icon=None, source=None):
def edit_node(
self,
session_id: int,
node_id: int,
position: core_pb2.Position,
icon: str = None,
source: str = None,
) -> core_pb2.EditNodeResponse:
"""
Edit a node, currently only changes position.
@ -458,7 +495,7 @@ class CoreGrpcClient:
)
return self.stub.EditNode(request)
def delete_node(self, session_id, node_id):
def delete_node(self, session_id: int, node_id: int) -> core_pb2.DeleteNodeResponse:
"""
Delete node from session.
@ -471,12 +508,15 @@ class CoreGrpcClient:
request = core_pb2.DeleteNodeRequest(session_id=session_id, node_id=node_id)
return self.stub.DeleteNode(request)
def node_command(self, session_id, node_id, command):
def node_command(
self, session_id: int, node_id: int, command: str
) -> core_pb2.NodeCommandResponse:
"""
Send command to a node and get the output.
:param int session_id: session id
:param int node_id: node id
:param str command: command to run on node
:return: response with command combined stdout/stderr
:rtype: core_pb2.NodeCommandResponse
:raises grpc.RpcError: when session or node doesn't exist
@ -486,7 +526,9 @@ class CoreGrpcClient:
)
return self.stub.NodeCommand(request)
def get_node_terminal(self, session_id, node_id):
def get_node_terminal(
self, session_id: int, node_id: int
) -> core_pb2.GetNodeTerminalResponse:
"""
Retrieve terminal command string for launching a local terminal.
@ -501,7 +543,9 @@ class CoreGrpcClient:
)
return self.stub.GetNodeTerminal(request)
def get_node_links(self, session_id, node_id):
def get_node_links(
self, session_id: int, node_id: int
) -> core_pb2.GetNodeLinksResponse:
"""
Get current links for a node.
@ -516,13 +560,13 @@ class CoreGrpcClient:
def add_link(
self,
session_id,
node_one_id,
node_two_id,
interface_one=None,
interface_two=None,
options=None,
):
session_id: int,
node_one_id: int,
node_two_id: int,
interface_one: core_pb2.Interface = None,
interface_two: core_pb2.Interface = None,
options: core_pb2.LinkOptions = None,
) -> core_pb2.AddLinkResponse:
"""
Add a link between nodes.
@ -549,13 +593,13 @@ class CoreGrpcClient:
def edit_link(
self,
session_id,
node_one_id,
node_two_id,
options,
interface_one_id=None,
interface_two_id=None,
):
session_id: int,
node_one_id: int,
node_two_id: int,
options: core_pb2.LinkOptions,
interface_one_id: int = None,
interface_two_id: int = None,
) -> core_pb2.EditLinkResponse:
"""
Edit a link between nodes.
@ -581,12 +625,12 @@ class CoreGrpcClient:
def delete_link(
self,
session_id,
node_one_id,
node_two_id,
interface_one_id=None,
interface_two_id=None,
):
session_id: int,
node_one_id: int,
node_two_id: int,
interface_one_id: int = None,
interface_two_id: int = None,
) -> core_pb2.DeleteLinkResponse:
"""
Delete a link between nodes.
@ -608,7 +652,7 @@ class CoreGrpcClient:
)
return self.stub.DeleteLink(request)
def get_hooks(self, session_id):
def get_hooks(self, session_id: int) -> core_pb2.GetHooksResponse:
"""
Get all hook scripts.
@ -620,7 +664,13 @@ class CoreGrpcClient:
request = core_pb2.GetHooksRequest(session_id=session_id)
return self.stub.GetHooks(request)
def add_hook(self, session_id, state, file_name, file_data):
def add_hook(
self,
session_id: int,
state: core_pb2.SessionState,
file_name: str,
file_data: bytes,
) -> core_pb2.AddHookResponse:
"""
Add hook scripts.
@ -636,7 +686,9 @@ class CoreGrpcClient:
request = core_pb2.AddHookRequest(session_id=session_id, hook=hook)
return self.stub.AddHook(request)
def get_mobility_configs(self, session_id):
def get_mobility_configs(
self, session_id: int
) -> core_pb2.GetMobilityConfigsResponse:
"""
Get all mobility configurations.
@ -648,7 +700,9 @@ class CoreGrpcClient:
request = core_pb2.GetMobilityConfigsRequest(session_id=session_id)
return self.stub.GetMobilityConfigs(request)
def get_mobility_config(self, session_id, node_id):
def get_mobility_config(
self, session_id: int, node_id: int
) -> core_pb2.GetMobilityConfigResponse:
"""
Get mobility configuration for a node.
@ -663,7 +717,9 @@ class CoreGrpcClient:
)
return self.stub.GetMobilityConfig(request)
def set_mobility_config(self, session_id, node_id, config):
def set_mobility_config(
self, session_id: int, node_id: int, config: Dict[str, str]
) -> core_pb2.SetMobilityConfigResponse:
"""
Set mobility configuration for a node.
@ -680,7 +736,9 @@ class CoreGrpcClient:
)
return self.stub.SetMobilityConfig(request)
def mobility_action(self, session_id, node_id, action):
def mobility_action(
self, session_id: int, node_id: int, action: core_pb2.ServiceAction
) -> core_pb2.MobilityActionResponse:
"""
Send a mobility action for a node.
@ -696,7 +754,7 @@ class CoreGrpcClient:
)
return self.stub.MobilityAction(request)
def get_services(self):
def get_services(self) -> core_pb2.GetServicesResponse:
"""
Get all currently loaded services.
@ -706,7 +764,9 @@ class CoreGrpcClient:
request = core_pb2.GetServicesRequest()
return self.stub.GetServices(request)
def get_service_defaults(self, session_id):
def get_service_defaults(
self, session_id: int
) -> core_pb2.GetServiceDefaultsResponse:
"""
Get default services for different default node models.
@ -718,7 +778,9 @@ class CoreGrpcClient:
request = core_pb2.GetServiceDefaultsRequest(session_id=session_id)
return self.stub.GetServiceDefaults(request)
def set_service_defaults(self, session_id, service_defaults):
def set_service_defaults(
self, session_id: int, service_defaults: Dict[str, List[str]]
) -> core_pb2.SetServiceDefaultsResponse:
"""
Set default services for node models.
@ -738,7 +800,9 @@ class CoreGrpcClient:
)
return self.stub.SetServiceDefaults(request)
def get_node_service_configs(self, session_id):
def get_node_service_configs(
self, session_id: int
) -> core_pb2.GetNodeServiceConfigsResponse:
"""
Get service data for a node.
@ -750,7 +814,9 @@ class CoreGrpcClient:
request = core_pb2.GetNodeServiceConfigsRequest(session_id=session_id)
return self.stub.GetNodeServiceConfigs(request)
def get_node_service(self, session_id, node_id, service):
def get_node_service(
self, session_id: int, node_id: int, service: str
) -> core_pb2.GetNodeServiceResponse:
"""
Get service data for a node.
@ -766,7 +832,9 @@ class CoreGrpcClient:
)
return self.stub.GetNodeService(request)
def get_node_service_file(self, session_id, node_id, service, file_name):
def get_node_service_file(
self, session_id: int, node_id: int, service: str, file_name: str
) -> core_pb2.GetNodeServiceFileResponse:
"""
Get a service file for a node.
@ -784,8 +852,14 @@ class CoreGrpcClient:
return self.stub.GetNodeServiceFile(request)
def set_node_service(
self, session_id, node_id, service, startup, validate, shutdown
):
self,
session_id: int,
node_id: int,
service: str,
startup: List[str],
validate: List[str],
shutdown: List[str],
) -> core_pb2.SetNodeServiceResponse:
"""
Set service data for a node.
@ -809,7 +883,9 @@ class CoreGrpcClient:
request = core_pb2.SetNodeServiceRequest(session_id=session_id, config=config)
return self.stub.SetNodeService(request)
def set_node_service_file(self, session_id, node_id, service, file_name, data):
def set_node_service_file(
self, session_id: int, node_id: int, service: str, file_name: str, data: bytes
) -> core_pb2.SetNodeServiceFileResponse:
"""
Set a service file for a node.
@ -830,14 +906,21 @@ class CoreGrpcClient:
)
return self.stub.SetNodeServiceFile(request)
def service_action(self, session_id, node_id, service, action):
def service_action(
self,
session_id: int,
node_id: int,
service: str,
action: core_pb2.ServiceAction,
) -> core_pb2.ServiceActionResponse:
"""
Send an action to a service for a node.
:param int session_id: session id
:param int node_id: node id
:param str service: service name
:param core_pb2.ServiceAction action: action for service (start, stop, restart, validate)
:param core_pb2.ServiceAction action: action for service (start, stop, restart,
validate)
:return: response with result of success or failure
:rtype: core_pb2.ServiceActionResponse
:raises grpc.RpcError: when session or node doesn't exist
@ -847,7 +930,7 @@ class CoreGrpcClient:
)
return self.stub.ServiceAction(request)
def get_wlan_configs(self, session_id):
def get_wlan_configs(self, session_id: int) -> core_pb2.GetWlanConfigsResponse:
"""
Get all wlan configurations.
@ -859,7 +942,9 @@ class CoreGrpcClient:
request = core_pb2.GetWlanConfigsRequest(session_id=session_id)
return self.stub.GetWlanConfigs(request)
def get_wlan_config(self, session_id, node_id):
def get_wlan_config(
self, session_id: int, node_id: int
) -> core_pb2.GetWlanConfigResponse:
"""
Get wlan configuration for a node.
@ -872,7 +957,9 @@ class CoreGrpcClient:
request = core_pb2.GetWlanConfigRequest(session_id=session_id, node_id=node_id)
return self.stub.GetWlanConfig(request)
def set_wlan_config(self, session_id, node_id, config):
def set_wlan_config(
self, session_id: int, node_id: int, config: Dict[str, str]
) -> core_pb2.SetWlanConfigResponse:
"""
Set wlan configuration for a node.
@ -889,7 +976,7 @@ class CoreGrpcClient:
)
return self.stub.SetWlanConfig(request)
def get_emane_config(self, session_id):
def get_emane_config(self, session_id: int) -> core_pb2.GetEmaneConfigResponse:
"""
Get session emane configuration.
@ -901,7 +988,9 @@ class CoreGrpcClient:
request = core_pb2.GetEmaneConfigRequest(session_id=session_id)
return self.stub.GetEmaneConfig(request)
def set_emane_config(self, session_id, config):
def set_emane_config(
self, session_id: int, config: Dict[str, str]
) -> core_pb2.SetEmaneConfigResponse:
"""
Set session emane configuration.
@ -914,7 +1003,7 @@ class CoreGrpcClient:
request = core_pb2.SetEmaneConfigRequest(session_id=session_id, config=config)
return self.stub.SetEmaneConfig(request)
def get_emane_models(self, session_id):
def get_emane_models(self, session_id: int) -> core_pb2.GetEmaneModelsResponse:
"""
Get session emane models.
@ -926,7 +1015,9 @@ class CoreGrpcClient:
request = core_pb2.GetEmaneModelsRequest(session_id=session_id)
return self.stub.GetEmaneModels(request)
def get_emane_model_config(self, session_id, node_id, model, interface_id=-1):
def get_emane_model_config(
self, session_id: int, node_id: int, model: str, interface_id: int = -1
) -> core_pb2.GetEmaneModelConfigResponse:
"""
Get emane model configuration for a node or a node's interface.
@ -944,8 +1035,13 @@ class CoreGrpcClient:
return self.stub.GetEmaneModelConfig(request)
def set_emane_model_config(
self, session_id, node_id, model, config, interface_id=-1
):
self,
session_id: int,
node_id: int,
model: str,
config: Dict[str, str],
interface_id: int = -1,
) -> core_pb2.SetEmaneModelConfigResponse:
"""
Set emane model configuration for a node or a node's interface.
@ -966,7 +1062,9 @@ class CoreGrpcClient:
)
return self.stub.SetEmaneModelConfig(request)
def get_emane_model_configs(self, session_id):
def get_emane_model_configs(
self, session_id: int
) -> core_pb2.GetEmaneModelConfigsResponse:
"""
Get all emane model configurations for a session.
@ -978,7 +1076,7 @@ class CoreGrpcClient:
request = core_pb2.GetEmaneModelConfigsRequest(session_id=session_id)
return self.stub.GetEmaneModelConfigs(request)
def save_xml(self, session_id, file_path):
def save_xml(self, session_id: int, file_path: str) -> core_pb2.SaveXmlResponse:
"""
Save the current scenario to an XML file.
@ -991,7 +1089,7 @@ class CoreGrpcClient:
with open(file_path, "w") as xml_file:
xml_file.write(response.data)
def open_xml(self, file_path, start=False):
def open_xml(self, file_path: str, start: bool = False) -> core_pb2.OpenXmlResponse:
"""
Load a local scenario XML file to open as a new session.
@ -1005,7 +1103,9 @@ class CoreGrpcClient:
request = core_pb2.OpenXmlRequest(data=data, start=start, file=file_path)
return self.stub.OpenXml(request)
def emane_link(self, session_id, nem_one, nem_two, linked):
def emane_link(
self, session_id: int, nem_one: int, nem_two: int, linked: bool
) -> core_pb2.EmaneLinkResponse:
"""
Helps broadcast wireless link/unlink between EMANE nodes.
@ -1020,7 +1120,7 @@ class CoreGrpcClient:
)
return self.stub.EmaneLink(request)
def get_interfaces(self):
def get_interfaces(self) -> core_pb2.GetInterfacesResponse:
"""
Retrieves a list of interfaces available on the host machine that are not
a part of a CORE session.
@ -1030,7 +1130,7 @@ class CoreGrpcClient:
request = core_pb2.GetInterfacesRequest()
return self.stub.GetInterfaces(request)
def connect(self):
def connect(self) -> None:
"""
Open connection to server, must be closed manually.
@ -1041,7 +1141,7 @@ class CoreGrpcClient:
)
self.stub = core_pb2_grpc.CoreApiStub(self.channel)
def close(self):
def close(self) -> None:
"""
Close currently opened server channel connection.
@ -1052,7 +1152,7 @@ class CoreGrpcClient:
self.channel = None
@contextmanager
def context_connect(self):
def context_connect(self) -> Generator:
"""
Makes a context manager based connection to the server, will close after context ends.

View file

@ -1,5 +1,6 @@
import logging
from queue import Empty, Queue
from typing import Iterable
from core.api.grpc import core_pb2
from core.api.grpc.grpcutils import convert_value
@ -11,9 +12,10 @@ from core.emulator.data import (
LinkData,
NodeData,
)
from core.emulator.session import Session
def handle_node_event(event):
def handle_node_event(event: NodeData) -> core_pb2.NodeEvent:
"""
Handle node event when there is a node event
@ -34,7 +36,7 @@ def handle_node_event(event):
return core_pb2.NodeEvent(node=node_proto, source=event.source)
def handle_link_event(event):
def handle_link_event(event: LinkData) -> core_pb2.LinkEvent:
"""
Handle link event when there is a link event
@ -90,7 +92,7 @@ def handle_link_event(event):
return core_pb2.LinkEvent(message_type=event.message_type, link=link)
def handle_session_event(event):
def handle_session_event(event: EventData) -> core_pb2.SessionEvent:
"""
Handle session event when there is a session event
@ -110,7 +112,7 @@ def handle_session_event(event):
)
def handle_config_event(event):
def handle_config_event(event: ConfigData) -> core_pb2.ConfigEvent:
"""
Handle configuration event when there is configuration event
@ -135,7 +137,7 @@ def handle_config_event(event):
)
def handle_exception_event(event):
def handle_exception_event(event: ExceptionData) -> core_pb2.ExceptionEvent:
"""
Handle exception event when there is exception event
@ -153,7 +155,7 @@ def handle_exception_event(event):
)
def handle_file_event(event):
def handle_file_event(event: FileData) -> core_pb2.FileEvent:
"""
Handle file event
@ -179,7 +181,9 @@ class EventStreamer:
Processes session events to generate grpc events.
"""
def __init__(self, session, event_types):
def __init__(
self, session: Session, event_types: Iterable[core_pb2.EventType]
) -> None:
"""
Create a EventStreamer instance.
@ -191,7 +195,7 @@ class EventStreamer:
self.queue = Queue()
self.add_handlers()
def add_handlers(self):
def add_handlers(self) -> None:
"""
Add a session event handler for desired event types.
@ -210,7 +214,7 @@ class EventStreamer:
if core_pb2.EventType.SESSION in self.event_types:
self.session.event_handlers.append(self.queue.put)
def process(self):
def process(self) -> core_pb2.Event:
"""
Process the next event in the queue.
@ -239,7 +243,7 @@ class EventStreamer:
event = None
return event
def remove_handlers(self):
def remove_handlers(self) -> None:
"""
Remove session event handlers for events being watched.

View file

@ -1,16 +1,21 @@
import logging
import time
from typing import Any, Dict, List, Tuple, Type
from core import utils
from core.api.grpc import core_pb2
from core.config import ConfigurableOptions
from core.emulator.data import LinkData
from core.emulator.emudata import InterfaceData, LinkOptions, NodeOptions
from core.emulator.enumerations import LinkTypes, NodeTypes
from core.nodes.base import CoreNetworkBase
from core.emulator.session import Session
from core.nodes.base import CoreNetworkBase, NodeBase
from core.services.coreservices import CoreService
WORKERS = 10
def add_node_data(node_proto):
def add_node_data(node_proto: core_pb2.Node) -> Tuple[NodeTypes, int, NodeOptions]:
"""
Convert node protobuf message to data for creating a node.
@ -40,7 +45,7 @@ def add_node_data(node_proto):
return _type, _id, options
def link_interface(interface_proto):
def link_interface(interface_proto: core_pb2.Interface) -> InterfaceData:
"""
Create interface data from interface proto.
@ -68,7 +73,9 @@ def link_interface(interface_proto):
return interface
def add_link_data(link_proto):
def add_link_data(
link_proto: core_pb2.Link
) -> Tuple[InterfaceData, InterfaceData, LinkOptions]:
"""
Convert link proto to link interfaces and options data.
@ -102,7 +109,9 @@ def add_link_data(link_proto):
return interface_one, interface_two, options
def create_nodes(session, node_protos):
def create_nodes(
session: Session, node_protos: List[core_pb2.Node]
) -> Tuple[List[NodeBase], List[Exception]]:
"""
Create nodes using a thread pool and wait for completion.
@ -123,7 +132,9 @@ def create_nodes(session, node_protos):
return results, exceptions
def create_links(session, link_protos):
def create_links(
session: Session, link_protos: List[core_pb2.Link]
) -> Tuple[List[NodeBase], List[Exception]]:
"""
Create links using a thread pool and wait for completion.
@ -146,7 +157,9 @@ def create_links(session, link_protos):
return results, exceptions
def edit_links(session, link_protos):
def edit_links(
session: Session, link_protos: List[core_pb2.Link]
) -> Tuple[List[None], List[Exception]]:
"""
Edit links using a thread pool and wait for completion.
@ -169,7 +182,7 @@ def edit_links(session, link_protos):
return results, exceptions
def convert_value(value):
def convert_value(value: Any) -> str:
"""
Convert value into string.
@ -182,7 +195,9 @@ def convert_value(value):
return value
def get_config_options(config, configurable_options):
def get_config_options(
config: Dict[str, str], configurable_options: Type[ConfigurableOptions]
) -> Dict[str, core_pb2.ConfigOption]:
"""
Retrieve configuration options in a form that is used by the grpc server.
@ -211,12 +226,12 @@ def get_config_options(config, configurable_options):
return results
def get_links(session, node):
def get_links(session: Session, node: NodeBase):
"""
Retrieve a list of links for grpc to use
:param core.emulator.Session session: node's section
:param core.nodes.base.CoreNode node: node to get links from
:param core.nodes.base.NodeBase node: node to get links from
:return: [core.api.grpc.core_pb2.Link]
"""
links = []
@ -226,7 +241,7 @@ def get_links(session, node):
return links
def get_emane_model_id(node_id, interface_id):
def get_emane_model_id(node_id: int, interface_id: int) -> int:
"""
Get EMANE model id
@ -241,7 +256,7 @@ def get_emane_model_id(node_id, interface_id):
return node_id
def parse_emane_model_id(_id):
def parse_emane_model_id(_id: int) -> Tuple[int, int]:
"""
Parses EMANE model id to get true node id and interface id.
@ -257,7 +272,7 @@ def parse_emane_model_id(_id):
return node_id, interface
def convert_link(session, link_data):
def convert_link(session: Session, link_data: LinkData) -> core_pb2.Link:
"""
Convert link_data into core protobuf Link
@ -324,7 +339,7 @@ def convert_link(session, link_data):
)
def get_net_stats():
def get_net_stats() -> Dict[str, Dict]:
"""
Retrieve status about the current interfaces in the system
@ -346,7 +361,7 @@ def get_net_stats():
return stats
def session_location(session, location):
def session_location(session: Session, location: core_pb2.SessionLocation) -> None:
"""
Set session location based on location proto.
@ -359,7 +374,7 @@ def session_location(session, location):
session.location.refscale = location.scale
def service_configuration(session, config):
def service_configuration(session: Session, config: core_pb2.ServiceConfig) -> None:
"""
Convenience method for setting a node service configuration.
@ -374,7 +389,7 @@ def service_configuration(session, config):
service.shutdown = tuple(config.shutdown)
def get_service_configuration(service):
def get_service_configuration(service: Type[CoreService]) -> core_pb2.NodeServiceData:
"""
Convenience for converting a service to service data proto.

View file

@ -7,6 +7,7 @@ import time
from concurrent import futures
import grpc
from grpc import ServicerContext
from core.api.grpc import core_pb2, core_pb2_grpc, grpcutils
from core.api.grpc.events import EventStreamer
@ -17,11 +18,14 @@ from core.api.grpc.grpcutils import (
get_net_stats,
)
from core.emane.nodes import EmaneNet
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.errors import CoreCommandError, CoreError
from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility
from core.nodes.base import NodeBase
from core.nodes.docker import DockerNode
from core.nodes.lxd import LxcNode
from core.services.coreservices import ServiceManager
@ -37,24 +41,24 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
:param core.emulator.coreemu.CoreEmu coreemu: coreemu object
"""
def __init__(self, coreemu):
def __init__(self, coreemu: CoreEmu) -> None:
super().__init__()
self.coreemu = coreemu
self.running = True
self.server = None
atexit.register(self._exit_handler)
def _exit_handler(self):
def _exit_handler(self) -> None:
logging.debug("catching exit, stop running")
self.running = False
def _is_running(self, context):
def _is_running(self, context) -> bool:
return self.running and context.is_active()
def _cancel_stream(self, context):
def _cancel_stream(self, context) -> None:
context.abort(grpc.StatusCode.CANCELLED, "server stopping")
def listen(self, address):
def listen(self, address: str) -> None:
logging.info("CORE gRPC API listening on: %s", address)
self.server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
core_pb2_grpc.add_CoreApiServicer_to_server(self, self.server)
@ -67,7 +71,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
except KeyboardInterrupt:
self.server.stop(None)
def get_session(self, session_id, context):
def get_session(self, session_id: int, context: ServicerContext) -> Session:
"""
Retrieve session given the session id
@ -82,7 +86,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
context.abort(grpc.StatusCode.NOT_FOUND, f"session {session_id} not found")
return session
def get_node(self, session, node_id, context):
def get_node(
self, session: Session, node_id: int, context: ServicerContext
) -> NodeBase:
"""
Retrieve node given session and node id
@ -97,7 +103,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
except CoreError:
context.abort(grpc.StatusCode.NOT_FOUND, f"node {node_id} not found")
def StartSession(self, request, context):
def StartSession(
self, request: core_pb2.StartSessionRequest, context: ServicerContext
) -> core_pb2.StartSessionResponse:
"""
Start a session.
@ -184,7 +192,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
return core_pb2.StartSessionResponse(result=True)
def StopSession(self, request, context):
def StopSession(
self, request: core_pb2.StopSessionRequest, context: ServicerContext
) -> core_pb2.StopSessionResponse:
"""
Stop a running session.
@ -201,7 +211,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
session.set_state(EventTypes.SHUTDOWN_STATE, send_event=True)
return core_pb2.StopSessionResponse(result=True)
def CreateSession(self, request, context):
def CreateSession(
self, request: core_pb2.CreateSessionRequest, context: ServicerContext
) -> core_pb2.CreateSessionResponse:
"""
Create a session
@ -219,7 +231,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
session_id=session.id, state=session.state
)
def DeleteSession(self, request, context):
def DeleteSession(
self, request: core_pb2.DeleteSessionRequest, context: ServicerContext
) -> core_pb2.DeleteSessionResponse:
"""
Delete the session
@ -232,7 +246,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
result = self.coreemu.delete_session(request.session_id)
return core_pb2.DeleteSessionResponse(result=result)
def GetSessions(self, request, context):
def GetSessions(
self, request: core_pb2.GetSessionsRequest, context: ServicerContext
) -> core_pb2.GetSessionsResponse:
"""
Delete the session
@ -254,7 +270,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
sessions.append(session_summary)
return core_pb2.GetSessionsResponse(sessions=sessions)
def GetSessionLocation(self, request, context):
def GetSessionLocation(
self, request: core_pb2.GetSessionLocationRequest, context: ServicerContext
) -> core_pb2.GetSessionLocationResponse:
"""
Retrieve a requested session location
@ -273,7 +291,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
)
return core_pb2.GetSessionLocationResponse(location=location)
def SetSessionLocation(self, request, context):
def SetSessionLocation(
self, request: core_pb2.SetSessionLocationRequest, context: ServicerContext
) -> core_pb2.SetSessionLocationResponse:
"""
Set session location
@ -287,7 +307,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
grpcutils.session_location(session, request.location)
return core_pb2.SetSessionLocationResponse(result=True)
def SetSessionState(self, request, context):
def SetSessionState(
self, request: core_pb2.SetSessionStateRequest, context: ServicerContext
) -> core_pb2.SetSessionStateResponse:
"""
Set session state
@ -320,7 +342,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
return core_pb2.SetSessionStateResponse(result=result)
def GetSessionOptions(self, request, context):
def GetSessionOptions(
self, request: core_pb2.GetSessionOptionsRequest, context: ServicerContext
) -> core_pb2.GetSessionOptionsResponse:
"""
Retrieve session options.
@ -338,7 +362,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
config = get_config_options(default_config, session.options)
return core_pb2.GetSessionOptionsResponse(config=config)
def SetSessionOptions(self, request, context):
def SetSessionOptions(
self, request: core_pb2.SetSessionOptionsRequest, context: ServicerContext
) -> core_pb2.SetSessionOptionsResponse:
"""
Update a session's configuration
@ -353,7 +379,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
config.update(request.config)
return core_pb2.SetSessionOptionsResponse(result=True)
def GetSessionMetadata(self, request, context):
def GetSessionMetadata(
self, request: core_pb2.GetSessionMetadataRequest, context: ServicerContext
) -> core_pb2.GetSessionMetadataResponse:
"""
Retrieve session metadata.
@ -367,7 +395,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
session = self.get_session(request.session_id, context)
return core_pb2.GetSessionMetadataResponse(config=session.metadata)
def SetSessionMetadata(self, request, context):
def SetSessionMetadata(
self, request: core_pb2.SetSessionMetadataRequest, context: ServicerContext
) -> core_pb2.SetSessionMetadataResponse:
"""
Update a session's metadata.
@ -381,7 +411,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
session.metadata = dict(request.config)
return core_pb2.SetSessionMetadataResponse(result=True)
def GetSession(self, request, context):
def GetSession(
self, request: core_pb2.GetSessionRequest, context: ServicerContext
) -> core_pb2.GetSessionResponse:
"""
Retrieve requested session
@ -435,7 +467,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
session_proto = core_pb2.Session(state=session.state, nodes=nodes, links=links)
return core_pb2.GetSessionResponse(session=session_proto)
def AddSessionServer(self, request, context):
def AddSessionServer(
self, request: core_pb2.AddSessionServerRequest, context: ServicerContext
) -> core_pb2.AddSessionServerResponse:
"""
Add distributed server to a session.
@ -449,7 +483,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
session.distributed.add_server(request.name, request.host)
return core_pb2.AddSessionServerResponse(result=True)
def Events(self, request, context):
def Events(self, request: core_pb2.EventsRequest, context: ServicerContext) -> None:
session = self.get_session(request.session_id, context)
event_types = set(request.events)
if not event_types:
@ -464,7 +498,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
streamer.remove_handlers()
self._cancel_stream(context)
def Throughputs(self, request, context):
def Throughputs(
self, request: core_pb2.ThroughputsRequest, context: ServicerContext
) -> None:
"""
Calculate average throughput after every certain amount of delay time
@ -532,7 +568,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
last_stats = stats
time.sleep(delay)
def AddNode(self, request, context):
def AddNode(
self, request: core_pb2.AddNodeRequest, context: ServicerContext
) -> core_pb2.AddNodeResponse:
"""
Add node to requested session
@ -547,7 +585,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
node = session.add_node(_type=_type, _id=_id, options=options)
return core_pb2.AddNodeResponse(node_id=node.id)
def GetNode(self, request, context):
def GetNode(
self, request: core_pb2.GetNodeRequest, context: ServicerContext
) -> core_pb2.GetNodeResponse:
"""
Retrieve node
@ -602,7 +642,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
return core_pb2.GetNodeResponse(node=node_proto, interfaces=interfaces)
def EditNode(self, request, context):
def EditNode(
self, request: core_pb2.EditNodeRequest, context: ServicerContext
) -> core_pb2.EditNodeResponse:
"""
Edit node
@ -635,7 +677,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
result = False
return core_pb2.EditNodeResponse(result=result)
def DeleteNode(self, request, context):
def DeleteNode(
self, request: core_pb2.DeleteNodeRequest, context: ServicerContext
) -> core_pb2.DeleteNodeResponse:
"""
Delete node
@ -648,7 +692,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
result = session.delete_node(request.node_id)
return core_pb2.DeleteNodeResponse(result=result)
def NodeCommand(self, request, context):
def NodeCommand(
self, request: core_pb2.NodeCommandRequest, context: ServicerContext
) -> core_pb2.NodeCommandResponse:
"""
Run command on a node
@ -665,7 +711,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
output = e.stderr
return core_pb2.NodeCommandResponse(output=output)
def GetNodeTerminal(self, request, context):
def GetNodeTerminal(
self, request: core_pb2.GetNodeTerminalRequest, context: ServicerContext
) -> core_pb2.GetNodeTerminalResponse:
"""
Retrieve terminal command string of a node
@ -680,7 +728,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
terminal = node.termcmdstring("/bin/bash")
return core_pb2.GetNodeTerminalResponse(terminal=terminal)
def GetNodeLinks(self, request, context):
def GetNodeLinks(
self, request: core_pb2.GetNodeLinksRequest, context: ServicerContext
) -> core_pb2.GetNodeLinksResponse:
"""
Retrieve all links form a requested node
@ -695,7 +745,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
links = get_links(session, node)
return core_pb2.GetNodeLinksResponse(links=links)
def AddLink(self, request, context):
def AddLink(
self, request: core_pb2.AddLinkRequest, context: ServicerContext
) -> core_pb2.AddLinkResponse:
"""
Add link to a session
@ -718,7 +770,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
)
return core_pb2.AddLinkResponse(result=True)
def EditLink(self, request, context):
def EditLink(
self, request: core_pb2.EditLinkRequest, context: ServicerContext
) -> core_pb2.EditLinkResponse:
"""
Edit a link
@ -751,7 +805,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
)
return core_pb2.EditLinkResponse(result=True)
def DeleteLink(self, request, context):
def DeleteLink(
self, request: core_pb2.DeleteLinkRequest, context: ServicerContext
) -> core_pb2.DeleteLinkResponse:
"""
Delete a link
@ -771,7 +827,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
)
return core_pb2.DeleteLinkResponse(result=True)
def GetHooks(self, request, context):
def GetHooks(
self, request: core_pb2.GetHooksRequest, context: ServicerContext
) -> core_pb2.GetHooksResponse:
"""
Retrieve all hooks from a session
@ -790,7 +848,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
hooks.append(hook)
return core_pb2.GetHooksResponse(hooks=hooks)
def AddHook(self, request, context):
def AddHook(
self, request: core_pb2.AddHookRequest, context: ServicerContext
) -> core_pb2.AddHookResponse:
"""
Add hook to a session
@ -805,7 +865,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
session.add_hook(hook.state, hook.file, None, hook.data)
return core_pb2.AddHookResponse(result=True)
def GetMobilityConfigs(self, request, context):
def GetMobilityConfigs(
self, request: core_pb2.GetMobilityConfigsRequest, context: ServicerContext
) -> core_pb2.GetMobilityConfigsResponse:
"""
Retrieve all mobility configurations from a session
@ -831,7 +893,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
response.configs[node_id].CopyFrom(mapped_config)
return response
def GetMobilityConfig(self, request, context):
def GetMobilityConfig(
self, request: core_pb2.GetMobilityConfigRequest, context: ServicerContext
) -> core_pb2.GetMobilityConfigResponse:
"""
Retrieve mobility configuration of a node
@ -849,7 +913,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
config = get_config_options(current_config, Ns2ScriptedMobility)
return core_pb2.GetMobilityConfigResponse(config=config)
def SetMobilityConfig(self, request, context):
def SetMobilityConfig(
self, request: core_pb2.SetMobilityConfigRequest, context: ServicerContext
) -> core_pb2.SetMobilityConfigResponse:
"""
Set mobility configuration of a node
@ -867,7 +933,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
)
return core_pb2.SetMobilityConfigResponse(result=True)
def MobilityAction(self, request, context):
def MobilityAction(
self, request: core_pb2.MobilityActionRequest, context: ServicerContext
) -> core_pb2.MobilityActionResponse:
"""
Take mobility action whether to start, pause, stop or none of those
@ -891,7 +959,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
result = False
return core_pb2.MobilityActionResponse(result=result)
def GetServices(self, request, context):
def GetServices(
self, request: core_pb2.GetServicesRequest, context: ServicerContext
) -> core_pb2.GetServicesResponse:
"""
Retrieve all the services that are running
@ -908,7 +978,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
services.append(service_proto)
return core_pb2.GetServicesResponse(services=services)
def GetServiceDefaults(self, request, context):
def GetServiceDefaults(
self, request: core_pb2.GetServiceDefaultsRequest, context: ServicerContext
) -> core_pb2.GetServiceDefaultsResponse:
"""
Retrieve all the default services of all node types in a session
@ -929,7 +1001,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
all_service_defaults.append(service_defaults)
return core_pb2.GetServiceDefaultsResponse(defaults=all_service_defaults)
def SetServiceDefaults(self, request, context):
def SetServiceDefaults(
self, request: core_pb2.SetServiceDefaultsRequest, context: ServicerContext
) -> core_pb2.SetServiceDefaultsResponse:
"""
Set new default services to the session after whipping out the old ones
:param core.api.grpc.core_pb2.SetServiceDefaults request: set-service-defaults
@ -947,7 +1021,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
] = service_defaults.services
return core_pb2.SetServiceDefaultsResponse(result=True)
def GetNodeServiceConfigs(self, request, context):
def GetNodeServiceConfigs(
self, request: core_pb2.GetNodeServiceConfigsRequest, context: ServicerContext
) -> core_pb2.GetNodeServiceConfigsResponse:
"""
Retrieve all node service configurations.
@ -973,7 +1049,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
configs.append(config)
return core_pb2.GetNodeServiceConfigsResponse(configs=configs)
def GetNodeService(self, request, context):
def GetNodeService(
self, request: core_pb2.GetNodeServiceRequest, context: ServicerContext
) -> core_pb2.GetNodeServiceResponse:
"""
Retrieve a requested service from a node
@ -991,7 +1069,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
service_proto = grpcutils.get_service_configuration(service)
return core_pb2.GetNodeServiceResponse(service=service_proto)
def GetNodeServiceFile(self, request, context):
def GetNodeServiceFile(
self, request: core_pb2.GetNodeServiceFileRequest, context: ServicerContext
) -> core_pb2.GetNodeServiceFileResponse:
"""
Retrieve a requested service file from a node
@ -1009,7 +1089,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
)
return core_pb2.GetNodeServiceFileResponse(data=file_data.data)
def SetNodeService(self, request, context):
def SetNodeService(
self, request: core_pb2.SetNodeServiceRequest, context: ServicerContext
) -> core_pb2.SetNodeServiceResponse:
"""
Set a node service for a node
@ -1025,7 +1107,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
grpcutils.service_configuration(session, config)
return core_pb2.SetNodeServiceResponse(result=True)
def SetNodeServiceFile(self, request, context):
def SetNodeServiceFile(
self, request: core_pb2.SetNodeServiceFileRequest, context: ServicerContext
) -> core_pb2.SetNodeServiceFileResponse:
"""
Store the customized service file in the service config
@ -1043,9 +1127,12 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
)
return core_pb2.SetNodeServiceFileResponse(result=True)
def ServiceAction(self, request, context):
def ServiceAction(
self, request: core_pb2.ServiceActionRequest, context: ServicerContext
) -> core_pb2.ServiceActionResponse:
"""
Take action whether to start, stop, restart, validate the service or none of the above
Take action whether to start, stop, restart, validate the service or none of
the above.
:param core.api.grpc.core_pb2.ServiceActionRequest request: service-action request
:param grpcServicerContext context: context object
@ -1082,7 +1169,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
return core_pb2.ServiceActionResponse(result=result)
def GetWlanConfigs(self, request, context):
def GetWlanConfigs(
self, request: core_pb2.GetWlanConfigsRequest, context: ServicerContext
) -> core_pb2.GetWlanConfigsResponse:
"""
Retrieve all wireless-lan configurations.
@ -1107,7 +1196,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
response.configs[node_id].CopyFrom(mapped_config)
return response
def GetWlanConfig(self, request, context):
def GetWlanConfig(
self, request: core_pb2.GetWlanConfigRequest, context: ServicerContext
) -> core_pb2.GetWlanConfigResponse:
"""
Retrieve wireless-lan configuration of a node
@ -1124,7 +1215,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
config = get_config_options(current_config, BasicRangeModel)
return core_pb2.GetWlanConfigResponse(config=config)
def SetWlanConfig(self, request, context):
def SetWlanConfig(
self, request: core_pb2.SetWlanConfigRequest, context: ServicerContext
) -> core_pb2.SetWlanConfigResponse:
"""
Set configuration data for a model
@ -1144,7 +1237,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
node.updatemodel(wlan_config.config)
return core_pb2.SetWlanConfigResponse(result=True)
def GetEmaneConfig(self, request, context):
def GetEmaneConfig(
self, request: core_pb2.GetEmaneConfigRequest, context: ServicerContext
) -> core_pb2.GetEmaneConfigResponse:
"""
Retrieve EMANE configuration of a session
@ -1159,7 +1254,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
config = get_config_options(current_config, session.emane.emane_config)
return core_pb2.GetEmaneConfigResponse(config=config)
def SetEmaneConfig(self, request, context):
def SetEmaneConfig(
self, request: core_pb2.SetEmaneConfigRequest, context: ServicerContext
) -> core_pb2.SetEmaneConfigResponse:
"""
Set EMANE configuration of a session
@ -1174,7 +1271,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
config.update(request.config)
return core_pb2.SetEmaneConfigResponse(result=True)
def GetEmaneModels(self, request, context):
def GetEmaneModels(
self, request: core_pb2.GetEmaneModelsRequest, context: ServicerContext
) -> core_pb2.GetEmaneModelsResponse:
"""
Retrieve all the EMANE models in the session
@ -1192,7 +1291,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
models.append(model)
return core_pb2.GetEmaneModelsResponse(models=models)
def GetEmaneModelConfig(self, request, context):
def GetEmaneModelConfig(
self, request: core_pb2.GetEmaneModelConfigRequest, context: ServicerContext
) -> core_pb2.GetEmaneModelConfigResponse:
"""
Retrieve EMANE model configuration of a node
@ -1210,7 +1311,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
config = get_config_options(current_config, model)
return core_pb2.GetEmaneModelConfigResponse(config=config)
def SetEmaneModelConfig(self, request, context):
def SetEmaneModelConfig(
self, request: core_pb2.SetEmaneModelConfigRequest, context: ServicerContext
) -> core_pb2.SetEmaneModelConfigResponse:
"""
Set EMANE model configuration of a node
@ -1227,7 +1330,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
session.emane.set_model_config(_id, model_config.model, model_config.config)
return core_pb2.SetEmaneModelConfigResponse(result=True)
def GetEmaneModelConfigs(self, request, context):
def GetEmaneModelConfigs(
self, request: core_pb2.GetEmaneModelConfigsRequest, context: ServicerContext
) -> core_pb2.GetEmaneModelConfigsResponse:
"""
Retrieve all EMANE model configurations of a session
@ -1261,7 +1366,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
configs.append(model_config)
return core_pb2.GetEmaneModelConfigsResponse(configs=configs)
def SaveXml(self, request, context):
def SaveXml(
self, request: core_pb2.SaveXmlRequest, context: ServicerContext
) -> core_pb2.SaveXmlResponse:
"""
Export the session nto the EmulationScript XML format
@ -1281,7 +1388,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
return core_pb2.SaveXmlResponse(data=data)
def OpenXml(self, request, context):
def OpenXml(
self, request: core_pb2.OpenXmlRequest, context: ServicerContext
) -> core_pb2.OpenXmlResponse:
"""
Import a session from the EmulationScript XML format
@ -1309,7 +1418,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
finally:
os.unlink(temp.name)
def GetInterfaces(self, request, context):
def GetInterfaces(
self, request: core_pb2.GetInterfacesRequest, context: ServicerContext
) -> core_pb2.GetInterfacesResponse:
"""
Retrieve all the interfaces of the system including bridges, virtual ethernet, and loopback
@ -1329,7 +1440,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
interfaces.append(interface)
return core_pb2.GetInterfacesResponse(interfaces=interfaces)
def EmaneLink(self, request, context):
def EmaneLink(
self, request: core_pb2.EmaneLinkRequest, context: ServicerContext
) -> core_pb2.EmaneLinkResponse:
"""
Helps broadcast wireless link/unlink between EMANE nodes.

View file

@ -4,8 +4,111 @@ Common support for configurable CORE objects.
import logging
from collections import OrderedDict
from typing import TYPE_CHECKING, Dict, List, Tuple, Type, Union
from core.emane.nodes import EmaneNet
from core.emulator.data import ConfigData
from core.emulator.enumerations import ConfigDataTypes
from core.nodes.network import WlanNode
if TYPE_CHECKING:
from core.location.mobility import WirelessModel
class ConfigGroup:
"""
Defines configuration group tabs used for display by ConfigurationOptions.
"""
def __init__(self, name: str, start: int, stop: int) -> None:
"""
Creates a ConfigGroup object.
:param str name: configuration group display name
:param int start: configurations start index for this group
:param int stop: configurations stop index for this group
"""
self.name = name
self.start = start
self.stop = stop
class Configuration:
"""
Represents a configuration options.
"""
def __init__(
self,
_id: str,
_type: ConfigDataTypes,
label: str = None,
default: str = "",
options: List[str] = None,
) -> None:
"""
Creates a Configuration object.
:param str _id: unique name for configuration
:param core.enumerations.ConfigDataTypes _type: configuration data type
:param str label: configuration label for display
:param str default: default value for configuration
:param list options: list options if this is a configuration with a combobox
"""
self.id = _id
self.type = _type
self.default = default
if not options:
options = []
self.options = options
if not label:
label = _id
self.label = label
def __str__(self):
return f"{self.__class__.__name__}(id={self.id}, type={self.type}, default={self.default}, options={self.options})"
class ConfigurableOptions:
"""
Provides a base for defining configuration options within CORE.
"""
name = None
bitmap = None
options = []
@classmethod
def configurations(cls) -> List[Configuration]:
"""
Provides the configurations for this class.
:return: configurations
:rtype: list[Configuration]
"""
return cls.options
@classmethod
def config_groups(cls) -> List[ConfigGroup]:
"""
Defines how configurations are grouped.
:return: configuration group definition
:rtype: list[ConfigGroup]
"""
return [ConfigGroup("Options", 1, len(cls.configurations()))]
@classmethod
def default_values(cls) -> Dict[str, str]:
"""
Provides an ordered mapping of configuration keys to default values.
:return: ordered configuration mapping default values
:rtype: OrderedDict
"""
return OrderedDict(
[(config.id, config.default) for config in cls.configurations()]
)
class ConfigShim:
@ -14,7 +117,7 @@ class ConfigShim:
"""
@classmethod
def str_to_dict(cls, key_values):
def str_to_dict(cls, key_values: str) -> Dict[str, str]:
"""
Converts a TLV key/value string into an ordered mapping.
@ -30,7 +133,7 @@ class ConfigShim:
return values
@classmethod
def groups_to_str(cls, config_groups):
def groups_to_str(cls, config_groups: List[ConfigGroup]) -> str:
"""
Converts configuration groups to a TLV formatted string.
@ -47,7 +150,14 @@ class ConfigShim:
return "|".join(group_strings)
@classmethod
def config_data(cls, flags, node_id, type_flags, configurable_options, config):
def config_data(
cls,
flags: int,
node_id: int,
type_flags: int,
configurable_options: ConfigurableOptions,
config: Dict[str, str],
) -> ConfigData:
"""
Convert this class to a Config API message. Some TLVs are defined
by the class, but node number, conf type flags, and values must
@ -102,50 +212,22 @@ class ConfigShim:
)
class Configuration:
"""
Represents a configuration options.
"""
def __init__(self, _id, _type, label=None, default="", options=None):
"""
Creates a Configuration object.
:param str _id: unique name for configuration
:param core.enumerations.ConfigDataTypes _type: configuration data type
:param str label: configuration label for display
:param str default: default value for configuration
:param list options: list options if this is a configuration with a combobox
"""
self.id = _id
self.type = _type
self.default = default
if not options:
options = []
self.options = options
if not label:
label = _id
self.label = label
def __str__(self):
return f"{self.__class__.__name__}(id={self.id}, type={self.type}, default={self.default}, options={self.options})"
class ConfigurableManager:
"""
Provides convenience methods for storing and retrieving configuration options for nodes.
Provides convenience methods for storing and retrieving configuration options for
nodes.
"""
_default_node = -1
_default_type = _default_node
def __init__(self):
def __init__(self) -> None:
"""
Creates a ConfigurableManager object.
"""
self.node_configurations = {}
def nodes(self):
def nodes(self) -> List[int]:
"""
Retrieves the ids of all node configurations known by this manager.
@ -154,7 +236,7 @@ class ConfigurableManager:
"""
return [x for x in self.node_configurations if x != self._default_node]
def config_reset(self, node_id=None):
def config_reset(self, node_id: int = None) -> None:
"""
Clears all configurations or configuration for a specific node.
@ -166,7 +248,13 @@ class ConfigurableManager:
elif node_id in self.node_configurations:
self.node_configurations.pop(node_id)
def set_config(self, _id, value, node_id=_default_node, config_type=_default_type):
def set_config(
self,
_id: str,
value: str,
node_id: int = _default_node,
config_type: str = _default_type,
) -> None:
"""
Set a specific configuration value for a node and configuration type.
@ -180,7 +268,12 @@ class ConfigurableManager:
node_type_configs = node_configs.setdefault(config_type, OrderedDict())
node_type_configs[_id] = value
def set_configs(self, config, node_id=_default_node, config_type=_default_type):
def set_configs(
self,
config: Dict[str, str],
node_id: int = _default_node,
config_type: str = _default_type,
) -> None:
"""
Set configurations for a node and configuration type.
@ -196,8 +289,12 @@ class ConfigurableManager:
node_configs[config_type] = config
def get_config(
self, _id, node_id=_default_node, config_type=_default_type, default=None
):
self,
_id: str,
node_id: int = _default_node,
config_type: str = _default_type,
default: str = None,
) -> str:
"""
Retrieves a specific configuration for a node and configuration type.
@ -214,7 +311,9 @@ class ConfigurableManager:
result = node_type_configs.get(_id, default)
return result
def get_configs(self, node_id=_default_node, config_type=_default_type):
def get_configs(
self, node_id: int = _default_node, config_type: str = _default_type
) -> Dict[str, str]:
"""
Retrieve configurations for a node and configuration type.
@ -229,7 +328,7 @@ class ConfigurableManager:
result = node_configs.get(config_type)
return result
def get_all_configs(self, node_id=_default_node):
def get_all_configs(self, node_id: int = _default_node) -> List[Dict[str, str]]:
"""
Retrieve all current configuration types for a node.
@ -240,72 +339,12 @@ class ConfigurableManager:
return self.node_configurations.get(node_id)
class ConfigGroup:
"""
Defines configuration group tabs used for display by ConfigurationOptions.
"""
def __init__(self, name, start, stop):
"""
Creates a ConfigGroup object.
:param str name: configuration group display name
:param int start: configurations start index for this group
:param int stop: configurations stop index for this group
"""
self.name = name
self.start = start
self.stop = stop
class ConfigurableOptions:
"""
Provides a base for defining configuration options within CORE.
"""
name = None
bitmap = None
options = []
@classmethod
def configurations(cls):
"""
Provides the configurations for this class.
:return: configurations
:rtype: list[Configuration]
"""
return cls.options
@classmethod
def config_groups(cls):
"""
Defines how configurations are grouped.
:return: configuration group definition
:rtype: list[ConfigGroup]
"""
return [ConfigGroup("Options", 1, len(cls.configurations()))]
@classmethod
def default_values(cls):
"""
Provides an ordered mapping of configuration keys to default values.
:return: ordered configuration mapping default values
:rtype: OrderedDict
"""
return OrderedDict(
[(config.id, config.default) for config in cls.configurations()]
)
class ModelManager(ConfigurableManager):
"""
Helps handle setting models for nodes and managing their model configurations.
"""
def __init__(self):
def __init__(self) -> None:
"""
Creates a ModelManager object.
"""
@ -313,7 +352,9 @@ class ModelManager(ConfigurableManager):
self.models = {}
self.node_models = {}
def set_model_config(self, node_id, model_name, config=None):
def set_model_config(
self, node_id: int, model_name: str, config: Dict[str, str] = None
) -> None:
"""
Set configuration data for a model.
@ -341,7 +382,7 @@ class ModelManager(ConfigurableManager):
# set configuration
self.set_configs(model_config, node_id=node_id, config_type=model_name)
def get_model_config(self, node_id, model_name):
def get_model_config(self, node_id: int, model_name: str) -> Dict[str, str]:
"""
Retrieve configuration data for a model.
@ -363,7 +404,12 @@ class ModelManager(ConfigurableManager):
return config
def set_model(self, node, model_class, config=None):
def set_model(
self,
node: Union[WlanNode, EmaneNet],
model_class: "WirelessModel",
config: Dict[str, str] = None,
) -> None:
"""
Set model and model configuration for node.
@ -379,7 +425,9 @@ class ModelManager(ConfigurableManager):
config = self.get_model_config(node.id, model_class.name)
node.setmodel(model_class, config)
def get_models(self, node):
def get_models(
self, node: Union[WlanNode, EmaneNet]
) -> List[Tuple[Type, Dict[str, str]]]:
"""
Return a list of model classes and values for a net if one has been
configured. This is invoked when exporting a session to XML.

View file

@ -1,6 +1,8 @@
"""
EMANE Bypass model for CORE
"""
from typing import List
from core.config import ConfigGroup, Configuration
from core.emane import emanemodel
from core.emulator.enumerations import ConfigDataTypes
@ -29,11 +31,11 @@ class EmaneBypassModel(emanemodel.EmaneModel):
phy_config = []
@classmethod
def load(cls, emane_prefix):
def load(cls, emane_prefix: str) -> None:
# ignore default logic
pass
# override config groups
@classmethod
def config_groups(cls):
def config_groups(cls) -> List[ConfigGroup]:
return [ConfigGroup("Bypass Parameters", 1, 1)]

View file

@ -4,11 +4,13 @@ commeffect.py: EMANE CommEffect model for CORE
import logging
import os
from typing import Dict, List
from lxml import etree
from core.config import ConfigGroup
from core.config import ConfigGroup, Configuration
from core.emane import emanemanifest, emanemodel
from core.nodes.interface import CoreInterface
from core.xml import emanexml
try:
@ -20,7 +22,7 @@ except ImportError:
logging.debug("compatible emane python bindings not installed")
def convert_none(x):
def convert_none(x: float) -> int:
"""
Helper to use 0 for None values.
"""
@ -45,19 +47,21 @@ class EmaneCommEffectModel(emanemodel.EmaneModel):
external_config = []
@classmethod
def load(cls, emane_prefix):
def load(cls, emane_prefix: str) -> None:
shim_xml_path = os.path.join(emane_prefix, "share/emane/manifest", cls.shim_xml)
cls.config_shim = emanemanifest.parse(shim_xml_path, cls.shim_defaults)
@classmethod
def configurations(cls):
def configurations(cls) -> List[Configuration]:
return cls.config_shim
@classmethod
def config_groups(cls):
def config_groups(cls) -> List[ConfigGroup]:
return [ConfigGroup("CommEffect SHIM Parameters", 1, len(cls.configurations()))]
def build_xml_files(self, config, interface=None):
def build_xml_files(
self, config: Dict[str, str], interface: CoreInterface = None
) -> None:
"""
Build the necessary nem and commeffect XMLs in the given path.
If an individual NEM has a nonstandard config, we need to build
@ -109,14 +113,14 @@ class EmaneCommEffectModel(emanemodel.EmaneModel):
def linkconfig(
self,
netif,
bw=None,
delay=None,
loss=None,
duplicate=None,
jitter=None,
netif2=None,
):
netif: CoreInterface,
bw: float = None,
delay: float = None,
loss: float = None,
duplicate: float = None,
jitter: float = None,
netif2: CoreInterface = None,
) -> None:
"""
Generate CommEffect events when a Link Message is received having
link parameters.

View file

@ -6,6 +6,7 @@ import logging
import os
import threading
from collections import OrderedDict
from typing import TYPE_CHECKING, Dict, List, Set, Tuple, Type
from core import utils
from core.config import ConfigGroup, Configuration, ModelManager
@ -19,8 +20,15 @@ from core.emane.rfpipe import EmaneRfPipeModel
from core.emane.tdma import EmaneTdmaModel
from core.emulator.enumerations import ConfigDataTypes, RegisterTlvs
from core.errors import CoreCommandError, CoreError
from core.nodes.base import CoreNode
from core.nodes.interface import CoreInterface
from core.nodes.network import CtrlNet
from core.xml import emanexml
if TYPE_CHECKING:
from core.emulator.session import Session
try:
from emane.events import EventService
from emane.events import LocationEvent
@ -57,7 +65,7 @@ class EmaneManager(ModelManager):
EVENTCFGVAR = "LIBEMANEEVENTSERVICECONFIG"
DEFAULT_LOG_LEVEL = 3
def __init__(self, session):
def __init__(self, session: "Session") -> None:
"""
Creates a Emane instance.
@ -86,7 +94,9 @@ class EmaneManager(ModelManager):
self.event_device = None
self.emane_check()
def getifcconfig(self, node_id, interface, model_name):
def getifcconfig(
self, node_id: int, interface: CoreInterface, model_name: str
) -> Dict[str, str]:
"""
Retrieve interface configuration or node configuration if not provided.
@ -129,11 +139,11 @@ class EmaneManager(ModelManager):
return config
def config_reset(self, node_id=None):
def config_reset(self, node_id: int = None) -> None:
super().config_reset(node_id)
self.set_configs(self.emane_config.default_values())
def emane_check(self):
def emane_check(self) -> None:
"""
Check if emane is installed and load models.
@ -157,7 +167,7 @@ class EmaneManager(ModelManager):
except CoreCommandError:
logging.info("emane is not installed")
def deleteeventservice(self):
def deleteeventservice(self) -> None:
if self.service:
for fd in self.service._readFd, self.service._writeFd:
if fd >= 0:
@ -168,7 +178,7 @@ class EmaneManager(ModelManager):
self.service = None
self.event_device = None
def initeventservice(self, filename=None, shutdown=False):
def initeventservice(self, filename: str = None, shutdown: bool = False) -> None:
"""
Re-initialize the EMANE Event service.
The multicast group and/or port may be configured.
@ -186,7 +196,7 @@ class EmaneManager(ModelManager):
logging.error(
"invalid emane event service device provided: %s", self.event_device
)
return False
return
# make sure the event control network is in place
eventnet = self.session.add_remove_control_net(
@ -205,9 +215,7 @@ class EmaneManager(ModelManager):
except EventServiceException:
logging.exception("error instantiating emane EventService")
return True
def load_models(self, emane_models):
def load_models(self, emane_models: List[Type[EmaneModel]]) -> None:
"""
Load EMANE models and make them available.
"""
@ -219,7 +227,7 @@ class EmaneManager(ModelManager):
emane_model.load(emane_prefix)
self.models[emane_model.name] = emane_model
def add_node(self, emane_net):
def add_node(self, emane_net: EmaneNet) -> None:
"""
Add EMANE network object to this manager.
@ -233,7 +241,7 @@ class EmaneManager(ModelManager):
)
self._emane_nets[emane_net.id] = emane_net
def getnodes(self):
def getnodes(self) -> Set[CoreNode]:
"""
Return a set of CoreNodes that are linked to an EMANE network,
e.g. containers having one or more radio interfaces.
@ -245,7 +253,7 @@ class EmaneManager(ModelManager):
nodes.add(netif.node)
return nodes
def setup(self):
def setup(self) -> int:
"""
Setup duties for EMANE manager.
@ -303,7 +311,7 @@ class EmaneManager(ModelManager):
self.check_node_models()
return EmaneManager.SUCCESS
def startup(self):
def startup(self) -> int:
"""
After all the EMANE networks have been added, build XML files
and start the daemons.
@ -347,7 +355,7 @@ class EmaneManager(ModelManager):
return EmaneManager.SUCCESS
def poststartup(self):
def poststartup(self) -> None:
"""
Retransmit location events now that all NEMs are active.
"""
@ -367,7 +375,7 @@ class EmaneManager(ModelManager):
x, y, z = netif.node.position.get()
emane_node.setnemposition(netif, x, y, z)
def reset(self):
def reset(self) -> None:
"""
Remove all EMANE networks from the dictionary, reset port numbers and
nem id counters
@ -382,7 +390,7 @@ class EmaneManager(ModelManager):
"emane_transform_port", 8200
)
def shutdown(self):
def shutdown(self) -> None:
"""
stop all EMANE daemons
"""
@ -394,7 +402,7 @@ class EmaneManager(ModelManager):
self.stopdaemons()
self.stopeventmonitor()
def buildxml(self):
def buildxml(self) -> None:
"""
Build XML files required to run EMANE on each node.
NEMs run inside containers using the control network for passing
@ -410,7 +418,7 @@ class EmaneManager(ModelManager):
self.buildnemxml()
self.buildeventservicexml()
def check_node_models(self):
def check_node_models(self) -> None:
"""
Associate EMANE model classes with EMANE network nodes.
"""
@ -438,7 +446,7 @@ class EmaneManager(ModelManager):
model_class = self.models[model_name]
emane_node.setmodel(model_class, config)
def nemlookup(self, nemid):
def nemlookup(self, nemid) -> Tuple[EmaneNet, CoreInterface]:
"""
Look for the given numerical NEM ID and return the first matching
EMANE network and NEM interface.
@ -456,7 +464,7 @@ class EmaneManager(ModelManager):
return emane_node, netif
def numnems(self):
def numnems(self) -> int:
"""
Return the number of NEMs emulated locally.
"""
@ -466,7 +474,7 @@ class EmaneManager(ModelManager):
count += len(emane_node.netifs())
return count
def buildplatformxml(self, ctrlnet):
def buildplatformxml(self, ctrlnet: CtrlNet) -> None:
"""
Build a platform.xml file now that all nodes are configured.
"""
@ -480,7 +488,7 @@ class EmaneManager(ModelManager):
self, ctrlnet, emane_node, nemid, platform_xmls
)
def buildnemxml(self):
def buildnemxml(self) -> None:
"""
Builds the nem, mac, and phy xml files for each EMANE network.
"""
@ -488,7 +496,7 @@ class EmaneManager(ModelManager):
emane_net = self._emane_nets[key]
emanexml.build_xml_files(self, emane_net)
def buildeventservicexml(self):
def buildeventservicexml(self) -> None:
"""
Build the libemaneeventservice.xml file if event service options
were changed in the global config.
@ -520,7 +528,7 @@ class EmaneManager(ModelManager):
)
)
def startdaemons(self):
def startdaemons(self) -> None:
"""
Start one EMANE daemon per node having a radio.
Add a control network even if the user has not configured one.
@ -596,7 +604,7 @@ class EmaneManager(ModelManager):
self.session.distributed.execute(lambda x: x.remote_cmd(emanecmd, cwd=path))
logging.info("host emane daemon running: %s", emanecmd)
def stopdaemons(self):
def stopdaemons(self) -> None:
"""
Kill the appropriate EMANE daemons.
"""
@ -623,7 +631,7 @@ class EmaneManager(ModelManager):
except CoreCommandError:
logging.exception("error shutting down emane daemons")
def installnetifs(self):
def installnetifs(self) -> None:
"""
Install TUN/TAP virtual interfaces into their proper namespaces
now that the EMANE daemons are running.
@ -633,7 +641,7 @@ class EmaneManager(ModelManager):
logging.info("emane install netifs for node: %d", key)
emane_node.installnetifs()
def deinstallnetifs(self):
def deinstallnetifs(self) -> None:
"""
Uninstall TUN/TAP virtual interfaces.
"""
@ -641,7 +649,7 @@ class EmaneManager(ModelManager):
emane_node = self._emane_nets[key]
emane_node.deinstallnetifs()
def doeventmonitor(self):
def doeventmonitor(self) -> bool:
"""
Returns boolean whether or not EMANE events will be monitored.
"""
@ -649,7 +657,7 @@ class EmaneManager(ModelManager):
# generate the EMANE events when nodes are moved
return self.session.options.get_config_bool("emane_event_monitor")
def genlocationevents(self):
def genlocationevents(self) -> bool:
"""
Returns boolean whether or not EMANE events will be generated.
"""
@ -660,7 +668,7 @@ class EmaneManager(ModelManager):
tmp = not self.doeventmonitor()
return tmp
def starteventmonitor(self):
def starteventmonitor(self) -> None:
"""
Start monitoring EMANE location events if configured to do so.
"""
@ -681,7 +689,7 @@ class EmaneManager(ModelManager):
self.eventmonthread.daemon = True
self.eventmonthread.start()
def stopeventmonitor(self):
def stopeventmonitor(self) -> None:
"""
Stop monitoring EMANE location events.
"""
@ -697,7 +705,7 @@ class EmaneManager(ModelManager):
self.eventmonthread.join()
self.eventmonthread = None
def eventmonitorloop(self):
def eventmonitorloop(self) -> None:
"""
Thread target that monitors EMANE location events.
"""
@ -724,7 +732,7 @@ class EmaneManager(ModelManager):
threading.currentThread().getName(),
)
def handlelocationevent(self, rxnemid, eid, data):
def handlelocationevent(self, rxnemid: int, eid: int, data: str) -> None:
"""
Handle an EMANE location event.
"""
@ -747,7 +755,9 @@ class EmaneManager(ModelManager):
logging.debug("emane location event: %s,%s,%s", lat, lon, alt)
self.handlelocationeventtoxyz(txnemid, lat, lon, alt)
def handlelocationeventtoxyz(self, nemid, lat, lon, alt):
def handlelocationeventtoxyz(
self, nemid: int, lat: float, lon: float, alt: float
) -> bool:
"""
Convert the (NEM ID, lat, long, alt) from a received location event
into a node and x,y,z coordinate values, sending a Node Message.
@ -800,11 +810,11 @@ class EmaneManager(ModelManager):
# don"t use node.setposition(x,y,z) which generates an event
node.position.set(x, y, z)
node_data = node.data(message_type=0, lat=str(lat), lon=str(lon), alt=str(alt))
node_data = node.data(message_type=0, lat=lat, lon=lon, alt=alt)
self.session.broadcast_node(node_data)
return True
def emanerunning(self, node):
def emanerunning(self, node: CoreNode) -> bool:
"""
Return True if an EMANE process associated with the given node is running,
False otherwise.
@ -827,7 +837,7 @@ class EmaneGlobalModel:
name = "emane"
bitmap = None
def __init__(self, session):
def __init__(self, session: "Session") -> None:
self.session = session
self.nem_config = [
Configuration(
@ -840,7 +850,7 @@ class EmaneGlobalModel:
self.emulator_config = None
self.parse_config()
def parse_config(self):
def parse_config(self) -> None:
emane_prefix = self.session.options.get_config(
"emane_prefix", default=DEFAULT_EMANE_PREFIX
)
@ -862,10 +872,10 @@ class EmaneGlobalModel:
),
)
def configurations(self):
def configurations(self) -> List[Configuration]:
return self.emulator_config + self.nem_config
def config_groups(self):
def config_groups(self) -> List[ConfigGroup]:
emulator_len = len(self.emulator_config)
config_len = len(self.configurations())
return [
@ -873,7 +883,7 @@ class EmaneGlobalModel:
ConfigGroup("NEM Parameters", emulator_len + 1, config_len),
]
def default_values(self):
def default_values(self) -> Dict[str, str]:
return OrderedDict(
[(config.id, config.default) for config in self.configurations()]
)

View file

@ -1,4 +1,5 @@
import logging
from typing import Dict, List
from core.config import Configuration
from core.emulator.enumerations import ConfigDataTypes
@ -13,12 +14,12 @@ except ImportError:
logging.debug("compatible emane python bindings not installed")
def _type_value(config_type):
def _type_value(config_type: str) -> ConfigDataTypes:
"""
Convert emane configuration type to core configuration value.
:param str config_type: emane configuration type
:return:
:return: core config type
"""
config_type = config_type.upper()
if config_type == "DOUBLE":
@ -28,7 +29,7 @@ def _type_value(config_type):
return ConfigDataTypes[config_type]
def _get_possible(config_type, config_regex):
def _get_possible(config_type: str, config_regex: str) -> List[str]:
"""
Retrieve possible config value options based on emane regexes.
@ -47,7 +48,7 @@ def _get_possible(config_type, config_regex):
return []
def _get_default(config_type_name, config_value):
def _get_default(config_type_name: str, config_value: List[str]) -> str:
"""
Convert default configuration values to one used by core.
@ -72,7 +73,7 @@ def _get_default(config_type_name, config_value):
return config_default
def parse(manifest_path, defaults):
def parse(manifest_path: str, defaults: Dict[str, str]) -> List[Configuration]:
"""
Parses a valid emane manifest file and converts the provided configuration values into ones used by core.

View file

@ -3,12 +3,14 @@ Defines Emane Models used within CORE.
"""
import logging
import os
from typing import Dict, List
from core.config import ConfigGroup, Configuration
from core.emane import emanemanifest
from core.emulator.enumerations import ConfigDataTypes
from core.errors import CoreError
from core.location.mobility import WirelessModel
from core.nodes.interface import CoreInterface
from core.xml import emanexml
@ -45,7 +47,7 @@ class EmaneModel(WirelessModel):
config_ignore = set()
@classmethod
def load(cls, emane_prefix):
def load(cls, emane_prefix: str) -> None:
"""
Called after being loaded within the EmaneManager. Provides configured emane_prefix for
parsing xml files.
@ -63,7 +65,7 @@ class EmaneModel(WirelessModel):
cls.phy_config = emanemanifest.parse(phy_xml_path, cls.phy_defaults)
@classmethod
def configurations(cls):
def configurations(cls) -> List[Configuration]:
"""
Returns the combination all all configurations (mac, phy, and external).
@ -73,7 +75,7 @@ class EmaneModel(WirelessModel):
return cls.mac_config + cls.phy_config + cls.external_config
@classmethod
def config_groups(cls):
def config_groups(cls) -> List[ConfigGroup]:
"""
Returns the defined configuration groups.
@ -89,10 +91,12 @@ class EmaneModel(WirelessModel):
ConfigGroup("External Parameters", phy_len + 1, config_len),
]
def build_xml_files(self, config, interface=None):
def build_xml_files(
self, config: Dict[str, str], interface: CoreInterface = None
) -> None:
"""
Builds xml files for this emane model. Creates a nem.xml file that points to both mac.xml and phy.xml
definitions.
Builds xml files for this emane model. Creates a nem.xml file that points to
both mac.xml and phy.xml definitions.
:param dict config: emane model configuration for the node and interface
:param interface: interface for the emane node
@ -127,7 +131,7 @@ class EmaneModel(WirelessModel):
phy_file = os.path.join(self.session.session_dir, phy_name)
emanexml.create_phy_xml(self, config, phy_file, server)
def post_startup(self):
def post_startup(self) -> None:
"""
Logic to execute after the emane manager is finished with startup.
@ -135,7 +139,7 @@ class EmaneModel(WirelessModel):
"""
logging.debug("emane model(%s) has no post setup tasks", self.name)
def update(self, moved, moved_netifs):
def update(self, moved: bool, 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
@ -143,7 +147,7 @@ class EmaneModel(WirelessModel):
:param bool moved: were nodes moved
:param list moved_netifs: interfaces that were moved
:return:
:return: nothing
"""
try:
wlan = self.session.get_node(self.id)
@ -153,14 +157,14 @@ class EmaneModel(WirelessModel):
def linkconfig(
self,
netif,
bw=None,
delay=None,
loss=None,
duplicate=None,
jitter=None,
netif2=None,
):
netif: CoreInterface,
bw: float = None,
delay: float = None,
loss: float = None,
duplicate: float = None,
jitter: float = None,
netif2: CoreInterface = None,
) -> None:
"""
Invoked when a Link Message is received. Default is unimplemented.

View file

@ -15,7 +15,7 @@ class EmaneIeee80211abgModel(emanemodel.EmaneModel):
mac_xml = "ieee80211abgmaclayer.xml"
@classmethod
def load(cls, emane_prefix):
def load(cls, emane_prefix: str) -> None:
cls.mac_defaults["pcrcurveuri"] = os.path.join(
emane_prefix, "share/emane/xml/models/mac/ieee80211abg/ieee80211pcr.xml"
)

View file

@ -4,9 +4,18 @@ share the same MAC+PHY model.
"""
import logging
from typing import TYPE_CHECKING, Dict, List, Optional, Type
from core.emulator.distributed import DistributedServer
from core.emulator.enumerations import LinkTypes, NodeTypes, RegisterTlvs
from core.nodes.base import CoreNetworkBase
from core.nodes.interface import CoreInterface
if TYPE_CHECKING:
from core.emulator.session import Session
from core.location.mobility import WirelessModel
WirelessModelType = Type[WirelessModel]
try:
from emane.events import LocationEvent
@ -29,7 +38,14 @@ class EmaneNet(CoreNetworkBase):
type = "wlan"
is_emane = True
def __init__(self, session, _id=None, name=None, start=True, server=None):
def __init__(
self,
session: "Session",
_id: int = None,
name: str = None,
start: bool = True,
server: DistributedServer = None,
) -> None:
super().__init__(session, _id, name, start, server)
self.conf = ""
self.up = False
@ -39,20 +55,20 @@ class EmaneNet(CoreNetworkBase):
def linkconfig(
self,
netif,
bw=None,
delay=None,
loss=None,
duplicate=None,
jitter=None,
netif2=None,
):
netif: CoreInterface,
bw: float = None,
delay: float = None,
loss: float = None,
duplicate: float = None,
jitter: float = None,
netif2: CoreInterface = None,
) -> None:
"""
The CommEffect model supports link configuration.
"""
if not self.model:
return
return self.model.linkconfig(
self.model.linkconfig(
netif=netif,
bw=bw,
delay=delay,
@ -62,19 +78,19 @@ class EmaneNet(CoreNetworkBase):
netif2=netif2,
)
def config(self, conf):
def config(self, conf: str) -> None:
self.conf = conf
def shutdown(self):
def shutdown(self) -> None:
pass
def link(self, netif1, netif2):
def link(self, netif1: CoreInterface, netif2: CoreInterface) -> None:
pass
def unlink(self, netif1, netif2):
def unlink(self, netif1: CoreInterface, netif2: CoreInterface) -> None:
pass
def updatemodel(self, config):
def updatemodel(self, config: Dict[str, str]) -> None:
if not self.model:
raise ValueError("no model set to update for node(%s)", self.id)
logging.info(
@ -82,7 +98,7 @@ class EmaneNet(CoreNetworkBase):
)
self.model.set_configs(config, node_id=self.id)
def setmodel(self, model, config):
def setmodel(self, model: "WirelessModelType", config: Dict[str, str]) -> None:
"""
set the EmaneModel associated with this node
"""
@ -96,14 +112,14 @@ class EmaneNet(CoreNetworkBase):
self.mobility = model(session=self.session, _id=self.id)
self.mobility.update_config(config)
def setnemid(self, netif, nemid):
def setnemid(self, netif: CoreInterface, nemid: int) -> None:
"""
Record an interface to numerical ID mapping. The Emane controller
object manages and assigns these IDs for all NEMs.
"""
self.nemidmap[netif] = nemid
def getnemid(self, netif):
def getnemid(self, netif: CoreInterface) -> Optional[int]:
"""
Given an interface, return its numerical ID.
"""
@ -112,7 +128,7 @@ class EmaneNet(CoreNetworkBase):
else:
return self.nemidmap[netif]
def getnemnetif(self, nemid):
def getnemnetif(self, nemid: int) -> Optional[CoreInterface]:
"""
Given a numerical NEM ID, return its interface. This returns the
first interface that matches the given NEM ID.
@ -122,13 +138,13 @@ class EmaneNet(CoreNetworkBase):
return netif
return None
def netifs(self, sort=True):
def netifs(self, sort: bool = True) -> List[CoreInterface]:
"""
Retrieve list of linked interfaces sorted by node number.
"""
return sorted(self._netif.values(), key=lambda ifc: ifc.node.id)
def installnetifs(self):
def installnetifs(self) -> None:
"""
Install TAP devices into their namespaces. This is done after
EMANE daemons have been started, because that is their only chance
@ -159,7 +175,7 @@ class EmaneNet(CoreNetworkBase):
x, y, z = netif.node.position.get()
self.setnemposition(netif, x, y, z)
def deinstallnetifs(self):
def deinstallnetifs(self) -> None:
"""
Uninstall TAP devices. This invokes their shutdown method for
any required cleanup; the device may be actually removed when
@ -170,7 +186,9 @@ class EmaneNet(CoreNetworkBase):
netif.shutdown()
netif.poshook = None
def setnemposition(self, netif, x, y, z):
def setnemposition(
self, netif: CoreInterface, x: float, y: float, z: float
) -> None:
"""
Publish a NEM location change event using the EMANE event service.
"""
@ -191,7 +209,7 @@ class EmaneNet(CoreNetworkBase):
event.append(nemid, latitude=lat, longitude=lon, altitude=alt)
self.session.emane.service.publish(0, event)
def setnempositions(self, moved_netifs):
def setnempositions(self, moved_netifs: List[CoreInterface]) -> None:
"""
Several NEMs have moved, from e.g. a WaypointMobilityModel
calculation. Generate an EMANE Location Event having several

View file

@ -15,7 +15,7 @@ class EmaneRfPipeModel(emanemodel.EmaneModel):
mac_xml = "rfpipemaclayer.xml"
@classmethod
def load(cls, emane_prefix):
def load(cls, emane_prefix: str) -> None:
cls.mac_defaults["pcrcurveuri"] = os.path.join(
emane_prefix, "share/emane/xml/models/mac/rfpipe/rfpipepcr.xml"
)

View file

@ -27,7 +27,7 @@ class EmaneTdmaModel(emanemodel.EmaneModel):
config_ignore = {schedule_name}
@classmethod
def load(cls, emane_prefix):
def load(cls, emane_prefix: str) -> None:
cls.mac_defaults["pcrcurveuri"] = os.path.join(
emane_prefix,
"share/emane/xml/models/mac/tdmaeventscheduler/tdmabasemodelpcr.xml",
@ -43,7 +43,7 @@ class EmaneTdmaModel(emanemodel.EmaneModel):
),
)
def post_startup(self):
def post_startup(self) -> None:
"""
Logic to execute after the emane manager is finished with startup.

View file

@ -3,13 +3,14 @@ import logging
import os
import signal
import sys
from typing import Mapping, Type
import core.services
from core.emulator.session import Session
from core.services.coreservices import ServiceManager
def signal_handler(signal_number, _):
def signal_handler(signal_number: int, _) -> None:
"""
Handle signals and force an exit with cleanup.
@ -33,7 +34,7 @@ class CoreEmu:
Provides logic for creating and configuring CORE sessions and the nodes within them.
"""
def __init__(self, config=None):
def __init__(self, config: Mapping[str, str] = None) -> None:
"""
Create a CoreEmu object.
@ -57,7 +58,7 @@ class CoreEmu:
# catch exit event
atexit.register(self.shutdown)
def load_services(self):
def load_services(self) -> None:
# load default services
self.service_errors = core.services.load()
@ -70,7 +71,7 @@ class CoreEmu:
custom_service_errors = ServiceManager.add_services(service_path)
self.service_errors.extend(custom_service_errors)
def shutdown(self):
def shutdown(self) -> None:
"""
Shutdown all CORE session.
@ -83,7 +84,7 @@ class CoreEmu:
session = sessions[_id]
session.shutdown()
def create_session(self, _id=None, _cls=Session):
def create_session(self, _id: int = None, _cls: Type[Session] = Session) -> Session:
"""
Create a new CORE session.
@ -101,7 +102,7 @@ class CoreEmu:
self.sessions[_id] = session
return session
def delete_session(self, _id):
def delete_session(self, _id: int) -> bool:
"""
Shutdown and delete a CORE session.

View file

@ -7,6 +7,7 @@ import os
import threading
from collections import OrderedDict
from tempfile import NamedTemporaryFile
from typing import TYPE_CHECKING, Callable, Dict, Tuple
import netaddr
from fabric import Connection
@ -17,6 +18,9 @@ from core.errors import CoreCommandError
from core.nodes.interface import GreTap
from core.nodes.network import CoreNetwork, CtrlNet
if TYPE_CHECKING:
from core.emulator.session import Session
LOCK = threading.Lock()
CMD_HIDE = True
@ -26,7 +30,7 @@ class DistributedServer:
Provides distributed server interactions.
"""
def __init__(self, name, host):
def __init__(self, name: str, host: str) -> None:
"""
Create a DistributedServer instance.
@ -38,7 +42,9 @@ class DistributedServer:
self.conn = Connection(host, user="root")
self.lock = threading.Lock()
def remote_cmd(self, cmd, env=None, cwd=None, wait=True):
def remote_cmd(
self, cmd: str, env: Dict[str, str] = None, cwd: str = None, wait: bool = True
) -> str:
"""
Run command remotely using server connection.
@ -73,7 +79,7 @@ class DistributedServer:
stdout, stderr = e.streams_for_display()
raise CoreCommandError(e.result.exited, cmd, stdout, stderr)
def remote_put(self, source, destination):
def remote_put(self, source: str, destination: str) -> None:
"""
Push file to remote server.
@ -84,7 +90,7 @@ class DistributedServer:
with self.lock:
self.conn.put(source, destination)
def remote_put_temp(self, destination, data):
def remote_put_temp(self, destination: str, data: str) -> None:
"""
Remote push file contents to a remote server, using a temp file as an
intermediate step.
@ -106,11 +112,11 @@ class DistributedController:
Provides logic for dealing with remote tunnels and distributed servers.
"""
def __init__(self, session):
def __init__(self, session: "Session") -> None:
"""
Create
:param session:
:param session: session
"""
self.session = session
self.servers = OrderedDict()
@ -119,7 +125,7 @@ class DistributedController:
"distributed_address", default=None
)
def add_server(self, name, host):
def add_server(self, name: str, host: str) -> None:
"""
Add distributed server configuration.
@ -132,7 +138,7 @@ class DistributedController:
cmd = f"mkdir -p {self.session.session_dir}"
server.remote_cmd(cmd)
def execute(self, func):
def execute(self, func: Callable[[DistributedServer], None]) -> None:
"""
Convenience for executing logic against all distributed servers.
@ -143,7 +149,7 @@ class DistributedController:
server = self.servers[name]
func(server)
def shutdown(self):
def shutdown(self) -> None:
"""
Shutdown logic for dealing with distributed tunnels and server session
directories.
@ -165,7 +171,7 @@ class DistributedController:
# clear tunnels
self.tunnels.clear()
def start(self):
def start(self) -> None:
"""
Start distributed network tunnels.
@ -184,7 +190,9 @@ class DistributedController:
server = self.servers[name]
self.create_gre_tunnel(node, server)
def create_gre_tunnel(self, node, server):
def create_gre_tunnel(
self, node: CoreNetwork, server: DistributedServer
) -> Tuple[GreTap, GreTap]:
"""
Create gre tunnel using a pair of gre taps between the local and remote server.
@ -222,7 +230,7 @@ class DistributedController:
self.tunnels[key] = tunnel
return tunnel
def tunnel_key(self, n1_id, n2_id):
def tunnel_key(self, n1_id: int, n2_id: int) -> int:
"""
Compute a 32-bit key used to uniquely identify a GRE tunnel.
The hash(n1num), hash(n2num) values are used, so node numbers may be
@ -239,7 +247,7 @@ class DistributedController:
)
return key & 0xFFFFFFFF
def get_tunnel(self, n1_id, n2_id):
def get_tunnel(self, n1_id: int, n2_id: int) -> Tuple[GreTap, GreTap]:
"""
Return the GreTap between two nodes if it exists.

View file

@ -1,40 +1,32 @@
from typing import 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=0):
def __init__(self, _id: int = 0) -> None:
self.id = _id
def next(self):
def next(self) -> int:
self.id += 1
return self.id
def create_interface(node, network, interface_data):
"""
Create an interface for a node on a network using provided interface data.
:param node: node to create interface for
:param core.nodes.base.CoreNetworkBase network: network to associate interface with
:param core.emulator.emudata.InterfaceData 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)
def link_config(network, interface, link_options, devname=None, interface_two=None):
def link_config(
network: CoreNetworkBase,
interface: CoreInterface,
link_options: LinkOptions,
devname: str = None,
interface_two: CoreInterface = None,
) -> None:
"""
Convenience method for configuring a link,
@ -68,7 +60,7 @@ class NodeOptions:
Options for creating and updating nodes within core.
"""
def __init__(self, name=None, model="PC", image=None):
def __init__(self, name: str = None, model: str = "PC", image: str = None) -> None:
"""
Create a NodeOptions object.
@ -93,7 +85,7 @@ class NodeOptions:
self.image = image
self.emane = None
def set_position(self, x, y):
def set_position(self, x: float, y: float) -> None:
"""
Convenience method for setting position.
@ -104,7 +96,7 @@ class NodeOptions:
self.x = x
self.y = y
def set_location(self, lat, lon, alt):
def set_location(self, lat: float, lon: float, alt: float) -> None:
"""
Convenience method for setting location.
@ -123,7 +115,7 @@ class LinkOptions:
Options for creating and updating links within core.
"""
def __init__(self, _type=LinkTypes.WIRED):
def __init__(self, _type: LinkTypes = LinkTypes.WIRED) -> None:
"""
Create a LinkOptions object.
@ -148,12 +140,96 @@ class LinkOptions:
self.opaque = None
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 int _id: interface id
:param str name: name for interface
:param str mac: mac address
:param str ip4: ipv4 address
:param int ip4_mask: ipv4 bit mask
:param str ip6: ipv6 address
:param int ip6_mask: ipv6 bit mask
"""
self.id = _id
self.name = name
self.mac = mac
self.ip4 = ip4
self.ip4_mask = ip4_mask
self.ip6 = ip6
self.ip6_mask = ip6_mask
def has_ip4(self) -> bool:
"""
Determines if interface has an ip4 address.
:return: True if has ip4, False otherwise
"""
return all([self.ip4, self.ip4_mask])
def has_ip6(self) -> bool:
"""
Determines if interface has an ip6 address.
:return: True if has ip6, False otherwise
"""
return all([self.ip6, self.ip6_mask])
def ip4_address(self) -> Optional[str]:
"""
Retrieve a string representation of the ip4 address and netmask.
:return: ip4 string or None
"""
if self.has_ip4():
return f"{self.ip4}/{self.ip4_mask}"
else:
return None
def ip6_address(self) -> Optional[str]:
"""
Retrieve a string representation of the ip6 address and netmask.
:return: ip4 string or None
"""
if self.has_ip6():
return f"{self.ip6}/{self.ip6_mask}"
else:
return None
def get_addresses(self) -> List[str]:
"""
Returns a list of ip4 and ip6 address when present.
:return: list of addresses
:rtype: list
"""
ip4 = self.ip4_address()
ip6 = self.ip6_address()
return [i for i in [ip4, ip6] if i]
class IpPrefixes:
"""
Convenience class to help generate IP4 and IP6 addresses for nodes within CORE.
"""
def __init__(self, ip4_prefix=None, ip6_prefix=None):
def __init__(self, ip4_prefix: str = None, ip6_prefix: str = None) -> None:
"""
Creates an IpPrefixes object.
@ -171,7 +247,7 @@ class IpPrefixes:
if ip6_prefix:
self.ip6 = netaddr.IPNetwork(ip6_prefix)
def ip4_address(self, node):
def ip4_address(self, node: CoreNode) -> str:
"""
Convenience method to return the IP4 address for a node.
@ -183,7 +259,7 @@ class IpPrefixes:
raise ValueError("ip4 prefixes have not been set")
return str(self.ip4[node.id])
def ip6_address(self, node):
def ip6_address(self, node: CoreNode) -> str:
"""
Convenience method to return the IP6 address for a node.
@ -195,7 +271,9 @@ class IpPrefixes:
raise ValueError("ip6 prefixes have not been set")
return str(self.ip6[node.id])
def create_interface(self, node, name=None, mac=None):
def create_interface(
self, node: CoreNode, name: str = None, mac: str = None
) -> InterfaceData:
"""
Creates interface data for linking nodes, using the nodes unique id for
generation, along with a random mac address, unless provided.
@ -239,76 +317,22 @@ class IpPrefixes:
)
class InterfaceData:
"""
Convenience class for storing interface data.
def create_interface(
node: CoreNode, network: CoreNetworkBase, interface_data: InterfaceData
):
"""
Create an interface for a node on a network using provided interface data.
def __init__(self, _id, name, mac, ip4, ip4_mask, ip6, ip6_mask):
:param node: node to create interface for
:param core.nodes.base.CoreNetworkBase network: network to associate interface with
:param core.emulator.emudata.InterfaceData interface_data: interface data
:return: created interface
"""
Creates an InterfaceData object.
:param int _id: interface id
:param str name: name for interface
:param str mac: mac address
:param str ip4: ipv4 address
:param int ip4_mask: ipv4 bit mask
:param str ip6: ipv6 address
:param int 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):
"""
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):
"""
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):
"""
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):
"""
Retrieve a string representation of the ip6 address and netmask.
:return: ip4 string or None
"""
if self.has_ip6():
return f"{self.ip6}/{self.ip6_mask}"
else:
return None
def get_addresses(self):
"""
Returns a list of ip4 and ip6 address when present.
:return: list of addresses
:rtype: list
"""
ip4 = self.ip4_address()
ip6 = self.ip6_address()
return [i for i in [ip4, ip6] if i]
node.newnetif(
network,
addrlist=interface_data.get_addresses(),
hwaddr=interface_data.mac,
ifindex=interface_data.id,
ifname=interface_data.name,
)
return node.netif(interface_data.id)

View file

@ -12,14 +12,23 @@ import subprocess
import tempfile
import threading
import time
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Type
from core import constants, utils
from core.emane.emanemanager import EmaneManager
from core.emane.nodes import EmaneNet
from core.emulator.data import EventData, ExceptionData, NodeData
from core.emulator.data import (
ConfigData,
EventData,
ExceptionData,
FileData,
LinkData,
NodeData,
)
from core.emulator.distributed import DistributedController
from core.emulator.emudata import (
IdGen,
InterfaceData,
LinkOptions,
NodeOptions,
create_interface,
@ -31,8 +40,9 @@ from core.errors import CoreError
from core.location.corelocation import CoreLocation
from core.location.event import EventLoop
from core.location.mobility import BasicRangeModel, MobilityManager
from core.nodes.base import CoreNetworkBase, CoreNode, CoreNodeBase
from core.nodes.base import CoreNetworkBase, CoreNode, CoreNodeBase, NodeBase
from core.nodes.docker import DockerNode
from core.nodes.interface import GreTap
from core.nodes.lxd import LxcNode
from core.nodes.network import (
CtrlNet,
@ -45,7 +55,7 @@ from core.nodes.network import (
)
from core.nodes.physical import PhysicalNode, Rj45Node
from core.plugins.sdt import Sdt
from core.services.coreservices import CoreServices
from core.services.coreservices import CoreServices, ServiceBootError
from core.xml import corexml, corexmldeployment
from core.xml.corexml import CoreXmlReader, CoreXmlWriter
@ -74,7 +84,9 @@ class Session:
CORE session manager.
"""
def __init__(self, _id, config=None, mkdir=True):
def __init__(
self, _id: int, config: Dict[str, str] = None, mkdir: bool = True
) -> None:
"""
Create a Session instance.
@ -150,7 +162,7 @@ class Session:
}
@classmethod
def get_node_class(cls, _type):
def get_node_class(cls, _type: NodeTypes) -> Type[NodeBase]:
"""
Retrieve the class for a given node type.
@ -163,20 +175,25 @@ class Session:
return node_class
@classmethod
def get_node_type(cls, _class):
def get_node_type(cls, _class: Type[NodeBase]) -> NodeTypes:
"""
Retrieve node type for a given node class.
:param _class: node class to get a node type for
:return: node type
:rtype: core.emulator.enumerations.NodeTypes
:raises CoreError: when node type does not exist
"""
node_type = NODES_TYPE.get(_class)
if node_type is None:
raise CoreError(f"invalid node class: {_class}")
return node_type
def _link_nodes(self, node_one_id, node_two_id):
def _link_nodes(
self, node_one_id: int, node_two_id: int
) -> Tuple[
CoreNode, CoreNode, CoreNetworkBase, CoreNetworkBase, Tuple[GreTap, GreTap]
]:
"""
Convenience method for retrieving nodes within link data.
@ -237,14 +254,15 @@ class Session:
)
return node_one, node_two, net_one, net_two, tunnel
def _link_wireless(self, objects, connect):
def _link_wireless(self, objects: Iterable[CoreNodeBase], connect: bool) -> None:
"""
Objects to deal with when connecting/disconnecting wireless links.
:param list objects: possible objects to deal with
:param bool connect: link interfaces if True, unlink otherwise
:return: nothing
:raises core.CoreError: when objects to link is less than 2, or no common networks are found
:raises core.CoreError: when objects to link is less than 2, or no common
networks are found
"""
objects = [x for x in objects if x]
if len(objects) < 2:
@ -277,20 +295,23 @@ class Session:
def add_link(
self,
node_one_id,
node_two_id,
interface_one=None,
interface_two=None,
link_options=None,
):
node_one_id: int,
node_two_id: int,
interface_one: InterfaceData = None,
interface_two: InterfaceData = None,
link_options: LinkOptions = None,
) -> None:
"""
Add a link between nodes.
:param int node_one_id: node one id
:param int node_two_id: node two id
:param core.emulator.emudata.InterfaceData interface_one: node one interface data, defaults to none
:param core.emulator.emudata.InterfaceData interface_two: node two interface data, defaults to none
:param core.emulator.emudata.LinkOptions link_options: data for creating link, defaults to no options
:param core.emulator.emudata.InterfaceData interface_one: node one interface
data, defaults to none
:param core.emulator.emudata.InterfaceData interface_two: node two interface
data, defaults to none
:param core.emulator.emudata.LinkOptions link_options: data for creating link,
defaults to no options
:return: nothing
"""
if not link_options:
@ -406,12 +427,12 @@ class Session:
def delete_link(
self,
node_one_id,
node_two_id,
interface_one_id,
interface_two_id,
link_type=LinkTypes.WIRED,
):
node_one_id: int,
node_two_id: int,
interface_one_id: int,
interface_two_id: int,
link_type: LinkTypes = LinkTypes.WIRED,
) -> None:
"""
Delete a link between nodes.
@ -512,12 +533,12 @@ class Session:
def update_link(
self,
node_one_id,
node_two_id,
interface_one_id=None,
interface_two_id=None,
link_options=None,
):
node_one_id: int,
node_two_id: int,
interface_one_id: int = None,
interface_two_id: int = None,
link_options: LinkOptions = None,
) -> None:
"""
Update link information between nodes.
@ -623,7 +644,13 @@ class Session:
if node_two:
node_two.lock.release()
def add_node(self, _type=NodeTypes.DEFAULT, _id=None, options=None, _cls=None):
def add_node(
self,
_type: NodeTypes = NodeTypes.DEFAULT,
_id: int = None,
options: NodeOptions = None,
_cls: Type[NodeBase] = None,
) -> NodeBase:
"""
Add a node to the session, based on the provided node data.
@ -717,14 +744,14 @@ class Session:
return node
def edit_node(self, node_id, options):
def edit_node(self, node_id: int, options: NodeOptions) -> None:
"""
Edit node information.
:param int node_id: id of node to update
:param core.emulator.emudata.NodeOptions options: data to update node with
:return: True if node updated, False otherwise
:rtype: bool
:rtype: nothing
:raises core.CoreError: when node to update does not exist
"""
# get node to update
@ -737,7 +764,7 @@ class Session:
node.canvas = options.canvas
node.icon = options.icon
def set_node_position(self, node, options):
def set_node_position(self, node: NodeBase, options: NodeOptions) -> None:
"""
Set position for a node, use lat/lon/alt if needed.
@ -767,7 +794,7 @@ class Session:
if using_lat_lon_alt:
self.broadcast_node_location(node)
def broadcast_node_location(self, node):
def broadcast_node_location(self, node: NodeBase) -> None:
"""
Broadcast node location to all listeners.
@ -782,7 +809,7 @@ class Session:
)
self.broadcast_node(node_data)
def start_mobility(self, node_ids=None):
def start_mobility(self, node_ids: List[int] = None) -> None:
"""
Start mobility for the provided node ids.
@ -791,7 +818,7 @@ class Session:
"""
self.mobility.startup(node_ids)
def is_active(self):
def is_active(self) -> bool:
"""
Determine if this session is considered to be active. (Runtime or Data collect states)
@ -804,7 +831,7 @@ class Session:
logging.info("session(%s) checking if active: %s", self.id, result)
return result
def open_xml(self, file_name, start=False):
def open_xml(self, file_name: str, start: bool = False) -> None:
"""
Import a session from the EmulationScript XML format.
@ -832,7 +859,7 @@ class Session:
if start:
self.instantiate()
def save_xml(self, file_name):
def save_xml(self, file_name: str) -> None:
"""
Export a session to the EmulationScript XML format.
@ -841,7 +868,7 @@ class Session:
"""
CoreXmlWriter(self).write(file_name)
def add_hook(self, state, file_name, source_name, data):
def add_hook(self, state: int, file_name: str, source_name: str, data: str) -> None:
"""
Store a hook from a received file message.
@ -855,7 +882,9 @@ class Session:
state = f":{state}"
self.set_hook(state, file_name, source_name, data)
def add_node_file(self, node_id, source_name, file_name, data):
def add_node_file(
self, node_id: int, source_name: str, file_name: str, data: str
) -> None:
"""
Add a file to a node.
@ -873,7 +902,7 @@ class Session:
elif data is not None:
node.nodefile(file_name, data)
def clear(self):
def clear(self) -> None:
"""
Clear all CORE session data. (nodes, hooks, etc)
@ -889,7 +918,7 @@ class Session:
self.services.reset()
self.mobility.config_reset()
def start_events(self):
def start_events(self) -> None:
"""
Start event loop.
@ -897,7 +926,7 @@ class Session:
"""
self.event_loop.run()
def mobility_event(self, event_data):
def mobility_event(self, event_data: EventData) -> None:
"""
Handle a mobility event.
@ -906,7 +935,7 @@ class Session:
"""
self.mobility.handleevent(event_data)
def set_location(self, lat, lon, alt, scale):
def set_location(self, lat: float, lon: float, alt: float, scale: float) -> None:
"""
Set session geospatial location.
@ -919,7 +948,7 @@ class Session:
self.location.setrefgeo(lat, lon, alt)
self.location.refscale = scale
def shutdown(self):
def shutdown(self) -> None:
"""
Shutdown all session nodes and remove the session directory.
"""
@ -942,7 +971,7 @@ class Session:
for handler in self.shutdown_handlers:
handler(self)
def broadcast_event(self, event_data):
def broadcast_event(self, event_data: EventData) -> None:
"""
Handle event data that should be provided to event handler.
@ -953,7 +982,7 @@ class Session:
for handler in self.event_handlers:
handler(event_data)
def broadcast_exception(self, exception_data):
def broadcast_exception(self, exception_data: ExceptionData) -> None:
"""
Handle exception data that should be provided to exception handlers.
@ -964,7 +993,7 @@ class Session:
for handler in self.exception_handlers:
handler(exception_data)
def broadcast_node(self, node_data):
def broadcast_node(self, node_data: NodeData) -> None:
"""
Handle node data that should be provided to node handlers.
@ -975,7 +1004,7 @@ class Session:
for handler in self.node_handlers:
handler(node_data)
def broadcast_file(self, file_data):
def broadcast_file(self, file_data: FileData) -> None:
"""
Handle file data that should be provided to file handlers.
@ -986,7 +1015,7 @@ class Session:
for handler in self.file_handlers:
handler(file_data)
def broadcast_config(self, config_data):
def broadcast_config(self, config_data: ConfigData) -> None:
"""
Handle config data that should be provided to config handlers.
@ -997,7 +1026,7 @@ class Session:
for handler in self.config_handlers:
handler(config_data)
def broadcast_link(self, link_data):
def broadcast_link(self, link_data: LinkData) -> None:
"""
Handle link data that should be provided to link handlers.
@ -1008,7 +1037,7 @@ class Session:
for handler in self.link_handlers:
handler(link_data)
def set_state(self, state, send_event=False):
def set_state(self, state: EventTypes, send_event: bool = False) -> None:
"""
Set the session's current state.
@ -1039,7 +1068,7 @@ class Session:
event_data = EventData(event_type=state_value, time=str(time.monotonic()))
self.broadcast_event(event_data)
def write_state(self, state):
def write_state(self, state: int) -> None:
"""
Write the current state to a state file in the session dir.
@ -1053,9 +1082,10 @@ class Session:
except IOError:
logging.exception("error writing state file: %s", state)
def run_hooks(self, state):
def run_hooks(self, state: int) -> None:
"""
Run hook scripts upon changing states. If hooks is not specified, run all hooks in the given state.
Run hook scripts upon changing states. If hooks is not specified, run all hooks
in the given state.
:param int state: state to run hooks for
:return: nothing
@ -1075,7 +1105,9 @@ class Session:
else:
logging.info("no state hooks for %s", state)
def set_hook(self, hook_type, file_name, source_name, data):
def set_hook(
self, hook_type: str, file_name: str, source_name: str, data: str
) -> None:
"""
Store a hook from a received file message.
@ -1107,13 +1139,13 @@ class Session:
logging.info("immediately running new state hook")
self.run_hook(hook)
def del_hooks(self):
def del_hooks(self) -> None:
"""
Clear the hook scripts dict.
"""
self._hooks.clear()
def run_hook(self, hook):
def run_hook(self, hook: Tuple[str, str]) -> None:
"""
Run a hook.
@ -1154,7 +1186,7 @@ class Session:
except (OSError, subprocess.CalledProcessError):
logging.exception("error running hook: %s", file_name)
def run_state_hooks(self, state):
def run_state_hooks(self, state: int) -> None:
"""
Run state hooks.
@ -1174,7 +1206,7 @@ class Session:
ExceptionLevels.ERROR, "Session.run_state_hooks", None, message
)
def add_state_hook(self, state, hook):
def add_state_hook(self, state: int, hook: Callable[[int], None]) -> None:
"""
Add a state hook.
@ -1190,18 +1222,18 @@ class Session:
if self.state == state:
hook(state)
def del_state_hook(self, state, hook):
def del_state_hook(self, state: int, hook: Callable[[int], None]) -> None:
"""
Delete a state hook.
:param int state: state to delete hook for
:param func hook: hook to delete
:return:
:return: nothing
"""
hooks = self._state_hooks.setdefault(state, [])
hooks.remove(hook)
def runtime_state_hook(self, state):
def runtime_state_hook(self, state: int) -> None:
"""
Runtime state hook check.
@ -1217,7 +1249,7 @@ class Session:
corexmldeployment.CoreXmlDeployment(self, xml_writer.scenario)
xml_writer.write(xml_file_name)
def get_environment(self, state=True):
def get_environment(self, state: bool = True) -> Dict[str, str]:
"""
Get an environment suitable for a subprocess.Popen call.
This is the current process environment with some session-specific
@ -1265,7 +1297,7 @@ class Session:
return env
def set_thumbnail(self, thumb_file):
def set_thumbnail(self, thumb_file: str) -> None:
"""
Set the thumbnail filename. Move files from /tmp to session dir.
@ -1281,7 +1313,7 @@ class Session:
shutil.copy(thumb_file, destination_file)
self.thumbnail = destination_file
def set_user(self, user):
def set_user(self, user: str) -> None:
"""
Set the username for this session. Update the permissions of the
session dir to allow the user write access.
@ -1299,7 +1331,7 @@ class Session:
self.user = user
def get_node_id(self):
def get_node_id(self) -> int:
"""
Return a unique, new node id.
"""
@ -1308,10 +1340,9 @@ class Session:
node_id = random.randint(1, 0xFFFF)
if node_id not in self.nodes:
break
return node_id
def create_node(self, cls, *args, **kwargs):
def create_node(self, cls: Type[NodeBase], *args: Any, **kwargs: Any) -> NodeBase:
"""
Create an emulation node.
@ -1322,29 +1353,27 @@ class Session:
:raises core.CoreError: when id of the node to create already exists
"""
node = cls(self, *args, **kwargs)
with self._nodes_lock:
if node.id in self.nodes:
node.shutdown()
raise CoreError(f"duplicate node id {node.id} for {node.name}")
self.nodes[node.id] = node
return node
def get_node(self, _id):
def get_node(self, _id: int) -> NodeBase:
"""
Get a session node.
:param int _id: node id to retrieve
:return: node for the given id
:rtype: core.nodes.base.CoreNode
:rtype: core.nodes.base.NodeBase
: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]
def delete_node(self, _id):
def delete_node(self, _id: int) -> bool:
"""
Delete a node from the session and check if session should shutdown, if no nodes are left.
@ -1365,7 +1394,7 @@ class Session:
return node is not None
def delete_nodes(self):
def delete_nodes(self) -> None:
"""
Clear the nodes dictionary, and call shutdown for each node.
"""
@ -1377,7 +1406,7 @@ class Session:
utils.threadpool(funcs)
self.node_id_gen.id = 0
def write_nodes(self):
def write_nodes(self) -> None:
"""
Write nodes to a 'nodes' file in the session dir.
The 'nodes' file lists: number, name, api-type, class-type
@ -1392,7 +1421,7 @@ class Session:
except IOError:
logging.exception("error writing nodes file")
def dump_session(self):
def dump_session(self) -> None:
"""
Log information about the session in its current state.
"""
@ -1405,7 +1434,9 @@ class Session:
len(self.nodes),
)
def exception(self, level, source, node_id, text):
def exception(
self, level: ExceptionLevels, source: str, node_id: int, text: str
) -> None:
"""
Generate and broadcast an exception event.
@ -1425,27 +1456,28 @@ class Session:
)
self.broadcast_exception(exception_data)
def instantiate(self):
def instantiate(self) -> List[ServiceBootError]:
"""
We have entered the instantiation state, invoke startup methods
of various managers and boot the nodes. Validate nodes and check
for transition to the runtime state.
"""
:return: list of service boot errors during startup
"""
# write current nodes out to session directory file
self.write_nodes()
# create control net interfaces and network tunnels
# which need to exist for emane to sync on location events
# in distributed scenarios
self.add_remove_control_interface(node=None, remove=False)
self.add_remove_control_net(0, remove=False)
# initialize distributed tunnels
self.distributed.start()
# instantiate will be invoked again upon Emane configure
# instantiate will be invoked again upon emane configure
if self.emane.startup() == self.emane.NOT_READY:
return
return []
# boot node services and then start mobility
exceptions = self.boot_nodes()
@ -1462,12 +1494,13 @@ class Session:
self.check_runtime()
return exceptions
def get_node_count(self):
def get_node_count(self) -> int:
"""
Returns the number of CoreNodes and CoreNets, except for those
that are not considered in the GUI's node count.
"""
:return: created node count
"""
with self._nodes_lock:
count = 0
for node_id in self.nodes:
@ -1480,14 +1513,15 @@ class Session:
continue
count += 1
return count
def check_runtime(self):
def check_runtime(self) -> None:
"""
Check if we have entered the runtime state, that all nodes have been
started and the emulation is running. Start the event loop once we
have entered runtime (time=0).
:return: nothing
"""
# this is called from instantiate() after receiving an event message
# for the instantiation state
@ -1504,10 +1538,12 @@ class Session:
self.event_loop.run()
self.set_state(EventTypes.RUNTIME_STATE, send_event=True)
def data_collect(self):
def data_collect(self) -> None:
"""
Tear down a running session. Stop the event loop and any running
nodes, and perform clean-up.
:return: nothing
"""
# stop event loop
self.event_loop.stop()
@ -1528,58 +1564,59 @@ class Session:
# update control interface hosts
self.update_control_interface_hosts(remove=True)
# remove all four possible control networks. Does nothing if ctrlnet is not
# installed.
self.add_remove_control_interface(node=None, net_index=0, remove=True)
self.add_remove_control_interface(node=None, net_index=1, remove=True)
self.add_remove_control_interface(node=None, net_index=2, remove=True)
self.add_remove_control_interface(node=None, net_index=3, remove=True)
# remove all four possible control networks
self.add_remove_control_net(0, remove=True)
self.add_remove_control_net(1, remove=True)
self.add_remove_control_net(2, remove=True)
self.add_remove_control_net(3, remove=True)
def check_shutdown(self):
def check_shutdown(self) -> bool:
"""
Check if we have entered the shutdown state, when no running nodes
and links remain.
:return: True if should shutdown, False otherwise
"""
node_count = self.get_node_count()
logging.debug(
"session(%s) checking shutdown: %s nodes remaining", self.id, node_count
)
shutdown = False
if node_count == 0:
shutdown = True
self.set_state(EventTypes.SHUTDOWN_STATE)
return shutdown
def short_session_id(self):
def short_session_id(self) -> str:
"""
Return a shorter version of the session ID, appropriate for
interface names, where length may be limited.
:return: short session id
"""
ssid = (self.id >> 8) ^ (self.id & ((1 << 8) - 1))
return f"{ssid:x}"
def boot_node(self, node):
def boot_node(self, node: CoreNode) -> None:
"""
Boot node by adding a control interface when necessary and starting
node services.
:param core.nodes.base.CoreNodeBase node: node to boot
:param core.nodes.base.CoreNode node: node to boot
:return: nothing
"""
logging.info("booting node(%s): %s", node.name, [x.name for x in node.services])
self.add_remove_control_interface(node=node, remove=False)
self.services.boot_services(node)
def boot_nodes(self):
def boot_nodes(self) -> List[Exception]:
"""
Invoke the boot() procedure for all nodes and send back node
messages to the GUI for node messages that had the status
request flag.
:return: service boot exceptions
:rtype: list[core.services.coreservices.ServiceBootError]
:rtype: list[Exception]
"""
with self._nodes_lock:
funcs = []
@ -1596,7 +1633,7 @@ class Session:
self.update_control_interface_hosts()
return exceptions
def get_control_net_prefixes(self):
def get_control_net_prefixes(self) -> List[str]:
"""
Retrieve control net prefixes.
@ -1608,13 +1645,11 @@ class Session:
p1 = self.options.get_config("controlnet1")
p2 = self.options.get_config("controlnet2")
p3 = self.options.get_config("controlnet3")
if not p0 and p:
p0 = p
return [p0, p1, p2, p3]
def get_control_net_server_interfaces(self):
def get_control_net_server_interfaces(self) -> List[str]:
"""
Retrieve control net server interfaces.
@ -1629,7 +1664,7 @@ class Session:
d3 = self.options.get_config("controlnetif3")
return [None, d1, d2, d3]
def get_control_net_index(self, dev):
def get_control_net_index(self, dev: str) -> int:
"""
Retrieve control net index.
@ -1645,10 +1680,22 @@ class Session:
return index
return -1
def get_control_net(self, net_index):
return self.get_node(CTRL_NET_ID + net_index)
def get_control_net(self, net_index: int) -> CtrlNet:
"""
Retrieve a control net based on index.
def add_remove_control_net(self, net_index, remove=False, conf_required=True):
:param net_index: control net index
: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
def add_remove_control_net(
self, net_index: int, remove: bool = False, conf_required: bool = True
) -> Optional[CtrlNet]:
"""
Create a control network bridge as necessary.
When the remove flag is True, remove the bridge that connects control
@ -1682,11 +1729,9 @@ class Session:
# return any existing controlnet bridge
try:
control_net = self.get_control_net(net_index)
if remove:
self.delete_node(control_net.id)
return None
return control_net
except CoreError:
if remove:
@ -1730,12 +1775,15 @@ class Session:
updown_script=updown_script,
serverintf=server_interface,
)
return control_net
def add_remove_control_interface(
self, node, net_index=0, remove=False, conf_required=True
):
self,
node: CoreNode,
net_index: int = 0,
remove: bool = False,
conf_required: bool = True,
) -> None:
"""
Add a control interface to a node when a 'controlnet' prefix is
listed in the config file or session options. Uses
@ -1782,7 +1830,9 @@ class Session:
)
node.netif(interface1).control = True
def update_control_interface_hosts(self, net_index=0, remove=False):
def update_control_interface_hosts(
self, net_index: int = 0, remove: bool = False
) -> None:
"""
Add the IP addresses of control interfaces to the /etc/hosts file.
@ -1813,10 +1863,9 @@ class Session:
entries.append(f"{address} {name}")
logging.info("Adding %d /etc/hosts file entries.", len(entries))
utils.file_munge("/etc/hosts", header, "\n".join(entries) + "\n")
def runtime(self):
def runtime(self) -> float:
"""
Return the current time we have been in the runtime state, or zero
if not in runtime.
@ -1826,7 +1875,13 @@ class Session:
else:
return 0.0
def add_event(self, event_time, node=None, name=None, data=None):
def add_event(
self,
event_time: float,
node: CoreNode = None,
name: str = None,
data: str = None,
) -> None:
"""
Add an event to the event queue, with a start time relative to the
start of the runtime state.
@ -1865,7 +1920,9 @@ class Session:
# TODO: if data is None, this blows up, but this ties into how event functions
# are ran, need to clean that up
def run_event(self, node_id=None, name=None, data=None):
def run_event(
self, node_id: int = None, name: str = None, data: str = None
) -> None:
"""
Run a scheduled event, executing commands in the data string.

View file

@ -1,3 +1,5 @@
from typing import Any
from core.config import ConfigurableManager, ConfigurableOptions, Configuration
from core.emulator.enumerations import ConfigDataTypes, RegisterTlvs
from core.plugins.sdt import Sdt
@ -60,29 +62,53 @@ class SessionConfig(ConfigurableManager, ConfigurableOptions):
]
config_type = RegisterTlvs.UTILITY.value
def __init__(self):
def __init__(self) -> None:
super().__init__()
self.set_configs(self.default_values())
def get_config(
self,
_id,
node_id=ConfigurableManager._default_node,
config_type=ConfigurableManager._default_type,
default=None,
):
_id: str,
node_id: int = ConfigurableManager._default_node,
config_type: str = ConfigurableManager._default_type,
default: Any = None,
) -> str:
"""
Retrieves a specific configuration for a node and configuration type.
:param str _id: specific configuration to retrieve
:param int node_id: node id to store configuration for
:param str config_type: configuration type to store configuration for
:param default: default value to return when value is not found
:return: configuration value
:rtype str
"""
value = super().get_config(_id, node_id, config_type, default)
if value == "":
value = default
return value
def get_config_bool(self, name, default=None):
def get_config_bool(self, name: str, default: Any = None) -> bool:
"""
Get configuration value as a boolean.
:param name: configuration name
:param default: default value if not found
:return: boolean for configuration value
"""
value = self.get_config(name)
if value is None:
return default
return value.lower() == "true"
def get_config_int(self, name, default=None):
def get_config_int(self, name: str, default: Any = None) -> int:
"""
Get configuration value as int.
:param name: configuration name
:param default: default value if not found
:return: int for configuration value
"""
value = self.get_config(name, default=default)
if value is not None:
value = int(value)

View file

@ -9,7 +9,7 @@ class CoreCommandError(subprocess.CalledProcessError):
Used when encountering internal CORE command errors.
"""
def __str__(self):
def __str__(self) -> str:
return (
f"Command({self.cmd}), Status({self.returncode}):\n"
f"stdout: {self.output}\nstderr: {self.stderr}"

View file

@ -6,6 +6,7 @@ https://pypi.python.org/pypi/utm (version 0.3.0).
"""
import logging
from typing import Optional, Tuple
from core.emulator.enumerations import RegisterTlvs
from core.location import utm
@ -21,7 +22,7 @@ class CoreLocation:
name = "location"
config_type = RegisterTlvs.UTILITY.value
def __init__(self):
def __init__(self) -> None:
"""
Creates a MobilityManager instance.
@ -37,7 +38,7 @@ class CoreLocation:
for n, l in utm.ZONE_LETTERS:
self.zonemap[l] = n
def reset(self):
def reset(self) -> None:
"""
Reset to initial state.
"""
@ -50,7 +51,7 @@ class CoreLocation:
# cached distance to refpt in other zones
self.zoneshifts = {}
def px2m(self, val):
def px2m(self, val: float) -> float:
"""
Convert the specified value in pixels to meters using the
configured scale. The scale is given as s, where
@ -61,7 +62,7 @@ class CoreLocation:
"""
return (val / 100.0) * self.refscale
def m2px(self, val):
def m2px(self, val: float) -> float:
"""
Convert the specified value in meters to pixels using the
configured scale. The scale is given as s, where
@ -74,7 +75,7 @@ class CoreLocation:
return 0.0
return 100.0 * (val / self.refscale)
def setrefgeo(self, lat, lon, alt):
def setrefgeo(self, lat: float, lon: float, alt: float) -> None:
"""
Record the geographical reference point decimal (lat, lon, alt)
and convert and store its UTM equivalent for later use.
@ -89,7 +90,7 @@ class CoreLocation:
e, n, zonen, zonel = utm.from_latlon(lat, lon)
self.refutm = ((zonen, zonel), e, n, alt)
def getgeo(self, x, y, z):
def getgeo(self, x: float, y: float, z: float) -> Tuple[float, float, float]:
"""
Given (x, y, z) Cartesian coordinates, convert them to latitude,
longitude, and altitude based on the configured reference point
@ -130,7 +131,7 @@ class CoreLocation:
lat, lon = self.refgeo[:2]
return lat, lon, alt
def getxyz(self, lat, lon, alt):
def getxyz(self, lat: float, lon: float, alt: float) -> Tuple[float, float, float]:
"""
Given latitude, longitude, and altitude location data, convert them
to (x, y, z) Cartesian coordinates based on the configured
@ -165,7 +166,7 @@ class CoreLocation:
z = self.m2px(zm) + self.refxyz[2]
return x, y, z
def geteastingshift(self, zonen, zonel):
def geteastingshift(self, zonen: float, zonel: float) -> Optional[float]:
"""
If the lat, lon coordinates being converted are located in a
different UTM zone than the canvas reference point, the UTM meters
@ -201,7 +202,7 @@ class CoreLocation:
self.zoneshifts[z] = (xshift, yshift)
return xshift
def getnorthingshift(self, zonen, zonel):
def getnorthingshift(self, zonen: float, zonel: float) -> Optional[float]:
"""
If the lat, lon coordinates being converted are located in a
different UTM zone than the canvas reference point, the UTM meters
@ -238,7 +239,9 @@ class CoreLocation:
self.zoneshifts[z] = (xshift, yshift)
return yshift
def getutmzoneshift(self, e, n):
def getutmzoneshift(
self, e: float, n: float
) -> Tuple[float, float, Tuple[float, str]]:
"""
Given UTM easting and northing values, check if they fall outside
the reference point's zone boundary. Return the UTM coordinates in a

View file

@ -6,6 +6,7 @@ import heapq
import threading
import time
from functools import total_ordering
from typing import Any, Callable
class Timer(threading.Thread):
@ -14,7 +15,9 @@ class Timer(threading.Thread):
already running.
"""
def __init__(self, interval, function, args=None, kwargs=None):
def __init__(
self, interval: float, function: Callable, args: Any = None, kwargs: Any = None
) -> None:
"""
Create a Timer instance.
@ -42,7 +45,7 @@ class Timer(threading.Thread):
else:
self.kwargs = {}
def cancel(self):
def cancel(self) -> bool:
"""
Stop the timer if it hasn't finished yet. Return False if
the timer was already running.
@ -56,7 +59,7 @@ class Timer(threading.Thread):
self._running.release()
return locked
def run(self):
def run(self) -> None:
"""
Run the timer.
@ -75,7 +78,9 @@ class Event:
Provides event objects that can be used within the EventLoop class.
"""
def __init__(self, eventnum, event_time, func, *args, **kwds):
def __init__(
self, eventnum: int, event_time: float, func: Callable, *args: Any, **kwds: Any
) -> None:
"""
Create an Event instance.
@ -92,13 +97,13 @@ class Event:
self.kwds = kwds
self.canceled = False
def __lt__(self, other):
def __lt__(self, other: "Event") -> bool:
result = self.time < other.time
if result:
result = self.eventnum < other.eventnum
return result
def run(self):
def run(self) -> None:
"""
Run an event.
@ -108,7 +113,7 @@ class Event:
return
self.func(*self.args, **self.kwds)
def cancel(self):
def cancel(self) -> None:
"""
Cancel event.
@ -123,7 +128,7 @@ class EventLoop:
Provides an event loop for running events.
"""
def __init__(self):
def __init__(self) -> None:
"""
Creates a EventLoop instance.
"""
@ -134,7 +139,7 @@ class EventLoop:
self.running = False
self.start = None
def __run_events(self):
def __run_events(self) -> None:
"""
Run events.
@ -159,7 +164,7 @@ class EventLoop:
if schedule:
self.__schedule_event()
def __schedule_event(self):
def __schedule_event(self) -> None:
"""
Schedule event.
@ -177,7 +182,7 @@ class EventLoop:
self.timer.daemon = True
self.timer.start()
def run(self):
def run(self) -> None:
"""
Start event loop.
@ -192,7 +197,7 @@ class EventLoop:
event.time += self.start
self.__schedule_event()
def stop(self):
def stop(self) -> None:
"""
Stop event loop.
@ -209,7 +214,7 @@ class EventLoop:
self.running = False
self.start = None
def add_event(self, delaysec, func, *args, **kwds):
def add_event(self, delaysec: float, func: Callable, *args: Any, **kwds: Any):
"""
Add an event to the event loop.

View file

@ -9,6 +9,7 @@ import os
import threading
import time
from functools import total_ordering
from typing import TYPE_CHECKING, Dict, List, Tuple
from core import utils
from core.config import ConfigGroup, ConfigurableOptions, Configuration, ModelManager
@ -21,6 +22,11 @@ from core.emulator.enumerations import (
RegisterTlvs,
)
from core.errors import CoreError
from core.nodes.base import CoreNode, NodeBase
from core.nodes.interface import CoreInterface
if TYPE_CHECKING:
from core.emulator.session import Session
class MobilityManager(ModelManager):
@ -32,7 +38,7 @@ class MobilityManager(ModelManager):
name = "MobilityManager"
config_type = RegisterTlvs.WIRELESS.value
def __init__(self, session):
def __init__(self, session: "Session") -> None:
"""
Creates a MobilityManager instance.
@ -43,7 +49,7 @@ class MobilityManager(ModelManager):
self.models[BasicRangeModel.name] = BasicRangeModel
self.models[Ns2ScriptedMobility.name] = Ns2ScriptedMobility
def reset(self):
def reset(self) -> None:
"""
Clear out all current configurations.
@ -51,7 +57,7 @@ class MobilityManager(ModelManager):
"""
self.config_reset()
def startup(self, node_ids=None):
def startup(self, node_ids: List[int] = None) -> None:
"""
Session is transitioning from instantiation to runtime state.
Instantiate any mobility models that have been configured for a WLAN.
@ -86,7 +92,7 @@ class MobilityManager(ModelManager):
if node.mobility:
self.session.event_loop.add_event(0.0, node.mobility.startup)
def handleevent(self, event_data):
def handleevent(self, event_data: EventData) -> None:
"""
Handle an Event Message used to start, stop, or pause
mobility scripts for a given WlanNode.
@ -149,7 +155,7 @@ class MobilityManager(ModelManager):
if event_type == EventTypes.PAUSE.value:
model.pause()
def sendevent(self, model):
def sendevent(self, model: "WayPointMobility") -> None:
"""
Send an event message on behalf of a mobility model.
This communicates the current and end (max) times to the GUI.
@ -179,7 +185,9 @@ class MobilityManager(ModelManager):
self.session.broadcast_event(event_data)
def updatewlans(self, moved, moved_netifs):
def updatewlans(
self, moved: List[NodeBase], moved_netifs: List[CoreInterface]
) -> None:
"""
A mobility script has caused nodes in the 'moved' list to move.
Update every WlanNode. This saves range calculations if the model
@ -208,7 +216,7 @@ class WirelessModel(ConfigurableOptions):
bitmap = None
position_callback = None
def __init__(self, session, _id):
def __init__(self, session: "Session", _id: int):
"""
Create a WirelessModel instance.
@ -218,7 +226,7 @@ class WirelessModel(ConfigurableOptions):
self.session = session
self.id = _id
def all_link_data(self, flags):
def all_link_data(self, flags: int) -> List:
"""
May be used if the model can populate the GUI with wireless (green)
link lines.
@ -229,7 +237,7 @@ class WirelessModel(ConfigurableOptions):
"""
return []
def update(self, moved, moved_netifs):
def update(self, moved: bool, moved_netifs: List[CoreInterface]) -> None:
"""
Update this wireless model.
@ -239,10 +247,10 @@ class WirelessModel(ConfigurableOptions):
"""
raise NotImplementedError
def update_config(self, config):
def update_config(self, config: Dict[str, str]) -> None:
"""
For run-time updates of model config. Returns True when position callback and set link
parameters should be invoked.
For run-time updates of model config. Returns True when position callback and
set link parameters should be invoked.
:param dict config: configuration values to update
:return: nothing
@ -295,7 +303,7 @@ class BasicRangeModel(WirelessModel):
def config_groups(cls):
return [ConfigGroup("Basic Range Parameters", 1, len(cls.configurations()))]
def __init__(self, session, _id):
def __init__(self, session: "Session", _id: int) -> None:
"""
Create a BasicRangeModel instance.
@ -314,7 +322,7 @@ class BasicRangeModel(WirelessModel):
self.loss = None
self.jitter = None
def values_from_config(self, config):
def values_from_config(self, config: Dict[str, str]) -> None:
"""
Values to convert to link parameters.
@ -340,7 +348,7 @@ class BasicRangeModel(WirelessModel):
if self.jitter == 0:
self.jitter = None
def setlinkparams(self):
def setlinkparams(self) -> None:
"""
Apply link parameters to all interfaces. This is invoked from
WlanNode.setmodel() after the position callback has been set.
@ -356,7 +364,7 @@ class BasicRangeModel(WirelessModel):
jitter=self.jitter,
)
def get_position(self, netif):
def get_position(self, netif: CoreInterface) -> Tuple[float, float, float]:
"""
Retrieve network interface position.
@ -366,7 +374,9 @@ class BasicRangeModel(WirelessModel):
with self._netifslock:
return self._netifs[netif]
def set_position(self, netif, x=None, y=None, z=None):
def set_position(
self, netif: CoreInterface, x: float = None, y: float = None, z: float = None
) -> None:
"""
A node has moved; given an interface, a new (x,y,z) position has
been set; calculate the new distance between other nodes and link or
@ -389,7 +399,7 @@ class BasicRangeModel(WirelessModel):
position_callback = set_position
def update(self, moved, moved_netifs):
def update(self, moved: bool, 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.
@ -411,7 +421,7 @@ class BasicRangeModel(WirelessModel):
continue
self.calclink(netif, netif2)
def calclink(self, netif, netif2):
def calclink(self, netif: CoreInterface, netif2: CoreInterface) -> None:
"""
Helper used by set_position() and update() to
calculate distance between two interfaces and perform
@ -455,7 +465,9 @@ class BasicRangeModel(WirelessModel):
logging.exception("error getting interfaces during calclinkS")
@staticmethod
def calcdistance(p1, p2):
def calcdistance(
p1: Tuple[float, float, float], p2: Tuple[float, float, float]
) -> float:
"""
Calculate the distance between two three-dimensional points.
@ -471,7 +483,7 @@ class BasicRangeModel(WirelessModel):
c = p1[2] - p2[2]
return math.hypot(math.hypot(a, b), c)
def update_config(self, config):
def update_config(self, config: Dict[str, str]) -> None:
"""
Configuration has changed during runtime.
@ -482,12 +494,14 @@ class BasicRangeModel(WirelessModel):
self.setlinkparams()
return True
def create_link_data(self, interface1, interface2, message_type):
def create_link_data(
self, interface1: CoreInterface, interface2: CoreInterface, message_type: int
) -> LinkData:
"""
Create a wireless link/unlink data message.
:param core.coreobj.PyCoreNetIf interface1: interface one
:param core.coreobj.PyCoreNetIf interface2: interface two
:param core.nodes.interface.CoreInterface interface1: interface one
:param core.nodes.interface.CoreInterface interface2: interface two
:param message_type: link message type
:return: link data
:rtype: LinkData
@ -500,7 +514,9 @@ class BasicRangeModel(WirelessModel):
link_type=LinkTypes.WIRELESS.value,
)
def sendlinkmsg(self, netif, netif2, unlink=False):
def sendlinkmsg(
self, netif: CoreInterface, netif2: CoreInterface, unlink: bool = False
) -> None:
"""
Send a wireless link/unlink API message to the GUI.
@ -517,7 +533,7 @@ class BasicRangeModel(WirelessModel):
link_data = self.create_link_data(netif, netif2, message_type)
self.session.broadcast_link(link_data)
def all_link_data(self, flags):
def all_link_data(self, flags: int) -> List[LinkData]:
"""
Return a list of wireless link messages for when the GUI reconnects.
@ -540,7 +556,7 @@ class WayPoint:
Maintains information regarding waypoints.
"""
def __init__(self, time, nodenum, coords, speed):
def __init__(self, time: float, nodenum: int, coords, speed: float):
"""
Creates a WayPoint instance.
@ -554,13 +570,13 @@ class WayPoint:
self.coords = coords
self.speed = speed
def __eq__(self, other):
return (self.time, self.nodenum) == (other.time, other.nodedum)
def __eq__(self, other: "WayPoint") -> bool:
return (self.time, self.nodenum) == (other.time, other.nodenum)
def __ne__(self, other):
def __ne__(self, other: "WayPoint") -> bool:
return not self == other
def __lt__(self, other):
def __lt__(self, other: "WayPoint") -> bool:
result = self.time < other.time
if result:
result = self.nodenum < other.nodenum
@ -579,7 +595,7 @@ class WayPointMobility(WirelessModel):
STATE_RUNNING = 1
STATE_PAUSED = 2
def __init__(self, session, _id):
def __init__(self, session: "Session", _id: int) -> None:
"""
Create a WayPointMobility instance.
@ -603,7 +619,7 @@ class WayPointMobility(WirelessModel):
# (ns-3 sets this to False as new waypoints may be added from trace)
self.empty_queue_stop = True
def runround(self):
def runround(self) -> None:
"""
Advance script time and move nodes.
@ -657,7 +673,7 @@ class WayPointMobility(WirelessModel):
# TODO: check session state
self.session.event_loop.add_event(0.001 * self.refresh_ms, self.runround)
def run(self):
def run(self) -> None:
"""
Run the waypoint mobility scenario.
@ -670,7 +686,7 @@ class WayPointMobility(WirelessModel):
self.runround()
self.session.mobility.sendevent(self)
def movenode(self, node, dt):
def movenode(self, node: CoreNode, dt: float) -> bool:
"""
Calculate next node location and update its coordinates.
Returns True if the node's position has changed.
@ -723,7 +739,7 @@ class WayPointMobility(WirelessModel):
self.setnodeposition(node, x1 + dx, y1 + dy, z1)
return True
def movenodesinitial(self):
def movenodesinitial(self) -> None:
"""
Move nodes to their initial positions. Then calculate the ranges.
@ -741,11 +757,13 @@ class WayPointMobility(WirelessModel):
moved_netifs.append(netif)
self.session.mobility.updatewlans(moved, moved_netifs)
def addwaypoint(self, time, nodenum, x, y, z, speed):
def addwaypoint(
self, _time: float, nodenum: int, x: float, y: float, z: float, speed: float
) -> None:
"""
Waypoints are pushed to a heapq, sorted by time.
:param time: waypoint time
:param _time: waypoint time
:param int nodenum: node id
:param x: x position
:param y: y position
@ -753,10 +771,10 @@ class WayPointMobility(WirelessModel):
:param speed: speed
:return: nothing
"""
wp = WayPoint(time, nodenum, coords=(x, y, z), speed=speed)
wp = WayPoint(_time, nodenum, coords=(x, y, z), speed=speed)
heapq.heappush(self.queue, wp)
def addinitial(self, nodenum, x, y, z):
def addinitial(self, nodenum: int, x: float, y: float, z: float) -> None:
"""
Record initial position in a dict.
@ -769,11 +787,11 @@ class WayPointMobility(WirelessModel):
wp = WayPoint(0, nodenum, coords=(x, y, z), speed=0)
self.initial[nodenum] = wp
def updatepoints(self, now):
def updatepoints(self, now: float) -> None:
"""
Move items from self.queue to self.points when their time has come.
:param int now: current timestamp
:param float now: current timestamp
:return: nothing
"""
while len(self.queue):
@ -782,7 +800,7 @@ class WayPointMobility(WirelessModel):
wp = heapq.heappop(self.queue)
self.points[wp.nodenum] = wp
def copywaypoints(self):
def copywaypoints(self) -> None:
"""
Store backup copy of waypoints for looping and stopping.
@ -790,7 +808,7 @@ class WayPointMobility(WirelessModel):
"""
self.queue_copy = list(self.queue)
def loopwaypoints(self):
def loopwaypoints(self) -> None:
"""
Restore backup copy of waypoints when looping.
@ -799,13 +817,13 @@ class WayPointMobility(WirelessModel):
self.queue = list(self.queue_copy)
return self.loop
def setnodeposition(self, node, x, y, z):
def setnodeposition(self, node: CoreNode, x: float, y: float, z: float) -> None:
"""
Helper to move a node, notify any GUI (connected session handlers),
without invoking the interface poshook callback that may perform
range calculation.
:param core.netns.vnode.CoreNode node: node to set position for
:param core.nodes.base.CoreNode node: node to set position for
:param x: x position
:param y: y position
:param z: z position
@ -815,7 +833,7 @@ class WayPointMobility(WirelessModel):
node_data = node.data(message_type=0)
self.session.broadcast_node(node_data)
def setendtime(self):
def setendtime(self) -> None:
"""
Set self.endtime to the time of the last waypoint in the queue of
waypoints. This is just an estimate. The endtime will later be
@ -829,7 +847,7 @@ class WayPointMobility(WirelessModel):
except IndexError:
self.endtime = 0
def start(self):
def start(self) -> None:
"""
Run the script from the beginning or unpause from where it
was before.
@ -849,11 +867,12 @@ class WayPointMobility(WirelessModel):
self.lasttime = now - (0.001 * self.refresh_ms)
self.runround()
def stop(self, move_initial=True):
def stop(self, move_initial: bool = True) -> None:
"""
Stop the script and move nodes to initial positions.
:param bool move_initial: flag to check if we should move nodes to initial position
:param bool move_initial: flag to check if we should move nodes to initial
position
:return: nothing
"""
self.state = self.STATE_STOPPED
@ -864,7 +883,7 @@ class WayPointMobility(WirelessModel):
self.movenodesinitial()
self.session.mobility.sendevent(self)
def pause(self):
def pause(self) -> None:
"""
Pause the script; pause time is stored to self.lasttime.
@ -926,12 +945,12 @@ class Ns2ScriptedMobility(WayPointMobility):
]
@classmethod
def config_groups(cls):
def config_groups(cls) -> List[ConfigGroup]:
return [
ConfigGroup("ns-2 Mobility Script Parameters", 1, len(cls.configurations()))
]
def __init__(self, session, _id):
def __init__(self, session: "Session", _id: int):
"""
Creates a Ns2ScriptedMobility instance.
@ -951,7 +970,7 @@ class Ns2ScriptedMobility(WayPointMobility):
self.script_pause = None
self.script_stop = None
def update_config(self, config):
def update_config(self, config: Dict[str, str]) -> None:
self.file = config["file"]
logging.info(
"ns-2 scripted mobility configured for WLAN %d using file: %s",
@ -969,7 +988,7 @@ class Ns2ScriptedMobility(WayPointMobility):
self.copywaypoints()
self.setendtime()
def readscriptfile(self):
def readscriptfile(self) -> None:
"""
Read in mobility script from a file. This adds waypoints to a
priority queue, sorted by waypoint time. Initial waypoints are
@ -1012,7 +1031,6 @@ class Ns2ScriptedMobility(WayPointMobility):
# initial position (time=0, speed=0):
# $node_(6) set X_ 780.0
parts = line.split()
time = 0.0
nodenum = parts[0][1 + parts[0].index("(") : parts[0].index(")")]
if parts[2] == "X_":
if ix is not None and iy is not None:
@ -1036,7 +1054,7 @@ class Ns2ScriptedMobility(WayPointMobility):
if ix is not None and iy is not None:
self.addinitial(self.map(inodenum), ix, iy, iz)
def findfile(self, file_name):
def findfile(self, file_name: str) -> str:
"""
Locate a script file. If the specified file doesn't exist, look in the
same directory as the scenario file, or in the default
@ -1065,7 +1083,7 @@ class Ns2ScriptedMobility(WayPointMobility):
return file_name
def parsemap(self, mapstr):
def parsemap(self, mapstr: str) -> None:
"""
Parse a node mapping string, given as a configuration parameter.
@ -1085,18 +1103,18 @@ class Ns2ScriptedMobility(WayPointMobility):
except ValueError:
logging.exception("ns-2 mobility node map error")
def map(self, nodenum):
def map(self, nodenum: str) -> int:
"""
Map one node number (from a script file) to another.
:param str nodenum: node id to map
:param int nodenum: node id to map
:return: mapped value or the node id itself
:rtype: int
"""
nodenum = int(nodenum)
return self.nodemap.get(nodenum, nodenum)
def startup(self):
def startup(self) -> None:
"""
Start running the script if autostart is enabled.
Move node to initial positions when any autostart time is specified.
@ -1122,7 +1140,7 @@ class Ns2ScriptedMobility(WayPointMobility):
self.state = self.STATE_RUNNING
self.session.event_loop.add_event(t, self.run)
def start(self):
def start(self) -> None:
"""
Handle the case when un-paused.
@ -1134,7 +1152,7 @@ class Ns2ScriptedMobility(WayPointMobility):
if laststate == self.STATE_PAUSED:
self.statescript("unpause")
def run(self):
def run(self) -> None:
"""
Start is pressed or autostart is triggered.
@ -1143,7 +1161,7 @@ class Ns2ScriptedMobility(WayPointMobility):
super().run()
self.statescript("run")
def pause(self):
def pause(self) -> None:
"""
Pause the mobility script.
@ -1152,17 +1170,18 @@ class Ns2ScriptedMobility(WayPointMobility):
super().pause()
self.statescript("pause")
def stop(self, move_initial=True):
def stop(self, move_initial: bool = True) -> None:
"""
Stop the mobility script.
:param bool move_initial: flag to check if we should move node to initial position
:param bool move_initial: flag to check if we should move node to initial
position
:return: nothing
"""
super().stop(move_initial=move_initial)
self.statescript("stop")
def statescript(self, typestr):
def statescript(self, typestr: str) -> None:
"""
State of the mobility script.

View file

@ -6,6 +6,7 @@ import logging
import os
import shutil
import threading
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple
import netaddr
@ -15,8 +16,12 @@ from core.emulator.data import LinkData, NodeData
from core.emulator.enumerations import LinkTypes, NodeTypes
from core.errors import CoreCommandError
from core.nodes import client
from core.nodes.interface import TunTap, Veth
from core.nodes.netclient import get_net_client
from core.nodes.interface import CoreInterface, TunTap, Veth
from core.nodes.netclient import LinuxNetClient, get_net_client
if TYPE_CHECKING:
from core.emulator.distributed import DistributedServer
from core.emulator.session import Session
_DEFAULT_MTU = 1500
@ -29,9 +34,16 @@ class NodeBase:
apitype = None
# TODO: appears start has no usage, verify and remove
def __init__(self, session, _id=None, name=None, start=True, server=None):
def __init__(
self,
session: "Session",
_id: int = None,
name: str = None,
start: bool = True,
server: "DistributedServer" = None,
) -> None:
"""
Creates a PyCoreObj instance.
Creates a NodeBase instance.
:param core.emulator.session.Session session: CORE session object
:param int _id: id
@ -63,7 +75,7 @@ class NodeBase:
use_ovs = session.options.get_config("ovs") == "True"
self.net_client = get_net_client(use_ovs, self.host_cmd)
def startup(self):
def startup(self) -> None:
"""
Each object implements its own startup method.
@ -71,7 +83,7 @@ class NodeBase:
"""
raise NotImplementedError
def shutdown(self):
def shutdown(self) -> None:
"""
Each object implements its own shutdown method.
@ -79,7 +91,14 @@ class NodeBase:
"""
raise NotImplementedError
def host_cmd(self, args, env=None, cwd=None, wait=True, shell=False):
def host_cmd(
self,
args: str,
env: Dict[str, str] = None,
cwd: str = None,
wait: bool = True,
shell: bool = False,
) -> str:
"""
Runs a command on the host system or distributed server.
@ -97,7 +116,7 @@ class NodeBase:
else:
return self.server.remote_cmd(args, env, cwd, wait)
def setposition(self, x=None, y=None, z=None):
def setposition(self, x: float = None, y: float = None, z: float = None) -> bool:
"""
Set the (x,y,z) position of the object.
@ -109,7 +128,7 @@ class NodeBase:
"""
return self.position.set(x=x, y=y, z=z)
def getposition(self):
def getposition(self) -> Tuple[float, float, float]:
"""
Return an (x,y,z) tuple representing this object's position.
@ -118,7 +137,7 @@ class NodeBase:
"""
return self.position.get()
def ifname(self, ifindex):
def ifname(self, ifindex: int) -> str:
"""
Retrieve interface name for index.
@ -128,7 +147,7 @@ class NodeBase:
"""
return self._netif[ifindex].name
def netifs(self, sort=False):
def netifs(self, sort: bool = False) -> List[CoreInterface]:
"""
Retrieve network interfaces, sorted if desired.
@ -141,7 +160,7 @@ class NodeBase:
else:
return list(self._netif.values())
def numnetif(self):
def numnetif(self) -> int:
"""
Return the attached interface count.
@ -150,7 +169,7 @@ class NodeBase:
"""
return len(self._netif)
def getifindex(self, netif):
def getifindex(self, netif: CoreInterface) -> int:
"""
Retrieve index for an interface.
@ -163,7 +182,7 @@ class NodeBase:
return ifindex
return -1
def newifindex(self):
def newifindex(self) -> int:
"""
Create a new interface index.
@ -176,7 +195,14 @@ class NodeBase:
self.ifindex += 1
return ifindex
def data(self, message_type, lat=None, lon=None, alt=None, source=None):
def data(
self,
message_type: int,
lat: float = None,
lon: float = None,
alt: float = None,
source: str = None,
) -> NodeData:
"""
Build a data object for this node.
@ -223,7 +249,7 @@ class NodeBase:
return node_data
def all_link_data(self, flags):
def all_link_data(self, flags: int) -> List:
"""
Build CORE Link data for this object. There is no default
method for PyCoreObjs as PyCoreNodes do not implement this but
@ -241,7 +267,14 @@ class CoreNodeBase(NodeBase):
Base class for CORE nodes.
"""
def __init__(self, session, _id=None, name=None, start=True, server=None):
def __init__(
self,
session: "Session",
_id: int = None,
name: str = None,
start: bool = True,
server: "DistributedServer" = None,
) -> None:
"""
Create a CoreNodeBase instance.
@ -257,7 +290,7 @@ class CoreNodeBase(NodeBase):
self.nodedir = None
self.tmpnodedir = False
def makenodedir(self):
def makenodedir(self) -> None:
"""
Create the node directory.
@ -270,7 +303,7 @@ class CoreNodeBase(NodeBase):
else:
self.tmpnodedir = False
def rmnodedir(self):
def rmnodedir(self) -> None:
"""
Remove the node directory, unless preserve directory has been set.
@ -283,7 +316,7 @@ class CoreNodeBase(NodeBase):
if self.tmpnodedir:
self.host_cmd(f"rm -rf {self.nodedir}")
def addnetif(self, netif, ifindex):
def addnetif(self, netif: CoreInterface, ifindex: int) -> None:
"""
Add network interface to node and set the network interface index if successful.
@ -296,7 +329,7 @@ class CoreNodeBase(NodeBase):
self._netif[ifindex] = netif
netif.netindex = ifindex
def delnetif(self, ifindex):
def delnetif(self, ifindex: int) -> None:
"""
Delete a network interface
@ -309,7 +342,7 @@ class CoreNodeBase(NodeBase):
netif.shutdown()
del netif
def netif(self, ifindex):
def netif(self, ifindex: int) -> Optional[CoreInterface]:
"""
Retrieve network interface.
@ -322,7 +355,7 @@ class CoreNodeBase(NodeBase):
else:
return None
def attachnet(self, ifindex, net):
def attachnet(self, ifindex: int, net: "CoreNetworkBase") -> None:
"""
Attach a network.
@ -334,7 +367,7 @@ class CoreNodeBase(NodeBase):
raise ValueError(f"ifindex {ifindex} does not exist")
self._netif[ifindex].attachnet(net)
def detachnet(self, ifindex):
def detachnet(self, ifindex: int) -> None:
"""
Detach network interface.
@ -345,7 +378,7 @@ class CoreNodeBase(NodeBase):
raise ValueError(f"ifindex {ifindex} does not exist")
self._netif[ifindex].detachnet()
def setposition(self, x=None, y=None, z=None):
def setposition(self, x: float = None, y: float = None, z: float = None) -> None:
"""
Set position.
@ -359,7 +392,9 @@ class CoreNodeBase(NodeBase):
for netif in self.netifs(sort=True):
netif.setposition(x, y, z)
def commonnets(self, obj, want_ctrl=False):
def commonnets(
self, obj: "CoreNodeBase", want_ctrl: bool = False
) -> List[Tuple[NodeBase, 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
@ -377,10 +412,9 @@ class CoreNodeBase(NodeBase):
for netif2 in obj.netifs():
if netif1.net == netif2.net:
common.append((netif1.net, netif1, netif2))
return common
def cmd(self, args, wait=True, shell=False):
def cmd(self, args: str, wait: bool = True, shell: bool = False) -> str:
"""
Runs a command within a node container.
@ -393,7 +427,7 @@ class CoreNodeBase(NodeBase):
"""
raise NotImplementedError
def termcmdstring(self, sh):
def termcmdstring(self, sh: str) -> str:
"""
Create a terminal command string.
@ -413,14 +447,14 @@ class CoreNode(CoreNodeBase):
def __init__(
self,
session,
_id=None,
name=None,
nodedir=None,
bootsh="boot.sh",
start=True,
server=None,
):
session: "Session",
_id: int = None,
name: str = None,
nodedir: str = None,
bootsh: str = "boot.sh",
start: bool = True,
server: "DistributedServer" = None,
) -> None:
"""
Create a CoreNode instance.
@ -451,7 +485,7 @@ class CoreNode(CoreNodeBase):
if start:
self.startup()
def create_node_net_client(self, use_ovs):
def create_node_net_client(self, use_ovs: bool) -> LinuxNetClient:
"""
Create node network client for running network commands within the nodes
container.
@ -461,7 +495,7 @@ class CoreNode(CoreNodeBase):
"""
return get_net_client(use_ovs, self.cmd)
def alive(self):
def alive(self) -> bool:
"""
Check if the node is alive.
@ -475,7 +509,7 @@ class CoreNode(CoreNodeBase):
return True
def startup(self):
def startup(self) -> None:
"""
Start a new namespace node by invoking the vnoded process that
allocates a new namespace. Bring up the loopback device and set
@ -521,7 +555,7 @@ class CoreNode(CoreNodeBase):
self.privatedir("/var/run")
self.privatedir("/var/log")
def shutdown(self):
def shutdown(self) -> None:
"""
Shutdown logic for simple lxc nodes.
@ -562,7 +596,7 @@ class CoreNode(CoreNodeBase):
finally:
self.rmnodedir()
def cmd(self, args, wait=True, shell=False):
def cmd(self, args: str, wait: bool = True, shell: bool = False) -> str:
"""
Runs a command that is used to configure and setup the network within a
node.
@ -580,7 +614,7 @@ class CoreNode(CoreNodeBase):
args = self.client.create_cmd(args)
return self.server.remote_cmd(args, wait=wait)
def termcmdstring(self, sh="/bin/sh"):
def termcmdstring(self, sh: str = "/bin/sh") -> str:
"""
Create a terminal command string.
@ -593,7 +627,7 @@ class CoreNode(CoreNodeBase):
else:
return f"ssh -X -f {self.server.host} xterm -e {terminal}"
def privatedir(self, path):
def privatedir(self, path: str) -> None:
"""
Create a private directory.
@ -608,7 +642,7 @@ class CoreNode(CoreNodeBase):
self.host_cmd(f"mkdir -p {hostpath}")
self.mount(hostpath, path)
def mount(self, source, target):
def mount(self, source: str, target: str) -> None:
"""
Create and mount a directory.
@ -623,7 +657,7 @@ class CoreNode(CoreNodeBase):
self.cmd(f"{MOUNT_BIN} -n --bind {source} {target}")
self._mounts.append((source, target))
def newifindex(self):
def newifindex(self) -> int:
"""
Retrieve a new interface index.
@ -633,7 +667,7 @@ class CoreNode(CoreNodeBase):
with self.lock:
return super().newifindex()
def newveth(self, ifindex=None, ifname=None):
def newveth(self, ifindex: int = None, ifname: str = None) -> int:
"""
Create a new interface.
@ -690,7 +724,7 @@ class CoreNode(CoreNodeBase):
return ifindex
def newtuntap(self, ifindex=None, ifname=None):
def newtuntap(self, ifindex: int = None, ifname: str = None) -> int:
"""
Create a new tunnel tap.
@ -720,7 +754,7 @@ class CoreNode(CoreNodeBase):
return ifindex
def sethwaddr(self, ifindex, addr):
def sethwaddr(self, ifindex: int, addr: str) -> None:
"""
Set hardware addres for an interface.
@ -735,7 +769,7 @@ class CoreNode(CoreNodeBase):
if self.up:
self.node_net_client.device_mac(interface.name, addr)
def addaddr(self, ifindex, addr):
def addaddr(self, ifindex: int, addr: str) -> None:
"""
Add interface address.
@ -753,7 +787,7 @@ class CoreNode(CoreNodeBase):
broadcast = "+"
self.node_net_client.create_address(interface.name, addr, broadcast)
def deladdr(self, ifindex, addr):
def deladdr(self, ifindex: int, addr: str) -> None:
"""
Delete address from an interface.
@ -772,7 +806,7 @@ class CoreNode(CoreNodeBase):
if self.up:
self.node_net_client.delete_address(interface.name, addr)
def ifup(self, ifindex):
def ifup(self, ifindex: int) -> None:
"""
Bring an interface up.
@ -783,7 +817,14 @@ class CoreNode(CoreNodeBase):
interface_name = self.ifname(ifindex)
self.node_net_client.device_up(interface_name)
def newnetif(self, net=None, addrlist=None, hwaddr=None, ifindex=None, ifname=None):
def newnetif(
self,
net: "CoreNetworkBase" = None,
addrlist: List[str] = None,
hwaddr: str = None,
ifindex: int = None,
ifname: str = None,
) -> int:
"""
Create a new network interface.
@ -827,7 +868,7 @@ class CoreNode(CoreNodeBase):
self.ifup(ifindex)
return ifindex
def addfile(self, srcname, filename):
def addfile(self, srcname: str, filename: str) -> None:
"""
Add a file.
@ -846,7 +887,7 @@ class CoreNode(CoreNodeBase):
self.host_cmd(f"mkdir -p {directory}")
self.server.remote_put(srcname, filename)
def hostfilename(self, filename):
def hostfilename(self, filename: str) -> str:
"""
Return the name of a node"s file on the host filesystem.
@ -862,7 +903,7 @@ class CoreNode(CoreNodeBase):
dirname = os.path.join(self.nodedir, dirname)
return os.path.join(dirname, basename)
def nodefile(self, filename, contents, mode=0o644):
def nodefile(self, filename: str, contents: str, mode: int = 0o644) -> None:
"""
Create a node file with a given mode.
@ -887,7 +928,7 @@ class CoreNode(CoreNodeBase):
"node(%s) added file: %s; mode: 0%o", self.name, hostfilename, mode
)
def nodefilecopy(self, filename, srcfilename, mode=None):
def nodefilecopy(self, filename: str, srcfilename: str, mode: int = None) -> None:
"""
Copy a file to a node, following symlinks and preserving metadata.
Change file mode if specified.
@ -917,7 +958,14 @@ class CoreNetworkBase(NodeBase):
linktype = LinkTypes.WIRED.value
is_emane = False
def __init__(self, session, _id, name, start=True, server=None):
def __init__(
self,
session: "Session",
_id: int,
name: str,
start: bool = True,
server: "DistributedServer" = None,
) -> None:
"""
Create a CoreNetworkBase instance.
@ -932,7 +980,7 @@ class CoreNetworkBase(NodeBase):
self._linked = {}
self._linked_lock = threading.Lock()
def startup(self):
def startup(self) -> None:
"""
Each object implements its own startup method.
@ -940,7 +988,7 @@ class CoreNetworkBase(NodeBase):
"""
raise NotImplementedError
def shutdown(self):
def shutdown(self) -> None:
"""
Each object implements its own shutdown method.
@ -948,7 +996,30 @@ class CoreNetworkBase(NodeBase):
"""
raise NotImplementedError
def attach(self, netif):
def linknet(self, net: "CoreNetworkBase") -> CoreInterface:
"""
Link network to another.
:param core.nodes.base.CoreNetworkBase net: network to link with
:return: created interface
:rtype: core.nodes.interface.Veth
"""
pass
def getlinknetif(self, net: "CoreNetworkBase") -> CoreInterface:
"""
Return the interface of that links this net with another net.
:param core.nodes.base.CoreNetworkBase net: interface to get link for
:return: interface the provided network is linked to
:rtype: core.nodes.interface.CoreInterface
"""
for netif in self.netifs():
if hasattr(netif, "othernet") and netif.othernet == net:
return netif
return None
def attach(self, netif: CoreInterface) -> None:
"""
Attach network interface.
@ -961,7 +1032,7 @@ class CoreNetworkBase(NodeBase):
with self._linked_lock:
self._linked[netif] = {}
def detach(self, netif):
def detach(self, netif: CoreInterface) -> None:
"""
Detach network interface.
@ -973,7 +1044,7 @@ class CoreNetworkBase(NodeBase):
with self._linked_lock:
del self._linked[netif]
def all_link_data(self, flags):
def all_link_data(self, flags: int) -> List[LinkData]:
"""
Build link data objects for this network. Each link object describes a link
between this network and a node.
@ -981,7 +1052,6 @@ class CoreNetworkBase(NodeBase):
:param int flags: message type
:return: list of link data
:rtype: list[core.data.LinkData]
"""
all_links = []
@ -1072,7 +1142,7 @@ class Position:
Helper class for Cartesian coordinate position
"""
def __init__(self, x=None, y=None, z=None):
def __init__(self, x: float = None, y: float = None, z: float = None) -> None:
"""
Creates a Position instance.
@ -1085,7 +1155,7 @@ class Position:
self.y = y
self.z = z
def set(self, x=None, y=None, z=None):
def set(self, x: float = None, y: float = None, z: float = None) -> bool:
"""
Returns True if the position has actually changed.
@ -1102,7 +1172,7 @@ class Position:
self.z = z
return True
def get(self):
def get(self) -> Tuple[float, float, float]:
"""
Retrieve x,y,z position.

View file

@ -13,7 +13,7 @@ class VnodeClient:
Provides client functionality for interacting with a virtual node.
"""
def __init__(self, name, ctrlchnlname):
def __init__(self, name: str, ctrlchnlname: str) -> None:
"""
Create a VnodeClient instance.
@ -23,7 +23,7 @@ class VnodeClient:
self.name = name
self.ctrlchnlname = ctrlchnlname
def _verify_connection(self):
def _verify_connection(self) -> None:
"""
Checks that the vcmd client is properly connected.
@ -33,7 +33,7 @@ class VnodeClient:
if not self.connected():
raise IOError("vcmd not connected")
def connected(self):
def connected(self) -> bool:
"""
Check if node is connected or not.
@ -42,7 +42,7 @@ class VnodeClient:
"""
return True
def close(self):
def close(self) -> None:
"""
Close the client connection.
@ -50,10 +50,10 @@ class VnodeClient:
"""
pass
def create_cmd(self, args):
def create_cmd(self, args: str) -> str:
return f"{VCMD_BIN} -c {self.ctrlchnlname} -- {args}"
def check_cmd(self, args, wait=True, shell=False):
def check_cmd(self, args: str, wait: bool = True, shell: bool = False) -> str:
"""
Run command and return exit status and combined stdout and stderr.

View file

@ -2,22 +2,27 @@ import json
import logging
import os
from tempfile import NamedTemporaryFile
from typing import TYPE_CHECKING, Callable, Dict
from core import utils
from core.emulator.distributed import DistributedServer
from core.emulator.enumerations import NodeTypes
from core.errors import CoreCommandError
from core.nodes.base import CoreNode
from core.nodes.netclient import get_net_client
from core.nodes.netclient import LinuxNetClient, get_net_client
if TYPE_CHECKING:
from core.emulator.session import Session
class DockerClient:
def __init__(self, name, image, run):
def __init__(self, name: str, image: str, run: Callable[..., str]) -> None:
self.name = name
self.image = image
self.run = run
self.pid = None
def create_container(self):
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"
@ -25,7 +30,7 @@ class DockerClient:
self.pid = self.get_pid()
return self.pid
def get_info(self):
def get_info(self) -> Dict:
args = f"docker inspect {self.name}"
output = self.run(args)
data = json.loads(output)
@ -33,35 +38,35 @@ class DockerClient:
raise CoreCommandError(-1, args, f"docker({self.name}) not present")
return data[0]
def is_alive(self):
def is_alive(self) -> bool:
try:
data = self.get_info()
return data["State"]["Running"]
except CoreCommandError:
return False
def stop_container(self):
def stop_container(self) -> None:
self.run(f"docker rm -f {self.name}")
def check_cmd(self, cmd, wait=True, shell=False):
def check_cmd(self, cmd: str, wait: bool = True, shell: bool = False) -> str:
logging.info("docker cmd output: %s", cmd)
return utils.cmd(f"docker exec {self.name} {cmd}", wait=wait, shell=shell)
def create_ns_cmd(self, cmd):
def create_ns_cmd(self, cmd: str) -> str:
return f"nsenter -t {self.pid} -u -i -p -n {cmd}"
def ns_cmd(self, cmd, wait):
def ns_cmd(self, cmd: str, wait: bool) -> str:
args = f"nsenter -t {self.pid} -u -i -p -n {cmd}"
return utils.cmd(args, wait=wait)
def get_pid(self):
def get_pid(self) -> str:
args = f"docker inspect -f '{{{{.State.Pid}}}}' {self.name}"
output = self.run(args)
self.pid = output
logging.debug("node(%s) pid: %s", self.name, self.pid)
return output
def copy_file(self, source, destination):
def copy_file(self, source: str, destination: str) -> str:
args = f"docker cp {source} {self.name}:{destination}"
return self.run(args)
@ -71,15 +76,15 @@ class DockerNode(CoreNode):
def __init__(
self,
session,
_id=None,
name=None,
nodedir=None,
bootsh="boot.sh",
start=True,
server=None,
image=None
):
session: "Session",
_id: int = None,
name: str = None,
nodedir: str = None,
bootsh: str = "boot.sh",
start: bool = True,
server: DistributedServer = None,
image: str = None
) -> None:
"""
Create a DockerNode instance.
@ -98,7 +103,7 @@ class DockerNode(CoreNode):
self.image = image
super().__init__(session, _id, name, nodedir, bootsh, start, server)
def create_node_net_client(self, use_ovs):
def create_node_net_client(self, use_ovs: bool) -> LinuxNetClient:
"""
Create node network client for running network commands within the nodes
container.
@ -108,7 +113,7 @@ class DockerNode(CoreNode):
"""
return get_net_client(use_ovs, self.nsenter_cmd)
def alive(self):
def alive(self) -> bool:
"""
Check if the node is alive.
@ -117,7 +122,7 @@ class DockerNode(CoreNode):
"""
return self.client.is_alive()
def startup(self):
def startup(self) -> None:
"""
Start a new namespace node by invoking the vnoded process that
allocates a new namespace. Bring up the loopback device and set
@ -133,7 +138,7 @@ class DockerNode(CoreNode):
self.pid = self.client.create_container()
self.up = True
def shutdown(self):
def shutdown(self) -> None:
"""
Shutdown logic.
@ -148,7 +153,7 @@ class DockerNode(CoreNode):
self.client.stop_container()
self.up = False
def nsenter_cmd(self, args, wait=True, shell=False):
def nsenter_cmd(self, args: str, wait: bool = True, shell: bool = False) -> str:
if self.server is None:
args = self.client.create_ns_cmd(args)
return utils.cmd(args, wait=wait, shell=shell)
@ -156,7 +161,7 @@ class DockerNode(CoreNode):
args = self.client.create_ns_cmd(args)
return self.server.remote_cmd(args, wait=wait)
def termcmdstring(self, sh="/bin/sh"):
def termcmdstring(self, sh: str = "/bin/sh") -> str:
"""
Create a terminal command string.
@ -165,7 +170,7 @@ class DockerNode(CoreNode):
"""
return f"docker exec -it {self.name} bash"
def privatedir(self, path):
def privatedir(self, path: str) -> None:
"""
Create a private directory.
@ -176,7 +181,7 @@ class DockerNode(CoreNode):
args = f"mkdir -p {path}"
self.cmd(args)
def mount(self, source, target):
def mount(self, source: str, target: str) -> None:
"""
Create and mount a directory.
@ -188,7 +193,7 @@ class DockerNode(CoreNode):
logging.debug("mounting source(%s) target(%s)", source, target)
raise Exception("not supported")
def nodefile(self, filename, contents, mode=0o644):
def nodefile(self, filename: str, contents: str, mode: int = 0o644) -> None:
"""
Create a node file with a given mode.
@ -216,7 +221,7 @@ class DockerNode(CoreNode):
"node(%s) added file: %s; mode: 0%o", self.name, filename, mode
)
def nodefilecopy(self, filename, srcfilename, mode=None):
def nodefilecopy(self, filename: str, srcfilename: str, mode: int = None) -> None:
"""
Copy a file to a node, following symlinks and preserving metadata.
Change file mode if specified.

View file

@ -4,18 +4,31 @@ virtual ethernet classes that implement the interfaces available under Linux.
import logging
import time
from typing import TYPE_CHECKING, Callable, Dict, List, Tuple
from core import utils
from core.errors import CoreCommandError
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.nodes.base import CoreNetworkBase, CoreNode
class CoreInterface:
"""
Base class for network interfaces.
"""
def __init__(self, session, node, name, mtu, server=None):
def __init__(
self,
session: "Session",
node: "CoreNode",
name: str,
mtu: int,
server: "DistributedServer" = None,
) -> None:
"""
Creates a CoreInterface instance.
@ -50,7 +63,14 @@ class CoreInterface:
use_ovs = session.options.get_config("ovs") == "True"
self.net_client = get_net_client(use_ovs, self.host_cmd)
def host_cmd(self, args, env=None, cwd=None, wait=True, shell=False):
def host_cmd(
self,
args: str,
env: Dict[str, str] = None,
cwd: str = None,
wait: bool = True,
shell: bool = False,
) -> str:
"""
Runs a command on the host system or distributed server.
@ -68,7 +88,7 @@ class CoreInterface:
else:
return self.server.remote_cmd(args, env, cwd, wait)
def startup(self):
def startup(self) -> None:
"""
Startup method for the interface.
@ -76,7 +96,7 @@ class CoreInterface:
"""
pass
def shutdown(self):
def shutdown(self) -> None:
"""
Shutdown method for the interface.
@ -84,7 +104,7 @@ class CoreInterface:
"""
pass
def attachnet(self, net):
def attachnet(self, net: "CoreNetworkBase") -> None:
"""
Attach network.
@ -98,7 +118,7 @@ class CoreInterface:
net.attach(self)
self.net = net
def detachnet(self):
def detachnet(self) -> None:
"""
Detach from a network.
@ -107,7 +127,7 @@ class CoreInterface:
if self.net is not None:
self.net.detach(self)
def addaddr(self, addr):
def addaddr(self, addr: str) -> None:
"""
Add address.
@ -117,7 +137,7 @@ class CoreInterface:
addr = utils.validate_ip(addr)
self.addrlist.append(addr)
def deladdr(self, addr):
def deladdr(self, addr: str) -> None:
"""
Delete address.
@ -126,7 +146,7 @@ class CoreInterface:
"""
self.addrlist.remove(addr)
def sethwaddr(self, addr):
def sethwaddr(self, addr: str) -> None:
"""
Set hardware address.
@ -136,7 +156,7 @@ class CoreInterface:
addr = utils.validate_mac(addr)
self.hwaddr = addr
def getparam(self, key):
def getparam(self, key: str) -> float:
"""
Retrieve a parameter from the, or None if the parameter does not exist.
@ -145,7 +165,7 @@ class CoreInterface:
"""
return self._params.get(key)
def getparams(self):
def getparams(self) -> List[Tuple[str, float]]:
"""
Return (key, value) pairs for parameters.
"""
@ -154,7 +174,7 @@ class CoreInterface:
parameters.append((k, self._params[k]))
return parameters
def setparam(self, key, value):
def setparam(self, key: str, value: float) -> bool:
"""
Set a parameter value, returns True if the parameter has changed.
@ -174,7 +194,7 @@ class CoreInterface:
self._params[key] = value
return True
def swapparams(self, name):
def swapparams(self, name: str) -> None:
"""
Swap out parameters dict for name. If name does not exist,
intialize it. This is for supporting separate upstream/downstream
@ -189,7 +209,7 @@ class CoreInterface:
self._params = getattr(self, name)
setattr(self, name, tmp)
def setposition(self, x, y, z):
def setposition(self, x: float, y: float, z: float) -> None:
"""
Dispatch position hook handler.
@ -200,7 +220,7 @@ class CoreInterface:
"""
self.poshook(self, x, y, z)
def __lt__(self, other):
def __lt__(self, other: "CoreInterface") -> bool:
"""
Used for comparisons of this object.
@ -217,8 +237,15 @@ class Veth(CoreInterface):
"""
def __init__(
self, session, node, name, localname, mtu=1500, server=None, start=True
):
self,
session: "Session",
node: "CoreNode",
name: str,
localname: str,
mtu: int = 1500,
server: "DistributedServer" = None,
start: bool = True,
) -> None:
"""
Creates a VEth instance.
@ -239,7 +266,7 @@ class Veth(CoreInterface):
if start:
self.startup()
def startup(self):
def startup(self) -> None:
"""
Interface startup logic.
@ -250,7 +277,7 @@ class Veth(CoreInterface):
self.net_client.device_up(self.localname)
self.up = True
def shutdown(self):
def shutdown(self) -> None:
"""
Interface shutdown logic.
@ -280,8 +307,15 @@ class TunTap(CoreInterface):
"""
def __init__(
self, session, node, name, localname, mtu=1500, server=None, start=True
):
self,
session: "Session",
node: "CoreNode",
name: str,
localname: str,
mtu: int = 1500,
server: "DistributedServer" = None,
start: bool = True,
) -> None:
"""
Create a TunTap instance.
@ -301,7 +335,7 @@ class TunTap(CoreInterface):
if start:
self.startup()
def startup(self):
def startup(self) -> None:
"""
Startup logic for a tunnel tap.
@ -315,7 +349,7 @@ class TunTap(CoreInterface):
# self.install()
self.up = True
def shutdown(self):
def shutdown(self) -> None:
"""
Shutdown functionality for a tunnel tap.
@ -331,7 +365,9 @@ class TunTap(CoreInterface):
self.up = False
def waitfor(self, func, attempts=10, maxretrydelay=0.25):
def waitfor(
self, func: Callable[[], int], attempts: int = 10, maxretrydelay: float = 0.25
) -> bool:
"""
Wait for func() to return zero with exponential backoff.
@ -362,7 +398,7 @@ class TunTap(CoreInterface):
return result
def waitfordevicelocal(self):
def waitfordevicelocal(self) -> None:
"""
Check for presence of a local device - tap device may not
appear right away waits
@ -381,7 +417,7 @@ class TunTap(CoreInterface):
self.waitfor(localdevexists)
def waitfordevicenode(self):
def waitfordevicenode(self) -> None:
"""
Check for presence of a node device - tap device may not appear right away waits.
@ -412,7 +448,7 @@ class TunTap(CoreInterface):
else:
raise RuntimeError("node device failed to exist")
def install(self):
def install(self) -> None:
"""
Install this TAP into its namespace. This is not done from the
startup() method but called at a later time when a userspace
@ -428,7 +464,7 @@ class TunTap(CoreInterface):
self.node.node_net_client.device_name(self.localname, self.name)
self.node.node_net_client.device_up(self.name)
def setaddrs(self):
def setaddrs(self) -> None:
"""
Set interface addresses based on self.addrlist.
@ -448,18 +484,18 @@ class GreTap(CoreInterface):
def __init__(
self,
node=None,
name=None,
session=None,
mtu=1458,
remoteip=None,
_id=None,
localip=None,
ttl=255,
key=None,
start=True,
server=None,
):
node: "CoreNode" = None,
name: str = None,
session: "Session" = None,
mtu: int = 1458,
remoteip: str = None,
_id: int = None,
localip: str = None,
ttl: int = 255,
key: int = None,
start: bool = True,
server: "DistributedServer" = None,
) -> None:
"""
Creates a GreTap instance.
@ -497,7 +533,7 @@ class GreTap(CoreInterface):
self.net_client.device_up(self.localname)
self.up = True
def shutdown(self):
def shutdown(self) -> None:
"""
Shutdown logic for a GreTap.
@ -512,7 +548,7 @@ class GreTap(CoreInterface):
self.localname = None
def data(self, message_type):
def data(self, message_type: int) -> None:
"""
Data for a gre tap.
@ -521,7 +557,7 @@ class GreTap(CoreInterface):
"""
return None
def all_link_data(self, flags):
def all_link_data(self, flags: int) -> List:
"""
Retrieve link data.

View file

@ -3,27 +3,33 @@ import logging
import os
import time
from tempfile import NamedTemporaryFile
from typing import TYPE_CHECKING, Callable, Dict
from core import utils
from core.emulator.distributed import DistributedServer
from core.emulator.enumerations import NodeTypes
from core.errors import CoreCommandError
from core.nodes.base import CoreNode
from core.nodes.interface import CoreInterface
if TYPE_CHECKING:
from core.emulator.session import Session
class LxdClient:
def __init__(self, name, image, run):
def __init__(self, name: str, image: str, run: Callable[..., str]) -> None:
self.name = name
self.image = image
self.run = run
self.pid = None
def create_container(self):
def create_container(self) -> int:
self.run(f"lxc launch {self.image} {self.name}")
data = self.get_info()
self.pid = data["state"]["pid"]
return self.pid
def get_info(self):
def get_info(self) -> Dict:
args = f"lxc list {self.name} --format json"
output = self.run(args)
data = json.loads(output)
@ -31,27 +37,27 @@ class LxdClient:
raise CoreCommandError(-1, args, f"LXC({self.name}) not present")
return data[0]
def is_alive(self):
def is_alive(self) -> bool:
try:
data = self.get_info()
return data["state"]["status"] == "Running"
except CoreCommandError:
return False
def stop_container(self):
def stop_container(self) -> None:
self.run(f"lxc delete --force {self.name}")
def create_cmd(self, cmd):
def create_cmd(self, cmd: str) -> str:
return f"lxc exec -nT {self.name} -- {cmd}"
def create_ns_cmd(self, cmd):
def create_ns_cmd(self, cmd: str) -> str:
return f"nsenter -t {self.pid} -m -u -i -p -n {cmd}"
def check_cmd(self, cmd, wait=True, shell=False):
def check_cmd(self, cmd: str, wait: bool = True, shell: bool = False) -> str:
args = self.create_cmd(cmd)
return utils.cmd(args, wait=wait, shell=shell)
def copy_file(self, source, destination):
def copy_file(self, source: str, destination: str) -> None:
if destination[0] != "/":
destination = os.path.join("/root/", destination)
@ -64,15 +70,15 @@ class LxcNode(CoreNode):
def __init__(
self,
session,
_id=None,
name=None,
nodedir=None,
bootsh="boot.sh",
start=True,
server=None,
image=None,
):
session: "Session",
_id: int = None,
name: str = None,
nodedir: str = None,
bootsh: str = "boot.sh",
start: bool = True,
server: DistributedServer = None,
image: str = None,
) -> None:
"""
Create a LxcNode instance.
@ -91,7 +97,7 @@ class LxcNode(CoreNode):
self.image = image
super().__init__(session, _id, name, nodedir, bootsh, start, server)
def alive(self):
def alive(self) -> bool:
"""
Check if the node is alive.
@ -100,7 +106,7 @@ class LxcNode(CoreNode):
"""
return self.client.is_alive()
def startup(self):
def startup(self) -> None:
"""
Startup logic.
@ -114,7 +120,7 @@ class LxcNode(CoreNode):
self.pid = self.client.create_container()
self.up = True
def shutdown(self):
def shutdown(self) -> None:
"""
Shutdown logic.
@ -129,7 +135,7 @@ class LxcNode(CoreNode):
self.client.stop_container()
self.up = False
def termcmdstring(self, sh="/bin/sh"):
def termcmdstring(self, sh: str = "/bin/sh") -> str:
"""
Create a terminal command string.
@ -138,7 +144,7 @@ class LxcNode(CoreNode):
"""
return f"lxc exec {self.name} -- {sh}"
def privatedir(self, path):
def privatedir(self, path: str) -> None:
"""
Create a private directory.
@ -147,9 +153,9 @@ class LxcNode(CoreNode):
"""
logging.info("creating node dir: %s", path)
args = f"mkdir -p {path}"
return self.cmd(args)
self.cmd(args)
def mount(self, source, target):
def mount(self, source: str, target: str) -> None:
"""
Create and mount a directory.
@ -161,7 +167,7 @@ class LxcNode(CoreNode):
logging.debug("mounting source(%s) target(%s)", source, target)
raise Exception("not supported")
def nodefile(self, filename, contents, mode=0o644):
def nodefile(self, filename: str, contents: str, mode: int = 0o644) -> None:
"""
Create a node file with a given mode.
@ -188,7 +194,7 @@ class LxcNode(CoreNode):
os.unlink(temp.name)
logging.debug("node(%s) added file: %s; mode: 0%o", self.name, filename, mode)
def nodefilecopy(self, filename, srcfilename, mode=None):
def nodefilecopy(self, filename: str, srcfilename: str, mode: int = None) -> None:
"""
Copy a file to a node, following symlinks and preserving metadata.
Change file mode if specified.
@ -214,7 +220,7 @@ class LxcNode(CoreNode):
self.client.copy_file(source, filename)
self.cmd(f"chmod {mode:o} {filename}")
def addnetif(self, netif, ifindex):
def addnetif(self, netif: CoreInterface, ifindex: int) -> None:
super().addnetif(netif, ifindex)
# adding small delay to allow time for adding addresses to work correctly
time.sleep(0.5)

View file

@ -2,30 +2,17 @@
Clients for dealing with bridge/interface commands.
"""
import json
from typing import Callable
from core.constants import ETHTOOL_BIN, IP_BIN, OVS_BIN, TC_BIN
def get_net_client(use_ovs, run):
"""
Retrieve desired net client for running network commands.
:param bool use_ovs: True for OVS bridges, False for Linux bridges
:param func run: function used to run net client commands
:return: net client class
"""
if use_ovs:
return OvsNetClient(run)
else:
return LinuxNetClient(run)
class LinuxNetClient:
"""
Client for creating Linux bridges and ip interfaces for nodes.
"""
def __init__(self, run):
def __init__(self, run: Callable[..., str]) -> None:
"""
Create LinuxNetClient instance.
@ -33,7 +20,7 @@ class LinuxNetClient:
"""
self.run = run
def set_hostname(self, name):
def set_hostname(self, name: str) -> None:
"""
Set network hostname.
@ -42,7 +29,7 @@ class LinuxNetClient:
"""
self.run(f"hostname {name}")
def create_route(self, route, device):
def create_route(self, route: str, device: str) -> None:
"""
Create a new route for a device.
@ -52,7 +39,7 @@ class LinuxNetClient:
"""
self.run(f"{IP_BIN} route add {route} dev {device}")
def device_up(self, device):
def device_up(self, device: str) -> None:
"""
Bring a device up.
@ -61,7 +48,7 @@ class LinuxNetClient:
"""
self.run(f"{IP_BIN} link set {device} up")
def device_down(self, device):
def device_down(self, device: str) -> None:
"""
Bring a device down.
@ -70,7 +57,7 @@ class LinuxNetClient:
"""
self.run(f"{IP_BIN} link set {device} down")
def device_name(self, device, name):
def device_name(self, device: str, name: str) -> None:
"""
Set a device name.
@ -80,7 +67,7 @@ class LinuxNetClient:
"""
self.run(f"{IP_BIN} link set {device} name {name}")
def device_show(self, device):
def device_show(self, device: str) -> str:
"""
Show information for a device.
@ -90,7 +77,7 @@ class LinuxNetClient:
"""
return self.run(f"{IP_BIN} link show {device}")
def get_mac(self, device):
def get_mac(self, device: str) -> str:
"""
Retrieve MAC address for a given device.
@ -100,7 +87,7 @@ class LinuxNetClient:
"""
return self.run(f"cat /sys/class/net/{device}/address")
def get_ifindex(self, device):
def get_ifindex(self, device: str) -> str:
"""
Retrieve ifindex for a given device.
@ -110,7 +97,7 @@ class LinuxNetClient:
"""
return self.run(f"cat /sys/class/net/{device}/ifindex")
def device_ns(self, device, namespace):
def device_ns(self, device: str, namespace: str) -> None:
"""
Set netns for a device.
@ -120,7 +107,7 @@ class LinuxNetClient:
"""
self.run(f"{IP_BIN} link set {device} netns {namespace}")
def device_flush(self, device):
def device_flush(self, device: str) -> None:
"""
Flush device addresses.
@ -132,7 +119,7 @@ class LinuxNetClient:
shell=True,
)
def device_mac(self, device, mac):
def device_mac(self, device: str, mac: str) -> None:
"""
Set MAC address for a device.
@ -142,7 +129,7 @@ class LinuxNetClient:
"""
self.run(f"{IP_BIN} link set dev {device} address {mac}")
def delete_device(self, device):
def delete_device(self, device: str) -> None:
"""
Delete device.
@ -151,7 +138,7 @@ class LinuxNetClient:
"""
self.run(f"{IP_BIN} link delete {device}")
def delete_tc(self, device):
def delete_tc(self, device: str) -> None:
"""
Remove traffic control settings for a device.
@ -160,7 +147,7 @@ class LinuxNetClient:
"""
self.run(f"{TC_BIN} qdisc delete dev {device} root")
def checksums_off(self, interface_name):
def checksums_off(self, interface_name: str) -> None:
"""
Turns interface checksums off.
@ -169,7 +156,7 @@ class LinuxNetClient:
"""
self.run(f"{ETHTOOL_BIN} -K {interface_name} rx off tx off")
def create_address(self, device, address, broadcast=None):
def create_address(self, device: str, address: str, broadcast: str = None) -> None:
"""
Create address for a device.
@ -185,7 +172,7 @@ class LinuxNetClient:
else:
self.run(f"{IP_BIN} address add {address} dev {device}")
def delete_address(self, device, address):
def delete_address(self, device: str, address: str) -> None:
"""
Delete an address from a device.
@ -195,7 +182,7 @@ class LinuxNetClient:
"""
self.run(f"{IP_BIN} address delete {address} dev {device}")
def create_veth(self, name, peer):
def create_veth(self, name: str, peer: str) -> None:
"""
Create a veth pair.
@ -205,7 +192,9 @@ class LinuxNetClient:
"""
self.run(f"{IP_BIN} link add name {name} type veth peer name {peer}")
def create_gretap(self, device, address, local, ttl, key):
def create_gretap(
self, device: str, address: str, local: str, ttl: int, key: int
) -> None:
"""
Create a GRE tap on a device.
@ -225,7 +214,7 @@ class LinuxNetClient:
cmd += f" key {key}"
self.run(cmd)
def create_bridge(self, name):
def create_bridge(self, name: str) -> None:
"""
Create a Linux bridge and bring it up.
@ -238,7 +227,7 @@ class LinuxNetClient:
self.run(f"{IP_BIN} link set {name} type bridge mcast_snooping 0")
self.device_up(name)
def delete_bridge(self, name):
def delete_bridge(self, name: str) -> None:
"""
Bring down and delete a Linux bridge.
@ -248,7 +237,7 @@ class LinuxNetClient:
self.device_down(name)
self.run(f"{IP_BIN} link delete {name} type bridge")
def create_interface(self, bridge_name, interface_name):
def create_interface(self, bridge_name: str, interface_name: str) -> None:
"""
Create an interface associated with a Linux bridge.
@ -259,7 +248,7 @@ class LinuxNetClient:
self.run(f"{IP_BIN} link set dev {interface_name} master {bridge_name}")
self.device_up(interface_name)
def delete_interface(self, bridge_name, interface_name):
def delete_interface(self, bridge_name: str, interface_name: str) -> None:
"""
Delete an interface associated with a Linux bridge.
@ -269,11 +258,12 @@ class LinuxNetClient:
"""
self.run(f"{IP_BIN} link set dev {interface_name} nomaster")
def existing_bridges(self, _id):
def existing_bridges(self, _id: int) -> bool:
"""
Checks if there are any existing Linux bridges for a node.
: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)
@ -286,7 +276,7 @@ class LinuxNetClient:
return True
return False
def disable_mac_learning(self, name):
def disable_mac_learning(self, name: str) -> None:
"""
Disable mac learning for a Linux bridge.
@ -301,7 +291,7 @@ class OvsNetClient(LinuxNetClient):
Client for creating OVS bridges and ip interfaces for nodes.
"""
def create_bridge(self, name):
def create_bridge(self, name: str) -> None:
"""
Create a OVS bridge and bring it up.
@ -314,7 +304,7 @@ class OvsNetClient(LinuxNetClient):
self.run(f"{OVS_BIN} set bridge {name} other_config:stp-forward-delay=4")
self.device_up(name)
def delete_bridge(self, name):
def delete_bridge(self, name: str) -> None:
"""
Bring down and delete a OVS bridge.
@ -324,7 +314,7 @@ class OvsNetClient(LinuxNetClient):
self.device_down(name)
self.run(f"{OVS_BIN} del-br {name}")
def create_interface(self, bridge_name, interface_name):
def create_interface(self, bridge_name: str, interface_name: str) -> None:
"""
Create an interface associated with a network bridge.
@ -335,7 +325,7 @@ class OvsNetClient(LinuxNetClient):
self.run(f"{OVS_BIN} add-port {bridge_name} {interface_name}")
self.device_up(interface_name)
def delete_interface(self, bridge_name, interface_name):
def delete_interface(self, bridge_name: str, interface_name: str) -> None:
"""
Delete an interface associated with a OVS bridge.
@ -345,11 +335,12 @@ class OvsNetClient(LinuxNetClient):
"""
self.run(f"{OVS_BIN} del-port {bridge_name} {interface_name}")
def existing_bridges(self, _id):
def existing_bridges(self, _id: int) -> bool:
"""
Checks if there are any existing OVS bridges for a node.
:param _id: node id to check bridges for
:return: True if there are existing bridges, False otherwise
"""
output = self.run(f"{OVS_BIN} list-br")
if output:
@ -359,7 +350,7 @@ class OvsNetClient(LinuxNetClient):
return True
return False
def disable_mac_learning(self, name):
def disable_mac_learning(self, name: str) -> None:
"""
Disable mac learning for a OVS bridge.
@ -367,3 +358,17 @@ class OvsNetClient(LinuxNetClient):
:return: nothing
"""
self.run(f"{OVS_BIN} set bridge {name} other_config:mac-aging-time=0")
def get_net_client(use_ovs: bool, run: Callable[..., str]) -> LinuxNetClient:
"""
Retrieve desired net client for running network commands.
:param bool use_ovs: True for OVS bridges, False for Linux bridges
:param func run: function used to run net client commands
:return: net client class
"""
if use_ovs:
return OvsNetClient(run)
else:
return LinuxNetClient(run)

View file

@ -5,18 +5,26 @@ Defines network nodes used within core.
import logging
import threading
import time
from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Type
import netaddr
from core import utils
from core.constants import EBTABLES_BIN, TC_BIN
from core.emulator.data import LinkData
from core.emulator.data import LinkData, NodeData
from core.emulator.enumerations import LinkTypes, NodeTypes, RegisterTlvs
from core.errors import CoreCommandError, CoreError
from core.nodes.base import CoreNetworkBase
from core.nodes.interface import GreTap, Veth
from core.nodes.interface import CoreInterface, GreTap, Veth
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
WirelessModelType = Type[WirelessModel]
ebtables_lock = threading.Lock()
@ -32,7 +40,7 @@ class EbtablesQueue:
# ebtables
atomic_file = "/tmp/pycore.ebtables.atomic"
def __init__(self):
def __init__(self) -> None:
"""
Initialize the helper class, but don't start the update thread
until a WLAN is instantiated.
@ -49,7 +57,7 @@ class EbtablesQueue:
# using this queue
self.last_update_time = {}
def startupdateloop(self, wlan):
def startupdateloop(self, wlan: "CoreNetwork") -> None:
"""
Kick off the update loop; only needs to be invoked once.
@ -66,7 +74,7 @@ class EbtablesQueue:
self.updatethread.daemon = True
self.updatethread.start()
def stopupdateloop(self, wlan):
def stopupdateloop(self, wlan: "CoreNetwork") -> None:
"""
Kill the update loop thread if there are no more WLANs using it.
@ -88,17 +96,17 @@ class EbtablesQueue:
self.updatethread.join()
self.updatethread = None
def ebatomiccmd(self, cmd):
def ebatomiccmd(self, cmd: str) -> str:
"""
Helper for building ebtables atomic file command list.
:param str cmd: ebtable command
:return: ebtable atomic command
:rtype: list[str]
:rtype: str
"""
return f"{EBTABLES_BIN} --atomic-file {self.atomic_file} {cmd}"
def lastupdate(self, wlan):
def lastupdate(self, wlan: "CoreNetwork") -> float:
"""
Return the time elapsed since this WLAN was last updated.
@ -114,7 +122,7 @@ class EbtablesQueue:
return elapsed
def updated(self, wlan):
def updated(self, wlan: "CoreNetwork") -> None:
"""
Keep track of when this WLAN was last updated.
@ -124,7 +132,7 @@ class EbtablesQueue:
self.last_update_time[wlan] = time.monotonic()
self.updates.remove(wlan)
def updateloop(self):
def updateloop(self) -> None:
"""
Thread target that looks for WLANs needing update, and
rate limits the amount of ebtables activity. Only one userspace program
@ -153,7 +161,7 @@ class EbtablesQueue:
time.sleep(self.rate)
def ebcommit(self, wlan):
def ebcommit(self, wlan: "CoreNetwork") -> None:
"""
Perform ebtables atomic commit using commands built in the self.cmds list.
@ -178,7 +186,7 @@ class EbtablesQueue:
except CoreCommandError:
logging.exception("error removing atomic file: %s", self.atomic_file)
def ebchange(self, wlan):
def ebchange(self, wlan: "CoreNetwork") -> None:
"""
Flag a change to the given WLAN's _linked dict, so the ebtables
chain will be rebuilt at the next interval.
@ -189,7 +197,7 @@ class EbtablesQueue:
if wlan not in self.updates:
self.updates.append(wlan)
def buildcmds(self, wlan):
def buildcmds(self, wlan: "CoreNetwork") -> None:
"""
Inspect a _linked dict from a wlan, and rebuild the ebtables chain for that WLAN.
@ -231,7 +239,7 @@ class EbtablesQueue:
ebq = EbtablesQueue()
def ebtablescmds(call, cmds):
def ebtablescmds(call: Callable[..., str], cmds: List[str]) -> None:
"""
Run ebtable commands.
@ -252,8 +260,14 @@ class CoreNetwork(CoreNetworkBase):
policy = "DROP"
def __init__(
self, session, _id=None, name=None, start=True, server=None, policy=None
):
self,
session: "Session",
_id: int = None,
name: str = None,
start: bool = True,
server: "DistributedServer" = None,
policy: str = None,
) -> None:
"""
Creates a LxBrNet instance.
@ -279,7 +293,14 @@ class CoreNetwork(CoreNetworkBase):
self.startup()
ebq.startupdateloop(self)
def host_cmd(self, args, env=None, cwd=None, wait=True, shell=False):
def host_cmd(
self,
args: str,
env: Dict[str, str] = None,
cwd: str = None,
wait: bool = True,
shell: bool = False,
) -> str:
"""
Runs a command that is used to configure and setup the network on the host
system and all configured distributed servers.
@ -298,7 +319,7 @@ class CoreNetwork(CoreNetworkBase):
self.session.distributed.execute(lambda x: x.remote_cmd(args, env, cwd, wait))
return output
def startup(self):
def startup(self) -> None:
"""
Linux bridge starup logic.
@ -309,7 +330,7 @@ class CoreNetwork(CoreNetworkBase):
self.has_ebtables_chain = False
self.up = True
def shutdown(self):
def shutdown(self) -> None:
"""
Linux bridge shutdown logic.
@ -340,18 +361,18 @@ class CoreNetwork(CoreNetworkBase):
del self.session
self.up = False
def attach(self, netif):
def attach(self, netif: CoreInterface) -> None:
"""
Attach a network interface.
:param core.nodes.interface.Veth netif: network interface to attach
:param core.nodes.interface.CoreInterface netif: network interface to attach
:return: nothing
"""
if self.up:
netif.net_client.create_interface(self.brname, netif.localname)
super().attach(netif)
def detach(self, netif):
def detach(self, netif: CoreInterface) -> None:
"""
Detach a network interface.
@ -362,7 +383,7 @@ class CoreNetwork(CoreNetworkBase):
netif.net_client.delete_interface(self.brname, netif.localname)
super().detach(netif)
def linked(self, netif1, netif2):
def linked(self, netif1: CoreInterface, netif2: CoreInterface) -> bool:
"""
Determine if the provided network interfaces are linked.
@ -391,9 +412,9 @@ class CoreNetwork(CoreNetworkBase):
return linked
def unlink(self, netif1, netif2):
def unlink(self, netif1: CoreInterface, netif2: CoreInterface) -> None:
"""
Unlink two PyCoreNetIfs, resulting in adding or removing ebtables
Unlink two interfaces, resulting in adding or removing ebtables
filtering rules.
:param core.nodes.interface.CoreInterface netif1: interface one
@ -407,9 +428,9 @@ class CoreNetwork(CoreNetworkBase):
ebq.ebchange(self)
def link(self, netif1, netif2):
def link(self, netif1: CoreInterface, netif2: CoreInterface) -> None:
"""
Link two PyCoreNetIfs together, resulting in adding or removing
Link two interfaces together, resulting in adding or removing
ebtables filtering rules.
:param core.nodes.interface.CoreInterface netif1: interface one
@ -425,19 +446,19 @@ class CoreNetwork(CoreNetworkBase):
def linkconfig(
self,
netif,
bw=None,
delay=None,
loss=None,
duplicate=None,
jitter=None,
netif2=None,
devname=None,
):
netif: CoreInterface,
bw: float = None,
delay: float = None,
loss: float = None,
duplicate: float = None,
jitter: float = None,
netif2: float = None,
devname: str = None,
) -> None:
"""
Configure link parameters by applying tc queuing disciplines on the interface.
:param core.nodes.interface.Veth netif: interface one
:param core.nodes.interface.CoreInterface netif: interface one
:param bw: bandwidth to set to
:param delay: packet delay to set to
:param loss: packet loss to set to
@ -520,14 +541,14 @@ class CoreNetwork(CoreNetworkBase):
netif.host_cmd(cmd)
netif.setparam("has_netem", True)
def linknet(self, net):
def linknet(self, net: CoreNetworkBase) -> CoreInterface:
"""
Link this bridge with another by creating a veth pair and installing
each device into each bridge.
:param core.netns.vnet.LxBrNet net: network to link with
:param core.nodes.base.CoreNetworkBase net: network to link with
:return: created interface
:rtype: Veth
:rtype: core.nodes.interface.CoreInterface
"""
sessionid = self.session.short_session_id()
try:
@ -561,7 +582,7 @@ class CoreNetwork(CoreNetworkBase):
netif.othernet = net
return netif
def getlinknetif(self, net):
def getlinknetif(self, net: CoreNetworkBase) -> Optional[CoreInterface]:
"""
Return the interface of that links this net with another net
(that were linked using linknet()).
@ -573,10 +594,9 @@ class CoreNetwork(CoreNetworkBase):
for netif in self.netifs():
if hasattr(netif, "othernet") and netif.othernet == net:
return netif
return None
def addrconfig(self, addrlist):
def addrconfig(self, addrlist: List[str]) -> None:
"""
Set addresses on the bridge.
@ -598,17 +618,17 @@ class GreTapBridge(CoreNetwork):
def __init__(
self,
session,
remoteip=None,
_id=None,
name=None,
policy="ACCEPT",
localip=None,
ttl=255,
key=None,
start=True,
server=None,
):
session: "Session",
remoteip: str = None,
_id: int = None,
name: str = None,
policy: str = "ACCEPT",
localip: str = None,
ttl: int = 255,
key: int = None,
start: bool = True,
server: "DistributedServer" = None,
) -> None:
"""
Create a GreTapBridge instance.
@ -647,7 +667,7 @@ class GreTapBridge(CoreNetwork):
if start:
self.startup()
def startup(self):
def startup(self) -> None:
"""
Creates a bridge and adds the gretap device to it.
@ -657,7 +677,7 @@ class GreTapBridge(CoreNetwork):
if self.gretap:
self.attach(self.gretap)
def shutdown(self):
def shutdown(self) -> None:
"""
Detach the gretap device and remove the bridge.
@ -669,7 +689,7 @@ class GreTapBridge(CoreNetwork):
self.gretap = None
super().shutdown()
def addrconfig(self, addrlist):
def addrconfig(self, addrlist: List[str]) -> None:
"""
Set the remote tunnel endpoint. This is a one-time method for
creating the GreTap device, which requires the remoteip at startup.
@ -694,7 +714,7 @@ class GreTapBridge(CoreNetwork):
)
self.attach(self.gretap)
def setkey(self, key):
def setkey(self, key: int) -> None:
"""
Set the GRE key used for the GreTap device. This needs to be set
prior to instantiating the GreTap device (before addrconfig).
@ -722,17 +742,17 @@ class CtrlNet(CoreNetwork):
def __init__(
self,
session,
_id=None,
name=None,
prefix=None,
hostid=None,
start=True,
server=None,
assign_address=True,
updown_script=None,
serverintf=None,
):
session: "Session",
_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,
) -> None:
"""
Creates a CtrlNet instance.
@ -756,7 +776,7 @@ class CtrlNet(CoreNetwork):
self.serverintf = serverintf
super().__init__(session, _id, name, start, server)
def add_addresses(self, index):
def add_addresses(self, index: int) -> None:
"""
Add addresses used for created control networks,
@ -777,7 +797,7 @@ class CtrlNet(CoreNetwork):
net_client = get_net_client(use_ovs, server.remote_cmd)
net_client.create_address(self.brname, current)
def startup(self):
def startup(self) -> None:
"""
Startup functionality for the control network.
@ -806,7 +826,7 @@ class CtrlNet(CoreNetwork):
if self.serverintf:
self.net_client.create_interface(self.brname, self.serverintf)
def shutdown(self):
def shutdown(self) -> None:
"""
Control network shutdown.
@ -835,7 +855,7 @@ class CtrlNet(CoreNetwork):
super().shutdown()
def all_link_data(self, flags):
def all_link_data(self, flags: int) -> List[LinkData]:
"""
Do not include CtrlNet in link messages describing this session.
@ -853,11 +873,11 @@ class PtpNet(CoreNetwork):
policy = "ACCEPT"
def attach(self, netif):
def attach(self, netif: CoreInterface) -> None:
"""
Attach a network interface, but limit attachment to two interfaces.
:param core.netns.vif.VEth netif: network interface
:param core.nodes.interface.CoreInterface netif: network interface
:return: nothing
"""
if len(self._netif) >= 2:
@ -866,7 +886,14 @@ class PtpNet(CoreNetwork):
)
super().attach(netif)
def data(self, message_type, lat=None, lon=None, alt=None):
def data(
self,
message_type: int,
lat: float = None,
lon: float = None,
alt: float = None,
source: str = None,
) -> NodeData:
"""
Do not generate a Node Message for point-to-point links. They are
built using a link message instead.
@ -875,12 +902,13 @@ class PtpNet(CoreNetwork):
:param float lat: latitude
:param float lon: longitude
:param float alt: altitude
:param str source: source of node data
:return: node data object
:rtype: core.emulator.data.NodeData
"""
return None
def all_link_data(self, flags):
def all_link_data(self, flags: int) -> List[LinkData]:
"""
Build CORE API TLVs for a point-to-point link. One Link message
describes this network.
@ -997,7 +1025,7 @@ class HubNode(CoreNetwork):
policy = "ACCEPT"
type = "hub"
def startup(self):
def startup(self) -> None:
"""
Startup for a hub node, that disables mac learning after normal startup.
@ -1018,8 +1046,14 @@ class WlanNode(CoreNetwork):
type = "wlan"
def __init__(
self, session, _id=None, name=None, start=True, server=None, policy=None
):
self,
session: "Session",
_id: int = None,
name: str = None,
start: bool = True,
server: "DistributedServer" = None,
policy: str = None,
) -> None:
"""
Create a WlanNode instance.
@ -1036,7 +1070,7 @@ class WlanNode(CoreNetwork):
self.model = None
self.mobility = None
def startup(self):
def startup(self) -> None:
"""
Startup for a wlan node, that disables mac learning after normal startup.
@ -1045,11 +1079,11 @@ class WlanNode(CoreNetwork):
super().startup()
self.net_client.disable_mac_learning(self.brname)
def attach(self, netif):
def attach(self, netif: CoreInterface) -> None:
"""
Attach a network interface.
:param core.nodes.interface.Veth netif: network interface
:param core.nodes.interface.CoreInterface netif: network interface
:return: nothing
"""
super().attach(netif)
@ -1061,7 +1095,7 @@ class WlanNode(CoreNetwork):
# invokes any netif.poshook
netif.setposition(x, y, z)
def setmodel(self, model, config):
def setmodel(self, model: "WirelessModelType", config: Dict[str, str]):
"""
Sets the mobility and wireless model.
@ -1082,12 +1116,12 @@ class WlanNode(CoreNetwork):
self.mobility = model(session=self.session, _id=self.id)
self.mobility.update_config(config)
def update_mobility(self, config):
def update_mobility(self, config: Dict[str, str]) -> None:
if not self.mobility:
raise ValueError(f"no mobility set to update for node({self.id})")
self.mobility.update_config(config)
def updatemodel(self, config):
def updatemodel(self, config: Dict[str, str]) -> None:
if not self.model:
raise ValueError(f"no model set to update for node({self.id})")
logging.debug(
@ -1099,7 +1133,7 @@ class WlanNode(CoreNetwork):
x, y, z = netif.node.position.get()
netif.poshook(netif, x, y, z)
def all_link_data(self, flags):
def all_link_data(self, flags: int) -> List[LinkData]:
"""
Retrieve all link data.

View file

@ -5,20 +5,31 @@ 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 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.errors import CoreCommandError, CoreError
from core.nodes.base import CoreNodeBase
from core.nodes.interface import CoreInterface
from core.nodes.base import CoreNetworkBase, CoreNodeBase
from core.nodes.interface import CoreInterface, Veth
from core.nodes.network import CoreNetwork, GreTap
if TYPE_CHECKING:
from core.emulator.session import Session
class PhysicalNode(CoreNodeBase):
def __init__(
self, session, _id=None, name=None, nodedir=None, start=True, server=None
):
self,
session,
_id: int = None,
name: str = None,
nodedir: str = None,
start: bool = True,
server: DistributedServer = None,
) -> None:
super().__init__(session, _id, name, start, server)
if not self.server:
raise CoreError("physical nodes must be assigned to a remote server")
@ -29,11 +40,11 @@ class PhysicalNode(CoreNodeBase):
if start:
self.startup()
def startup(self):
def startup(self) -> None:
with self.lock:
self.makenodedir()
def shutdown(self):
def shutdown(self) -> None:
if not self.up:
return
@ -47,7 +58,7 @@ class PhysicalNode(CoreNodeBase):
self.rmnodedir()
def termcmdstring(self, sh="/bin/sh"):
def termcmdstring(self, sh: str = "/bin/sh") -> str:
"""
Create a terminal command string.
@ -56,7 +67,7 @@ class PhysicalNode(CoreNodeBase):
"""
return sh
def sethwaddr(self, ifindex, addr):
def sethwaddr(self, ifindex: int, addr: str) -> None:
"""
Set hardware address for an interface.
@ -71,7 +82,7 @@ class PhysicalNode(CoreNodeBase):
if self.up:
self.net_client.device_mac(interface.name, addr)
def addaddr(self, ifindex, addr):
def addaddr(self, ifindex: int, addr: str) -> None:
"""
Add an address to an interface.
@ -85,9 +96,13 @@ class PhysicalNode(CoreNodeBase):
self.net_client.create_address(interface.name, addr)
interface.addaddr(addr)
def deladdr(self, ifindex, addr):
def deladdr(self, ifindex: int, addr: str) -> None:
"""
Delete an address from an interface.
:param int ifindex: index of interface to delete
:param str addr: address to delete
:return: nothing
"""
interface = self._netif[ifindex]
@ -99,7 +114,9 @@ class PhysicalNode(CoreNodeBase):
if self.up:
self.net_client.delete_address(interface.name, str(addr))
def adoptnetif(self, netif, ifindex, hwaddr, addrlist):
def adoptnetif(
self, netif: CoreInterface, ifindex: int, hwaddr: str, addrlist: List[str]
) -> None:
"""
When a link message is received linking this node to another part of
the emulation, no new interface is created; instead, adopt the
@ -127,18 +144,17 @@ class PhysicalNode(CoreNodeBase):
def linkconfig(
self,
netif,
bw=None,
delay=None,
loss=None,
duplicate=None,
jitter=None,
netif2=None,
):
netif: CoreInterface,
bw: float = None,
delay: float = None,
loss: float = None,
duplicate: float = None,
jitter: float = None,
netif2: CoreInterface = None,
) -> None:
"""
Apply tc queing disciplines using LxBrNet.linkconfig()
Apply tc queing disciplines using linkconfig.
"""
# borrow the tc qdisc commands from LxBrNet.linkconfig()
linux_bridge = CoreNetwork(session=self.session, start=False)
linux_bridge.up = True
linux_bridge.linkconfig(
@ -152,7 +168,7 @@ class PhysicalNode(CoreNodeBase):
)
del linux_bridge
def newifindex(self):
def newifindex(self) -> int:
with self.lock:
while self.ifindex in self._netif:
self.ifindex += 1
@ -160,7 +176,14 @@ class PhysicalNode(CoreNodeBase):
self.ifindex += 1
return ifindex
def newnetif(self, net=None, addrlist=None, hwaddr=None, ifindex=None, ifname=None):
def newnetif(
self,
net: Veth = None,
addrlist: List[str] = None,
hwaddr: str = None,
ifindex: int = None,
ifname: str = None,
) -> int:
logging.info("creating interface")
if not addrlist:
addrlist = []
@ -186,7 +209,7 @@ class PhysicalNode(CoreNodeBase):
self.adoptnetif(netif, ifindex, hwaddr, addrlist)
return ifindex
def privatedir(self, path):
def privatedir(self, path: str) -> None:
if path[0] != "/":
raise ValueError(f"path not fully qualified: {path}")
hostpath = os.path.join(
@ -195,21 +218,21 @@ class PhysicalNode(CoreNodeBase):
os.mkdir(hostpath)
self.mount(hostpath, path)
def mount(self, source, target):
def mount(self, source: str, target: str) -> None:
source = os.path.abspath(source)
logging.info("mounting %s at %s", source, target)
os.makedirs(target)
self.host_cmd(f"{MOUNT_BIN} --bind {source} {target}", cwd=self.nodedir)
self._mounts.append((source, target))
def umount(self, target):
def umount(self, target: str) -> None:
logging.info("unmounting '%s'", target)
try:
self.host_cmd(f"{UMOUNT_BIN} -l {target}", cwd=self.nodedir)
except CoreCommandError:
logging.exception("unmounting failed for %s", target)
def opennodefile(self, filename, mode="w"):
def opennodefile(self, filename: str, mode: str = "w") -> IO:
dirname, basename = os.path.split(filename)
if not basename:
raise ValueError("no basename for filename: " + filename)
@ -225,13 +248,13 @@ class PhysicalNode(CoreNodeBase):
hostfilename = os.path.join(dirname, basename)
return open(hostfilename, mode)
def nodefile(self, filename, contents, mode=0o644):
def nodefile(self, filename: str, contents: str, mode: int = 0o644) -> None:
with self.opennodefile(filename, "w") as node_file:
node_file.write(contents)
os.chmod(node_file.name, mode)
logging.info("created nodefile: '%s'; mode: 0%o", node_file.name, mode)
def cmd(self, args, wait=True):
def cmd(self, args: str, wait: bool = True, shell: bool = False) -> str:
return self.host_cmd(args, wait=wait)
@ -244,7 +267,15 @@ class Rj45Node(CoreNodeBase, CoreInterface):
apitype = NodeTypes.RJ45.value
type = "rj45"
def __init__(self, session, _id=None, name=None, mtu=1500, start=True, server=None):
def __init__(
self,
session: "Session",
_id: int = None,
name: str = None,
mtu: int = 1500,
start: bool = True,
server: DistributedServer = None,
) -> None:
"""
Create an RJ45Node instance.
@ -270,7 +301,7 @@ class Rj45Node(CoreNodeBase, CoreInterface):
if start:
self.startup()
def startup(self):
def startup(self) -> None:
"""
Set the interface in the up state.
@ -282,7 +313,7 @@ class Rj45Node(CoreNodeBase, CoreInterface):
self.net_client.device_up(self.localname)
self.up = True
def shutdown(self):
def shutdown(self) -> None:
"""
Bring the interface down. Remove any addresses and queuing
disciplines.
@ -304,18 +335,18 @@ class Rj45Node(CoreNodeBase, CoreInterface):
# TODO: issue in that both classes inherited from provide the same method with
# different signatures
def attachnet(self, net):
def attachnet(self, net: CoreNetworkBase) -> None:
"""
Attach a network.
:param core.coreobj.PyCoreNet net: network to attach
:param core.nodes.base.CoreNetworkBase 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):
def detachnet(self) -> None:
"""
Detach a network.
@ -323,7 +354,14 @@ class Rj45Node(CoreNodeBase, CoreInterface):
"""
CoreInterface.detachnet(self)
def newnetif(self, net=None, addrlist=None, hwaddr=None, ifindex=None, ifname=None):
def newnetif(
self,
net: CoreNetworkBase = None,
addrlist: List[str] = None,
hwaddr: str = None,
ifindex: int = None,
ifname: str = None,
) -> int:
"""
This is called when linking with another node. Since this node
represents an interface, we do not create another object here,
@ -359,7 +397,7 @@ class Rj45Node(CoreNodeBase, CoreInterface):
return ifindex
def delnetif(self, ifindex):
def delnetif(self, ifindex: int) -> None:
"""
Delete a network interface.
@ -376,7 +414,9 @@ class Rj45Node(CoreNodeBase, CoreInterface):
else:
raise ValueError(f"ifindex {ifindex} does not exist")
def netif(self, ifindex, net=None):
def netif(
self, ifindex: int, net: CoreNetworkBase = None
) -> Optional[CoreInterface]:
"""
This object is considered the network interface, so we only
return self here. This keeps the RJ45Node compatible with
@ -398,20 +438,20 @@ class Rj45Node(CoreNodeBase, CoreInterface):
return None
def getifindex(self, netif):
def getifindex(self, netif: CoreInterface) -> Optional[int]:
"""
Retrieve network interface index.
:param core.nodes.interface.CoreInterface netif: network interface to retrieve index for
:param core.nodes.interface.CoreInterface netif: network interface to retrieve
index for
:return: interface index, None otherwise
:rtype: int
"""
if netif != self:
return None
return self.ifindex
def addaddr(self, addr):
def addaddr(self, addr: str) -> None:
"""
Add address to to network interface.
@ -424,7 +464,7 @@ class Rj45Node(CoreNodeBase, CoreInterface):
self.net_client.create_address(self.name, addr)
CoreInterface.addaddr(self, addr)
def deladdr(self, addr):
def deladdr(self, addr: str) -> None:
"""
Delete address from network interface.
@ -434,10 +474,9 @@ class Rj45Node(CoreNodeBase, CoreInterface):
"""
if self.up:
self.net_client.delete_address(self.name, str(addr))
CoreInterface.deladdr(self, addr)
def savestate(self):
def savestate(self) -> None:
"""
Save the addresses and other interface state before using the
interface for emulation purposes. TODO: save/restore the PROMISC flag
@ -464,7 +503,7 @@ class Rj45Node(CoreNodeBase, CoreInterface):
continue
self.old_addrs.append((items[1], None))
def restorestate(self):
def restorestate(self) -> None:
"""
Restore the addresses and other interface state after using it.
@ -482,7 +521,7 @@ class Rj45Node(CoreNodeBase, CoreInterface):
if self.old_up:
self.net_client.device_up(self.localname)
def setposition(self, x=None, y=None, z=None):
def setposition(self, x: float = None, y: float = None, z: float = None) -> bool:
"""
Uses setposition from both parent classes.
@ -496,7 +535,7 @@ class Rj45Node(CoreNodeBase, CoreInterface):
CoreInterface.setposition(self, x, y, z)
return result
def termcmdstring(self, sh):
def termcmdstring(self, sh: str) -> str:
"""
Create a terminal command string.

View file

@ -4,17 +4,19 @@ sdt.py: Scripted Display Tool (SDT3D) helper
import logging
import socket
from typing import TYPE_CHECKING, Any, Optional
from urllib.parse import urlparse
from core import constants
from core.api.tlv.coreapi import CoreLinkMessage, CoreMessage, CoreNodeMessage
from core.constants import CORE_DATA_DIR
from core.emane.nodes import EmaneNet
from core.emulator.data import LinkData, NodeData
from core.emulator.enumerations import (
EventTypes,
LinkTlvs,
LinkTypes,
MessageFlags,
MessageTypes,
NodeTlvs,
NodeTypes,
)
@ -22,6 +24,9 @@ from core.errors import CoreError
from core.nodes.base import CoreNetworkBase, NodeBase
from core.nodes.network import WlanNode
if TYPE_CHECKING:
from core.emulator.session import Session
# TODO: A named tuple may be more appropriate, than abusing a class dict like this
class Bunch:
@ -29,7 +34,7 @@ class Bunch:
Helper class for recording a collection of attributes.
"""
def __init__(self, **kwargs):
def __init__(self, **kwargs: Any) -> None:
"""
Create a Bunch instance.
@ -62,7 +67,7 @@ class Sdt:
("tunnel", "tunnel.gif"),
]
def __init__(self, session):
def __init__(self, session: "Session") -> None:
"""
Creates a Sdt instance.
@ -83,7 +88,7 @@ class Sdt:
# add handler for link updates
self.session.link_handlers.append(self.handle_link_update)
def handle_node_update(self, node_data):
def handle_node_update(self, node_data: NodeData) -> None:
"""
Handler for node updates, specifically for updating their location.
@ -108,7 +113,7 @@ class Sdt:
# TODO: z is not currently supported by node messages
self.updatenode(node_data.id, 0, x, y, 0)
def handle_link_update(self, link_data):
def handle_link_update(self, link_data: LinkData) -> None:
"""
Handler for link updates, checking for wireless link/unlink messages.
@ -123,7 +128,7 @@ class Sdt:
wireless=True,
)
def is_enabled(self):
def is_enabled(self) -> bool:
"""
Check for "enablesdt" session option. Return False by default if
the option is missing.
@ -133,7 +138,7 @@ class Sdt:
"""
return self.session.options.get_config("enablesdt") == "1"
def seturl(self):
def seturl(self) -> None:
"""
Read "sdturl" from session options, or use the default value.
Set self.url, self.address, self.protocol
@ -147,7 +152,7 @@ class Sdt:
self.address = (self.url.hostname, self.url.port)
self.protocol = self.url.scheme
def connect(self, flags=0):
def connect(self, flags: int = 0) -> bool:
"""
Connect to the SDT address/port if enabled.
@ -185,7 +190,7 @@ class Sdt:
return True
def initialize(self):
def initialize(self) -> bool:
"""
Load icon sprites, and fly to the reference point location on
the virtual globe.
@ -202,7 +207,7 @@ class Sdt:
lat, long = self.session.location.refgeo[:2]
return self.cmd(f"flyto {long:.6f},{lat:.6f},{self.DEFAULT_ALT}")
def disconnect(self):
def disconnect(self) -> None:
"""
Disconnect from SDT.
@ -218,7 +223,7 @@ class Sdt:
self.connected = False
def shutdown(self):
def shutdown(self) -> None:
"""
Invoked from Session.shutdown() and Session.checkshutdown().
@ -228,7 +233,7 @@ class Sdt:
self.disconnect()
self.showerror = True
def cmd(self, cmdstr):
def cmd(self, cmdstr: str) -> bool:
"""
Send an SDT command over a UDP socket. socket.sendall() is used
as opposed to socket.sendto() because an exception is raised when
@ -250,7 +255,17 @@ class Sdt:
self.connected = False
return False
def updatenode(self, nodenum, flags, x, y, z, name=None, node_type=None, icon=None):
def updatenode(
self,
nodenum: int,
flags: int,
x: Optional[float],
y: Optional[float],
z: Optional[float],
name: str = None,
node_type: str = None,
icon: str = None,
) -> None:
"""
Node is updated from a Node Message or mobility script.
@ -283,13 +298,13 @@ class Sdt:
else:
self.cmd(f"node {nodenum} {pos}")
def updatenodegeo(self, nodenum, lat, long, alt):
def updatenodegeo(self, nodenum: int, lat: float, lon: float, alt: float) -> None:
"""
Node is updated upon receiving an EMANE Location Event.
:param int nodenum: node id to update geospatial for
:param lat: latitude
:param long: longitude
:param lon: longitude
:param alt: altitude
:return: nothing
"""
@ -297,10 +312,12 @@ class Sdt:
# TODO: received Node Message with lat/long/alt.
if not self.connect():
return
pos = f"pos {long:.6f},{lat:.6f},{alt:.6f}"
pos = f"pos {lon:.6f},{lat:.6f},{alt:.6f}"
self.cmd(f"node {nodenum} {pos}")
def updatelink(self, node1num, node2num, flags, wireless=False):
def updatelink(
self, node1num: int, node2num: int, flags: int, wireless: bool = False
) -> None:
"""
Link is updated from a Link Message or by a wireless model.
@ -323,7 +340,7 @@ class Sdt:
attr = " line red,2"
self.cmd(f"link {node1num},{node2num}{attr}")
def sendobjs(self):
def sendobjs(self) -> None:
"""
Session has already started, and the SDT3D GUI later connects.
Send all node and link objects for display. Otherwise, nodes and
@ -379,21 +396,21 @@ class Sdt:
for n2num, wireless_link in r.links:
self.updatelink(n1num, n2num, MessageFlags.ADD.value, wireless_link)
def handle_distributed(self, message):
def handle_distributed(self, message: CoreMessage) -> None:
"""
Broker handler for processing CORE API messages as they are
received. This is used to snoop the Node messages and update
node positions.
:param message: message to handle
:return: replies
:return: nothing
"""
if message.message_type == MessageTypes.LINK.value:
return self.handlelinkmsg(message)
elif message.message_type == MessageTypes.NODE.value:
return self.handlenodemsg(message)
if isinstance(message, CoreLinkMessage):
self.handlelinkmsg(message)
elif isinstance(message, CoreNodeMessage):
self.handlenodemsg(message)
def handlenodemsg(self, msg):
def handlenodemsg(self, msg: CoreNodeMessage) -> None:
"""
Process a Node Message to add/delete or move a node on
the SDT display. Node properties are found in a session or
@ -405,7 +422,7 @@ class Sdt:
# for distributed sessions to work properly, the SDT option should be
# enabled prior to starting the session
if not self.is_enabled():
return False
return
# node.(_id, type, icon, name) are used.
nodenum = msg.get_tlv(NodeTlvs.NUMBER.value)
if not nodenum:
@ -461,7 +478,7 @@ class Sdt:
remote.pos = (x, y, z)
self.updatenode(nodenum, msg.flags, x, y, z, name, nodetype, icon)
def handlelinkmsg(self, msg):
def handlelinkmsg(self, msg: CoreLinkMessage) -> None:
"""
Process a Link Message to add/remove links on the SDT display.
Links are recorded in the remotes[nodenum1].links set for updating
@ -471,7 +488,7 @@ class Sdt:
:return: nothing
"""
if not self.is_enabled():
return False
return
nodenum1 = msg.get_tlv(LinkTlvs.N1_NUMBER.value)
nodenum2 = msg.get_tlv(LinkTlvs.N2_NUMBER.value)
link_msg_type = msg.get_tlv(LinkTlvs.TYPE.value)
@ -488,7 +505,7 @@ class Sdt:
r.links.add((nodenum2, wl))
self.updatelink(nodenum1, nodenum2, msg.flags, wireless=wl)
def wlancheck(self, nodenum):
def wlancheck(self, nodenum: int) -> bool:
"""
Helper returns True if a node number corresponds to a WLAN or EMANE node.

View file

@ -10,12 +10,17 @@ services.
import enum
import logging
import time
from typing import TYPE_CHECKING, Iterable, List, Tuple, Type
from core import utils
from core.constants import which
from core.emulator.data import FileData
from core.emulator.enumerations import ExceptionLevels, MessageFlags, RegisterTlvs
from core.errors import CoreCommandError
from core.nodes.base import CoreNode
if TYPE_CHECKING:
from core.emulator.session import Session
class ServiceBootError(Exception):
@ -34,7 +39,7 @@ class ServiceDependencies:
that all services will be booted and that all dependencies exist within the services provided.
"""
def __init__(self, services):
def __init__(self, services: List["CoreService"]) -> None:
# helpers to check validity
self.dependents = {}
self.booted = set()
@ -50,7 +55,7 @@ class ServiceDependencies:
self.visited = set()
self.visiting = set()
def boot_paths(self):
def boot_paths(self) -> List[List["CoreService"]]:
"""
Generates the boot paths for the services provided to the class.
@ -78,17 +83,17 @@ class ServiceDependencies:
return paths
def _reset(self):
def _reset(self) -> None:
self.path = []
self.visited.clear()
self.visiting.clear()
def _start(self, service):
def _start(self, service: "CoreService") -> List["CoreService"]:
logging.debug("starting service dependency check: %s", service.name)
self._reset()
return self._visit(service)
def _visit(self, current_service):
def _visit(self, current_service: "CoreService") -> List["CoreService"]:
logging.debug("visiting service(%s): %s", current_service.name, self.path)
self.visited.add(current_service.name)
self.visiting.add(current_service.name)
@ -139,7 +144,7 @@ class ServiceShim:
]
@classmethod
def tovaluelist(cls, node, service):
def tovaluelist(cls, node: CoreNode, service: "CoreService") -> str:
"""
Convert service properties into a string list of key=value pairs,
separated by "|".
@ -168,7 +173,7 @@ class ServiceShim:
return "|".join(vals)
@classmethod
def fromvaluelist(cls, service, values):
def fromvaluelist(cls, service: "CoreService", values: None):
"""
Convert list of values into properties for this instantiated
(customized) service.
@ -186,7 +191,7 @@ class ServiceShim:
logging.exception("error indexing into key")
@classmethod
def setvalue(cls, service, key, value):
def setvalue(cls, service: "CoreService", key: str, value: str) -> None:
"""
Set values for this service.
@ -220,7 +225,7 @@ class ServiceShim:
service.meta = value
@classmethod
def servicesfromopaque(cls, opaque):
def servicesfromopaque(cls, opaque: str) -> List[str]:
"""
Build a list of services from an opaque data string.
@ -242,7 +247,7 @@ class ServiceManager:
services = {}
@classmethod
def add(cls, service):
def add(cls, service: "CoreService") -> None:
"""
Add a service to manager.
@ -272,7 +277,7 @@ class ServiceManager:
cls.services[name] = service
@classmethod
def get(cls, name):
def get(cls, name: str) -> Type["CoreService"]:
"""
Retrieve a service from the manager.
@ -283,7 +288,7 @@ class ServiceManager:
return cls.services.get(name)
@classmethod
def add_services(cls, path):
def add_services(cls, path: str) -> List[str]:
"""
Method for retrieving all CoreServices from a given path.
@ -317,7 +322,7 @@ class CoreServices:
name = "services"
config_type = RegisterTlvs.UTILITY.value
def __init__(self, session):
def __init__(self, session: "Session") -> None:
"""
Creates a CoreServices instance.
@ -329,13 +334,13 @@ class CoreServices:
# dict of node ids to dict of custom services by name
self.custom_services = {}
def reset(self):
def reset(self) -> None:
"""
Called when config message with reset flag is received
"""
self.custom_services.clear()
def get_default_services(self, node_type):
def get_default_services(self, node_type: str) -> List[Type["CoreService"]]:
"""
Get the list of default services that should be enabled for a
node for the given node type.
@ -356,16 +361,18 @@ class CoreServices:
results.append(service)
return results
def get_service(self, node_id, service_name, default_service=False):
def get_service(
self, node_id: int, service_name: str, default_service: bool = False
) -> "CoreService":
"""
Get any custom service configured for the given node that matches the specified service name.
If no custom service is found, return the specified service.
Get any custom service configured for the given node that matches the specified
service name. If no custom service is found, return the specified service.
:param int node_id: object id to get service from
:param str service_name: name of service to retrieve
:param bool default_service: True to return default service when custom does not exist, False returns None
:param bool default_service: True to return default service when custom does
not exist, False returns None
:return: custom service from the node
:rtype: CoreService
"""
node_services = self.custom_services.setdefault(node_id, {})
default = None
@ -373,7 +380,7 @@ class CoreServices:
default = ServiceManager.get(service_name)
return node_services.get(service_name, default)
def set_service(self, node_id, service_name):
def set_service(self, node_id: int, service_name: str) -> None:
"""
Store service customizations in an instantiated service object
using a list of values that came from a config message.
@ -392,7 +399,9 @@ class CoreServices:
node_services = self.custom_services.setdefault(node_id, {})
node_services[service.name] = service
def add_services(self, node, node_type, services=None):
def add_services(
self, node: CoreNode, node_type: str, services: List[str] = None
) -> None:
"""
Add services to a node.
@ -417,10 +426,10 @@ class CoreServices:
continue
node.services.append(service)
def all_configs(self):
def all_configs(self) -> List[Tuple[int, Type["CoreService"]]]:
"""
Return (node_id, service) tuples for all stored configs. Used when reconnecting to a
session or opening XML.
Return (node_id, service) tuples for all stored configs. Used when reconnecting
to a session or opening XML.
:return: list of tuples of node ids and services
:rtype: list[tuple]
@ -433,7 +442,7 @@ class CoreServices:
configs.append((node_id, service))
return configs
def all_files(self, service):
def all_files(self, service: "CoreService") -> List[Tuple[str, str]]:
"""
Return all customized files stored with a service.
Used when reconnecting to a session or opening XML.
@ -454,7 +463,7 @@ class CoreServices:
return files
def boot_services(self, node):
def boot_services(self, node: CoreNode) -> None:
"""
Start all services on a node.
@ -470,7 +479,7 @@ class CoreServices:
if exceptions:
raise ServiceBootError(*exceptions)
def _start_boot_paths(self, node, boot_path):
def _start_boot_paths(self, node: CoreNode, boot_path: List["CoreService"]) -> None:
"""
Start all service boot paths found, based on dependencies.
@ -491,7 +500,7 @@ class CoreServices:
logging.exception("exception booting service: %s", service.name)
raise
def boot_service(self, node, service):
def boot_service(self, node: CoreNode, service: "CoreService") -> None:
"""
Start a service on a node. Create private dirs, generate config
files, and execute startup commands.
@ -555,7 +564,7 @@ class CoreServices:
"node(%s) service(%s) failed validation" % (node.name, service.name)
)
def copy_service_file(self, node, filename, cfg):
def copy_service_file(self, node: CoreNode, filename: str, cfg: str) -> bool:
"""
Given a configured service filename and config, determine if the
config references an existing file that should be copied.
@ -576,7 +585,7 @@ class CoreServices:
return True
return False
def validate_service(self, node, service):
def validate_service(self, node: CoreNode, service: "CoreService") -> int:
"""
Run the validation command(s) for a service.
@ -605,7 +614,7 @@ class CoreServices:
return status
def stop_services(self, node):
def stop_services(self, node: CoreNode) -> None:
"""
Stop all services on a node.
@ -615,14 +624,13 @@ class CoreServices:
for service in node.services:
self.stop_service(node, service)
def stop_service(self, node, service):
def stop_service(self, node: CoreNode, service: "CoreService") -> int:
"""
Stop a service on a node.
:param core.nodes.base.CoreNode node: node to stop a service on
:param CoreService service: service to stop
:return: status for stopping the services
:rtype: str
"""
status = 0
for args in service.shutdown:
@ -639,7 +647,7 @@ class CoreServices:
status = -1
return status
def get_service_file(self, node, service_name, filename):
def get_service_file(self, node: CoreNode, service_name: str, filename: str) -> str:
"""
Send a File Message when the GUI has requested a service file.
The file data is either auto-generated or comes from an existing config.
@ -681,7 +689,9 @@ class CoreServices:
data=data,
)
def set_service_file(self, node_id, service_name, file_name, data):
def set_service_file(
self, node_id: int, service_name: str, file_name: str, data: str
) -> None:
"""
Receive a File Message from the GUI and store the customized file
in the service config. The filename must match one from the list of
@ -713,7 +723,9 @@ class CoreServices:
# set custom service file data
service.config_data[file_name] = data
def startup_service(self, node, service, wait=False):
def startup_service(
self, node: CoreNode, service: "CoreService", wait: bool = False
) -> int:
"""
Startup a node service.
@ -737,7 +749,7 @@ class CoreServices:
status = -1
return status
def create_service_files(self, node, service):
def create_service_files(self, node: CoreNode, service: "CoreService") -> None:
"""
Creates node service files.
@ -771,7 +783,7 @@ class CoreServices:
node.nodefile(file_name, cfg)
def service_reconfigure(self, node, service):
def service_reconfigure(self, node: CoreNode, service: "CoreService") -> None:
"""
Reconfigure a node service.
@ -846,7 +858,7 @@ class CoreService:
custom = False
custom_needed = False
def __init__(self):
def __init__(self) -> None:
"""
Services are not necessarily instantiated. Classmethods may be used
against their config. Services are instantiated when a custom
@ -856,11 +868,11 @@ class CoreService:
self.config_data = self.__class__.config_data.copy()
@classmethod
def on_load(cls):
def on_load(cls) -> None:
pass
@classmethod
def get_configs(cls, node):
def get_configs(cls, node: CoreNode) -> Iterable[str]:
"""
Return the tuple of configuration file filenames. This default method
returns the cls._configs tuple, but this method may be overriden to
@ -873,7 +885,7 @@ class CoreService:
return cls.configs
@classmethod
def generate_config(cls, node, filename):
def generate_config(cls, node: CoreNode, filename: str) -> None:
"""
Generate configuration file given a node object. The filename is
provided to allow for multiple config files.
@ -887,7 +899,7 @@ class CoreService:
raise NotImplementedError
@classmethod
def get_startup(cls, node):
def get_startup(cls, node: CoreNode) -> Iterable[str]:
"""
Return the tuple of startup commands. This default method
returns the cls.startup tuple, but this method may be
@ -901,7 +913,7 @@ class CoreService:
return cls.startup
@classmethod
def get_validate(cls, node):
def get_validate(cls, node: CoreNode) -> Iterable[str]:
"""
Return the tuple of validate commands. This default method
returns the cls.validate tuple, but this method may be

View file

@ -15,15 +15,36 @@ import random
import shlex
import sys
from subprocess import PIPE, STDOUT, Popen
from typing import (
TYPE_CHECKING,
Any,
Callable,
Dict,
Generic,
Iterable,
List,
Optional,
Tuple,
Type,
TypeVar,
Union,
)
import netaddr
from core.errors import CoreCommandError, CoreError
if TYPE_CHECKING:
from core.emulator.session import Session
from core.nodes.base import CoreNode
T = TypeVar("T")
DEVNULL = open(os.devnull, "wb")
def execute_file(path, exec_globals=None, exec_locals=None):
def execute_file(
path: str, exec_globals: Dict[str, str] = None, exec_locals: Dict[str, str] = None
) -> None:
"""
Provides an alternative way to run execfile to be compatible for
both python2/3.
@ -41,7 +62,7 @@ def execute_file(path, exec_globals=None, exec_locals=None):
exec(data, exec_globals, exec_locals)
def hashkey(value):
def hashkey(value: Union[str, int]) -> int:
"""
Provide a consistent hash that can be used in place
of the builtin hash, that no longer behaves consistently
@ -57,7 +78,7 @@ def hashkey(value):
return int(hashlib.sha256(value).hexdigest(), 16)
def _detach_init():
def _detach_init() -> None:
"""
Fork a child process and exit.
@ -69,7 +90,7 @@ def _detach_init():
os.setsid()
def _valid_module(path, file_name):
def _valid_module(path: str, file_name: str) -> bool:
"""
Check if file is a valid python module.
@ -91,7 +112,7 @@ def _valid_module(path, file_name):
return True
def _is_class(module, member, clazz):
def _is_class(module: Any, member: Type, clazz: Type) -> bool:
"""
Validates if a module member is a class and an instance of a CoreService.
@ -113,7 +134,7 @@ def _is_class(module, member, clazz):
return True
def close_onexec(fd):
def close_onexec(fd: int) -> None:
"""
Close on execution of a shell process.
@ -124,7 +145,7 @@ def close_onexec(fd):
fcntl.fcntl(fd, fcntl.F_SETFD, fdflags | fcntl.FD_CLOEXEC)
def which(command, required):
def which(command: str, required: bool) -> str:
"""
Find location of desired executable within current PATH.
@ -146,7 +167,7 @@ def which(command, required):
return found_path
def make_tuple(obj):
def make_tuple(obj: Generic[T]) -> Tuple[T]:
"""
Create a tuple from an object, or return the object itself.
@ -160,7 +181,7 @@ def make_tuple(obj):
return (obj,)
def make_tuple_fromstr(s, value_type):
def make_tuple_fromstr(s: str, value_type: Callable[[str], T]) -> Tuple[T]:
"""
Create a tuple from a string.
@ -179,11 +200,11 @@ def make_tuple_fromstr(s, value_type):
return tuple(value_type(i) for i in values)
def mute_detach(args, **kwargs):
def mute_detach(args: str, **kwargs: Dict[str, Any]) -> int:
"""
Run a muted detached process by forking it.
:param list[str]|str args: arguments for the command
:param str args: arguments for the command
:param dict kwargs: keyword arguments for the command
:return: process id of the command
:rtype: int
@ -195,7 +216,13 @@ def mute_detach(args, **kwargs):
return Popen(args, **kwargs).pid
def cmd(args, env=None, cwd=None, wait=True, shell=False):
def cmd(
args: str,
env: Dict[str, str] = None,
cwd: str = None,
wait: bool = True,
shell: bool = False,
) -> str:
"""
Execute a command on the host and return a tuple containing the exit status and
result string. stderr output is folded into the stdout result string.
@ -227,7 +254,7 @@ def cmd(args, env=None, cwd=None, wait=True, shell=False):
raise CoreCommandError(-1, args)
def file_munge(pathname, header, text):
def file_munge(pathname: str, header: str, text: str) -> None:
"""
Insert text at the end of a file, surrounded by header comments.
@ -245,7 +272,7 @@ def file_munge(pathname, header, text):
append_file.write(f"# END {header}\n")
def file_demunge(pathname, header):
def file_demunge(pathname: str, header: str) -> None:
"""
Remove text that was inserted in a file surrounded by header comments.
@ -273,7 +300,9 @@ def file_demunge(pathname, header):
write_file.write("".join(lines))
def expand_corepath(pathname, session=None, node=None):
def expand_corepath(
pathname: str, session: "Session" = None, node: "CoreNode" = None
) -> str:
"""
Expand a file path given session information.
@ -296,7 +325,7 @@ def expand_corepath(pathname, session=None, node=None):
return pathname
def sysctl_devname(devname):
def sysctl_devname(devname: str) -> Optional[str]:
"""
Translate a device name to the name used with sysctl.
@ -309,7 +338,7 @@ def sysctl_devname(devname):
return devname.replace(".", "/")
def load_config(filename, d):
def load_config(filename: str, d: Dict[str, str]) -> None:
"""
Read key=value pairs from a file, into a dict. Skip comments; strip newline
characters and spacing.
@ -332,7 +361,7 @@ def load_config(filename, d):
logging.exception("error reading file to dict: %s", filename)
def load_classes(path, clazz):
def load_classes(path: str, clazz: Generic[T]) -> T:
"""
Dynamically load classes for use within CORE.
@ -375,7 +404,7 @@ def load_classes(path, clazz):
return classes
def load_logging_config(config_path):
def load_logging_config(config_path: str) -> None:
"""
Load CORE logging configuration file.
@ -387,7 +416,9 @@ def load_logging_config(config_path):
logging.config.dictConfig(log_config)
def threadpool(funcs, workers=10):
def threadpool(
funcs: List[Tuple[Callable, Iterable[Any], Dict[Any, Any]]], workers: int = 10
) -> Tuple[List[Any], List[Exception]]:
"""
Run provided functions, arguments, and keywords within a threadpool
collecting results and exceptions.
@ -413,7 +444,7 @@ def threadpool(funcs, workers=10):
return results, exceptions
def random_mac():
def random_mac() -> str:
"""
Create a random mac address using Xen OID 00:16:3E.
@ -427,7 +458,7 @@ def random_mac():
return str(mac)
def validate_mac(value):
def validate_mac(value: str) -> str:
"""
Validate mac and return unix formatted version.
@ -443,7 +474,7 @@ def validate_mac(value):
raise CoreError(f"invalid mac address {value}: {e}")
def validate_ip(value):
def validate_ip(value: str) -> str:
"""
Validate ip address with prefix and return formatted version.

View file

@ -1,17 +1,30 @@
import logging
from typing import TYPE_CHECKING, Any, Dict, Generic, List, Optional, Type, TypeVar
from lxml import etree
import core.nodes.base
import core.nodes.physical
from core.emane.nodes import EmaneNet
from core.emulator.data import LinkData
from core.emulator.emudata import InterfaceData, LinkOptions, NodeOptions
from core.emulator.enumerations import NodeTypes
from core.nodes.base import CoreNetworkBase
from core.nodes.base import CoreNetworkBase, NodeBase
from core.nodes.network import CtrlNet
from core.services.coreservices import CoreService
if TYPE_CHECKING:
from core.emane.emanemanager import EmaneGlobalModel
from core.emane.emanemodel import EmaneModel
from core.emulator.session import Session
EmaneModelType = Type[EmaneModel]
T = TypeVar("T")
def write_xml_file(xml_element, file_path, doctype=None):
def write_xml_file(
xml_element: etree.Element, file_path: str, doctype: str = None
) -> None:
xml_data = etree.tostring(
xml_element,
xml_declaration=True,
@ -23,27 +36,27 @@ def write_xml_file(xml_element, file_path, doctype=None):
xml_file.write(xml_data)
def get_type(element, name, _type):
def get_type(element: etree.Element, name: str, _type: Generic[T]) -> Optional[T]:
value = element.get(name)
if value is not None:
value = _type(value)
return value
def get_float(element, name):
def get_float(element: etree.Element, name: str) -> float:
return get_type(element, name, float)
def get_int(element, name):
def get_int(element: etree.Element, name: str) -> int:
return get_type(element, name, int)
def add_attribute(element, name, value):
def add_attribute(element: etree.Element, name: str, value: Any) -> None:
if value is not None:
element.set(name, str(value))
def create_interface_data(interface_element):
def create_interface_data(interface_element: etree.Element) -> InterfaceData:
interface_id = int(interface_element.get("id"))
name = interface_element.get("name")
mac = interface_element.get("mac")
@ -54,7 +67,9 @@ def create_interface_data(interface_element):
return InterfaceData(interface_id, name, mac, ip4, ip4_mask, ip6, ip6_mask)
def create_emane_config(node_id, emane_config, config):
def create_emane_config(
node_id: int, emane_config: "EmaneGlobalModel", config: Dict[str, str]
) -> etree.Element:
emane_configuration = etree.Element("emane_configuration")
add_attribute(emane_configuration, "node", node_id)
add_attribute(emane_configuration, "model", "emane")
@ -72,7 +87,9 @@ def create_emane_config(node_id, emane_config, config):
return emane_configuration
def create_emane_model_config(node_id, model, config):
def create_emane_model_config(
node_id: int, model: "EmaneModelType", config: Dict[str, str]
) -> etree.Element:
emane_element = etree.Element("emane_configuration")
add_attribute(emane_element, "node", node_id)
add_attribute(emane_element, "model", model.name)
@ -95,14 +112,14 @@ def create_emane_model_config(node_id, model, config):
return emane_element
def add_configuration(parent, name, value):
def add_configuration(parent: etree.Element, name: str, value: str) -> None:
config_element = etree.SubElement(parent, "configuration")
add_attribute(config_element, "name", name)
add_attribute(config_element, "value", value)
class NodeElement:
def __init__(self, session, node, element_name):
def __init__(self, session: "Session", node: NodeBase, element_name: str) -> None:
self.session = session
self.node = node
self.element = etree.Element(element_name)
@ -112,7 +129,7 @@ class NodeElement:
add_attribute(self.element, "canvas", node.canvas)
self.add_position()
def add_position(self):
def add_position(self) -> None:
x = self.node.position.x
y = self.node.position.y
z = self.node.position.z
@ -129,7 +146,7 @@ class NodeElement:
class ServiceElement:
def __init__(self, service):
def __init__(self, service: Type[CoreService]) -> None:
self.service = service
self.element = etree.Element("service")
add_attribute(self.element, "name", service.name)
@ -139,7 +156,7 @@ class ServiceElement:
self.add_shutdown()
self.add_files()
def add_directories(self):
def add_directories(self) -> None:
# get custom directories
directories = etree.Element("directories")
for directory in self.service.dirs:
@ -149,7 +166,7 @@ class ServiceElement:
if directories.getchildren():
self.element.append(directories)
def add_files(self):
def add_files(self) -> None:
# get custom files
file_elements = etree.Element("files")
for file_name in self.service.config_data:
@ -161,7 +178,7 @@ class ServiceElement:
if file_elements.getchildren():
self.element.append(file_elements)
def add_startup(self):
def add_startup(self) -> None:
# get custom startup
startup_elements = etree.Element("startups")
for startup in self.service.startup:
@ -171,7 +188,7 @@ class ServiceElement:
if startup_elements.getchildren():
self.element.append(startup_elements)
def add_validate(self):
def add_validate(self) -> None:
# get custom validate
validate_elements = etree.Element("validates")
for validate in self.service.validate:
@ -181,7 +198,7 @@ class ServiceElement:
if validate_elements.getchildren():
self.element.append(validate_elements)
def add_shutdown(self):
def add_shutdown(self) -> None:
# get custom shutdown
shutdown_elements = etree.Element("shutdowns")
for shutdown in self.service.shutdown:
@ -193,12 +210,12 @@ class ServiceElement:
class DeviceElement(NodeElement):
def __init__(self, session, node):
def __init__(self, session: "Session", node: NodeBase) -> None:
super().__init__(session, node, "device")
add_attribute(self.element, "type", node.type)
self.add_services()
def add_services(self):
def add_services(self) -> None:
service_elements = etree.Element("services")
for service in self.node.services:
etree.SubElement(service_elements, "service", name=service.name)
@ -208,7 +225,7 @@ class DeviceElement(NodeElement):
class NetworkElement(NodeElement):
def __init__(self, session, node):
def __init__(self, session: "Session", node: NodeBase) -> None:
super().__init__(session, node, "network")
model = getattr(self.node, "model", None)
if model:
@ -221,7 +238,7 @@ class NetworkElement(NodeElement):
add_attribute(self.element, "grekey", grekey)
self.add_type()
def add_type(self):
def add_type(self) -> None:
if self.node.apitype:
node_type = NodeTypes(self.node.apitype).name
else:
@ -230,14 +247,14 @@ class NetworkElement(NodeElement):
class CoreXmlWriter:
def __init__(self, session):
def __init__(self, session: "Session") -> None:
self.session = session
self.scenario = etree.Element("scenario")
self.networks = None
self.devices = None
self.write_session()
def write_session(self):
def write_session(self) -> None:
# generate xml content
links = self.write_nodes()
self.write_links(links)
@ -250,7 +267,7 @@ class CoreXmlWriter:
self.write_session_metadata()
self.write_default_services()
def write(self, file_name):
def write(self, file_name: str) -> None:
self.scenario.set("name", file_name)
# write out generated xml
@ -259,7 +276,7 @@ class CoreXmlWriter:
file_name, xml_declaration=True, pretty_print=True, encoding="UTF-8"
)
def write_session_origin(self):
def write_session_origin(self) -> None:
# origin: geolocation of cartesian coordinate 0,0,0
lat, lon, alt = self.session.location.refgeo
origin = etree.Element("session_origin")
@ -279,7 +296,7 @@ class CoreXmlWriter:
add_attribute(origin, "y", y)
add_attribute(origin, "z", z)
def write_session_hooks(self):
def write_session_hooks(self) -> None:
# hook scripts
hooks = etree.Element("session_hooks")
for state in sorted(self.session._hooks.keys()):
@ -292,7 +309,7 @@ class CoreXmlWriter:
if hooks.getchildren():
self.scenario.append(hooks)
def write_session_options(self):
def write_session_options(self) -> None:
option_elements = etree.Element("session_options")
options_config = self.session.options.get_configs()
if not options_config:
@ -307,7 +324,7 @@ class CoreXmlWriter:
if option_elements.getchildren():
self.scenario.append(option_elements)
def write_session_metadata(self):
def write_session_metadata(self) -> None:
# metadata
metadata_elements = etree.Element("session_metadata")
config = self.session.metadata
@ -321,7 +338,7 @@ class CoreXmlWriter:
if metadata_elements.getchildren():
self.scenario.append(metadata_elements)
def write_emane_configs(self):
def write_emane_configs(self) -> None:
emane_configurations = etree.Element("emane_configurations")
for node_id in self.session.emane.nodes():
all_configs = self.session.emane.get_all_configs(node_id)
@ -347,7 +364,7 @@ class CoreXmlWriter:
if emane_configurations.getchildren():
self.scenario.append(emane_configurations)
def write_mobility_configs(self):
def write_mobility_configs(self) -> None:
mobility_configurations = etree.Element("mobility_configurations")
for node_id in self.session.mobility.nodes():
all_configs = self.session.mobility.get_all_configs(node_id)
@ -371,7 +388,7 @@ class CoreXmlWriter:
if mobility_configurations.getchildren():
self.scenario.append(mobility_configurations)
def write_service_configs(self):
def write_service_configs(self) -> None:
service_configurations = etree.Element("service_configurations")
service_configs = self.session.services.all_configs()
for node_id, service in service_configs:
@ -382,7 +399,7 @@ class CoreXmlWriter:
if service_configurations.getchildren():
self.scenario.append(service_configurations)
def write_default_services(self):
def write_default_services(self) -> None:
node_types = etree.Element("default_services")
for node_type in self.session.services.default_services:
services = self.session.services.default_services[node_type]
@ -393,7 +410,7 @@ class CoreXmlWriter:
if node_types.getchildren():
self.scenario.append(node_types)
def write_nodes(self):
def write_nodes(self) -> List[LinkData]:
self.networks = etree.SubElement(self.scenario, "networks")
self.devices = etree.SubElement(self.scenario, "devices")
@ -416,7 +433,7 @@ class CoreXmlWriter:
return links
def write_network(self, node):
def write_network(self, node: NodeBase) -> None:
# ignore p2p and other nodes that are not part of the api
if not node.apitype:
return
@ -424,7 +441,7 @@ class CoreXmlWriter:
network = NetworkElement(self.session, node)
self.networks.append(network.element)
def write_links(self, links):
def write_links(self, links: List[LinkData]) -> None:
link_elements = etree.Element("links")
# add link data
for link_data in links:
@ -438,13 +455,21 @@ class CoreXmlWriter:
if link_elements.getchildren():
self.scenario.append(link_elements)
def write_device(self, node):
def write_device(self, node: NodeBase) -> None:
device = DeviceElement(self.session, node)
self.devices.append(device.element)
def create_interface_element(
self, element_name, node_id, interface_id, mac, ip4, ip4_mask, ip6, ip6_mask
):
self,
element_name: str,
node_id: int,
interface_id: int,
mac: str,
ip4: str,
ip4_mask: int,
ip6: str,
ip6_mask: int,
) -> etree.Element:
interface = etree.Element(element_name)
node = self.session.get_node(node_id)
interface_name = None
@ -467,7 +492,7 @@ class CoreXmlWriter:
return interface
def create_link_element(self, link_data):
def create_link_element(self, link_data: LinkData) -> etree.Element:
link_element = etree.Element("link")
add_attribute(link_element, "node_one", link_data.node1_id)
add_attribute(link_element, "node_two", link_data.node2_id)
@ -525,11 +550,11 @@ class CoreXmlWriter:
class CoreXmlReader:
def __init__(self, session):
def __init__(self, session: "Session") -> None:
self.session = session
self.scenario = None
def read(self, file_name):
def read(self, file_name: str) -> None:
xml_tree = etree.parse(file_name)
self.scenario = xml_tree.getroot()
@ -545,7 +570,7 @@ class CoreXmlReader:
self.read_nodes()
self.read_links()
def read_default_services(self):
def read_default_services(self) -> None:
default_services = self.scenario.find("default_services")
if default_services is None:
return
@ -560,7 +585,7 @@ class CoreXmlReader:
)
self.session.services.default_services[node_type] = services
def read_session_metadata(self):
def read_session_metadata(self) -> None:
session_metadata = self.scenario.find("session_metadata")
if session_metadata is None:
return
@ -573,7 +598,7 @@ class CoreXmlReader:
logging.info("reading session metadata: %s", configs)
self.session.metadata = configs
def read_session_options(self):
def read_session_options(self) -> None:
session_options = self.scenario.find("session_options")
if session_options is None:
return
@ -586,7 +611,7 @@ class CoreXmlReader:
logging.info("reading session options: %s", configs)
self.session.options.set_configs(configs)
def read_session_hooks(self):
def read_session_hooks(self) -> None:
session_hooks = self.scenario.find("session_hooks")
if session_hooks is None:
return
@ -601,7 +626,7 @@ class CoreXmlReader:
hook_type, file_name=name, source_name=None, data=data
)
def read_session_origin(self):
def read_session_origin(self) -> None:
session_origin = self.scenario.find("session_origin")
if session_origin is None:
return
@ -625,7 +650,7 @@ class CoreXmlReader:
logging.info("reading session reference xyz: %s, %s, %s", x, y, z)
self.session.location.refxyz = (x, y, z)
def read_service_configs(self):
def read_service_configs(self) -> None:
service_configurations = self.scenario.find("service_configurations")
if service_configurations is None:
return
@ -669,7 +694,7 @@ class CoreXmlReader:
files.add(name)
service.configs = tuple(files)
def read_emane_configs(self):
def read_emane_configs(self) -> None:
emane_configurations = self.scenario.find("emane_configurations")
if emane_configurations is None:
return
@ -702,7 +727,7 @@ class CoreXmlReader:
)
self.session.emane.set_model_config(node_id, model_name, configs)
def read_mobility_configs(self):
def read_mobility_configs(self) -> None:
mobility_configurations = self.scenario.find("mobility_configurations")
if mobility_configurations is None:
return
@ -722,7 +747,7 @@ class CoreXmlReader:
)
self.session.mobility.set_model_config(node_id, model_name, configs)
def read_nodes(self):
def read_nodes(self) -> None:
device_elements = self.scenario.find("devices")
if device_elements is not None:
for device_element in device_elements.iterchildren():
@ -733,7 +758,7 @@ class CoreXmlReader:
for network_element in network_elements.iterchildren():
self.read_network(network_element)
def read_device(self, device_element):
def read_device(self, device_element: etree.Element) -> None:
node_id = get_int(device_element, "id")
name = device_element.get("name")
model = device_element.get("type")
@ -759,7 +784,7 @@ class CoreXmlReader:
logging.info("reading node id(%s) model(%s) name(%s)", node_id, model, name)
self.session.add_node(_id=node_id, options=options)
def read_network(self, network_element):
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")]
@ -783,7 +808,7 @@ class CoreXmlReader:
)
self.session.add_node(_type=node_type, _id=node_id, options=options)
def read_links(self):
def read_links(self) -> None:
link_elements = self.scenario.find("links")
if link_elements is None:
return

View file

@ -1,5 +1,6 @@
import os
import socket
from typing import TYPE_CHECKING, List, Tuple
import netaddr
from lxml import etree
@ -7,26 +8,40 @@ from lxml import etree
from core import utils
from core.constants import IP_BIN
from core.emane.nodes import EmaneNet
from core.nodes.base import CoreNodeBase
from core.nodes.base import CoreNodeBase, NodeBase
from core.nodes.interface import CoreInterface
if TYPE_CHECKING:
from core.emulator.session import Session
def add_type(parent_element, name):
def add_type(parent_element: etree.Element, name: str) -> None:
type_element = etree.SubElement(parent_element, "type")
type_element.text = name
def add_address(parent_element, address_type, address, interface_name=None):
def add_address(
parent_element: etree.Element,
address_type: str,
address: str,
interface_name: str = None,
) -> None:
address_element = etree.SubElement(parent_element, "address", type=address_type)
address_element.text = address
if interface_name is not None:
address_element.set("iface", interface_name)
def add_mapping(parent_element, maptype, mapref):
def add_mapping(parent_element: etree.Element, maptype: str, mapref: str) -> None:
etree.SubElement(parent_element, "mapping", type=maptype, ref=mapref)
def add_emane_interface(host_element, netif, platform_name="p1", transport_name="t1"):
def add_emane_interface(
host_element: etree.Element,
netif: CoreInterface,
platform_name: str = "p1",
transport_name: str = "t1",
) -> etree.Element:
nem_id = netif.net.nemidmap[netif]
host_id = host_element.get("id")
@ -54,7 +69,7 @@ def add_emane_interface(host_element, netif, platform_name="p1", transport_name=
return platform_element
def get_address_type(address):
def get_address_type(address: str) -> str:
addr, _slash, _prefixlen = address.partition("/")
if netaddr.valid_ipv4(addr):
address_type = "IPv4"
@ -65,7 +80,7 @@ def get_address_type(address):
return address_type
def get_ipv4_addresses(hostname):
def get_ipv4_addresses(hostname: str) -> List[Tuple[str, str]]:
if hostname == "localhost":
addresses = []
args = f"{IP_BIN} -o -f inet address show"
@ -85,7 +100,7 @@ def get_ipv4_addresses(hostname):
class CoreXmlDeployment:
def __init__(self, session, scenario):
def __init__(self, session: "Session", scenario: etree.Element) -> None:
self.session = session
self.scenario = scenario
self.root = etree.SubElement(
@ -93,17 +108,17 @@ class CoreXmlDeployment:
)
self.add_deployment()
def find_device(self, name):
def find_device(self, name: str) -> etree.Element:
device = self.scenario.find(f"devices/device[@name='{name}']")
return device
def find_interface(self, device, name):
def find_interface(self, device: NodeBase, name: str) -> etree.Element:
interface = self.scenario.find(
f"devices/device[@name='{device.name}']/interfaces/interface[@name='{name}']"
)
return interface
def add_deployment(self):
def add_deployment(self) -> None:
physical_host = self.add_physical_host(socket.gethostname())
for node_id in self.session.nodes:
@ -111,7 +126,7 @@ class CoreXmlDeployment:
if isinstance(node, CoreNodeBase):
self.add_virtual_host(physical_host, node)
def add_physical_host(self, name):
def add_physical_host(self, name: str) -> etree.Element:
# add host
root_id = self.root.get("id")
host_id = f"{root_id}/{name}"
@ -126,7 +141,7 @@ class CoreXmlDeployment:
return host_element
def add_virtual_host(self, physical_host, node):
def add_virtual_host(self, physical_host: etree.Element, node: NodeBase) -> None:
if not isinstance(node, CoreNodeBase):
raise TypeError(f"invalid node type: {node}")

View file

@ -1,16 +1,26 @@
import logging
import os
from tempfile import NamedTemporaryFile
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple
from lxml import etree
from core import utils
from core.config import Configuration
from core.emane.nodes import EmaneNet
from core.emulator.distributed import DistributedServer
from core.nodes.interface import CoreInterface
from core.nodes.network import CtrlNet
from core.xml import corexml
if TYPE_CHECKING:
from core.emane.emanemanager import EmaneManager
from core.emane.emanemodel import EmaneModel
_hwaddr_prefix = "02:02"
def is_external(config):
def is_external(config: Dict[str, str]) -> bool:
"""
Checks if the configuration is for an external transport.
@ -21,7 +31,7 @@ def is_external(config):
return config.get("external") == "1"
def _value_to_params(value):
def _value_to_params(value: str) -> Optional[Tuple[str]]:
"""
Helper to convert a parameter to a parameter tuple.
@ -44,7 +54,12 @@ def _value_to_params(value):
return None
def create_file(xml_element, doc_name, file_path, server=None):
def create_file(
xml_element: etree.Element,
doc_name: str,
file_path: str,
server: DistributedServer = None,
) -> None:
"""
Create xml file.
@ -68,7 +83,7 @@ def create_file(xml_element, doc_name, file_path, server=None):
corexml.write_xml_file(xml_element, file_path, doctype=doctype)
def add_param(xml_element, name, value):
def add_param(xml_element: etree.Element, name: str, value: str) -> None:
"""
Add emane configuration parameter to xml element.
@ -80,7 +95,12 @@ def add_param(xml_element, name, value):
etree.SubElement(xml_element, "param", name=name, value=value)
def add_configurations(xml_element, configurations, config, config_ignore):
def add_configurations(
xml_element: etree.Element,
configurations: List[Configuration],
config: Dict[str, str],
config_ignore: Set,
) -> None:
"""
Add emane model configurations to xml element.
@ -107,7 +127,13 @@ def add_configurations(xml_element, configurations, config, config_ignore):
add_param(xml_element, name, value)
def build_node_platform_xml(emane_manager, control_net, node, nem_id, platform_xmls):
def build_node_platform_xml(
emane_manager: "EmaneManager",
control_net: CtrlNet,
node: EmaneNet,
nem_id: int,
platform_xmls: Dict[str, etree.Element],
) -> int:
"""
Create platform xml for a specific node.
@ -131,7 +157,7 @@ def build_node_platform_xml(emane_manager, control_net, node, nem_id, platform_x
if node.model is None:
logging.warning("warning: EMANE network %s has no associated model", node.name)
return nem_entries
return nem_id
for netif in node.netifs():
logging.debug(
@ -228,7 +254,7 @@ def build_node_platform_xml(emane_manager, control_net, node, nem_id, platform_x
return nem_id
def build_xml_files(emane_manager, node):
def build_xml_files(emane_manager: "EmaneManager", node: EmaneNet) -> None:
"""
Generate emane xml files required for node.
@ -276,7 +302,9 @@ def build_xml_files(emane_manager, node):
build_transport_xml(emane_manager, node, rtype)
def build_transport_xml(emane_manager, node, transport_type):
def build_transport_xml(
emane_manager: "EmaneManager", node: EmaneNet, transport_type: str
) -> None:
"""
Build transport xml file for node and transport type.
@ -317,7 +345,12 @@ def build_transport_xml(emane_manager, node, transport_type):
)
def create_phy_xml(emane_model, config, file_path, server):
def create_phy_xml(
emane_model: "EmaneModel",
config: Dict[str, str],
file_path: str,
server: DistributedServer,
) -> None:
"""
Create the phy xml document.
@ -345,7 +378,12 @@ def create_phy_xml(emane_model, config, file_path, server):
)
def create_mac_xml(emane_model, config, file_path, server):
def create_mac_xml(
emane_model: "EmaneModel",
config: Dict[str, str],
file_path: str,
server: DistributedServer,
) -> None:
"""
Create the mac xml document.
@ -376,14 +414,14 @@ def create_mac_xml(emane_model, config, file_path, server):
def create_nem_xml(
emane_model,
config,
nem_file,
transport_definition,
mac_definition,
phy_definition,
server,
):
emane_model: "EmaneModel",
config: Dict[str, str],
nem_file: str,
transport_definition: str,
mac_definition: str,
phy_definition: str,
server: DistributedServer,
) -> None:
"""
Create the nem xml document.
@ -413,7 +451,13 @@ def create_nem_xml(
)
def create_event_service_xml(group, port, device, file_directory, server=None):
def create_event_service_xml(
group: str,
port: str,
device: str,
file_directory: str,
server: DistributedServer = None,
) -> None:
"""
Create a emane event service xml file.
@ -440,7 +484,7 @@ def create_event_service_xml(group, port, device, file_directory, server=None):
create_file(event_element, "emaneeventmsgsvc", file_path, server)
def transport_file_name(node_id, transport_type):
def transport_file_name(node_id: int, transport_type: str) -> str:
"""
Create name for a transport xml file.
@ -451,10 +495,11 @@ def transport_file_name(node_id, transport_type):
return f"n{node_id}trans{transport_type}.xml"
def _basename(emane_model, interface=None):
def _basename(emane_model: "EmaneModel", interface: CoreInterface = None) -> str:
"""
Create name that is leveraged for configuration file creation.
:param emane_model: emane model to create name for
:param interface: interface for this model
:return: basename used for file creation
:rtype: str
@ -469,7 +514,7 @@ def _basename(emane_model, interface=None):
return f"{name}{emane_model.name}"
def nem_file_name(emane_model, interface=None):
def nem_file_name(emane_model: "EmaneModel", interface: CoreInterface = None) -> str:
"""
Return the string name for the NEM XML file, e.g. "n3rfpipenem.xml"
@ -485,7 +530,7 @@ def nem_file_name(emane_model, interface=None):
return f"{basename}nem{append}.xml"
def shim_file_name(emane_model, interface=None):
def shim_file_name(emane_model: "EmaneModel", interface: CoreInterface = None) -> str:
"""
Return the string name for the SHIM XML file, e.g. "commeffectshim.xml"
@ -498,7 +543,7 @@ def shim_file_name(emane_model, interface=None):
return f"{name}shim.xml"
def mac_file_name(emane_model, interface=None):
def mac_file_name(emane_model: "EmaneModel", interface: CoreInterface = None) -> str:
"""
Return the string name for the MAC XML file, e.g. "n3rfpipemac.xml"
@ -511,7 +556,7 @@ def mac_file_name(emane_model, interface=None):
return f"{name}mac.xml"
def phy_file_name(emane_model, interface=None):
def phy_file_name(emane_model: "EmaneModel", interface: CoreInterface = None) -> str:
"""
Return the string name for the PHY XML file, e.g. "n3rfpipephy.xml"