diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 6098db25..a2641e87 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -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. diff --git a/daemon/core/api/grpc/events.py b/daemon/core/api/grpc/events.py index 5c4ee25e..2eebd8ae 100644 --- a/daemon/core/api/grpc/events.py +++ b/daemon/core/api/grpc/events.py @@ -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. diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index 5468e617..89a1d298 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -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. diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index ea343165..06fde7e8 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -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. diff --git a/daemon/core/config.py b/daemon/core/config.py index e8e73300..b117ce54 100644 --- a/daemon/core/config.py +++ b/daemon/core/config.py @@ -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. diff --git a/daemon/core/emane/bypass.py b/daemon/core/emane/bypass.py index 68e6eee4..24f5d45a 100644 --- a/daemon/core/emane/bypass.py +++ b/daemon/core/emane/bypass.py @@ -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)] diff --git a/daemon/core/emane/commeffect.py b/daemon/core/emane/commeffect.py index 33edc342..c7224068 100644 --- a/daemon/core/emane/commeffect.py +++ b/daemon/core/emane/commeffect.py @@ -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. diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index a6237a7a..8561c68e 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -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()] ) diff --git a/daemon/core/emane/emanemanifest.py b/daemon/core/emane/emanemanifest.py index a6583b9e..0fc5facc 100644 --- a/daemon/core/emane/emanemanifest.py +++ b/daemon/core/emane/emanemanifest.py @@ -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. diff --git a/daemon/core/emane/emanemodel.py b/daemon/core/emane/emanemodel.py index 3ca2a18f..4e5dbbfa 100644 --- a/daemon/core/emane/emanemodel.py +++ b/daemon/core/emane/emanemodel.py @@ -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. diff --git a/daemon/core/emane/ieee80211abg.py b/daemon/core/emane/ieee80211abg.py index e7a4d0d7..ecfd3694 100644 --- a/daemon/core/emane/ieee80211abg.py +++ b/daemon/core/emane/ieee80211abg.py @@ -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" ) diff --git a/daemon/core/emane/nodes.py b/daemon/core/emane/nodes.py index bd76ed81..3a1834f3 100644 --- a/daemon/core/emane/nodes.py +++ b/daemon/core/emane/nodes.py @@ -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 diff --git a/daemon/core/emane/rfpipe.py b/daemon/core/emane/rfpipe.py index 51820b7d..23790b3c 100644 --- a/daemon/core/emane/rfpipe.py +++ b/daemon/core/emane/rfpipe.py @@ -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" ) diff --git a/daemon/core/emane/tdma.py b/daemon/core/emane/tdma.py index 59ed9e04..17f5328f 100644 --- a/daemon/core/emane/tdma.py +++ b/daemon/core/emane/tdma.py @@ -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. diff --git a/daemon/core/emulator/coreemu.py b/daemon/core/emulator/coreemu.py index 158dc296..ed51e076 100644 --- a/daemon/core/emulator/coreemu.py +++ b/daemon/core/emulator/coreemu.py @@ -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. diff --git a/daemon/core/emulator/distributed.py b/daemon/core/emulator/distributed.py index a9cba815..105b767f 100644 --- a/daemon/core/emulator/distributed.py +++ b/daemon/core/emulator/distributed.py @@ -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. diff --git a/daemon/core/emulator/emudata.py b/daemon/core/emulator/emudata.py index 8929f72a..acf105eb 100644 --- a/daemon/core/emulator/emudata.py +++ b/daemon/core/emulator/emudata.py @@ -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: +def create_interface( + node: CoreNode, network: CoreNetworkBase, interface_data: InterfaceData +): """ - Convenience class for storing 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 """ - - def __init__(self, _id, name, mac, ip4, ip4_mask, ip6, ip6_mask): - """ - 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) diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index a81ba103..ca585c31 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -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. diff --git a/daemon/core/emulator/sessionconfig.py b/daemon/core/emulator/sessionconfig.py index eb38474b..b403b8d6 100644 --- a/daemon/core/emulator/sessionconfig.py +++ b/daemon/core/emulator/sessionconfig.py @@ -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) diff --git a/daemon/core/errors.py b/daemon/core/errors.py index f5c38b5b..319bd190 100644 --- a/daemon/core/errors.py +++ b/daemon/core/errors.py @@ -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}" diff --git a/daemon/core/location/corelocation.py b/daemon/core/location/corelocation.py index aeab8896..fc803fac 100644 --- a/daemon/core/location/corelocation.py +++ b/daemon/core/location/corelocation.py @@ -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 diff --git a/daemon/core/location/event.py b/daemon/core/location/event.py index f930d9b7..d553e4ee 100644 --- a/daemon/core/location/event.py +++ b/daemon/core/location/event.py @@ -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. diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index 41678d43..e1917636 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -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. diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index c34a42b4..ccfc2c1c 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -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,17 +485,17 @@ 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. :param bool use_ovs: True for OVS bridges, False for Linux bridges - :return:node network client + :return: node network client """ 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. diff --git a/daemon/core/nodes/client.py b/daemon/core/nodes/client.py index 66b61c37..c2624d6b 100644 --- a/daemon/core/nodes/client.py +++ b/daemon/core/nodes/client.py @@ -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. diff --git a/daemon/core/nodes/docker.py b/daemon/core/nodes/docker.py index b56fcc5c..1d7f3f02 100644 --- a/daemon/core/nodes/docker.py +++ b/daemon/core/nodes/docker.py @@ -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. diff --git a/daemon/core/nodes/interface.py b/daemon/core/nodes/interface.py index 236bdd5c..3a5836af 100644 --- a/daemon/core/nodes/interface.py +++ b/daemon/core/nodes/interface.py @@ -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. diff --git a/daemon/core/nodes/lxd.py b/daemon/core/nodes/lxd.py index 2d7a6d3d..20e2bc43 100644 --- a/daemon/core/nodes/lxd.py +++ b/daemon/core/nodes/lxd.py @@ -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) diff --git a/daemon/core/nodes/netclient.py b/daemon/core/nodes/netclient.py index 8053a0e8..1236468f 100644 --- a/daemon/core/nodes/netclient.py +++ b/daemon/core/nodes/netclient.py @@ -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) diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index b5199062..c7c36a1e 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -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. diff --git a/daemon/core/nodes/physical.py b/daemon/core/nodes/physical.py index 1d470b98..a963721f 100644 --- a/daemon/core/nodes/physical.py +++ b/daemon/core/nodes/physical.py @@ -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. diff --git a/daemon/core/plugins/sdt.py b/daemon/core/plugins/sdt.py index 410bba9d..575cbcda 100644 --- a/daemon/core/plugins/sdt.py +++ b/daemon/core/plugins/sdt.py @@ -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. diff --git a/daemon/core/services/coreservices.py b/daemon/core/services/coreservices.py index 686023a2..9abbc977 100644 --- a/daemon/core/services/coreservices.py +++ b/daemon/core/services/coreservices.py @@ -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 diff --git a/daemon/core/utils.py b/daemon/core/utils.py index cf394a15..0d7e4a33 100644 --- a/daemon/core/utils.py +++ b/daemon/core/utils.py @@ -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. diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index 0266912d..df73901f 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -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 diff --git a/daemon/core/xml/corexmldeployment.py b/daemon/core/xml/corexmldeployment.py index 5c817fc2..5f340b69 100644 --- a/daemon/core/xml/corexmldeployment.py +++ b/daemon/core/xml/corexmldeployment.py @@ -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}") diff --git a/daemon/core/xml/emanexml.py b/daemon/core/xml/emanexml.py index a62b54e5..da1e089e 100644 --- a/daemon/core/xml/emanexml.py +++ b/daemon/core/xml/emanexml.py @@ -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"