Merge pull request #344 from coreemu/enhancement/type-hinting
Enhancement/type hinting
This commit is contained in:
commit
e574968e38
37 changed files with 2101 additions and 1299 deletions
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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()]
|
||||
)
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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}"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}")
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
Loading…
Reference in a new issue