fix merge conflict
This commit is contained in:
commit
5c087141bd
86 changed files with 3313 additions and 1832 deletions
|
@ -5,6 +5,7 @@ gRpc client for interfacing with CORE, when gRPC mode is enabled.
|
||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
from typing import Any, Callable, Dict, Generator, List
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
import netaddr
|
import netaddr
|
||||||
|
@ -18,7 +19,7 @@ class InterfaceHelper:
|
||||||
Convenience class to help generate IP4 and IP6 addresses for gRPC clients.
|
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.
|
Creates an InterfaceHelper object.
|
||||||
|
|
||||||
|
@ -36,7 +37,7 @@ class InterfaceHelper:
|
||||||
if ip6_prefix:
|
if ip6_prefix:
|
||||||
self.ip6 = netaddr.IPNetwork(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.
|
Convenience method to return the IP4 address for a node.
|
||||||
|
|
||||||
|
@ -48,7 +49,7 @@ class InterfaceHelper:
|
||||||
raise ValueError("ip4 prefixes have not been set")
|
raise ValueError("ip4 prefixes have not been set")
|
||||||
return str(self.ip4[node_id])
|
return str(self.ip4[node_id])
|
||||||
|
|
||||||
def ip6_address(self, node_id):
|
def ip6_address(self, node_id: int) -> str:
|
||||||
"""
|
"""
|
||||||
Convenience method to return the IP6 address for a node.
|
Convenience method to return the IP6 address for a node.
|
||||||
|
|
||||||
|
@ -60,15 +61,18 @@ class InterfaceHelper:
|
||||||
raise ValueError("ip6 prefixes have not been set")
|
raise ValueError("ip6 prefixes have not been set")
|
||||||
return str(self.ip6[node_id])
|
return str(self.ip6[node_id])
|
||||||
|
|
||||||
def 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
|
Creates interface data for linking nodes, using the nodes unique id for
|
||||||
mac address, unless provided.
|
generation, along with a random mac address, unless provided.
|
||||||
|
|
||||||
:param int node_id: node id to create interface for
|
:param int node_id: node id to create interface for
|
||||||
:param int interface_id: interface id for interface
|
:param int interface_id: interface id for interface
|
||||||
:param str name: name to set for interface, default is eth{id}
|
: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
|
:return: new interface data for the provided node
|
||||||
:rtype: core_pb2.Interface
|
: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.
|
Listen for stream events and provide them to the handler.
|
||||||
|
|
||||||
|
@ -119,7 +123,7 @@ def stream_listener(stream, handler):
|
||||||
logging.exception("stream error")
|
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.
|
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.
|
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.
|
Creates a CoreGrpcClient instance.
|
||||||
|
|
||||||
|
@ -150,19 +154,19 @@ class CoreGrpcClient:
|
||||||
|
|
||||||
def start_session(
|
def start_session(
|
||||||
self,
|
self,
|
||||||
session_id,
|
session_id: int,
|
||||||
nodes,
|
nodes: List[core_pb2.Node],
|
||||||
links,
|
links: List[core_pb2.Link],
|
||||||
location=None,
|
location: core_pb2.SessionLocation = None,
|
||||||
hooks=None,
|
hooks: List[core_pb2.Hook] = None,
|
||||||
emane_config=None,
|
emane_config: Dict[str, str] = None,
|
||||||
emane_model_configs=None,
|
emane_model_configs: List[core_pb2.EmaneModelConfig] = None,
|
||||||
wlan_configs=None,
|
wlan_configs: List[core_pb2.WlanConfig] = None,
|
||||||
mobility_configs=None,
|
mobility_configs: List[core_pb2.MobilityConfig] = None,
|
||||||
service_configs=None,
|
service_configs: List[core_pb2.ServiceConfig] = None,
|
||||||
service_file_configs=None,
|
service_file_configs: List[core_pb2.ServiceFileConfig] = None,
|
||||||
asymmetric_links=None,
|
asymmetric_links: List[core_pb2.Link] = None,
|
||||||
):
|
) -> core_pb2.StartSessionResponse:
|
||||||
"""
|
"""
|
||||||
Start a session.
|
Start a session.
|
||||||
|
|
||||||
|
@ -197,7 +201,7 @@ class CoreGrpcClient:
|
||||||
)
|
)
|
||||||
return self.stub.StartSession(request)
|
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.
|
Stop a running session.
|
||||||
|
|
||||||
|
@ -208,18 +212,19 @@ class CoreGrpcClient:
|
||||||
request = core_pb2.StopSessionRequest(session_id=session_id)
|
request = core_pb2.StopSessionRequest(session_id=session_id)
|
||||||
return self.stub.StopSession(request)
|
return self.stub.StopSession(request)
|
||||||
|
|
||||||
def create_session(self, session_id=None):
|
def create_session(self, session_id: int = None) -> core_pb2.CreateSessionResponse:
|
||||||
"""
|
"""
|
||||||
Create a session.
|
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
|
:return: response with created session id
|
||||||
:rtype: core_pb2.CreateSessionResponse
|
:rtype: core_pb2.CreateSessionResponse
|
||||||
"""
|
"""
|
||||||
request = core_pb2.CreateSessionRequest(session_id=session_id)
|
request = core_pb2.CreateSessionRequest(session_id=session_id)
|
||||||
return self.stub.CreateSession(request)
|
return self.stub.CreateSession(request)
|
||||||
|
|
||||||
def delete_session(self, session_id):
|
def delete_session(self, session_id: int) -> core_pb2.DeleteSessionResponse:
|
||||||
"""
|
"""
|
||||||
Delete a session.
|
Delete a session.
|
||||||
|
|
||||||
|
@ -231,16 +236,17 @@ class CoreGrpcClient:
|
||||||
request = core_pb2.DeleteSessionRequest(session_id=session_id)
|
request = core_pb2.DeleteSessionRequest(session_id=session_id)
|
||||||
return self.stub.DeleteSession(request)
|
return self.stub.DeleteSession(request)
|
||||||
|
|
||||||
def get_sessions(self):
|
def get_sessions(self) -> core_pb2.GetSessionsResponse:
|
||||||
"""
|
"""
|
||||||
Retrieves all currently known sessions.
|
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
|
:rtype: core_pb2.GetSessionsResponse
|
||||||
"""
|
"""
|
||||||
return self.stub.GetSessions(core_pb2.GetSessionsRequest())
|
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.
|
Retrieve a session.
|
||||||
|
|
||||||
|
@ -252,7 +258,9 @@ class CoreGrpcClient:
|
||||||
request = core_pb2.GetSessionRequest(session_id=session_id)
|
request = core_pb2.GetSessionRequest(session_id=session_id)
|
||||||
return self.stub.GetSession(request)
|
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.
|
Retrieve session options as a dict with id mapping.
|
||||||
|
|
||||||
|
@ -264,7 +272,9 @@ class CoreGrpcClient:
|
||||||
request = core_pb2.GetSessionOptionsRequest(session_id=session_id)
|
request = core_pb2.GetSessionOptionsRequest(session_id=session_id)
|
||||||
return self.stub.GetSessionOptions(request)
|
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.
|
Set options for a session.
|
||||||
|
|
||||||
|
@ -279,7 +289,9 @@ class CoreGrpcClient:
|
||||||
)
|
)
|
||||||
return self.stub.SetSessionOptions(request)
|
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.
|
Retrieve session metadata as a dict with id mapping.
|
||||||
|
|
||||||
|
@ -291,7 +303,9 @@ class CoreGrpcClient:
|
||||||
request = core_pb2.GetSessionMetadataRequest(session_id=session_id)
|
request = core_pb2.GetSessionMetadataRequest(session_id=session_id)
|
||||||
return self.stub.GetSessionMetadata(request)
|
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.
|
Set metadata for a session.
|
||||||
|
|
||||||
|
@ -306,7 +320,9 @@ class CoreGrpcClient:
|
||||||
)
|
)
|
||||||
return self.stub.SetSessionMetadata(request)
|
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.
|
Get session location.
|
||||||
|
|
||||||
|
@ -320,15 +336,15 @@ class CoreGrpcClient:
|
||||||
|
|
||||||
def set_session_location(
|
def set_session_location(
|
||||||
self,
|
self,
|
||||||
session_id,
|
session_id: int,
|
||||||
x=None,
|
x: float = None,
|
||||||
y=None,
|
y: float = None,
|
||||||
z=None,
|
z: float = None,
|
||||||
lat=None,
|
lat: float = None,
|
||||||
lon=None,
|
lon: float = None,
|
||||||
alt=None,
|
alt: float = None,
|
||||||
scale=None,
|
scale: float = None,
|
||||||
):
|
) -> core_pb2.SetSessionLocationResponse:
|
||||||
"""
|
"""
|
||||||
Set session location.
|
Set session location.
|
||||||
|
|
||||||
|
@ -352,7 +368,9 @@ class CoreGrpcClient:
|
||||||
)
|
)
|
||||||
return self.stub.SetSessionLocation(request)
|
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.
|
Set session state.
|
||||||
|
|
||||||
|
@ -365,7 +383,9 @@ class CoreGrpcClient:
|
||||||
request = core_pb2.SetSessionStateRequest(session_id=session_id, state=state)
|
request = core_pb2.SetSessionStateRequest(session_id=session_id, state=state)
|
||||||
return self.stub.SetSessionState(request)
|
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.
|
Add distributed session server.
|
||||||
|
|
||||||
|
@ -381,7 +401,12 @@ class CoreGrpcClient:
|
||||||
)
|
)
|
||||||
return self.stub.AddSessionServer(request)
|
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.
|
Listen for session events.
|
||||||
|
|
||||||
|
@ -393,10 +418,13 @@ class CoreGrpcClient:
|
||||||
"""
|
"""
|
||||||
request = core_pb2.EventsRequest(session_id=session_id, events=events)
|
request = core_pb2.EventsRequest(session_id=session_id, events=events)
|
||||||
stream = self.stub.Events(request)
|
stream = self.stub.Events(request)
|
||||||
|
logging.info("STREAM TYPE: %s", type(stream))
|
||||||
start_streamer(stream, handler)
|
start_streamer(stream, handler)
|
||||||
return stream
|
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.
|
Listen for throughput events with information for interfaces and bridges.
|
||||||
|
|
||||||
|
@ -410,7 +438,9 @@ class CoreGrpcClient:
|
||||||
start_streamer(stream, handler)
|
start_streamer(stream, handler)
|
||||||
return stream
|
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.
|
Add node to session.
|
||||||
|
|
||||||
|
@ -423,7 +453,7 @@ class CoreGrpcClient:
|
||||||
request = core_pb2.AddNodeRequest(session_id=session_id, node=node)
|
request = core_pb2.AddNodeRequest(session_id=session_id, node=node)
|
||||||
return self.stub.AddNode(request)
|
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.
|
Get node details.
|
||||||
|
|
||||||
|
@ -436,7 +466,14 @@ class CoreGrpcClient:
|
||||||
request = core_pb2.GetNodeRequest(session_id=session_id, node_id=node_id)
|
request = core_pb2.GetNodeRequest(session_id=session_id, node_id=node_id)
|
||||||
return self.stub.GetNode(request)
|
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.
|
Edit a node, currently only changes position.
|
||||||
|
|
||||||
|
@ -458,7 +495,7 @@ class CoreGrpcClient:
|
||||||
)
|
)
|
||||||
return self.stub.EditNode(request)
|
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.
|
Delete node from session.
|
||||||
|
|
||||||
|
@ -471,12 +508,15 @@ class CoreGrpcClient:
|
||||||
request = core_pb2.DeleteNodeRequest(session_id=session_id, node_id=node_id)
|
request = core_pb2.DeleteNodeRequest(session_id=session_id, node_id=node_id)
|
||||||
return self.stub.DeleteNode(request)
|
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.
|
Send command to a node and get the output.
|
||||||
|
|
||||||
:param int session_id: session id
|
:param int session_id: session id
|
||||||
:param int node_id: node id
|
:param int node_id: node id
|
||||||
|
:param str command: command to run on node
|
||||||
:return: response with command combined stdout/stderr
|
:return: response with command combined stdout/stderr
|
||||||
:rtype: core_pb2.NodeCommandResponse
|
:rtype: core_pb2.NodeCommandResponse
|
||||||
:raises grpc.RpcError: when session or node doesn't exist
|
:raises grpc.RpcError: when session or node doesn't exist
|
||||||
|
@ -486,7 +526,9 @@ class CoreGrpcClient:
|
||||||
)
|
)
|
||||||
return self.stub.NodeCommand(request)
|
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.
|
Retrieve terminal command string for launching a local terminal.
|
||||||
|
|
||||||
|
@ -501,7 +543,9 @@ class CoreGrpcClient:
|
||||||
)
|
)
|
||||||
return self.stub.GetNodeTerminal(request)
|
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.
|
Get current links for a node.
|
||||||
|
|
||||||
|
@ -516,13 +560,13 @@ class CoreGrpcClient:
|
||||||
|
|
||||||
def add_link(
|
def add_link(
|
||||||
self,
|
self,
|
||||||
session_id,
|
session_id: int,
|
||||||
node_one_id,
|
node_one_id: int,
|
||||||
node_two_id,
|
node_two_id: int,
|
||||||
interface_one=None,
|
interface_one: core_pb2.Interface = None,
|
||||||
interface_two=None,
|
interface_two: core_pb2.Interface = None,
|
||||||
options=None,
|
options: core_pb2.LinkOptions = None,
|
||||||
):
|
) -> core_pb2.AddLinkResponse:
|
||||||
"""
|
"""
|
||||||
Add a link between nodes.
|
Add a link between nodes.
|
||||||
|
|
||||||
|
@ -549,13 +593,13 @@ class CoreGrpcClient:
|
||||||
|
|
||||||
def edit_link(
|
def edit_link(
|
||||||
self,
|
self,
|
||||||
session_id,
|
session_id: int,
|
||||||
node_one_id,
|
node_one_id: int,
|
||||||
node_two_id,
|
node_two_id: int,
|
||||||
options,
|
options: core_pb2.LinkOptions,
|
||||||
interface_one_id=None,
|
interface_one_id: int = None,
|
||||||
interface_two_id=None,
|
interface_two_id: int = None,
|
||||||
):
|
) -> core_pb2.EditLinkResponse:
|
||||||
"""
|
"""
|
||||||
Edit a link between nodes.
|
Edit a link between nodes.
|
||||||
|
|
||||||
|
@ -581,12 +625,12 @@ class CoreGrpcClient:
|
||||||
|
|
||||||
def delete_link(
|
def delete_link(
|
||||||
self,
|
self,
|
||||||
session_id,
|
session_id: int,
|
||||||
node_one_id,
|
node_one_id: int,
|
||||||
node_two_id,
|
node_two_id: int,
|
||||||
interface_one_id=None,
|
interface_one_id: int = None,
|
||||||
interface_two_id=None,
|
interface_two_id: int = None,
|
||||||
):
|
) -> core_pb2.DeleteLinkResponse:
|
||||||
"""
|
"""
|
||||||
Delete a link between nodes.
|
Delete a link between nodes.
|
||||||
|
|
||||||
|
@ -608,7 +652,7 @@ class CoreGrpcClient:
|
||||||
)
|
)
|
||||||
return self.stub.DeleteLink(request)
|
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.
|
Get all hook scripts.
|
||||||
|
|
||||||
|
@ -620,7 +664,13 @@ class CoreGrpcClient:
|
||||||
request = core_pb2.GetHooksRequest(session_id=session_id)
|
request = core_pb2.GetHooksRequest(session_id=session_id)
|
||||||
return self.stub.GetHooks(request)
|
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.
|
Add hook scripts.
|
||||||
|
|
||||||
|
@ -636,7 +686,9 @@ class CoreGrpcClient:
|
||||||
request = core_pb2.AddHookRequest(session_id=session_id, hook=hook)
|
request = core_pb2.AddHookRequest(session_id=session_id, hook=hook)
|
||||||
return self.stub.AddHook(request)
|
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.
|
Get all mobility configurations.
|
||||||
|
|
||||||
|
@ -648,7 +700,9 @@ class CoreGrpcClient:
|
||||||
request = core_pb2.GetMobilityConfigsRequest(session_id=session_id)
|
request = core_pb2.GetMobilityConfigsRequest(session_id=session_id)
|
||||||
return self.stub.GetMobilityConfigs(request)
|
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.
|
Get mobility configuration for a node.
|
||||||
|
|
||||||
|
@ -663,7 +717,9 @@ class CoreGrpcClient:
|
||||||
)
|
)
|
||||||
return self.stub.GetMobilityConfig(request)
|
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.
|
Set mobility configuration for a node.
|
||||||
|
|
||||||
|
@ -680,7 +736,9 @@ class CoreGrpcClient:
|
||||||
)
|
)
|
||||||
return self.stub.SetMobilityConfig(request)
|
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.
|
Send a mobility action for a node.
|
||||||
|
|
||||||
|
@ -696,7 +754,7 @@ class CoreGrpcClient:
|
||||||
)
|
)
|
||||||
return self.stub.MobilityAction(request)
|
return self.stub.MobilityAction(request)
|
||||||
|
|
||||||
def get_services(self):
|
def get_services(self) -> core_pb2.GetServicesResponse:
|
||||||
"""
|
"""
|
||||||
Get all currently loaded services.
|
Get all currently loaded services.
|
||||||
|
|
||||||
|
@ -706,7 +764,9 @@ class CoreGrpcClient:
|
||||||
request = core_pb2.GetServicesRequest()
|
request = core_pb2.GetServicesRequest()
|
||||||
return self.stub.GetServices(request)
|
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.
|
Get default services for different default node models.
|
||||||
|
|
||||||
|
@ -718,7 +778,9 @@ class CoreGrpcClient:
|
||||||
request = core_pb2.GetServiceDefaultsRequest(session_id=session_id)
|
request = core_pb2.GetServiceDefaultsRequest(session_id=session_id)
|
||||||
return self.stub.GetServiceDefaults(request)
|
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.
|
Set default services for node models.
|
||||||
|
|
||||||
|
@ -738,7 +800,9 @@ class CoreGrpcClient:
|
||||||
)
|
)
|
||||||
return self.stub.SetServiceDefaults(request)
|
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.
|
Get service data for a node.
|
||||||
|
|
||||||
|
@ -750,7 +814,9 @@ class CoreGrpcClient:
|
||||||
request = core_pb2.GetNodeServiceConfigsRequest(session_id=session_id)
|
request = core_pb2.GetNodeServiceConfigsRequest(session_id=session_id)
|
||||||
return self.stub.GetNodeServiceConfigs(request)
|
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.
|
Get service data for a node.
|
||||||
|
|
||||||
|
@ -766,7 +832,9 @@ class CoreGrpcClient:
|
||||||
)
|
)
|
||||||
return self.stub.GetNodeService(request)
|
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.
|
Get a service file for a node.
|
||||||
|
|
||||||
|
@ -784,8 +852,14 @@ class CoreGrpcClient:
|
||||||
return self.stub.GetNodeServiceFile(request)
|
return self.stub.GetNodeServiceFile(request)
|
||||||
|
|
||||||
def set_node_service(
|
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.
|
Set service data for a node.
|
||||||
|
|
||||||
|
@ -809,7 +883,9 @@ class CoreGrpcClient:
|
||||||
request = core_pb2.SetNodeServiceRequest(session_id=session_id, config=config)
|
request = core_pb2.SetNodeServiceRequest(session_id=session_id, config=config)
|
||||||
return self.stub.SetNodeService(request)
|
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.
|
Set a service file for a node.
|
||||||
|
|
||||||
|
@ -830,14 +906,21 @@ class CoreGrpcClient:
|
||||||
)
|
)
|
||||||
return self.stub.SetNodeServiceFile(request)
|
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.
|
Send an action to a service for a node.
|
||||||
|
|
||||||
:param int session_id: session id
|
:param int session_id: session id
|
||||||
:param int node_id: node id
|
:param int node_id: node id
|
||||||
:param str service: service name
|
: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
|
:return: response with result of success or failure
|
||||||
:rtype: core_pb2.ServiceActionResponse
|
:rtype: core_pb2.ServiceActionResponse
|
||||||
:raises grpc.RpcError: when session or node doesn't exist
|
:raises grpc.RpcError: when session or node doesn't exist
|
||||||
|
@ -847,7 +930,7 @@ class CoreGrpcClient:
|
||||||
)
|
)
|
||||||
return self.stub.ServiceAction(request)
|
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.
|
Get all wlan configurations.
|
||||||
|
|
||||||
|
@ -859,7 +942,9 @@ class CoreGrpcClient:
|
||||||
request = core_pb2.GetWlanConfigsRequest(session_id=session_id)
|
request = core_pb2.GetWlanConfigsRequest(session_id=session_id)
|
||||||
return self.stub.GetWlanConfigs(request)
|
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.
|
Get wlan configuration for a node.
|
||||||
|
|
||||||
|
@ -872,7 +957,9 @@ class CoreGrpcClient:
|
||||||
request = core_pb2.GetWlanConfigRequest(session_id=session_id, node_id=node_id)
|
request = core_pb2.GetWlanConfigRequest(session_id=session_id, node_id=node_id)
|
||||||
return self.stub.GetWlanConfig(request)
|
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.
|
Set wlan configuration for a node.
|
||||||
|
|
||||||
|
@ -889,7 +976,7 @@ class CoreGrpcClient:
|
||||||
)
|
)
|
||||||
return self.stub.SetWlanConfig(request)
|
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.
|
Get session emane configuration.
|
||||||
|
|
||||||
|
@ -901,7 +988,9 @@ class CoreGrpcClient:
|
||||||
request = core_pb2.GetEmaneConfigRequest(session_id=session_id)
|
request = core_pb2.GetEmaneConfigRequest(session_id=session_id)
|
||||||
return self.stub.GetEmaneConfig(request)
|
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.
|
Set session emane configuration.
|
||||||
|
|
||||||
|
@ -914,7 +1003,7 @@ class CoreGrpcClient:
|
||||||
request = core_pb2.SetEmaneConfigRequest(session_id=session_id, config=config)
|
request = core_pb2.SetEmaneConfigRequest(session_id=session_id, config=config)
|
||||||
return self.stub.SetEmaneConfig(request)
|
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.
|
Get session emane models.
|
||||||
|
|
||||||
|
@ -926,7 +1015,9 @@ class CoreGrpcClient:
|
||||||
request = core_pb2.GetEmaneModelsRequest(session_id=session_id)
|
request = core_pb2.GetEmaneModelsRequest(session_id=session_id)
|
||||||
return self.stub.GetEmaneModels(request)
|
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.
|
Get emane model configuration for a node or a node's interface.
|
||||||
|
|
||||||
|
@ -944,8 +1035,13 @@ class CoreGrpcClient:
|
||||||
return self.stub.GetEmaneModelConfig(request)
|
return self.stub.GetEmaneModelConfig(request)
|
||||||
|
|
||||||
def set_emane_model_config(
|
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.
|
Set emane model configuration for a node or a node's interface.
|
||||||
|
|
||||||
|
@ -966,7 +1062,9 @@ class CoreGrpcClient:
|
||||||
)
|
)
|
||||||
return self.stub.SetEmaneModelConfig(request)
|
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.
|
Get all emane model configurations for a session.
|
||||||
|
|
||||||
|
@ -978,7 +1076,7 @@ class CoreGrpcClient:
|
||||||
request = core_pb2.GetEmaneModelConfigsRequest(session_id=session_id)
|
request = core_pb2.GetEmaneModelConfigsRequest(session_id=session_id)
|
||||||
return self.stub.GetEmaneModelConfigs(request)
|
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.
|
Save the current scenario to an XML file.
|
||||||
|
|
||||||
|
@ -991,7 +1089,7 @@ class CoreGrpcClient:
|
||||||
with open(file_path, "w") as xml_file:
|
with open(file_path, "w") as xml_file:
|
||||||
xml_file.write(response.data)
|
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.
|
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)
|
request = core_pb2.OpenXmlRequest(data=data, start=start, file=file_path)
|
||||||
return self.stub.OpenXml(request)
|
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.
|
Helps broadcast wireless link/unlink between EMANE nodes.
|
||||||
|
|
||||||
|
@ -1020,7 +1120,7 @@ class CoreGrpcClient:
|
||||||
)
|
)
|
||||||
return self.stub.EmaneLink(request)
|
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
|
Retrieves a list of interfaces available on the host machine that are not
|
||||||
a part of a CORE session.
|
a part of a CORE session.
|
||||||
|
@ -1030,7 +1130,7 @@ class CoreGrpcClient:
|
||||||
request = core_pb2.GetInterfacesRequest()
|
request = core_pb2.GetInterfacesRequest()
|
||||||
return self.stub.GetInterfaces(request)
|
return self.stub.GetInterfaces(request)
|
||||||
|
|
||||||
def connect(self):
|
def connect(self) -> None:
|
||||||
"""
|
"""
|
||||||
Open connection to server, must be closed manually.
|
Open connection to server, must be closed manually.
|
||||||
|
|
||||||
|
@ -1041,7 +1141,7 @@ class CoreGrpcClient:
|
||||||
)
|
)
|
||||||
self.stub = core_pb2_grpc.CoreApiStub(self.channel)
|
self.stub = core_pb2_grpc.CoreApiStub(self.channel)
|
||||||
|
|
||||||
def close(self):
|
def close(self) -> None:
|
||||||
"""
|
"""
|
||||||
Close currently opened server channel connection.
|
Close currently opened server channel connection.
|
||||||
|
|
||||||
|
@ -1052,7 +1152,7 @@ class CoreGrpcClient:
|
||||||
self.channel = None
|
self.channel = None
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def context_connect(self):
|
def context_connect(self) -> Generator:
|
||||||
"""
|
"""
|
||||||
Makes a context manager based connection to the server, will close after context ends.
|
Makes a context manager based connection to the server, will close after context ends.
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import logging
|
import logging
|
||||||
from queue import Empty, Queue
|
from queue import Empty, Queue
|
||||||
|
from typing import Iterable
|
||||||
|
|
||||||
from core.api.grpc import core_pb2
|
from core.api.grpc import core_pb2
|
||||||
from core.api.grpc.grpcutils import convert_value
|
from core.api.grpc.grpcutils import convert_value
|
||||||
|
@ -11,9 +12,10 @@ from core.emulator.data import (
|
||||||
LinkData,
|
LinkData,
|
||||||
NodeData,
|
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
|
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)
|
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
|
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)
|
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
|
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
|
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
|
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
|
Handle file event
|
||||||
|
|
||||||
|
@ -179,7 +181,9 @@ class EventStreamer:
|
||||||
Processes session events to generate grpc events.
|
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.
|
Create a EventStreamer instance.
|
||||||
|
|
||||||
|
@ -191,7 +195,7 @@ class EventStreamer:
|
||||||
self.queue = Queue()
|
self.queue = Queue()
|
||||||
self.add_handlers()
|
self.add_handlers()
|
||||||
|
|
||||||
def add_handlers(self):
|
def add_handlers(self) -> None:
|
||||||
"""
|
"""
|
||||||
Add a session event handler for desired event types.
|
Add a session event handler for desired event types.
|
||||||
|
|
||||||
|
@ -210,7 +214,7 @@ class EventStreamer:
|
||||||
if core_pb2.EventType.SESSION in self.event_types:
|
if core_pb2.EventType.SESSION in self.event_types:
|
||||||
self.session.event_handlers.append(self.queue.put)
|
self.session.event_handlers.append(self.queue.put)
|
||||||
|
|
||||||
def process(self):
|
def process(self) -> core_pb2.Event:
|
||||||
"""
|
"""
|
||||||
Process the next event in the queue.
|
Process the next event in the queue.
|
||||||
|
|
||||||
|
@ -239,7 +243,7 @@ class EventStreamer:
|
||||||
event = None
|
event = None
|
||||||
return event
|
return event
|
||||||
|
|
||||||
def remove_handlers(self):
|
def remove_handlers(self) -> None:
|
||||||
"""
|
"""
|
||||||
Remove session event handlers for events being watched.
|
Remove session event handlers for events being watched.
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,21 @@
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
|
from typing import Any, Dict, List, Tuple, Type
|
||||||
|
|
||||||
from core import utils
|
from core import utils
|
||||||
from core.api.grpc import core_pb2
|
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.emudata import InterfaceData, LinkOptions, NodeOptions
|
||||||
from core.emulator.enumerations import LinkTypes, NodeTypes
|
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
|
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.
|
Convert node protobuf message to data for creating a node.
|
||||||
|
|
||||||
|
@ -40,7 +45,7 @@ def add_node_data(node_proto):
|
||||||
return _type, _id, options
|
return _type, _id, options
|
||||||
|
|
||||||
|
|
||||||
def link_interface(interface_proto):
|
def link_interface(interface_proto: core_pb2.Interface) -> InterfaceData:
|
||||||
"""
|
"""
|
||||||
Create interface data from interface proto.
|
Create interface data from interface proto.
|
||||||
|
|
||||||
|
@ -68,7 +73,9 @@ def link_interface(interface_proto):
|
||||||
return interface
|
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.
|
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
|
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.
|
Create nodes using a thread pool and wait for completion.
|
||||||
|
|
||||||
|
@ -123,7 +132,9 @@ def create_nodes(session, node_protos):
|
||||||
return results, exceptions
|
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.
|
Create links using a thread pool and wait for completion.
|
||||||
|
|
||||||
|
@ -146,7 +157,9 @@ def create_links(session, link_protos):
|
||||||
return results, exceptions
|
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.
|
Edit links using a thread pool and wait for completion.
|
||||||
|
|
||||||
|
@ -169,7 +182,7 @@ def edit_links(session, link_protos):
|
||||||
return results, exceptions
|
return results, exceptions
|
||||||
|
|
||||||
|
|
||||||
def convert_value(value):
|
def convert_value(value: Any) -> str:
|
||||||
"""
|
"""
|
||||||
Convert value into string.
|
Convert value into string.
|
||||||
|
|
||||||
|
@ -182,7 +195,9 @@ def convert_value(value):
|
||||||
return 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.
|
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
|
return results
|
||||||
|
|
||||||
|
|
||||||
def get_links(session, node):
|
def get_links(session: Session, node: NodeBase):
|
||||||
"""
|
"""
|
||||||
Retrieve a list of links for grpc to use
|
Retrieve a list of links for grpc to use
|
||||||
|
|
||||||
:param core.emulator.Session session: node's section
|
: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]
|
:return: [core.api.grpc.core_pb2.Link]
|
||||||
"""
|
"""
|
||||||
links = []
|
links = []
|
||||||
|
@ -226,7 +241,7 @@ def get_links(session, node):
|
||||||
return links
|
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
|
Get EMANE model id
|
||||||
|
|
||||||
|
@ -241,7 +256,7 @@ def get_emane_model_id(node_id, interface_id):
|
||||||
return node_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.
|
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
|
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
|
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
|
Retrieve status about the current interfaces in the system
|
||||||
|
|
||||||
|
@ -346,7 +361,7 @@ def get_net_stats():
|
||||||
return 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.
|
Set session location based on location proto.
|
||||||
|
|
||||||
|
@ -359,7 +374,7 @@ def session_location(session, location):
|
||||||
session.location.refscale = location.scale
|
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.
|
Convenience method for setting a node service configuration.
|
||||||
|
|
||||||
|
@ -374,7 +389,7 @@ def service_configuration(session, config):
|
||||||
service.shutdown = tuple(config.shutdown)
|
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.
|
Convenience for converting a service to service data proto.
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ import time
|
||||||
from concurrent import futures
|
from concurrent import futures
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
|
from grpc import ServicerContext
|
||||||
|
|
||||||
from core.api.grpc import core_pb2, core_pb2_grpc, grpcutils
|
from core.api.grpc import core_pb2, core_pb2_grpc, grpcutils
|
||||||
from core.api.grpc.events import EventStreamer
|
from core.api.grpc.events import EventStreamer
|
||||||
|
@ -17,11 +18,14 @@ from core.api.grpc.grpcutils import (
|
||||||
get_net_stats,
|
get_net_stats,
|
||||||
)
|
)
|
||||||
from core.emane.nodes import EmaneNet
|
from core.emane.nodes import EmaneNet
|
||||||
|
from core.emulator.coreemu import CoreEmu
|
||||||
from core.emulator.data import LinkData
|
from core.emulator.data import LinkData
|
||||||
from core.emulator.emudata import LinkOptions, NodeOptions
|
from core.emulator.emudata import LinkOptions, NodeOptions
|
||||||
from core.emulator.enumerations import EventTypes, LinkTypes, MessageFlags
|
from core.emulator.enumerations import EventTypes, LinkTypes, MessageFlags
|
||||||
|
from core.emulator.session import Session
|
||||||
from core.errors import CoreCommandError, CoreError
|
from core.errors import CoreCommandError, CoreError
|
||||||
from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility
|
from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility
|
||||||
|
from core.nodes.base import NodeBase
|
||||||
from core.nodes.docker import DockerNode
|
from core.nodes.docker import DockerNode
|
||||||
from core.nodes.lxd import LxcNode
|
from core.nodes.lxd import LxcNode
|
||||||
from core.services.coreservices import ServiceManager
|
from core.services.coreservices import ServiceManager
|
||||||
|
@ -37,24 +41,24 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
:param core.emulator.coreemu.CoreEmu coreemu: coreemu object
|
:param core.emulator.coreemu.CoreEmu coreemu: coreemu object
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, coreemu):
|
def __init__(self, coreemu: CoreEmu) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.coreemu = coreemu
|
self.coreemu = coreemu
|
||||||
self.running = True
|
self.running = True
|
||||||
self.server = None
|
self.server = None
|
||||||
atexit.register(self._exit_handler)
|
atexit.register(self._exit_handler)
|
||||||
|
|
||||||
def _exit_handler(self):
|
def _exit_handler(self) -> None:
|
||||||
logging.debug("catching exit, stop running")
|
logging.debug("catching exit, stop running")
|
||||||
self.running = False
|
self.running = False
|
||||||
|
|
||||||
def _is_running(self, context):
|
def _is_running(self, context) -> bool:
|
||||||
return self.running and context.is_active()
|
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")
|
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)
|
logging.info("CORE gRPC API listening on: %s", address)
|
||||||
self.server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
|
self.server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
|
||||||
core_pb2_grpc.add_CoreApiServicer_to_server(self, self.server)
|
core_pb2_grpc.add_CoreApiServicer_to_server(self, self.server)
|
||||||
|
@ -67,7 +71,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
self.server.stop(None)
|
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
|
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")
|
context.abort(grpc.StatusCode.NOT_FOUND, f"session {session_id} not found")
|
||||||
return session
|
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
|
Retrieve node given session and node id
|
||||||
|
|
||||||
|
@ -97,7 +103,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
except CoreError:
|
except CoreError:
|
||||||
context.abort(grpc.StatusCode.NOT_FOUND, f"node {node_id} not found")
|
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.
|
Start a session.
|
||||||
|
|
||||||
|
@ -184,7 +192,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
|
|
||||||
return core_pb2.StartSessionResponse(result=True)
|
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.
|
Stop a running session.
|
||||||
|
|
||||||
|
@ -201,7 +211,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
session.set_state(EventTypes.SHUTDOWN_STATE, send_event=True)
|
session.set_state(EventTypes.SHUTDOWN_STATE, send_event=True)
|
||||||
return core_pb2.StopSessionResponse(result=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
|
Create a session
|
||||||
|
|
||||||
|
@ -219,7 +231,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
session_id=session.id, state=session.state
|
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
|
Delete the session
|
||||||
|
|
||||||
|
@ -232,7 +246,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
result = self.coreemu.delete_session(request.session_id)
|
result = self.coreemu.delete_session(request.session_id)
|
||||||
return core_pb2.DeleteSessionResponse(result=result)
|
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
|
Delete the session
|
||||||
|
|
||||||
|
@ -254,7 +270,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
sessions.append(session_summary)
|
sessions.append(session_summary)
|
||||||
return core_pb2.GetSessionsResponse(sessions=sessions)
|
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
|
Retrieve a requested session location
|
||||||
|
|
||||||
|
@ -273,7 +291,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
)
|
)
|
||||||
return core_pb2.GetSessionLocationResponse(location=location)
|
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
|
Set session location
|
||||||
|
|
||||||
|
@ -287,7 +307,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
grpcutils.session_location(session, request.location)
|
grpcutils.session_location(session, request.location)
|
||||||
return core_pb2.SetSessionLocationResponse(result=True)
|
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
|
Set session state
|
||||||
|
|
||||||
|
@ -320,7 +342,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
|
|
||||||
return core_pb2.SetSessionStateResponse(result=result)
|
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.
|
Retrieve session options.
|
||||||
|
|
||||||
|
@ -338,7 +362,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
config = get_config_options(default_config, session.options)
|
config = get_config_options(default_config, session.options)
|
||||||
return core_pb2.GetSessionOptionsResponse(config=config)
|
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
|
Update a session's configuration
|
||||||
|
|
||||||
|
@ -353,7 +379,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
config.update(request.config)
|
config.update(request.config)
|
||||||
return core_pb2.SetSessionOptionsResponse(result=True)
|
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.
|
Retrieve session metadata.
|
||||||
|
|
||||||
|
@ -367,7 +395,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
session = self.get_session(request.session_id, context)
|
session = self.get_session(request.session_id, context)
|
||||||
return core_pb2.GetSessionMetadataResponse(config=session.metadata)
|
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.
|
Update a session's metadata.
|
||||||
|
|
||||||
|
@ -381,7 +411,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
session.metadata = dict(request.config)
|
session.metadata = dict(request.config)
|
||||||
return core_pb2.SetSessionMetadataResponse(result=True)
|
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
|
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)
|
session_proto = core_pb2.Session(state=session.state, nodes=nodes, links=links)
|
||||||
return core_pb2.GetSessionResponse(session=session_proto)
|
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.
|
Add distributed server to a session.
|
||||||
|
|
||||||
|
@ -449,7 +483,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
session.distributed.add_server(request.name, request.host)
|
session.distributed.add_server(request.name, request.host)
|
||||||
return core_pb2.AddSessionServerResponse(result=True)
|
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)
|
session = self.get_session(request.session_id, context)
|
||||||
event_types = set(request.events)
|
event_types = set(request.events)
|
||||||
if not event_types:
|
if not event_types:
|
||||||
|
@ -464,7 +498,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
streamer.remove_handlers()
|
streamer.remove_handlers()
|
||||||
self._cancel_stream(context)
|
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
|
Calculate average throughput after every certain amount of delay time
|
||||||
|
|
||||||
|
@ -532,7 +568,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
last_stats = stats
|
last_stats = stats
|
||||||
time.sleep(delay)
|
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
|
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)
|
node = session.add_node(_type=_type, _id=_id, options=options)
|
||||||
return core_pb2.AddNodeResponse(node_id=node.id)
|
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
|
Retrieve node
|
||||||
|
|
||||||
|
@ -602,7 +642,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
|
|
||||||
return core_pb2.GetNodeResponse(node=node_proto, interfaces=interfaces)
|
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
|
Edit node
|
||||||
|
|
||||||
|
@ -635,7 +677,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
result = False
|
result = False
|
||||||
return core_pb2.EditNodeResponse(result=result)
|
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
|
Delete node
|
||||||
|
|
||||||
|
@ -648,7 +692,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
result = session.delete_node(request.node_id)
|
result = session.delete_node(request.node_id)
|
||||||
return core_pb2.DeleteNodeResponse(result=result)
|
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
|
Run command on a node
|
||||||
|
|
||||||
|
@ -665,7 +711,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
output = e.stderr
|
output = e.stderr
|
||||||
return core_pb2.NodeCommandResponse(output=output)
|
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
|
Retrieve terminal command string of a node
|
||||||
|
|
||||||
|
@ -680,7 +728,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
terminal = node.termcmdstring("/bin/bash")
|
terminal = node.termcmdstring("/bin/bash")
|
||||||
return core_pb2.GetNodeTerminalResponse(terminal=terminal)
|
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
|
Retrieve all links form a requested node
|
||||||
|
|
||||||
|
@ -695,7 +745,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
links = get_links(session, node)
|
links = get_links(session, node)
|
||||||
return core_pb2.GetNodeLinksResponse(links=links)
|
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
|
Add link to a session
|
||||||
|
|
||||||
|
@ -718,7 +770,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
)
|
)
|
||||||
return core_pb2.AddLinkResponse(result=True)
|
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
|
Edit a link
|
||||||
|
|
||||||
|
@ -751,7 +805,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
)
|
)
|
||||||
return core_pb2.EditLinkResponse(result=True)
|
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
|
Delete a link
|
||||||
|
|
||||||
|
@ -771,7 +827,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
)
|
)
|
||||||
return core_pb2.DeleteLinkResponse(result=True)
|
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
|
Retrieve all hooks from a session
|
||||||
|
|
||||||
|
@ -790,7 +848,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
hooks.append(hook)
|
hooks.append(hook)
|
||||||
return core_pb2.GetHooksResponse(hooks=hooks)
|
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
|
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)
|
session.add_hook(hook.state, hook.file, None, hook.data)
|
||||||
return core_pb2.AddHookResponse(result=True)
|
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
|
Retrieve all mobility configurations from a session
|
||||||
|
|
||||||
|
@ -831,7 +893,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
response.configs[node_id].CopyFrom(mapped_config)
|
response.configs[node_id].CopyFrom(mapped_config)
|
||||||
return response
|
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
|
Retrieve mobility configuration of a node
|
||||||
|
|
||||||
|
@ -849,7 +913,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
config = get_config_options(current_config, Ns2ScriptedMobility)
|
config = get_config_options(current_config, Ns2ScriptedMobility)
|
||||||
return core_pb2.GetMobilityConfigResponse(config=config)
|
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
|
Set mobility configuration of a node
|
||||||
|
|
||||||
|
@ -867,7 +933,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
)
|
)
|
||||||
return core_pb2.SetMobilityConfigResponse(result=True)
|
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
|
Take mobility action whether to start, pause, stop or none of those
|
||||||
|
|
||||||
|
@ -891,7 +959,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
result = False
|
result = False
|
||||||
return core_pb2.MobilityActionResponse(result=result)
|
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
|
Retrieve all the services that are running
|
||||||
|
|
||||||
|
@ -908,7 +978,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
services.append(service_proto)
|
services.append(service_proto)
|
||||||
return core_pb2.GetServicesResponse(services=services)
|
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
|
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)
|
all_service_defaults.append(service_defaults)
|
||||||
return core_pb2.GetServiceDefaultsResponse(defaults=all_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
|
Set new default services to the session after whipping out the old ones
|
||||||
:param core.api.grpc.core_pb2.SetServiceDefaults request: set-service-defaults
|
:param core.api.grpc.core_pb2.SetServiceDefaults request: set-service-defaults
|
||||||
|
@ -947,7 +1021,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
] = service_defaults.services
|
] = service_defaults.services
|
||||||
return core_pb2.SetServiceDefaultsResponse(result=True)
|
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.
|
Retrieve all node service configurations.
|
||||||
|
|
||||||
|
@ -973,7 +1049,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
configs.append(config)
|
configs.append(config)
|
||||||
return core_pb2.GetNodeServiceConfigsResponse(configs=configs)
|
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
|
Retrieve a requested service from a node
|
||||||
|
|
||||||
|
@ -991,7 +1069,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
service_proto = grpcutils.get_service_configuration(service)
|
service_proto = grpcutils.get_service_configuration(service)
|
||||||
return core_pb2.GetNodeServiceResponse(service=service_proto)
|
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
|
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)
|
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
|
Set a node service for a node
|
||||||
|
|
||||||
|
@ -1025,7 +1107,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
grpcutils.service_configuration(session, config)
|
grpcutils.service_configuration(session, config)
|
||||||
return core_pb2.SetNodeServiceResponse(result=True)
|
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
|
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)
|
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 core.api.grpc.core_pb2.ServiceActionRequest request: service-action request
|
||||||
:param grpcServicerContext context: context object
|
:param grpcServicerContext context: context object
|
||||||
|
@ -1082,7 +1169,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
|
|
||||||
return core_pb2.ServiceActionResponse(result=result)
|
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.
|
Retrieve all wireless-lan configurations.
|
||||||
|
|
||||||
|
@ -1107,7 +1196,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
response.configs[node_id].CopyFrom(mapped_config)
|
response.configs[node_id].CopyFrom(mapped_config)
|
||||||
return response
|
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
|
Retrieve wireless-lan configuration of a node
|
||||||
|
|
||||||
|
@ -1124,7 +1215,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
config = get_config_options(current_config, BasicRangeModel)
|
config = get_config_options(current_config, BasicRangeModel)
|
||||||
return core_pb2.GetWlanConfigResponse(config=config)
|
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
|
Set configuration data for a model
|
||||||
|
|
||||||
|
@ -1144,7 +1237,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
node.updatemodel(wlan_config.config)
|
node.updatemodel(wlan_config.config)
|
||||||
return core_pb2.SetWlanConfigResponse(result=True)
|
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
|
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)
|
config = get_config_options(current_config, session.emane.emane_config)
|
||||||
return core_pb2.GetEmaneConfigResponse(config=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
|
Set EMANE configuration of a session
|
||||||
|
|
||||||
|
@ -1174,7 +1271,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
config.update(request.config)
|
config.update(request.config)
|
||||||
return core_pb2.SetEmaneConfigResponse(result=True)
|
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
|
Retrieve all the EMANE models in the session
|
||||||
|
|
||||||
|
@ -1192,7 +1291,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
models.append(model)
|
models.append(model)
|
||||||
return core_pb2.GetEmaneModelsResponse(models=models)
|
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
|
Retrieve EMANE model configuration of a node
|
||||||
|
|
||||||
|
@ -1210,7 +1311,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
config = get_config_options(current_config, model)
|
config = get_config_options(current_config, model)
|
||||||
return core_pb2.GetEmaneModelConfigResponse(config=config)
|
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
|
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)
|
session.emane.set_model_config(_id, model_config.model, model_config.config)
|
||||||
return core_pb2.SetEmaneModelConfigResponse(result=True)
|
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
|
Retrieve all EMANE model configurations of a session
|
||||||
|
|
||||||
|
@ -1261,7 +1366,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
configs.append(model_config)
|
configs.append(model_config)
|
||||||
return core_pb2.GetEmaneModelConfigsResponse(configs=configs)
|
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
|
Export the session nto the EmulationScript XML format
|
||||||
|
|
||||||
|
@ -1281,7 +1388,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
|
|
||||||
return core_pb2.SaveXmlResponse(data=data)
|
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
|
Import a session from the EmulationScript XML format
|
||||||
|
|
||||||
|
@ -1309,7 +1418,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
finally:
|
finally:
|
||||||
os.unlink(temp.name)
|
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
|
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)
|
interfaces.append(interface)
|
||||||
return core_pb2.GetInterfacesResponse(interfaces=interfaces)
|
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.
|
Helps broadcast wireless link/unlink between EMANE nodes.
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,111 @@ Common support for configurable CORE objects.
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from collections import OrderedDict
|
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.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:
|
class ConfigShim:
|
||||||
|
@ -14,7 +117,7 @@ class ConfigShim:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@classmethod
|
@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.
|
Converts a TLV key/value string into an ordered mapping.
|
||||||
|
|
||||||
|
@ -30,7 +133,7 @@ class ConfigShim:
|
||||||
return values
|
return values
|
||||||
|
|
||||||
@classmethod
|
@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.
|
Converts configuration groups to a TLV formatted string.
|
||||||
|
|
||||||
|
@ -47,7 +150,14 @@ class ConfigShim:
|
||||||
return "|".join(group_strings)
|
return "|".join(group_strings)
|
||||||
|
|
||||||
@classmethod
|
@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
|
Convert this class to a Config API message. Some TLVs are defined
|
||||||
by the class, but node number, conf type flags, and values must
|
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:
|
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_node = -1
|
||||||
_default_type = _default_node
|
_default_type = _default_node
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
"""
|
"""
|
||||||
Creates a ConfigurableManager object.
|
Creates a ConfigurableManager object.
|
||||||
"""
|
"""
|
||||||
self.node_configurations = {}
|
self.node_configurations = {}
|
||||||
|
|
||||||
def nodes(self):
|
def nodes(self) -> List[int]:
|
||||||
"""
|
"""
|
||||||
Retrieves the ids of all node configurations known by this manager.
|
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]
|
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.
|
Clears all configurations or configuration for a specific node.
|
||||||
|
|
||||||
|
@ -166,7 +248,13 @@ class ConfigurableManager:
|
||||||
elif node_id in self.node_configurations:
|
elif node_id in self.node_configurations:
|
||||||
self.node_configurations.pop(node_id)
|
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.
|
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 = node_configs.setdefault(config_type, OrderedDict())
|
||||||
node_type_configs[_id] = value
|
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.
|
Set configurations for a node and configuration type.
|
||||||
|
|
||||||
|
@ -196,8 +289,12 @@ class ConfigurableManager:
|
||||||
node_configs[config_type] = config
|
node_configs[config_type] = config
|
||||||
|
|
||||||
def get_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.
|
Retrieves a specific configuration for a node and configuration type.
|
||||||
|
|
||||||
|
@ -214,7 +311,9 @@ class ConfigurableManager:
|
||||||
result = node_type_configs.get(_id, default)
|
result = node_type_configs.get(_id, default)
|
||||||
return result
|
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.
|
Retrieve configurations for a node and configuration type.
|
||||||
|
|
||||||
|
@ -229,7 +328,7 @@ class ConfigurableManager:
|
||||||
result = node_configs.get(config_type)
|
result = node_configs.get(config_type)
|
||||||
return result
|
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.
|
Retrieve all current configuration types for a node.
|
||||||
|
|
||||||
|
@ -240,72 +339,12 @@ class ConfigurableManager:
|
||||||
return self.node_configurations.get(node_id)
|
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):
|
class ModelManager(ConfigurableManager):
|
||||||
"""
|
"""
|
||||||
Helps handle setting models for nodes and managing their model configurations.
|
Helps handle setting models for nodes and managing their model configurations.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
"""
|
"""
|
||||||
Creates a ModelManager object.
|
Creates a ModelManager object.
|
||||||
"""
|
"""
|
||||||
|
@ -313,7 +352,9 @@ class ModelManager(ConfigurableManager):
|
||||||
self.models = {}
|
self.models = {}
|
||||||
self.node_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.
|
Set configuration data for a model.
|
||||||
|
|
||||||
|
@ -341,7 +382,7 @@ class ModelManager(ConfigurableManager):
|
||||||
# set configuration
|
# set configuration
|
||||||
self.set_configs(model_config, node_id=node_id, config_type=model_name)
|
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.
|
Retrieve configuration data for a model.
|
||||||
|
|
||||||
|
@ -363,7 +404,12 @@ class ModelManager(ConfigurableManager):
|
||||||
|
|
||||||
return config
|
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.
|
Set model and model configuration for node.
|
||||||
|
|
||||||
|
@ -379,7 +425,9 @@ class ModelManager(ConfigurableManager):
|
||||||
config = self.get_model_config(node.id, model_class.name)
|
config = self.get_model_config(node.id, model_class.name)
|
||||||
node.setmodel(model_class, config)
|
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
|
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.
|
configured. This is invoked when exporting a session to XML.
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
"""
|
"""
|
||||||
EMANE Bypass model for CORE
|
EMANE Bypass model for CORE
|
||||||
"""
|
"""
|
||||||
|
from typing import List
|
||||||
|
|
||||||
from core.config import ConfigGroup, Configuration
|
from core.config import ConfigGroup, Configuration
|
||||||
from core.emane import emanemodel
|
from core.emane import emanemodel
|
||||||
from core.emulator.enumerations import ConfigDataTypes
|
from core.emulator.enumerations import ConfigDataTypes
|
||||||
|
@ -29,11 +31,11 @@ class EmaneBypassModel(emanemodel.EmaneModel):
|
||||||
phy_config = []
|
phy_config = []
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load(cls, emane_prefix):
|
def load(cls, emane_prefix: str) -> None:
|
||||||
# ignore default logic
|
# ignore default logic
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# override config groups
|
# override config groups
|
||||||
@classmethod
|
@classmethod
|
||||||
def config_groups(cls):
|
def config_groups(cls) -> List[ConfigGroup]:
|
||||||
return [ConfigGroup("Bypass Parameters", 1, 1)]
|
return [ConfigGroup("Bypass Parameters", 1, 1)]
|
||||||
|
|
|
@ -4,11 +4,13 @@ commeffect.py: EMANE CommEffect model for CORE
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
from typing import Dict, List
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
from core.config import ConfigGroup
|
from core.config import ConfigGroup, Configuration
|
||||||
from core.emane import emanemanifest, emanemodel
|
from core.emane import emanemanifest, emanemodel
|
||||||
|
from core.nodes.interface import CoreInterface
|
||||||
from core.xml import emanexml
|
from core.xml import emanexml
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -20,7 +22,7 @@ except ImportError:
|
||||||
logging.debug("compatible emane python bindings not installed")
|
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.
|
Helper to use 0 for None values.
|
||||||
"""
|
"""
|
||||||
|
@ -45,19 +47,21 @@ class EmaneCommEffectModel(emanemodel.EmaneModel):
|
||||||
external_config = []
|
external_config = []
|
||||||
|
|
||||||
@classmethod
|
@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)
|
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)
|
cls.config_shim = emanemanifest.parse(shim_xml_path, cls.shim_defaults)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def configurations(cls):
|
def configurations(cls) -> List[Configuration]:
|
||||||
return cls.config_shim
|
return cls.config_shim
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def config_groups(cls):
|
def config_groups(cls) -> List[ConfigGroup]:
|
||||||
return [ConfigGroup("CommEffect SHIM Parameters", 1, len(cls.configurations()))]
|
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.
|
Build the necessary nem and commeffect XMLs in the given path.
|
||||||
If an individual NEM has a nonstandard config, we need to build
|
If an individual NEM has a nonstandard config, we need to build
|
||||||
|
@ -109,14 +113,14 @@ class EmaneCommEffectModel(emanemodel.EmaneModel):
|
||||||
|
|
||||||
def linkconfig(
|
def linkconfig(
|
||||||
self,
|
self,
|
||||||
netif,
|
netif: CoreInterface,
|
||||||
bw=None,
|
bw: float = None,
|
||||||
delay=None,
|
delay: float = None,
|
||||||
loss=None,
|
loss: float = None,
|
||||||
duplicate=None,
|
duplicate: float = None,
|
||||||
jitter=None,
|
jitter: float = None,
|
||||||
netif2=None,
|
netif2: CoreInterface = None,
|
||||||
):
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Generate CommEffect events when a Link Message is received having
|
Generate CommEffect events when a Link Message is received having
|
||||||
link parameters.
|
link parameters.
|
||||||
|
|
|
@ -6,6 +6,7 @@ import logging
|
||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
from typing import TYPE_CHECKING, Dict, List, Set, Tuple, Type
|
||||||
|
|
||||||
from core import utils
|
from core import utils
|
||||||
from core.config import ConfigGroup, Configuration, ModelManager
|
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.emane.tdma import EmaneTdmaModel
|
||||||
from core.emulator.enumerations import ConfigDataTypes, RegisterTlvs
|
from core.emulator.enumerations import ConfigDataTypes, RegisterTlvs
|
||||||
from core.errors import CoreCommandError, CoreError
|
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
|
from core.xml import emanexml
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.emulator.session import Session
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from emane.events import EventService
|
from emane.events import EventService
|
||||||
from emane.events import LocationEvent
|
from emane.events import LocationEvent
|
||||||
|
@ -57,7 +65,7 @@ class EmaneManager(ModelManager):
|
||||||
EVENTCFGVAR = "LIBEMANEEVENTSERVICECONFIG"
|
EVENTCFGVAR = "LIBEMANEEVENTSERVICECONFIG"
|
||||||
DEFAULT_LOG_LEVEL = 3
|
DEFAULT_LOG_LEVEL = 3
|
||||||
|
|
||||||
def __init__(self, session):
|
def __init__(self, session: "Session") -> None:
|
||||||
"""
|
"""
|
||||||
Creates a Emane instance.
|
Creates a Emane instance.
|
||||||
|
|
||||||
|
@ -86,7 +94,9 @@ class EmaneManager(ModelManager):
|
||||||
self.event_device = None
|
self.event_device = None
|
||||||
self.emane_check()
|
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.
|
Retrieve interface configuration or node configuration if not provided.
|
||||||
|
|
||||||
|
@ -129,11 +139,11 @@ class EmaneManager(ModelManager):
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
def config_reset(self, node_id=None):
|
def config_reset(self, node_id: int = None) -> None:
|
||||||
super().config_reset(node_id)
|
super().config_reset(node_id)
|
||||||
self.set_configs(self.emane_config.default_values())
|
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.
|
Check if emane is installed and load models.
|
||||||
|
|
||||||
|
@ -157,7 +167,7 @@ class EmaneManager(ModelManager):
|
||||||
except CoreCommandError:
|
except CoreCommandError:
|
||||||
logging.info("emane is not installed")
|
logging.info("emane is not installed")
|
||||||
|
|
||||||
def deleteeventservice(self):
|
def deleteeventservice(self) -> None:
|
||||||
if self.service:
|
if self.service:
|
||||||
for fd in self.service._readFd, self.service._writeFd:
|
for fd in self.service._readFd, self.service._writeFd:
|
||||||
if fd >= 0:
|
if fd >= 0:
|
||||||
|
@ -168,7 +178,7 @@ class EmaneManager(ModelManager):
|
||||||
self.service = None
|
self.service = None
|
||||||
self.event_device = 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.
|
Re-initialize the EMANE Event service.
|
||||||
The multicast group and/or port may be configured.
|
The multicast group and/or port may be configured.
|
||||||
|
@ -186,7 +196,7 @@ class EmaneManager(ModelManager):
|
||||||
logging.error(
|
logging.error(
|
||||||
"invalid emane event service device provided: %s", self.event_device
|
"invalid emane event service device provided: %s", self.event_device
|
||||||
)
|
)
|
||||||
return False
|
return
|
||||||
|
|
||||||
# make sure the event control network is in place
|
# make sure the event control network is in place
|
||||||
eventnet = self.session.add_remove_control_net(
|
eventnet = self.session.add_remove_control_net(
|
||||||
|
@ -205,9 +215,7 @@ class EmaneManager(ModelManager):
|
||||||
except EventServiceException:
|
except EventServiceException:
|
||||||
logging.exception("error instantiating emane EventService")
|
logging.exception("error instantiating emane EventService")
|
||||||
|
|
||||||
return True
|
def load_models(self, emane_models: List[Type[EmaneModel]]) -> None:
|
||||||
|
|
||||||
def load_models(self, emane_models):
|
|
||||||
"""
|
"""
|
||||||
Load EMANE models and make them available.
|
Load EMANE models and make them available.
|
||||||
"""
|
"""
|
||||||
|
@ -219,7 +227,7 @@ class EmaneManager(ModelManager):
|
||||||
emane_model.load(emane_prefix)
|
emane_model.load(emane_prefix)
|
||||||
self.models[emane_model.name] = emane_model
|
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.
|
Add EMANE network object to this manager.
|
||||||
|
|
||||||
|
@ -233,7 +241,7 @@ class EmaneManager(ModelManager):
|
||||||
)
|
)
|
||||||
self._emane_nets[emane_net.id] = emane_net
|
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,
|
Return a set of CoreNodes that are linked to an EMANE network,
|
||||||
e.g. containers having one or more radio interfaces.
|
e.g. containers having one or more radio interfaces.
|
||||||
|
@ -245,7 +253,7 @@ class EmaneManager(ModelManager):
|
||||||
nodes.add(netif.node)
|
nodes.add(netif.node)
|
||||||
return nodes
|
return nodes
|
||||||
|
|
||||||
def setup(self):
|
def setup(self) -> int:
|
||||||
"""
|
"""
|
||||||
Setup duties for EMANE manager.
|
Setup duties for EMANE manager.
|
||||||
|
|
||||||
|
@ -303,7 +311,7 @@ class EmaneManager(ModelManager):
|
||||||
self.check_node_models()
|
self.check_node_models()
|
||||||
return EmaneManager.SUCCESS
|
return EmaneManager.SUCCESS
|
||||||
|
|
||||||
def startup(self):
|
def startup(self) -> int:
|
||||||
"""
|
"""
|
||||||
After all the EMANE networks have been added, build XML files
|
After all the EMANE networks have been added, build XML files
|
||||||
and start the daemons.
|
and start the daemons.
|
||||||
|
@ -347,7 +355,7 @@ class EmaneManager(ModelManager):
|
||||||
|
|
||||||
return EmaneManager.SUCCESS
|
return EmaneManager.SUCCESS
|
||||||
|
|
||||||
def poststartup(self):
|
def poststartup(self) -> None:
|
||||||
"""
|
"""
|
||||||
Retransmit location events now that all NEMs are active.
|
Retransmit location events now that all NEMs are active.
|
||||||
"""
|
"""
|
||||||
|
@ -367,7 +375,7 @@ class EmaneManager(ModelManager):
|
||||||
x, y, z = netif.node.position.get()
|
x, y, z = netif.node.position.get()
|
||||||
emane_node.setnemposition(netif, x, y, z)
|
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
|
Remove all EMANE networks from the dictionary, reset port numbers and
|
||||||
nem id counters
|
nem id counters
|
||||||
|
@ -382,7 +390,7 @@ class EmaneManager(ModelManager):
|
||||||
"emane_transform_port", 8200
|
"emane_transform_port", 8200
|
||||||
)
|
)
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self) -> None:
|
||||||
"""
|
"""
|
||||||
stop all EMANE daemons
|
stop all EMANE daemons
|
||||||
"""
|
"""
|
||||||
|
@ -394,7 +402,7 @@ class EmaneManager(ModelManager):
|
||||||
self.stopdaemons()
|
self.stopdaemons()
|
||||||
self.stopeventmonitor()
|
self.stopeventmonitor()
|
||||||
|
|
||||||
def buildxml(self):
|
def buildxml(self) -> None:
|
||||||
"""
|
"""
|
||||||
Build XML files required to run EMANE on each node.
|
Build XML files required to run EMANE on each node.
|
||||||
NEMs run inside containers using the control network for passing
|
NEMs run inside containers using the control network for passing
|
||||||
|
@ -410,7 +418,7 @@ class EmaneManager(ModelManager):
|
||||||
self.buildnemxml()
|
self.buildnemxml()
|
||||||
self.buildeventservicexml()
|
self.buildeventservicexml()
|
||||||
|
|
||||||
def check_node_models(self):
|
def check_node_models(self) -> None:
|
||||||
"""
|
"""
|
||||||
Associate EMANE model classes with EMANE network nodes.
|
Associate EMANE model classes with EMANE network nodes.
|
||||||
"""
|
"""
|
||||||
|
@ -438,7 +446,7 @@ class EmaneManager(ModelManager):
|
||||||
model_class = self.models[model_name]
|
model_class = self.models[model_name]
|
||||||
emane_node.setmodel(model_class, config)
|
emane_node.setmodel(model_class, config)
|
||||||
|
|
||||||
def nemlookup(self, nemid):
|
def nemlookup(self, nemid) -> Tuple[EmaneNet, CoreInterface]:
|
||||||
"""
|
"""
|
||||||
Look for the given numerical NEM ID and return the first matching
|
Look for the given numerical NEM ID and return the first matching
|
||||||
EMANE network and NEM interface.
|
EMANE network and NEM interface.
|
||||||
|
@ -456,7 +464,7 @@ class EmaneManager(ModelManager):
|
||||||
|
|
||||||
return emane_node, netif
|
return emane_node, netif
|
||||||
|
|
||||||
def numnems(self):
|
def numnems(self) -> int:
|
||||||
"""
|
"""
|
||||||
Return the number of NEMs emulated locally.
|
Return the number of NEMs emulated locally.
|
||||||
"""
|
"""
|
||||||
|
@ -466,7 +474,7 @@ class EmaneManager(ModelManager):
|
||||||
count += len(emane_node.netifs())
|
count += len(emane_node.netifs())
|
||||||
return count
|
return count
|
||||||
|
|
||||||
def buildplatformxml(self, ctrlnet):
|
def buildplatformxml(self, ctrlnet: CtrlNet) -> None:
|
||||||
"""
|
"""
|
||||||
Build a platform.xml file now that all nodes are configured.
|
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
|
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.
|
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]
|
emane_net = self._emane_nets[key]
|
||||||
emanexml.build_xml_files(self, emane_net)
|
emanexml.build_xml_files(self, emane_net)
|
||||||
|
|
||||||
def buildeventservicexml(self):
|
def buildeventservicexml(self) -> None:
|
||||||
"""
|
"""
|
||||||
Build the libemaneeventservice.xml file if event service options
|
Build the libemaneeventservice.xml file if event service options
|
||||||
were changed in the global config.
|
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.
|
Start one EMANE daemon per node having a radio.
|
||||||
Add a control network even if the user has not configured one.
|
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))
|
self.session.distributed.execute(lambda x: x.remote_cmd(emanecmd, cwd=path))
|
||||||
logging.info("host emane daemon running: %s", emanecmd)
|
logging.info("host emane daemon running: %s", emanecmd)
|
||||||
|
|
||||||
def stopdaemons(self):
|
def stopdaemons(self) -> None:
|
||||||
"""
|
"""
|
||||||
Kill the appropriate EMANE daemons.
|
Kill the appropriate EMANE daemons.
|
||||||
"""
|
"""
|
||||||
|
@ -623,7 +631,7 @@ class EmaneManager(ModelManager):
|
||||||
except CoreCommandError:
|
except CoreCommandError:
|
||||||
logging.exception("error shutting down emane daemons")
|
logging.exception("error shutting down emane daemons")
|
||||||
|
|
||||||
def installnetifs(self):
|
def installnetifs(self) -> None:
|
||||||
"""
|
"""
|
||||||
Install TUN/TAP virtual interfaces into their proper namespaces
|
Install TUN/TAP virtual interfaces into their proper namespaces
|
||||||
now that the EMANE daemons are running.
|
now that the EMANE daemons are running.
|
||||||
|
@ -633,7 +641,7 @@ class EmaneManager(ModelManager):
|
||||||
logging.info("emane install netifs for node: %d", key)
|
logging.info("emane install netifs for node: %d", key)
|
||||||
emane_node.installnetifs()
|
emane_node.installnetifs()
|
||||||
|
|
||||||
def deinstallnetifs(self):
|
def deinstallnetifs(self) -> None:
|
||||||
"""
|
"""
|
||||||
Uninstall TUN/TAP virtual interfaces.
|
Uninstall TUN/TAP virtual interfaces.
|
||||||
"""
|
"""
|
||||||
|
@ -641,7 +649,7 @@ class EmaneManager(ModelManager):
|
||||||
emane_node = self._emane_nets[key]
|
emane_node = self._emane_nets[key]
|
||||||
emane_node.deinstallnetifs()
|
emane_node.deinstallnetifs()
|
||||||
|
|
||||||
def doeventmonitor(self):
|
def doeventmonitor(self) -> bool:
|
||||||
"""
|
"""
|
||||||
Returns boolean whether or not EMANE events will be monitored.
|
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
|
# generate the EMANE events when nodes are moved
|
||||||
return self.session.options.get_config_bool("emane_event_monitor")
|
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.
|
Returns boolean whether or not EMANE events will be generated.
|
||||||
"""
|
"""
|
||||||
|
@ -660,7 +668,7 @@ class EmaneManager(ModelManager):
|
||||||
tmp = not self.doeventmonitor()
|
tmp = not self.doeventmonitor()
|
||||||
return tmp
|
return tmp
|
||||||
|
|
||||||
def starteventmonitor(self):
|
def starteventmonitor(self) -> None:
|
||||||
"""
|
"""
|
||||||
Start monitoring EMANE location events if configured to do so.
|
Start monitoring EMANE location events if configured to do so.
|
||||||
"""
|
"""
|
||||||
|
@ -681,7 +689,7 @@ class EmaneManager(ModelManager):
|
||||||
self.eventmonthread.daemon = True
|
self.eventmonthread.daemon = True
|
||||||
self.eventmonthread.start()
|
self.eventmonthread.start()
|
||||||
|
|
||||||
def stopeventmonitor(self):
|
def stopeventmonitor(self) -> None:
|
||||||
"""
|
"""
|
||||||
Stop monitoring EMANE location events.
|
Stop monitoring EMANE location events.
|
||||||
"""
|
"""
|
||||||
|
@ -697,7 +705,7 @@ class EmaneManager(ModelManager):
|
||||||
self.eventmonthread.join()
|
self.eventmonthread.join()
|
||||||
self.eventmonthread = None
|
self.eventmonthread = None
|
||||||
|
|
||||||
def eventmonitorloop(self):
|
def eventmonitorloop(self) -> None:
|
||||||
"""
|
"""
|
||||||
Thread target that monitors EMANE location events.
|
Thread target that monitors EMANE location events.
|
||||||
"""
|
"""
|
||||||
|
@ -724,7 +732,7 @@ class EmaneManager(ModelManager):
|
||||||
threading.currentThread().getName(),
|
threading.currentThread().getName(),
|
||||||
)
|
)
|
||||||
|
|
||||||
def handlelocationevent(self, rxnemid, eid, data):
|
def handlelocationevent(self, rxnemid: int, eid: int, data: str) -> None:
|
||||||
"""
|
"""
|
||||||
Handle an EMANE location event.
|
Handle an EMANE location event.
|
||||||
"""
|
"""
|
||||||
|
@ -747,7 +755,9 @@ class EmaneManager(ModelManager):
|
||||||
logging.debug("emane location event: %s,%s,%s", lat, lon, alt)
|
logging.debug("emane location event: %s,%s,%s", lat, lon, alt)
|
||||||
self.handlelocationeventtoxyz(txnemid, 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
|
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.
|
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
|
# don"t use node.setposition(x,y,z) which generates an event
|
||||||
node.position.set(x, y, z)
|
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)
|
self.session.broadcast_node(node_data)
|
||||||
return True
|
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,
|
Return True if an EMANE process associated with the given node is running,
|
||||||
False otherwise.
|
False otherwise.
|
||||||
|
@ -827,7 +837,7 @@ class EmaneGlobalModel:
|
||||||
name = "emane"
|
name = "emane"
|
||||||
bitmap = None
|
bitmap = None
|
||||||
|
|
||||||
def __init__(self, session):
|
def __init__(self, session: "Session") -> None:
|
||||||
self.session = session
|
self.session = session
|
||||||
self.nem_config = [
|
self.nem_config = [
|
||||||
Configuration(
|
Configuration(
|
||||||
|
@ -840,7 +850,7 @@ class EmaneGlobalModel:
|
||||||
self.emulator_config = None
|
self.emulator_config = None
|
||||||
self.parse_config()
|
self.parse_config()
|
||||||
|
|
||||||
def parse_config(self):
|
def parse_config(self) -> None:
|
||||||
emane_prefix = self.session.options.get_config(
|
emane_prefix = self.session.options.get_config(
|
||||||
"emane_prefix", default=DEFAULT_EMANE_PREFIX
|
"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
|
return self.emulator_config + self.nem_config
|
||||||
|
|
||||||
def config_groups(self):
|
def config_groups(self) -> List[ConfigGroup]:
|
||||||
emulator_len = len(self.emulator_config)
|
emulator_len = len(self.emulator_config)
|
||||||
config_len = len(self.configurations())
|
config_len = len(self.configurations())
|
||||||
return [
|
return [
|
||||||
|
@ -873,7 +883,7 @@ class EmaneGlobalModel:
|
||||||
ConfigGroup("NEM Parameters", emulator_len + 1, config_len),
|
ConfigGroup("NEM Parameters", emulator_len + 1, config_len),
|
||||||
]
|
]
|
||||||
|
|
||||||
def default_values(self):
|
def default_values(self) -> Dict[str, str]:
|
||||||
return OrderedDict(
|
return OrderedDict(
|
||||||
[(config.id, config.default) for config in self.configurations()]
|
[(config.id, config.default) for config in self.configurations()]
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Dict, List
|
||||||
|
|
||||||
from core.config import Configuration
|
from core.config import Configuration
|
||||||
from core.emulator.enumerations import ConfigDataTypes
|
from core.emulator.enumerations import ConfigDataTypes
|
||||||
|
@ -13,12 +14,12 @@ except ImportError:
|
||||||
logging.debug("compatible emane python bindings not installed")
|
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.
|
Convert emane configuration type to core configuration value.
|
||||||
|
|
||||||
:param str config_type: emane configuration type
|
:param str config_type: emane configuration type
|
||||||
:return:
|
:return: core config type
|
||||||
"""
|
"""
|
||||||
config_type = config_type.upper()
|
config_type = config_type.upper()
|
||||||
if config_type == "DOUBLE":
|
if config_type == "DOUBLE":
|
||||||
|
@ -28,7 +29,7 @@ def _type_value(config_type):
|
||||||
return ConfigDataTypes[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.
|
Retrieve possible config value options based on emane regexes.
|
||||||
|
|
||||||
|
@ -47,7 +48,7 @@ def _get_possible(config_type, config_regex):
|
||||||
return []
|
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.
|
Convert default configuration values to one used by core.
|
||||||
|
|
||||||
|
@ -72,7 +73,7 @@ def _get_default(config_type_name, config_value):
|
||||||
return config_default
|
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.
|
Parses a valid emane manifest file and converts the provided configuration values into ones used by core.
|
||||||
|
|
||||||
|
|
|
@ -3,12 +3,14 @@ Defines Emane Models used within CORE.
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
from typing import Dict, List
|
||||||
|
|
||||||
from core.config import ConfigGroup, Configuration
|
from core.config import ConfigGroup, Configuration
|
||||||
from core.emane import emanemanifest
|
from core.emane import emanemanifest
|
||||||
from core.emulator.enumerations import ConfigDataTypes
|
from core.emulator.enumerations import ConfigDataTypes
|
||||||
from core.errors import CoreError
|
from core.errors import CoreError
|
||||||
from core.location.mobility import WirelessModel
|
from core.location.mobility import WirelessModel
|
||||||
|
from core.nodes.interface import CoreInterface
|
||||||
from core.xml import emanexml
|
from core.xml import emanexml
|
||||||
|
|
||||||
|
|
||||||
|
@ -45,7 +47,7 @@ class EmaneModel(WirelessModel):
|
||||||
config_ignore = set()
|
config_ignore = set()
|
||||||
|
|
||||||
@classmethod
|
@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
|
Called after being loaded within the EmaneManager. Provides configured emane_prefix for
|
||||||
parsing xml files.
|
parsing xml files.
|
||||||
|
@ -63,7 +65,7 @@ class EmaneModel(WirelessModel):
|
||||||
cls.phy_config = emanemanifest.parse(phy_xml_path, cls.phy_defaults)
|
cls.phy_config = emanemanifest.parse(phy_xml_path, cls.phy_defaults)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def configurations(cls):
|
def configurations(cls) -> List[Configuration]:
|
||||||
"""
|
"""
|
||||||
Returns the combination all all configurations (mac, phy, and external).
|
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
|
return cls.mac_config + cls.phy_config + cls.external_config
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def config_groups(cls):
|
def config_groups(cls) -> List[ConfigGroup]:
|
||||||
"""
|
"""
|
||||||
Returns the defined configuration groups.
|
Returns the defined configuration groups.
|
||||||
|
|
||||||
|
@ -89,10 +91,12 @@ class EmaneModel(WirelessModel):
|
||||||
ConfigGroup("External Parameters", phy_len + 1, config_len),
|
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
|
Builds xml files for this emane model. Creates a nem.xml file that points to
|
||||||
definitions.
|
both mac.xml and phy.xml definitions.
|
||||||
|
|
||||||
:param dict config: emane model configuration for the node and interface
|
:param dict config: emane model configuration for the node and interface
|
||||||
:param interface: interface for the emane node
|
: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)
|
phy_file = os.path.join(self.session.session_dir, phy_name)
|
||||||
emanexml.create_phy_xml(self, config, phy_file, server)
|
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.
|
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)
|
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
|
Invoked from MobilityModel when nodes are moved; this causes
|
||||||
emane location events to be generated for the nodes in the moved
|
emane location events to be generated for the nodes in the moved
|
||||||
|
@ -143,7 +147,7 @@ class EmaneModel(WirelessModel):
|
||||||
|
|
||||||
:param bool moved: were nodes moved
|
:param bool moved: were nodes moved
|
||||||
:param list moved_netifs: interfaces that were moved
|
:param list moved_netifs: interfaces that were moved
|
||||||
:return:
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
wlan = self.session.get_node(self.id)
|
wlan = self.session.get_node(self.id)
|
||||||
|
@ -153,14 +157,14 @@ class EmaneModel(WirelessModel):
|
||||||
|
|
||||||
def linkconfig(
|
def linkconfig(
|
||||||
self,
|
self,
|
||||||
netif,
|
netif: CoreInterface,
|
||||||
bw=None,
|
bw: float = None,
|
||||||
delay=None,
|
delay: float = None,
|
||||||
loss=None,
|
loss: float = None,
|
||||||
duplicate=None,
|
duplicate: float = None,
|
||||||
jitter=None,
|
jitter: float = None,
|
||||||
netif2=None,
|
netif2: CoreInterface = None,
|
||||||
):
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Invoked when a Link Message is received. Default is unimplemented.
|
Invoked when a Link Message is received. Default is unimplemented.
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ class EmaneIeee80211abgModel(emanemodel.EmaneModel):
|
||||||
mac_xml = "ieee80211abgmaclayer.xml"
|
mac_xml = "ieee80211abgmaclayer.xml"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load(cls, emane_prefix):
|
def load(cls, emane_prefix: str) -> None:
|
||||||
cls.mac_defaults["pcrcurveuri"] = os.path.join(
|
cls.mac_defaults["pcrcurveuri"] = os.path.join(
|
||||||
emane_prefix, "share/emane/xml/models/mac/ieee80211abg/ieee80211pcr.xml"
|
emane_prefix, "share/emane/xml/models/mac/ieee80211abg/ieee80211pcr.xml"
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,9 +4,18 @@ share the same MAC+PHY model.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
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.emulator.enumerations import LinkTypes, NodeTypes, RegisterTlvs
|
||||||
from core.nodes.base import CoreNetworkBase
|
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:
|
try:
|
||||||
from emane.events import LocationEvent
|
from emane.events import LocationEvent
|
||||||
|
@ -29,7 +38,14 @@ class EmaneNet(CoreNetworkBase):
|
||||||
type = "wlan"
|
type = "wlan"
|
||||||
is_emane = True
|
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)
|
super().__init__(session, _id, name, start, server)
|
||||||
self.conf = ""
|
self.conf = ""
|
||||||
self.up = False
|
self.up = False
|
||||||
|
@ -39,20 +55,20 @@ class EmaneNet(CoreNetworkBase):
|
||||||
|
|
||||||
def linkconfig(
|
def linkconfig(
|
||||||
self,
|
self,
|
||||||
netif,
|
netif: CoreInterface,
|
||||||
bw=None,
|
bw: float = None,
|
||||||
delay=None,
|
delay: float = None,
|
||||||
loss=None,
|
loss: float = None,
|
||||||
duplicate=None,
|
duplicate: float = None,
|
||||||
jitter=None,
|
jitter: float = None,
|
||||||
netif2=None,
|
netif2: CoreInterface = None,
|
||||||
):
|
) -> None:
|
||||||
"""
|
"""
|
||||||
The CommEffect model supports link configuration.
|
The CommEffect model supports link configuration.
|
||||||
"""
|
"""
|
||||||
if not self.model:
|
if not self.model:
|
||||||
return
|
return
|
||||||
return self.model.linkconfig(
|
self.model.linkconfig(
|
||||||
netif=netif,
|
netif=netif,
|
||||||
bw=bw,
|
bw=bw,
|
||||||
delay=delay,
|
delay=delay,
|
||||||
|
@ -62,19 +78,19 @@ class EmaneNet(CoreNetworkBase):
|
||||||
netif2=netif2,
|
netif2=netif2,
|
||||||
)
|
)
|
||||||
|
|
||||||
def config(self, conf):
|
def config(self, conf: str) -> None:
|
||||||
self.conf = conf
|
self.conf = conf
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def link(self, netif1, netif2):
|
def link(self, netif1: CoreInterface, netif2: CoreInterface) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def unlink(self, netif1, netif2):
|
def unlink(self, netif1: CoreInterface, netif2: CoreInterface) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def updatemodel(self, config):
|
def updatemodel(self, config: Dict[str, str]) -> None:
|
||||||
if not self.model:
|
if not self.model:
|
||||||
raise ValueError("no model set to update for node(%s)", self.id)
|
raise ValueError("no model set to update for node(%s)", self.id)
|
||||||
logging.info(
|
logging.info(
|
||||||
|
@ -82,7 +98,7 @@ class EmaneNet(CoreNetworkBase):
|
||||||
)
|
)
|
||||||
self.model.set_configs(config, node_id=self.id)
|
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
|
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 = model(session=self.session, _id=self.id)
|
||||||
self.mobility.update_config(config)
|
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
|
Record an interface to numerical ID mapping. The Emane controller
|
||||||
object manages and assigns these IDs for all NEMs.
|
object manages and assigns these IDs for all NEMs.
|
||||||
"""
|
"""
|
||||||
self.nemidmap[netif] = nemid
|
self.nemidmap[netif] = nemid
|
||||||
|
|
||||||
def getnemid(self, netif):
|
def getnemid(self, netif: CoreInterface) -> Optional[int]:
|
||||||
"""
|
"""
|
||||||
Given an interface, return its numerical ID.
|
Given an interface, return its numerical ID.
|
||||||
"""
|
"""
|
||||||
|
@ -112,7 +128,7 @@ class EmaneNet(CoreNetworkBase):
|
||||||
else:
|
else:
|
||||||
return self.nemidmap[netif]
|
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
|
Given a numerical NEM ID, return its interface. This returns the
|
||||||
first interface that matches the given NEM ID.
|
first interface that matches the given NEM ID.
|
||||||
|
@ -122,13 +138,13 @@ class EmaneNet(CoreNetworkBase):
|
||||||
return netif
|
return netif
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def netifs(self, sort=True):
|
def netifs(self, sort: bool = True) -> List[CoreInterface]:
|
||||||
"""
|
"""
|
||||||
Retrieve list of linked interfaces sorted by node number.
|
Retrieve list of linked interfaces sorted by node number.
|
||||||
"""
|
"""
|
||||||
return sorted(self._netif.values(), key=lambda ifc: ifc.node.id)
|
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
|
Install TAP devices into their namespaces. This is done after
|
||||||
EMANE daemons have been started, because that is their only chance
|
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()
|
x, y, z = netif.node.position.get()
|
||||||
self.setnemposition(netif, x, y, z)
|
self.setnemposition(netif, x, y, z)
|
||||||
|
|
||||||
def deinstallnetifs(self):
|
def deinstallnetifs(self) -> None:
|
||||||
"""
|
"""
|
||||||
Uninstall TAP devices. This invokes their shutdown method for
|
Uninstall TAP devices. This invokes their shutdown method for
|
||||||
any required cleanup; the device may be actually removed when
|
any required cleanup; the device may be actually removed when
|
||||||
|
@ -170,7 +186,9 @@ class EmaneNet(CoreNetworkBase):
|
||||||
netif.shutdown()
|
netif.shutdown()
|
||||||
netif.poshook = None
|
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.
|
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)
|
event.append(nemid, latitude=lat, longitude=lon, altitude=alt)
|
||||||
self.session.emane.service.publish(0, event)
|
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
|
Several NEMs have moved, from e.g. a WaypointMobilityModel
|
||||||
calculation. Generate an EMANE Location Event having several
|
calculation. Generate an EMANE Location Event having several
|
||||||
|
|
|
@ -15,7 +15,7 @@ class EmaneRfPipeModel(emanemodel.EmaneModel):
|
||||||
mac_xml = "rfpipemaclayer.xml"
|
mac_xml = "rfpipemaclayer.xml"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load(cls, emane_prefix):
|
def load(cls, emane_prefix: str) -> None:
|
||||||
cls.mac_defaults["pcrcurveuri"] = os.path.join(
|
cls.mac_defaults["pcrcurveuri"] = os.path.join(
|
||||||
emane_prefix, "share/emane/xml/models/mac/rfpipe/rfpipepcr.xml"
|
emane_prefix, "share/emane/xml/models/mac/rfpipe/rfpipepcr.xml"
|
||||||
)
|
)
|
||||||
|
|
|
@ -27,7 +27,7 @@ class EmaneTdmaModel(emanemodel.EmaneModel):
|
||||||
config_ignore = {schedule_name}
|
config_ignore = {schedule_name}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load(cls, emane_prefix):
|
def load(cls, emane_prefix: str) -> None:
|
||||||
cls.mac_defaults["pcrcurveuri"] = os.path.join(
|
cls.mac_defaults["pcrcurveuri"] = os.path.join(
|
||||||
emane_prefix,
|
emane_prefix,
|
||||||
"share/emane/xml/models/mac/tdmaeventscheduler/tdmabasemodelpcr.xml",
|
"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.
|
Logic to execute after the emane manager is finished with startup.
|
||||||
|
|
||||||
|
|
|
@ -3,13 +3,14 @@ import logging
|
||||||
import os
|
import os
|
||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
|
from typing import Mapping, Type
|
||||||
|
|
||||||
import core.services
|
import core.services
|
||||||
from core.emulator.session import Session
|
from core.emulator.session import Session
|
||||||
from core.services.coreservices import ServiceManager
|
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.
|
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.
|
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.
|
Create a CoreEmu object.
|
||||||
|
|
||||||
|
@ -57,7 +58,7 @@ class CoreEmu:
|
||||||
# catch exit event
|
# catch exit event
|
||||||
atexit.register(self.shutdown)
|
atexit.register(self.shutdown)
|
||||||
|
|
||||||
def load_services(self):
|
def load_services(self) -> None:
|
||||||
# load default services
|
# load default services
|
||||||
self.service_errors = core.services.load()
|
self.service_errors = core.services.load()
|
||||||
|
|
||||||
|
@ -70,7 +71,7 @@ class CoreEmu:
|
||||||
custom_service_errors = ServiceManager.add_services(service_path)
|
custom_service_errors = ServiceManager.add_services(service_path)
|
||||||
self.service_errors.extend(custom_service_errors)
|
self.service_errors.extend(custom_service_errors)
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self) -> None:
|
||||||
"""
|
"""
|
||||||
Shutdown all CORE session.
|
Shutdown all CORE session.
|
||||||
|
|
||||||
|
@ -83,7 +84,7 @@ class CoreEmu:
|
||||||
session = sessions[_id]
|
session = sessions[_id]
|
||||||
session.shutdown()
|
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.
|
Create a new CORE session.
|
||||||
|
|
||||||
|
@ -101,7 +102,7 @@ class CoreEmu:
|
||||||
self.sessions[_id] = session
|
self.sessions[_id] = session
|
||||||
return session
|
return session
|
||||||
|
|
||||||
def delete_session(self, _id):
|
def delete_session(self, _id: int) -> bool:
|
||||||
"""
|
"""
|
||||||
Shutdown and delete a CORE session.
|
Shutdown and delete a CORE session.
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ import os
|
||||||
import threading
|
import threading
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from tempfile import NamedTemporaryFile
|
from tempfile import NamedTemporaryFile
|
||||||
|
from typing import TYPE_CHECKING, Callable, Dict, Tuple
|
||||||
|
|
||||||
import netaddr
|
import netaddr
|
||||||
from fabric import Connection
|
from fabric import Connection
|
||||||
|
@ -17,6 +18,9 @@ from core.errors import CoreCommandError
|
||||||
from core.nodes.interface import GreTap
|
from core.nodes.interface import GreTap
|
||||||
from core.nodes.network import CoreNetwork, CtrlNet
|
from core.nodes.network import CoreNetwork, CtrlNet
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.emulator.session import Session
|
||||||
|
|
||||||
LOCK = threading.Lock()
|
LOCK = threading.Lock()
|
||||||
CMD_HIDE = True
|
CMD_HIDE = True
|
||||||
|
|
||||||
|
@ -26,7 +30,7 @@ class DistributedServer:
|
||||||
Provides distributed server interactions.
|
Provides distributed server interactions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name, host):
|
def __init__(self, name: str, host: str) -> None:
|
||||||
"""
|
"""
|
||||||
Create a DistributedServer instance.
|
Create a DistributedServer instance.
|
||||||
|
|
||||||
|
@ -38,7 +42,9 @@ class DistributedServer:
|
||||||
self.conn = Connection(host, user="root")
|
self.conn = Connection(host, user="root")
|
||||||
self.lock = threading.Lock()
|
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.
|
Run command remotely using server connection.
|
||||||
|
|
||||||
|
@ -73,7 +79,7 @@ class DistributedServer:
|
||||||
stdout, stderr = e.streams_for_display()
|
stdout, stderr = e.streams_for_display()
|
||||||
raise CoreCommandError(e.result.exited, cmd, stdout, stderr)
|
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.
|
Push file to remote server.
|
||||||
|
|
||||||
|
@ -84,7 +90,7 @@ class DistributedServer:
|
||||||
with self.lock:
|
with self.lock:
|
||||||
self.conn.put(source, destination)
|
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
|
Remote push file contents to a remote server, using a temp file as an
|
||||||
intermediate step.
|
intermediate step.
|
||||||
|
@ -106,11 +112,11 @@ class DistributedController:
|
||||||
Provides logic for dealing with remote tunnels and distributed servers.
|
Provides logic for dealing with remote tunnels and distributed servers.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, session):
|
def __init__(self, session: "Session") -> None:
|
||||||
"""
|
"""
|
||||||
Create
|
Create
|
||||||
|
|
||||||
:param session:
|
:param session: session
|
||||||
"""
|
"""
|
||||||
self.session = session
|
self.session = session
|
||||||
self.servers = OrderedDict()
|
self.servers = OrderedDict()
|
||||||
|
@ -119,7 +125,7 @@ class DistributedController:
|
||||||
"distributed_address", default=None
|
"distributed_address", default=None
|
||||||
)
|
)
|
||||||
|
|
||||||
def add_server(self, name, host):
|
def add_server(self, name: str, host: str) -> None:
|
||||||
"""
|
"""
|
||||||
Add distributed server configuration.
|
Add distributed server configuration.
|
||||||
|
|
||||||
|
@ -132,7 +138,7 @@ class DistributedController:
|
||||||
cmd = f"mkdir -p {self.session.session_dir}"
|
cmd = f"mkdir -p {self.session.session_dir}"
|
||||||
server.remote_cmd(cmd)
|
server.remote_cmd(cmd)
|
||||||
|
|
||||||
def execute(self, func):
|
def execute(self, func: Callable[[DistributedServer], None]) -> None:
|
||||||
"""
|
"""
|
||||||
Convenience for executing logic against all distributed servers.
|
Convenience for executing logic against all distributed servers.
|
||||||
|
|
||||||
|
@ -143,7 +149,7 @@ class DistributedController:
|
||||||
server = self.servers[name]
|
server = self.servers[name]
|
||||||
func(server)
|
func(server)
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self) -> None:
|
||||||
"""
|
"""
|
||||||
Shutdown logic for dealing with distributed tunnels and server session
|
Shutdown logic for dealing with distributed tunnels and server session
|
||||||
directories.
|
directories.
|
||||||
|
@ -165,7 +171,7 @@ class DistributedController:
|
||||||
# clear tunnels
|
# clear tunnels
|
||||||
self.tunnels.clear()
|
self.tunnels.clear()
|
||||||
|
|
||||||
def start(self):
|
def start(self) -> None:
|
||||||
"""
|
"""
|
||||||
Start distributed network tunnels.
|
Start distributed network tunnels.
|
||||||
|
|
||||||
|
@ -184,7 +190,9 @@ class DistributedController:
|
||||||
server = self.servers[name]
|
server = self.servers[name]
|
||||||
self.create_gre_tunnel(node, server)
|
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.
|
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
|
self.tunnels[key] = tunnel
|
||||||
return 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.
|
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
|
The hash(n1num), hash(n2num) values are used, so node numbers may be
|
||||||
|
@ -239,7 +247,7 @@ class DistributedController:
|
||||||
)
|
)
|
||||||
return key & 0xFFFFFFFF
|
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.
|
Return the GreTap between two nodes if it exists.
|
||||||
|
|
||||||
|
|
|
@ -1,40 +1,32 @@
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
import netaddr
|
import netaddr
|
||||||
|
|
||||||
from core import utils
|
from core import utils
|
||||||
|
from core.api.grpc.core_pb2 import LinkOptions
|
||||||
from core.emane.nodes import EmaneNet
|
from core.emane.nodes import EmaneNet
|
||||||
from core.emulator.enumerations import LinkTypes
|
from core.emulator.enumerations import LinkTypes
|
||||||
|
from core.nodes.base import CoreNetworkBase, CoreNode
|
||||||
|
from core.nodes.interface import CoreInterface
|
||||||
from core.nodes.physical import PhysicalNode
|
from core.nodes.physical import PhysicalNode
|
||||||
|
|
||||||
|
|
||||||
class IdGen:
|
class IdGen:
|
||||||
def __init__(self, _id=0):
|
def __init__(self, _id: int = 0) -> None:
|
||||||
self.id = _id
|
self.id = _id
|
||||||
|
|
||||||
def next(self):
|
def next(self) -> int:
|
||||||
self.id += 1
|
self.id += 1
|
||||||
return self.id
|
return self.id
|
||||||
|
|
||||||
|
|
||||||
def create_interface(node, network, interface_data):
|
def link_config(
|
||||||
"""
|
network: CoreNetworkBase,
|
||||||
Create an interface for a node on a network using provided interface data.
|
interface: CoreInterface,
|
||||||
|
link_options: LinkOptions,
|
||||||
:param node: node to create interface for
|
devname: str = None,
|
||||||
:param core.nodes.base.CoreNetworkBase network: network to associate interface with
|
interface_two: CoreInterface = None,
|
||||||
:param core.emulator.emudata.InterfaceData interface_data: interface data
|
) -> None:
|
||||||
: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):
|
|
||||||
"""
|
"""
|
||||||
Convenience method for configuring a link,
|
Convenience method for configuring a link,
|
||||||
|
|
||||||
|
@ -68,7 +60,7 @@ class NodeOptions:
|
||||||
Options for creating and updating nodes within core.
|
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.
|
Create a NodeOptions object.
|
||||||
|
|
||||||
|
@ -93,7 +85,7 @@ class NodeOptions:
|
||||||
self.image = image
|
self.image = image
|
||||||
self.emane = None
|
self.emane = None
|
||||||
|
|
||||||
def set_position(self, x, y):
|
def set_position(self, x: float, y: float) -> None:
|
||||||
"""
|
"""
|
||||||
Convenience method for setting position.
|
Convenience method for setting position.
|
||||||
|
|
||||||
|
@ -104,7 +96,7 @@ class NodeOptions:
|
||||||
self.x = x
|
self.x = x
|
||||||
self.y = y
|
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.
|
Convenience method for setting location.
|
||||||
|
|
||||||
|
@ -123,7 +115,7 @@ class LinkOptions:
|
||||||
Options for creating and updating links within core.
|
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.
|
Create a LinkOptions object.
|
||||||
|
|
||||||
|
@ -148,12 +140,96 @@ class LinkOptions:
|
||||||
self.opaque = None
|
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:
|
class IpPrefixes:
|
||||||
"""
|
"""
|
||||||
Convenience class to help generate IP4 and IP6 addresses for nodes within CORE.
|
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.
|
Creates an IpPrefixes object.
|
||||||
|
|
||||||
|
@ -171,7 +247,7 @@ class IpPrefixes:
|
||||||
if ip6_prefix:
|
if ip6_prefix:
|
||||||
self.ip6 = netaddr.IPNetwork(ip6_prefix)
|
self.ip6 = netaddr.IPNetwork(ip6_prefix)
|
||||||
|
|
||||||
def ip4_address(self, node):
|
def ip4_address(self, node: CoreNode) -> str:
|
||||||
"""
|
"""
|
||||||
Convenience method to return the IP4 address for a node.
|
Convenience method to return the IP4 address for a node.
|
||||||
|
|
||||||
|
@ -183,7 +259,7 @@ class IpPrefixes:
|
||||||
raise ValueError("ip4 prefixes have not been set")
|
raise ValueError("ip4 prefixes have not been set")
|
||||||
return str(self.ip4[node.id])
|
return str(self.ip4[node.id])
|
||||||
|
|
||||||
def ip6_address(self, node):
|
def ip6_address(self, node: CoreNode) -> str:
|
||||||
"""
|
"""
|
||||||
Convenience method to return the IP6 address for a node.
|
Convenience method to return the IP6 address for a node.
|
||||||
|
|
||||||
|
@ -195,7 +271,9 @@ class IpPrefixes:
|
||||||
raise ValueError("ip6 prefixes have not been set")
|
raise ValueError("ip6 prefixes have not been set")
|
||||||
return str(self.ip6[node.id])
|
return str(self.ip6[node.id])
|
||||||
|
|
||||||
def 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
|
Creates interface data for linking nodes, using the nodes unique id for
|
||||||
generation, along with a random mac address, unless provided.
|
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
|
||||||
"""
|
"""
|
||||||
|
node.newnetif(
|
||||||
def __init__(self, _id, name, mac, ip4, ip4_mask, ip6, ip6_mask):
|
network,
|
||||||
"""
|
addrlist=interface_data.get_addresses(),
|
||||||
Creates an InterfaceData object.
|
hwaddr=interface_data.mac,
|
||||||
|
ifindex=interface_data.id,
|
||||||
:param int _id: interface id
|
ifname=interface_data.name,
|
||||||
:param str name: name for interface
|
)
|
||||||
:param str mac: mac address
|
return node.netif(interface_data.id)
|
||||||
: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]
|
|
||||||
|
|
|
@ -12,14 +12,23 @@ import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Type
|
||||||
|
|
||||||
from core import constants, utils
|
from core import constants, utils
|
||||||
from core.emane.emanemanager import EmaneManager
|
from core.emane.emanemanager import EmaneManager
|
||||||
from core.emane.nodes import EmaneNet
|
from core.emane.nodes import EmaneNet
|
||||||
from core.emulator.data import EventData, ExceptionData, NodeData
|
from core.emulator.data import (
|
||||||
|
ConfigData,
|
||||||
|
EventData,
|
||||||
|
ExceptionData,
|
||||||
|
FileData,
|
||||||
|
LinkData,
|
||||||
|
NodeData,
|
||||||
|
)
|
||||||
from core.emulator.distributed import DistributedController
|
from core.emulator.distributed import DistributedController
|
||||||
from core.emulator.emudata import (
|
from core.emulator.emudata import (
|
||||||
IdGen,
|
IdGen,
|
||||||
|
InterfaceData,
|
||||||
LinkOptions,
|
LinkOptions,
|
||||||
NodeOptions,
|
NodeOptions,
|
||||||
create_interface,
|
create_interface,
|
||||||
|
@ -31,8 +40,9 @@ from core.errors import CoreError
|
||||||
from core.location.corelocation import CoreLocation
|
from core.location.corelocation import CoreLocation
|
||||||
from core.location.event import EventLoop
|
from core.location.event import EventLoop
|
||||||
from core.location.mobility import BasicRangeModel, MobilityManager
|
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.docker import DockerNode
|
||||||
|
from core.nodes.interface import GreTap
|
||||||
from core.nodes.lxd import LxcNode
|
from core.nodes.lxd import LxcNode
|
||||||
from core.nodes.network import (
|
from core.nodes.network import (
|
||||||
CtrlNet,
|
CtrlNet,
|
||||||
|
@ -45,7 +55,7 @@ from core.nodes.network import (
|
||||||
)
|
)
|
||||||
from core.nodes.physical import PhysicalNode, Rj45Node
|
from core.nodes.physical import PhysicalNode, Rj45Node
|
||||||
from core.plugins.sdt import Sdt
|
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 import corexml, corexmldeployment
|
||||||
from core.xml.corexml import CoreXmlReader, CoreXmlWriter
|
from core.xml.corexml import CoreXmlReader, CoreXmlWriter
|
||||||
|
|
||||||
|
@ -74,7 +84,9 @@ class Session:
|
||||||
CORE session manager.
|
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.
|
Create a Session instance.
|
||||||
|
|
||||||
|
@ -150,7 +162,7 @@ class Session:
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_node_class(cls, _type):
|
def get_node_class(cls, _type: NodeTypes) -> Type[NodeBase]:
|
||||||
"""
|
"""
|
||||||
Retrieve the class for a given node type.
|
Retrieve the class for a given node type.
|
||||||
|
|
||||||
|
@ -163,20 +175,25 @@ class Session:
|
||||||
return node_class
|
return node_class
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_node_type(cls, _class):
|
def get_node_type(cls, _class: Type[NodeBase]) -> NodeTypes:
|
||||||
"""
|
"""
|
||||||
Retrieve node type for a given node class.
|
Retrieve node type for a given node class.
|
||||||
|
|
||||||
:param _class: node class to get a node type for
|
:param _class: node class to get a node type for
|
||||||
:return: node type
|
:return: node type
|
||||||
:rtype: core.emulator.enumerations.NodeTypes
|
:rtype: core.emulator.enumerations.NodeTypes
|
||||||
|
:raises CoreError: when node type does not exist
|
||||||
"""
|
"""
|
||||||
node_type = NODES_TYPE.get(_class)
|
node_type = NODES_TYPE.get(_class)
|
||||||
if node_type is None:
|
if node_type is None:
|
||||||
raise CoreError(f"invalid node class: {_class}")
|
raise CoreError(f"invalid node class: {_class}")
|
||||||
return node_type
|
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.
|
Convenience method for retrieving nodes within link data.
|
||||||
|
|
||||||
|
@ -237,14 +254,15 @@ class Session:
|
||||||
)
|
)
|
||||||
return node_one, node_two, net_one, net_two, tunnel
|
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.
|
Objects to deal with when connecting/disconnecting wireless links.
|
||||||
|
|
||||||
:param list objects: possible objects to deal with
|
:param list objects: possible objects to deal with
|
||||||
:param bool connect: link interfaces if True, unlink otherwise
|
:param bool connect: link interfaces if True, unlink otherwise
|
||||||
:return: nothing
|
: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]
|
objects = [x for x in objects if x]
|
||||||
if len(objects) < 2:
|
if len(objects) < 2:
|
||||||
|
@ -277,20 +295,23 @@ class Session:
|
||||||
|
|
||||||
def add_link(
|
def add_link(
|
||||||
self,
|
self,
|
||||||
node_one_id,
|
node_one_id: int,
|
||||||
node_two_id,
|
node_two_id: int,
|
||||||
interface_one=None,
|
interface_one: InterfaceData = None,
|
||||||
interface_two=None,
|
interface_two: InterfaceData = None,
|
||||||
link_options=None,
|
link_options: LinkOptions = None,
|
||||||
):
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Add a link between nodes.
|
Add a link between nodes.
|
||||||
|
|
||||||
:param int node_one_id: node one id
|
:param int node_one_id: node one id
|
||||||
:param int node_two_id: node two 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_one: node one interface
|
||||||
:param core.emulator.emudata.InterfaceData interface_two: node two interface data, defaults to none
|
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_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
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
if not link_options:
|
if not link_options:
|
||||||
|
@ -406,12 +427,12 @@ class Session:
|
||||||
|
|
||||||
def delete_link(
|
def delete_link(
|
||||||
self,
|
self,
|
||||||
node_one_id,
|
node_one_id: int,
|
||||||
node_two_id,
|
node_two_id: int,
|
||||||
interface_one_id,
|
interface_one_id: int,
|
||||||
interface_two_id,
|
interface_two_id: int,
|
||||||
link_type=LinkTypes.WIRED,
|
link_type: LinkTypes = LinkTypes.WIRED,
|
||||||
):
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Delete a link between nodes.
|
Delete a link between nodes.
|
||||||
|
|
||||||
|
@ -512,12 +533,12 @@ class Session:
|
||||||
|
|
||||||
def update_link(
|
def update_link(
|
||||||
self,
|
self,
|
||||||
node_one_id,
|
node_one_id: int,
|
||||||
node_two_id,
|
node_two_id: int,
|
||||||
interface_one_id=None,
|
interface_one_id: int = None,
|
||||||
interface_two_id=None,
|
interface_two_id: int = None,
|
||||||
link_options=None,
|
link_options: LinkOptions = None,
|
||||||
):
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Update link information between nodes.
|
Update link information between nodes.
|
||||||
|
|
||||||
|
@ -623,7 +644,13 @@ class Session:
|
||||||
if node_two:
|
if node_two:
|
||||||
node_two.lock.release()
|
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.
|
Add a node to the session, based on the provided node data.
|
||||||
|
|
||||||
|
@ -717,14 +744,14 @@ class Session:
|
||||||
|
|
||||||
return node
|
return node
|
||||||
|
|
||||||
def edit_node(self, node_id, options):
|
def edit_node(self, node_id: int, options: NodeOptions) -> None:
|
||||||
"""
|
"""
|
||||||
Edit node information.
|
Edit node information.
|
||||||
|
|
||||||
:param int node_id: id of node to update
|
:param int node_id: id of node to update
|
||||||
:param core.emulator.emudata.NodeOptions options: data to update node with
|
:param core.emulator.emudata.NodeOptions options: data to update node with
|
||||||
:return: True if node updated, False otherwise
|
:return: True if node updated, False otherwise
|
||||||
:rtype: bool
|
:rtype: nothing
|
||||||
:raises core.CoreError: when node to update does not exist
|
:raises core.CoreError: when node to update does not exist
|
||||||
"""
|
"""
|
||||||
# get node to update
|
# get node to update
|
||||||
|
@ -737,7 +764,7 @@ class Session:
|
||||||
node.canvas = options.canvas
|
node.canvas = options.canvas
|
||||||
node.icon = options.icon
|
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.
|
Set position for a node, use lat/lon/alt if needed.
|
||||||
|
|
||||||
|
@ -767,7 +794,7 @@ class Session:
|
||||||
if using_lat_lon_alt:
|
if using_lat_lon_alt:
|
||||||
self.broadcast_node_location(node)
|
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.
|
Broadcast node location to all listeners.
|
||||||
|
|
||||||
|
@ -782,7 +809,7 @@ class Session:
|
||||||
)
|
)
|
||||||
self.broadcast_node(node_data)
|
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.
|
Start mobility for the provided node ids.
|
||||||
|
|
||||||
|
@ -791,7 +818,7 @@ class Session:
|
||||||
"""
|
"""
|
||||||
self.mobility.startup(node_ids)
|
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)
|
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)
|
logging.info("session(%s) checking if active: %s", self.id, result)
|
||||||
return 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.
|
Import a session from the EmulationScript XML format.
|
||||||
|
|
||||||
|
@ -832,7 +859,7 @@ class Session:
|
||||||
if start:
|
if start:
|
||||||
self.instantiate()
|
self.instantiate()
|
||||||
|
|
||||||
def save_xml(self, file_name):
|
def save_xml(self, file_name: str) -> None:
|
||||||
"""
|
"""
|
||||||
Export a session to the EmulationScript XML format.
|
Export a session to the EmulationScript XML format.
|
||||||
|
|
||||||
|
@ -841,7 +868,7 @@ class Session:
|
||||||
"""
|
"""
|
||||||
CoreXmlWriter(self).write(file_name)
|
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.
|
Store a hook from a received file message.
|
||||||
|
|
||||||
|
@ -855,7 +882,9 @@ class Session:
|
||||||
state = f":{state}"
|
state = f":{state}"
|
||||||
self.set_hook(state, file_name, source_name, data)
|
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.
|
Add a file to a node.
|
||||||
|
|
||||||
|
@ -873,7 +902,7 @@ class Session:
|
||||||
elif data is not None:
|
elif data is not None:
|
||||||
node.nodefile(file_name, data)
|
node.nodefile(file_name, data)
|
||||||
|
|
||||||
def clear(self):
|
def clear(self) -> None:
|
||||||
"""
|
"""
|
||||||
Clear all CORE session data. (nodes, hooks, etc)
|
Clear all CORE session data. (nodes, hooks, etc)
|
||||||
|
|
||||||
|
@ -889,7 +918,7 @@ class Session:
|
||||||
self.services.reset()
|
self.services.reset()
|
||||||
self.mobility.config_reset()
|
self.mobility.config_reset()
|
||||||
|
|
||||||
def start_events(self):
|
def start_events(self) -> None:
|
||||||
"""
|
"""
|
||||||
Start event loop.
|
Start event loop.
|
||||||
|
|
||||||
|
@ -897,7 +926,7 @@ class Session:
|
||||||
"""
|
"""
|
||||||
self.event_loop.run()
|
self.event_loop.run()
|
||||||
|
|
||||||
def mobility_event(self, event_data):
|
def mobility_event(self, event_data: EventData) -> None:
|
||||||
"""
|
"""
|
||||||
Handle a mobility event.
|
Handle a mobility event.
|
||||||
|
|
||||||
|
@ -906,7 +935,7 @@ class Session:
|
||||||
"""
|
"""
|
||||||
self.mobility.handleevent(event_data)
|
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.
|
Set session geospatial location.
|
||||||
|
|
||||||
|
@ -919,7 +948,7 @@ class Session:
|
||||||
self.location.setrefgeo(lat, lon, alt)
|
self.location.setrefgeo(lat, lon, alt)
|
||||||
self.location.refscale = scale
|
self.location.refscale = scale
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self) -> None:
|
||||||
"""
|
"""
|
||||||
Shutdown all session nodes and remove the session directory.
|
Shutdown all session nodes and remove the session directory.
|
||||||
"""
|
"""
|
||||||
|
@ -942,7 +971,7 @@ class Session:
|
||||||
for handler in self.shutdown_handlers:
|
for handler in self.shutdown_handlers:
|
||||||
handler(self)
|
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.
|
Handle event data that should be provided to event handler.
|
||||||
|
|
||||||
|
@ -953,7 +982,7 @@ class Session:
|
||||||
for handler in self.event_handlers:
|
for handler in self.event_handlers:
|
||||||
handler(event_data)
|
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.
|
Handle exception data that should be provided to exception handlers.
|
||||||
|
|
||||||
|
@ -964,7 +993,7 @@ class Session:
|
||||||
for handler in self.exception_handlers:
|
for handler in self.exception_handlers:
|
||||||
handler(exception_data)
|
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.
|
Handle node data that should be provided to node handlers.
|
||||||
|
|
||||||
|
@ -975,7 +1004,7 @@ class Session:
|
||||||
for handler in self.node_handlers:
|
for handler in self.node_handlers:
|
||||||
handler(node_data)
|
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.
|
Handle file data that should be provided to file handlers.
|
||||||
|
|
||||||
|
@ -986,7 +1015,7 @@ class Session:
|
||||||
for handler in self.file_handlers:
|
for handler in self.file_handlers:
|
||||||
handler(file_data)
|
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.
|
Handle config data that should be provided to config handlers.
|
||||||
|
|
||||||
|
@ -997,7 +1026,7 @@ class Session:
|
||||||
for handler in self.config_handlers:
|
for handler in self.config_handlers:
|
||||||
handler(config_data)
|
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.
|
Handle link data that should be provided to link handlers.
|
||||||
|
|
||||||
|
@ -1008,7 +1037,7 @@ class Session:
|
||||||
for handler in self.link_handlers:
|
for handler in self.link_handlers:
|
||||||
handler(link_data)
|
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.
|
Set the session's current state.
|
||||||
|
|
||||||
|
@ -1039,7 +1068,7 @@ class Session:
|
||||||
event_data = EventData(event_type=state_value, time=str(time.monotonic()))
|
event_data = EventData(event_type=state_value, time=str(time.monotonic()))
|
||||||
self.broadcast_event(event_data)
|
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.
|
Write the current state to a state file in the session dir.
|
||||||
|
|
||||||
|
@ -1053,9 +1082,10 @@ class Session:
|
||||||
except IOError:
|
except IOError:
|
||||||
logging.exception("error writing state file: %s", state)
|
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
|
:param int state: state to run hooks for
|
||||||
:return: nothing
|
:return: nothing
|
||||||
|
@ -1075,7 +1105,9 @@ class Session:
|
||||||
else:
|
else:
|
||||||
logging.info("no state hooks for %s", state)
|
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.
|
Store a hook from a received file message.
|
||||||
|
|
||||||
|
@ -1107,13 +1139,13 @@ class Session:
|
||||||
logging.info("immediately running new state hook")
|
logging.info("immediately running new state hook")
|
||||||
self.run_hook(hook)
|
self.run_hook(hook)
|
||||||
|
|
||||||
def del_hooks(self):
|
def del_hooks(self) -> None:
|
||||||
"""
|
"""
|
||||||
Clear the hook scripts dict.
|
Clear the hook scripts dict.
|
||||||
"""
|
"""
|
||||||
self._hooks.clear()
|
self._hooks.clear()
|
||||||
|
|
||||||
def run_hook(self, hook):
|
def run_hook(self, hook: Tuple[str, str]) -> None:
|
||||||
"""
|
"""
|
||||||
Run a hook.
|
Run a hook.
|
||||||
|
|
||||||
|
@ -1154,7 +1186,7 @@ class Session:
|
||||||
except (OSError, subprocess.CalledProcessError):
|
except (OSError, subprocess.CalledProcessError):
|
||||||
logging.exception("error running hook: %s", file_name)
|
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.
|
Run state hooks.
|
||||||
|
|
||||||
|
@ -1174,7 +1206,7 @@ class Session:
|
||||||
ExceptionLevels.ERROR, "Session.run_state_hooks", None, message
|
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.
|
Add a state hook.
|
||||||
|
|
||||||
|
@ -1190,18 +1222,18 @@ class Session:
|
||||||
if self.state == state:
|
if self.state == state:
|
||||||
hook(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.
|
Delete a state hook.
|
||||||
|
|
||||||
:param int state: state to delete hook for
|
:param int state: state to delete hook for
|
||||||
:param func hook: hook to delete
|
:param func hook: hook to delete
|
||||||
:return:
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
hooks = self._state_hooks.setdefault(state, [])
|
hooks = self._state_hooks.setdefault(state, [])
|
||||||
hooks.remove(hook)
|
hooks.remove(hook)
|
||||||
|
|
||||||
def runtime_state_hook(self, state):
|
def runtime_state_hook(self, state: int) -> None:
|
||||||
"""
|
"""
|
||||||
Runtime state hook check.
|
Runtime state hook check.
|
||||||
|
|
||||||
|
@ -1217,7 +1249,7 @@ class Session:
|
||||||
corexmldeployment.CoreXmlDeployment(self, xml_writer.scenario)
|
corexmldeployment.CoreXmlDeployment(self, xml_writer.scenario)
|
||||||
xml_writer.write(xml_file_name)
|
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.
|
Get an environment suitable for a subprocess.Popen call.
|
||||||
This is the current process environment with some session-specific
|
This is the current process environment with some session-specific
|
||||||
|
@ -1265,7 +1297,7 @@ class Session:
|
||||||
|
|
||||||
return env
|
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.
|
Set the thumbnail filename. Move files from /tmp to session dir.
|
||||||
|
|
||||||
|
@ -1281,7 +1313,7 @@ class Session:
|
||||||
shutil.copy(thumb_file, destination_file)
|
shutil.copy(thumb_file, destination_file)
|
||||||
self.thumbnail = 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
|
Set the username for this session. Update the permissions of the
|
||||||
session dir to allow the user write access.
|
session dir to allow the user write access.
|
||||||
|
@ -1299,7 +1331,7 @@ class Session:
|
||||||
|
|
||||||
self.user = user
|
self.user = user
|
||||||
|
|
||||||
def get_node_id(self):
|
def get_node_id(self) -> int:
|
||||||
"""
|
"""
|
||||||
Return a unique, new node id.
|
Return a unique, new node id.
|
||||||
"""
|
"""
|
||||||
|
@ -1308,10 +1340,9 @@ class Session:
|
||||||
node_id = random.randint(1, 0xFFFF)
|
node_id = random.randint(1, 0xFFFF)
|
||||||
if node_id not in self.nodes:
|
if node_id not in self.nodes:
|
||||||
break
|
break
|
||||||
|
|
||||||
return node_id
|
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.
|
Create an emulation node.
|
||||||
|
|
||||||
|
@ -1322,29 +1353,27 @@ class Session:
|
||||||
:raises core.CoreError: when id of the node to create already exists
|
:raises core.CoreError: when id of the node to create already exists
|
||||||
"""
|
"""
|
||||||
node = cls(self, *args, **kwargs)
|
node = cls(self, *args, **kwargs)
|
||||||
|
|
||||||
with self._nodes_lock:
|
with self._nodes_lock:
|
||||||
if node.id in self.nodes:
|
if node.id in self.nodes:
|
||||||
node.shutdown()
|
node.shutdown()
|
||||||
raise CoreError(f"duplicate node id {node.id} for {node.name}")
|
raise CoreError(f"duplicate node id {node.id} for {node.name}")
|
||||||
self.nodes[node.id] = node
|
self.nodes[node.id] = node
|
||||||
|
|
||||||
return node
|
return node
|
||||||
|
|
||||||
def get_node(self, _id):
|
def get_node(self, _id: int) -> NodeBase:
|
||||||
"""
|
"""
|
||||||
Get a session node.
|
Get a session node.
|
||||||
|
|
||||||
:param int _id: node id to retrieve
|
:param int _id: node id to retrieve
|
||||||
:return: node for the given id
|
:return: node for the given id
|
||||||
:rtype: core.nodes.base.CoreNode
|
:rtype: core.nodes.base.NodeBase
|
||||||
:raises core.CoreError: when node does not exist
|
:raises core.CoreError: when node does not exist
|
||||||
"""
|
"""
|
||||||
if _id not in self.nodes:
|
if _id not in self.nodes:
|
||||||
raise CoreError(f"unknown node id {_id}")
|
raise CoreError(f"unknown node id {_id}")
|
||||||
return self.nodes[_id]
|
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.
|
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
|
return node is not None
|
||||||
|
|
||||||
def delete_nodes(self):
|
def delete_nodes(self) -> None:
|
||||||
"""
|
"""
|
||||||
Clear the nodes dictionary, and call shutdown for each node.
|
Clear the nodes dictionary, and call shutdown for each node.
|
||||||
"""
|
"""
|
||||||
|
@ -1377,7 +1406,7 @@ class Session:
|
||||||
utils.threadpool(funcs)
|
utils.threadpool(funcs)
|
||||||
self.node_id_gen.id = 0
|
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.
|
Write nodes to a 'nodes' file in the session dir.
|
||||||
The 'nodes' file lists: number, name, api-type, class-type
|
The 'nodes' file lists: number, name, api-type, class-type
|
||||||
|
@ -1392,7 +1421,7 @@ class Session:
|
||||||
except IOError:
|
except IOError:
|
||||||
logging.exception("error writing nodes file")
|
logging.exception("error writing nodes file")
|
||||||
|
|
||||||
def dump_session(self):
|
def dump_session(self) -> None:
|
||||||
"""
|
"""
|
||||||
Log information about the session in its current state.
|
Log information about the session in its current state.
|
||||||
"""
|
"""
|
||||||
|
@ -1405,7 +1434,9 @@ class Session:
|
||||||
len(self.nodes),
|
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.
|
Generate and broadcast an exception event.
|
||||||
|
|
||||||
|
@ -1425,27 +1456,28 @@ class Session:
|
||||||
)
|
)
|
||||||
self.broadcast_exception(exception_data)
|
self.broadcast_exception(exception_data)
|
||||||
|
|
||||||
def instantiate(self):
|
def instantiate(self) -> List[ServiceBootError]:
|
||||||
"""
|
"""
|
||||||
We have entered the instantiation state, invoke startup methods
|
We have entered the instantiation state, invoke startup methods
|
||||||
of various managers and boot the nodes. Validate nodes and check
|
of various managers and boot the nodes. Validate nodes and check
|
||||||
for transition to the runtime state.
|
for transition to the runtime state.
|
||||||
"""
|
|
||||||
|
|
||||||
|
:return: list of service boot errors during startup
|
||||||
|
"""
|
||||||
# write current nodes out to session directory file
|
# write current nodes out to session directory file
|
||||||
self.write_nodes()
|
self.write_nodes()
|
||||||
|
|
||||||
# create control net interfaces and network tunnels
|
# create control net interfaces and network tunnels
|
||||||
# which need to exist for emane to sync on location events
|
# which need to exist for emane to sync on location events
|
||||||
# in distributed scenarios
|
# in distributed scenarios
|
||||||
self.add_remove_control_interface(node=None, remove=False)
|
self.add_remove_control_net(0, remove=False)
|
||||||
|
|
||||||
# initialize distributed tunnels
|
# initialize distributed tunnels
|
||||||
self.distributed.start()
|
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:
|
if self.emane.startup() == self.emane.NOT_READY:
|
||||||
return
|
return []
|
||||||
|
|
||||||
# boot node services and then start mobility
|
# boot node services and then start mobility
|
||||||
exceptions = self.boot_nodes()
|
exceptions = self.boot_nodes()
|
||||||
|
@ -1462,12 +1494,13 @@ class Session:
|
||||||
self.check_runtime()
|
self.check_runtime()
|
||||||
return exceptions
|
return exceptions
|
||||||
|
|
||||||
def get_node_count(self):
|
def get_node_count(self) -> int:
|
||||||
"""
|
"""
|
||||||
Returns the number of CoreNodes and CoreNets, except for those
|
Returns the number of CoreNodes and CoreNets, except for those
|
||||||
that are not considered in the GUI's node count.
|
that are not considered in the GUI's node count.
|
||||||
"""
|
|
||||||
|
|
||||||
|
:return: created node count
|
||||||
|
"""
|
||||||
with self._nodes_lock:
|
with self._nodes_lock:
|
||||||
count = 0
|
count = 0
|
||||||
for node_id in self.nodes:
|
for node_id in self.nodes:
|
||||||
|
@ -1480,14 +1513,15 @@ class Session:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
count += 1
|
count += 1
|
||||||
|
|
||||||
return count
|
return count
|
||||||
|
|
||||||
def check_runtime(self):
|
def check_runtime(self) -> None:
|
||||||
"""
|
"""
|
||||||
Check if we have entered the runtime state, that all nodes have been
|
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
|
started and the emulation is running. Start the event loop once we
|
||||||
have entered runtime (time=0).
|
have entered runtime (time=0).
|
||||||
|
|
||||||
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
# this is called from instantiate() after receiving an event message
|
# this is called from instantiate() after receiving an event message
|
||||||
# for the instantiation state
|
# for the instantiation state
|
||||||
|
@ -1504,10 +1538,12 @@ class Session:
|
||||||
self.event_loop.run()
|
self.event_loop.run()
|
||||||
self.set_state(EventTypes.RUNTIME_STATE, send_event=True)
|
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
|
Tear down a running session. Stop the event loop and any running
|
||||||
nodes, and perform clean-up.
|
nodes, and perform clean-up.
|
||||||
|
|
||||||
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
# stop event loop
|
# stop event loop
|
||||||
self.event_loop.stop()
|
self.event_loop.stop()
|
||||||
|
@ -1528,58 +1564,59 @@ class Session:
|
||||||
# update control interface hosts
|
# update control interface hosts
|
||||||
self.update_control_interface_hosts(remove=True)
|
self.update_control_interface_hosts(remove=True)
|
||||||
|
|
||||||
# remove all four possible control networks. Does nothing if ctrlnet is not
|
# remove all four possible control networks
|
||||||
# installed.
|
self.add_remove_control_net(0, remove=True)
|
||||||
self.add_remove_control_interface(node=None, net_index=0, remove=True)
|
self.add_remove_control_net(1, remove=True)
|
||||||
self.add_remove_control_interface(node=None, net_index=1, remove=True)
|
self.add_remove_control_net(2, remove=True)
|
||||||
self.add_remove_control_interface(node=None, net_index=2, remove=True)
|
self.add_remove_control_net(3, remove=True)
|
||||||
self.add_remove_control_interface(node=None, net_index=3, remove=True)
|
|
||||||
|
|
||||||
def check_shutdown(self):
|
def check_shutdown(self) -> bool:
|
||||||
"""
|
"""
|
||||||
Check if we have entered the shutdown state, when no running nodes
|
Check if we have entered the shutdown state, when no running nodes
|
||||||
and links remain.
|
and links remain.
|
||||||
|
|
||||||
|
:return: True if should shutdown, False otherwise
|
||||||
"""
|
"""
|
||||||
node_count = self.get_node_count()
|
node_count = self.get_node_count()
|
||||||
logging.debug(
|
logging.debug(
|
||||||
"session(%s) checking shutdown: %s nodes remaining", self.id, node_count
|
"session(%s) checking shutdown: %s nodes remaining", self.id, node_count
|
||||||
)
|
)
|
||||||
|
|
||||||
shutdown = False
|
shutdown = False
|
||||||
if node_count == 0:
|
if node_count == 0:
|
||||||
shutdown = True
|
shutdown = True
|
||||||
self.set_state(EventTypes.SHUTDOWN_STATE)
|
self.set_state(EventTypes.SHUTDOWN_STATE)
|
||||||
|
|
||||||
return shutdown
|
return shutdown
|
||||||
|
|
||||||
def short_session_id(self):
|
def short_session_id(self) -> str:
|
||||||
"""
|
"""
|
||||||
Return a shorter version of the session ID, appropriate for
|
Return a shorter version of the session ID, appropriate for
|
||||||
interface names, where length may be limited.
|
interface names, where length may be limited.
|
||||||
|
|
||||||
|
:return: short session id
|
||||||
"""
|
"""
|
||||||
ssid = (self.id >> 8) ^ (self.id & ((1 << 8) - 1))
|
ssid = (self.id >> 8) ^ (self.id & ((1 << 8) - 1))
|
||||||
return f"{ssid:x}"
|
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
|
Boot node by adding a control interface when necessary and starting
|
||||||
node services.
|
node services.
|
||||||
|
|
||||||
:param core.nodes.base.CoreNodeBase node: node to boot
|
:param core.nodes.base.CoreNode node: node to boot
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
logging.info("booting node(%s): %s", node.name, [x.name for x in node.services])
|
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.add_remove_control_interface(node=node, remove=False)
|
||||||
self.services.boot_services(node)
|
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
|
Invoke the boot() procedure for all nodes and send back node
|
||||||
messages to the GUI for node messages that had the status
|
messages to the GUI for node messages that had the status
|
||||||
request flag.
|
request flag.
|
||||||
|
|
||||||
:return: service boot exceptions
|
:return: service boot exceptions
|
||||||
:rtype: list[core.services.coreservices.ServiceBootError]
|
:rtype: list[Exception]
|
||||||
"""
|
"""
|
||||||
with self._nodes_lock:
|
with self._nodes_lock:
|
||||||
funcs = []
|
funcs = []
|
||||||
|
@ -1596,7 +1633,7 @@ class Session:
|
||||||
self.update_control_interface_hosts()
|
self.update_control_interface_hosts()
|
||||||
return exceptions
|
return exceptions
|
||||||
|
|
||||||
def get_control_net_prefixes(self):
|
def get_control_net_prefixes(self) -> List[str]:
|
||||||
"""
|
"""
|
||||||
Retrieve control net prefixes.
|
Retrieve control net prefixes.
|
||||||
|
|
||||||
|
@ -1608,13 +1645,11 @@ class Session:
|
||||||
p1 = self.options.get_config("controlnet1")
|
p1 = self.options.get_config("controlnet1")
|
||||||
p2 = self.options.get_config("controlnet2")
|
p2 = self.options.get_config("controlnet2")
|
||||||
p3 = self.options.get_config("controlnet3")
|
p3 = self.options.get_config("controlnet3")
|
||||||
|
|
||||||
if not p0 and p:
|
if not p0 and p:
|
||||||
p0 = p
|
p0 = p
|
||||||
|
|
||||||
return [p0, p1, p2, p3]
|
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.
|
Retrieve control net server interfaces.
|
||||||
|
|
||||||
|
@ -1629,7 +1664,7 @@ class Session:
|
||||||
d3 = self.options.get_config("controlnetif3")
|
d3 = self.options.get_config("controlnetif3")
|
||||||
return [None, d1, d2, d3]
|
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.
|
Retrieve control net index.
|
||||||
|
|
||||||
|
@ -1645,10 +1680,22 @@ class Session:
|
||||||
return index
|
return index
|
||||||
return -1
|
return -1
|
||||||
|
|
||||||
def get_control_net(self, net_index):
|
def get_control_net(self, net_index: int) -> CtrlNet:
|
||||||
return self.get_node(CTRL_NET_ID + net_index)
|
"""
|
||||||
|
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.
|
Create a control network bridge as necessary.
|
||||||
When the remove flag is True, remove the bridge that connects control
|
When the remove flag is True, remove the bridge that connects control
|
||||||
|
@ -1682,11 +1729,9 @@ class Session:
|
||||||
# return any existing controlnet bridge
|
# return any existing controlnet bridge
|
||||||
try:
|
try:
|
||||||
control_net = self.get_control_net(net_index)
|
control_net = self.get_control_net(net_index)
|
||||||
|
|
||||||
if remove:
|
if remove:
|
||||||
self.delete_node(control_net.id)
|
self.delete_node(control_net.id)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return control_net
|
return control_net
|
||||||
except CoreError:
|
except CoreError:
|
||||||
if remove:
|
if remove:
|
||||||
|
@ -1730,12 +1775,15 @@ class Session:
|
||||||
updown_script=updown_script,
|
updown_script=updown_script,
|
||||||
serverintf=server_interface,
|
serverintf=server_interface,
|
||||||
)
|
)
|
||||||
|
|
||||||
return control_net
|
return control_net
|
||||||
|
|
||||||
def add_remove_control_interface(
|
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
|
Add a control interface to a node when a 'controlnet' prefix is
|
||||||
listed in the config file or session options. Uses
|
listed in the config file or session options. Uses
|
||||||
|
@ -1782,7 +1830,9 @@ class Session:
|
||||||
)
|
)
|
||||||
node.netif(interface1).control = True
|
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.
|
Add the IP addresses of control interfaces to the /etc/hosts file.
|
||||||
|
|
||||||
|
@ -1813,10 +1863,9 @@ class Session:
|
||||||
entries.append(f"{address} {name}")
|
entries.append(f"{address} {name}")
|
||||||
|
|
||||||
logging.info("Adding %d /etc/hosts file entries.", len(entries))
|
logging.info("Adding %d /etc/hosts file entries.", len(entries))
|
||||||
|
|
||||||
utils.file_munge("/etc/hosts", header, "\n".join(entries) + "\n")
|
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
|
Return the current time we have been in the runtime state, or zero
|
||||||
if not in runtime.
|
if not in runtime.
|
||||||
|
@ -1826,7 +1875,13 @@ class Session:
|
||||||
else:
|
else:
|
||||||
return 0.0
|
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
|
Add an event to the event queue, with a start time relative to the
|
||||||
start of the runtime state.
|
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
|
# TODO: if data is None, this blows up, but this ties into how event functions
|
||||||
# are ran, need to clean that up
|
# 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.
|
Run a scheduled event, executing commands in the data string.
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from core.config import ConfigurableManager, ConfigurableOptions, Configuration
|
from core.config import ConfigurableManager, ConfigurableOptions, Configuration
|
||||||
from core.emulator.enumerations import ConfigDataTypes, RegisterTlvs
|
from core.emulator.enumerations import ConfigDataTypes, RegisterTlvs
|
||||||
from core.plugins.sdt import Sdt
|
from core.plugins.sdt import Sdt
|
||||||
|
@ -60,29 +62,53 @@ class SessionConfig(ConfigurableManager, ConfigurableOptions):
|
||||||
]
|
]
|
||||||
config_type = RegisterTlvs.UTILITY.value
|
config_type = RegisterTlvs.UTILITY.value
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.set_configs(self.default_values())
|
self.set_configs(self.default_values())
|
||||||
|
|
||||||
def get_config(
|
def get_config(
|
||||||
self,
|
self,
|
||||||
_id,
|
_id: str,
|
||||||
node_id=ConfigurableManager._default_node,
|
node_id: int = ConfigurableManager._default_node,
|
||||||
config_type=ConfigurableManager._default_type,
|
config_type: str = ConfigurableManager._default_type,
|
||||||
default=None,
|
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)
|
value = super().get_config(_id, node_id, config_type, default)
|
||||||
if value == "":
|
if value == "":
|
||||||
value = default
|
value = default
|
||||||
return value
|
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)
|
value = self.get_config(name)
|
||||||
if value is None:
|
if value is None:
|
||||||
return default
|
return default
|
||||||
return value.lower() == "true"
|
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)
|
value = self.get_config(name, default=default)
|
||||||
if value is not None:
|
if value is not None:
|
||||||
value = int(value)
|
value = int(value)
|
||||||
|
|
|
@ -9,7 +9,7 @@ class CoreCommandError(subprocess.CalledProcessError):
|
||||||
Used when encountering internal CORE command errors.
|
Used when encountering internal CORE command errors.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
return (
|
return (
|
||||||
f"Command({self.cmd}), Status({self.returncode}):\n"
|
f"Command({self.cmd}), Status({self.returncode}):\n"
|
||||||
f"stdout: {self.output}\nstderr: {self.stderr}"
|
f"stdout: {self.output}\nstderr: {self.stderr}"
|
||||||
|
|
|
@ -17,7 +17,7 @@ HEIGHT = 800
|
||||||
|
|
||||||
|
|
||||||
class Application(tk.Frame):
|
class Application(tk.Frame):
|
||||||
def __init__(self, proxy):
|
def __init__(self, proxy: bool):
|
||||||
super().__init__(master=None)
|
super().__init__(master=None)
|
||||||
# load node icons
|
# load node icons
|
||||||
NodeUtils.setup()
|
NodeUtils.setup()
|
||||||
|
|
|
@ -5,6 +5,7 @@ import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import TYPE_CHECKING, Dict, List
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
|
|
||||||
|
@ -14,11 +15,16 @@ from core.gui.dialogs.mobilityplayer import MobilityPlayer
|
||||||
from core.gui.dialogs.sessions import SessionsDialog
|
from core.gui.dialogs.sessions import SessionsDialog
|
||||||
from core.gui.errors import show_grpc_error
|
from core.gui.errors import show_grpc_error
|
||||||
from core.gui.graph import tags
|
from core.gui.graph import tags
|
||||||
|
from core.gui.graph.edges import CanvasEdge
|
||||||
|
from core.gui.graph.node import CanvasNode
|
||||||
from core.gui.graph.shape import AnnotationData, Shape
|
from core.gui.graph.shape import AnnotationData, Shape
|
||||||
from core.gui.graph.shapeutils import ShapeType
|
from core.gui.graph.shapeutils import ShapeType
|
||||||
from core.gui.interface import InterfaceManager
|
from core.gui.interface import InterfaceManager
|
||||||
from core.gui.nodeutils import NodeDraw, NodeUtils
|
from core.gui.nodeutils import NodeDraw, NodeUtils
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
GUI_SOURCE = "gui"
|
GUI_SOURCE = "gui"
|
||||||
OBSERVERS = {
|
OBSERVERS = {
|
||||||
"processes": "ps",
|
"processes": "ps",
|
||||||
|
@ -34,20 +40,20 @@ OBSERVERS = {
|
||||||
|
|
||||||
|
|
||||||
class CoreServer:
|
class CoreServer:
|
||||||
def __init__(self, name, address, port):
|
def __init__(self, name: str, address: str, port: int):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.address = address
|
self.address = address
|
||||||
self.port = port
|
self.port = port
|
||||||
|
|
||||||
|
|
||||||
class Observer:
|
class Observer:
|
||||||
def __init__(self, name, cmd):
|
def __init__(self, name: str, cmd: str):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.cmd = cmd
|
self.cmd = cmd
|
||||||
|
|
||||||
|
|
||||||
class CoreClient:
|
class CoreClient:
|
||||||
def __init__(self, app, proxy):
|
def __init__(self, app: "Application", proxy: bool):
|
||||||
"""
|
"""
|
||||||
Create a CoreGrpc instance
|
Create a CoreGrpc instance
|
||||||
"""
|
"""
|
||||||
|
@ -110,7 +116,7 @@ class CoreClient:
|
||||||
self.handling_events.cancel()
|
self.handling_events.cancel()
|
||||||
self.handling_events = None
|
self.handling_events = None
|
||||||
|
|
||||||
def set_observer(self, value):
|
def set_observer(self, value: str):
|
||||||
self.observer = value
|
self.observer = value
|
||||||
|
|
||||||
def read_config(self):
|
def read_config(self):
|
||||||
|
@ -132,7 +138,7 @@ class CoreClient:
|
||||||
observer = Observer(config["name"], config["cmd"])
|
observer = Observer(config["name"], config["cmd"])
|
||||||
self.custom_observers[observer.name] = observer
|
self.custom_observers[observer.name] = observer
|
||||||
|
|
||||||
def handle_events(self, event):
|
def handle_events(self, event: core_pb2.Event):
|
||||||
if event.session_id != self.session_id:
|
if event.session_id != self.session_id:
|
||||||
logging.warning(
|
logging.warning(
|
||||||
"ignoring event session(%s) current(%s)",
|
"ignoring event session(%s) current(%s)",
|
||||||
|
@ -170,7 +176,7 @@ class CoreClient:
|
||||||
else:
|
else:
|
||||||
logging.info("unhandled event: %s", event)
|
logging.info("unhandled event: %s", event)
|
||||||
|
|
||||||
def handle_link_event(self, event):
|
def handle_link_event(self, event: core_pb2.LinkEvent):
|
||||||
node_one_id = event.link.node_one_id
|
node_one_id = event.link.node_one_id
|
||||||
node_two_id = event.link.node_two_id
|
node_two_id = event.link.node_two_id
|
||||||
canvas_node_one = self.canvas_nodes[node_one_id]
|
canvas_node_one = self.canvas_nodes[node_one_id]
|
||||||
|
@ -183,7 +189,7 @@ class CoreClient:
|
||||||
else:
|
else:
|
||||||
logging.warning("unknown link event: %s", event.message_type)
|
logging.warning("unknown link event: %s", event.message_type)
|
||||||
|
|
||||||
def handle_node_event(self, event):
|
def handle_node_event(self, event: core_pb2.NodeEvent):
|
||||||
if event.source == GUI_SOURCE:
|
if event.source == GUI_SOURCE:
|
||||||
return
|
return
|
||||||
node_id = event.node.id
|
node_id = event.node.id
|
||||||
|
@ -201,7 +207,7 @@ class CoreClient:
|
||||||
self.handling_throughputs.cancel()
|
self.handling_throughputs.cancel()
|
||||||
self.handling_throughputs = None
|
self.handling_throughputs = None
|
||||||
|
|
||||||
def handle_throughputs(self, event):
|
def handle_throughputs(self, event: core_pb2.ThroughputsEvent):
|
||||||
if event.session_id != self.session_id:
|
if event.session_id != self.session_id:
|
||||||
logging.warning(
|
logging.warning(
|
||||||
"ignoring throughput event session(%s) current(%s)",
|
"ignoring throughput event session(%s) current(%s)",
|
||||||
|
@ -212,11 +218,11 @@ class CoreClient:
|
||||||
logging.info("handling throughputs event: %s", event)
|
logging.info("handling throughputs event: %s", event)
|
||||||
self.app.canvas.set_throughputs(event)
|
self.app.canvas.set_throughputs(event)
|
||||||
|
|
||||||
def handle_exception_event(self, event):
|
def handle_exception_event(self, event: core_pb2.ExceptionEvent):
|
||||||
logging.info("exception event: %s", event)
|
logging.info("exception event: %s", event)
|
||||||
self.app.statusbar.core_alarms.append(event)
|
self.app.statusbar.core_alarms.append(event)
|
||||||
|
|
||||||
def join_session(self, session_id, query_location=True):
|
def join_session(self, session_id: int, query_location: bool = True):
|
||||||
# update session and title
|
# update session and title
|
||||||
self.session_id = session_id
|
self.session_id = session_id
|
||||||
self.master.title(f"CORE Session({self.session_id})")
|
self.master.title(f"CORE Session({self.session_id})")
|
||||||
|
@ -297,10 +303,10 @@ class CoreClient:
|
||||||
# update ui to represent current state
|
# update ui to represent current state
|
||||||
self.app.after(0, self.app.joined_session_update)
|
self.app.after(0, self.app.joined_session_update)
|
||||||
|
|
||||||
def is_runtime(self):
|
def is_runtime(self) -> bool:
|
||||||
return self.state == core_pb2.SessionState.RUNTIME
|
return self.state == core_pb2.SessionState.RUNTIME
|
||||||
|
|
||||||
def parse_metadata(self, config):
|
def parse_metadata(self, config: Dict[str, str]):
|
||||||
# canvas setting
|
# canvas setting
|
||||||
canvas_config = config.get("canvas")
|
canvas_config = config.get("canvas")
|
||||||
logging.info("canvas metadata: %s", canvas_config)
|
logging.info("canvas metadata: %s", canvas_config)
|
||||||
|
@ -364,8 +370,6 @@ class CoreClient:
|
||||||
def create_new_session(self):
|
def create_new_session(self):
|
||||||
"""
|
"""
|
||||||
Create a new session
|
Create a new session
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
response = self.client.create_session()
|
response = self.client.create_session()
|
||||||
|
@ -384,7 +388,7 @@ class CoreClient:
|
||||||
except grpc.RpcError as e:
|
except grpc.RpcError as e:
|
||||||
self.app.after(0, show_grpc_error, e)
|
self.app.after(0, show_grpc_error, e)
|
||||||
|
|
||||||
def delete_session(self, session_id=None):
|
def delete_session(self, session_id: int = None):
|
||||||
if session_id is None:
|
if session_id is None:
|
||||||
session_id = self.session_id
|
session_id = self.session_id
|
||||||
try:
|
try:
|
||||||
|
@ -396,8 +400,6 @@ class CoreClient:
|
||||||
def set_up(self):
|
def set_up(self):
|
||||||
"""
|
"""
|
||||||
Query sessions, if there exist any, prompt whether to join one
|
Query sessions, if there exist any, prompt whether to join one
|
||||||
|
|
||||||
:return: existing sessions
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self.client.connect()
|
self.client.connect()
|
||||||
|
@ -425,7 +427,7 @@ class CoreClient:
|
||||||
self.app.after(0, show_grpc_error, e)
|
self.app.after(0, show_grpc_error, e)
|
||||||
self.app.close()
|
self.app.close()
|
||||||
|
|
||||||
def edit_node(self, core_node):
|
def edit_node(self, core_node: core_pb2.Node):
|
||||||
try:
|
try:
|
||||||
self.client.edit_node(
|
self.client.edit_node(
|
||||||
self.session_id, core_node.id, core_node.position, source=GUI_SOURCE
|
self.session_id, core_node.id, core_node.position, source=GUI_SOURCE
|
||||||
|
@ -433,7 +435,7 @@ class CoreClient:
|
||||||
except grpc.RpcError as e:
|
except grpc.RpcError as e:
|
||||||
self.app.after(0, show_grpc_error, e)
|
self.app.after(0, show_grpc_error, e)
|
||||||
|
|
||||||
def start_session(self):
|
def start_session(self) -> core_pb2.StartSessionResponse:
|
||||||
nodes = [x.core_node for x in self.canvas_nodes.values()]
|
nodes = [x.core_node for x in self.canvas_nodes.values()]
|
||||||
links = [x.link for x in self.links.values()]
|
links = [x.link for x in self.links.values()]
|
||||||
wlan_configs = self.get_wlan_configs_proto()
|
wlan_configs = self.get_wlan_configs_proto()
|
||||||
|
@ -476,7 +478,7 @@ class CoreClient:
|
||||||
self.app.after(0, show_grpc_error, e)
|
self.app.after(0, show_grpc_error, e)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def stop_session(self, session_id=None):
|
def stop_session(self, session_id: int = None) -> core_pb2.StartSessionResponse:
|
||||||
if not session_id:
|
if not session_id:
|
||||||
session_id = self.session_id
|
session_id = self.session_id
|
||||||
response = core_pb2.StopSessionResponse(result=False)
|
response = core_pb2.StopSessionResponse(result=False)
|
||||||
|
@ -518,21 +520,19 @@ class CoreClient:
|
||||||
response = self.client.set_session_metadata(self.session_id, metadata)
|
response = self.client.set_session_metadata(self.session_id, metadata)
|
||||||
logging.info("set session metadata: %s", response)
|
logging.info("set session metadata: %s", response)
|
||||||
|
|
||||||
def launch_terminal(self, node_id):
|
def launch_terminal(self, node_id: int):
|
||||||
try:
|
try:
|
||||||
terminal = self.app.guiconfig["preferences"]["terminal"]
|
terminal = self.app.guiconfig["preferences"]["terminal"]
|
||||||
response = self.client.get_node_terminal(self.session_id, node_id)
|
response = self.client.get_node_terminal(self.session_id, node_id)
|
||||||
logging.info("get terminal %s", response.terminal)
|
cmd = f'{terminal} "{response.terminal}" &'
|
||||||
os.system(f"{terminal} {response.terminal} &")
|
logging.info("launching terminal %s", cmd)
|
||||||
|
os.system(cmd)
|
||||||
except grpc.RpcError as e:
|
except grpc.RpcError as e:
|
||||||
self.app.after(0, show_grpc_error, e)
|
self.app.after(0, show_grpc_error, e)
|
||||||
|
|
||||||
def save_xml(self, file_path):
|
def save_xml(self, file_path: str):
|
||||||
"""
|
"""
|
||||||
Save core session as to an xml file
|
Save core session as to an xml file
|
||||||
|
|
||||||
:param str file_path: file path that user pick
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
if self.state != core_pb2.SessionState.RUNTIME:
|
if self.state != core_pb2.SessionState.RUNTIME:
|
||||||
|
@ -545,12 +545,9 @@ class CoreClient:
|
||||||
except grpc.RpcError as e:
|
except grpc.RpcError as e:
|
||||||
self.app.after(0, show_grpc_error, e)
|
self.app.after(0, show_grpc_error, e)
|
||||||
|
|
||||||
def open_xml(self, file_path):
|
def open_xml(self, file_path: str):
|
||||||
"""
|
"""
|
||||||
Open core xml
|
Open core xml
|
||||||
|
|
||||||
:param str file_path: file to open
|
|
||||||
:return: session id
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
response = self.client.open_xml(file_path)
|
response = self.client.open_xml(file_path)
|
||||||
|
@ -559,12 +556,21 @@ class CoreClient:
|
||||||
except grpc.RpcError as e:
|
except grpc.RpcError as e:
|
||||||
self.app.after(0, show_grpc_error, e)
|
self.app.after(0, show_grpc_error, e)
|
||||||
|
|
||||||
def get_node_service(self, node_id, service_name):
|
def get_node_service(
|
||||||
|
self, node_id: int, service_name: str
|
||||||
|
) -> core_pb2.NodeServiceData:
|
||||||
response = self.client.get_node_service(self.session_id, node_id, service_name)
|
response = self.client.get_node_service(self.session_id, node_id, service_name)
|
||||||
logging.debug("get node service %s", response)
|
logging.debug("get node service %s", response)
|
||||||
return response.service
|
return response.service
|
||||||
|
|
||||||
def set_node_service(self, node_id, service_name, startups, validations, shutdowns):
|
def set_node_service(
|
||||||
|
self,
|
||||||
|
node_id: int,
|
||||||
|
service_name: str,
|
||||||
|
startups: List[str],
|
||||||
|
validations: List[str],
|
||||||
|
shutdowns: List[str],
|
||||||
|
) -> core_pb2.NodeServiceData:
|
||||||
response = self.client.set_node_service(
|
response = self.client.set_node_service(
|
||||||
self.session_id, node_id, service_name, startups, validations, shutdowns
|
self.session_id, node_id, service_name, startups, validations, shutdowns
|
||||||
)
|
)
|
||||||
|
@ -573,14 +579,18 @@ class CoreClient:
|
||||||
logging.debug("get node service : %s", response)
|
logging.debug("get node service : %s", response)
|
||||||
return response.service
|
return response.service
|
||||||
|
|
||||||
def get_node_service_file(self, node_id, service_name, file_name):
|
def get_node_service_file(
|
||||||
|
self, node_id: int, service_name: str, file_name: str
|
||||||
|
) -> str:
|
||||||
response = self.client.get_node_service_file(
|
response = self.client.get_node_service_file(
|
||||||
self.session_id, node_id, service_name, file_name
|
self.session_id, node_id, service_name, file_name
|
||||||
)
|
)
|
||||||
logging.debug("get service file %s", response)
|
logging.debug("get service file %s", response)
|
||||||
return response.data
|
return response.data
|
||||||
|
|
||||||
def set_node_service_file(self, node_id, service_name, file_name, data):
|
def set_node_service_file(
|
||||||
|
self, node_id: int, service_name: str, file_name: str, data: bytes
|
||||||
|
):
|
||||||
response = self.client.set_node_service_file(
|
response = self.client.set_node_service_file(
|
||||||
self.session_id, node_id, service_name, file_name, data
|
self.session_id, node_id, service_name, file_name, data
|
||||||
)
|
)
|
||||||
|
@ -589,8 +599,6 @@ class CoreClient:
|
||||||
def create_nodes_and_links(self):
|
def create_nodes_and_links(self):
|
||||||
"""
|
"""
|
||||||
create nodes and links that have not been created yet
|
create nodes and links that have not been created yet
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
node_protos = [x.core_node for x in self.canvas_nodes.values()]
|
node_protos = [x.core_node for x in self.canvas_nodes.values()]
|
||||||
link_protos = [x.link for x in self.links.values()]
|
link_protos = [x.link for x in self.links.values()]
|
||||||
|
@ -617,8 +625,6 @@ class CoreClient:
|
||||||
def send_data(self):
|
def send_data(self):
|
||||||
"""
|
"""
|
||||||
send to daemon all session info, but don't start the session
|
send to daemon all session info, but don't start the session
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
self.create_nodes_and_links()
|
self.create_nodes_and_links()
|
||||||
for config_proto in self.get_wlan_configs_proto():
|
for config_proto in self.get_wlan_configs_proto():
|
||||||
|
@ -663,18 +669,13 @@ class CoreClient:
|
||||||
def close(self):
|
def close(self):
|
||||||
"""
|
"""
|
||||||
Clean ups when done using grpc
|
Clean ups when done using grpc
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
logging.debug("close grpc")
|
logging.debug("close grpc")
|
||||||
self.client.close()
|
self.client.close()
|
||||||
|
|
||||||
def next_node_id(self):
|
def next_node_id(self) -> int:
|
||||||
"""
|
"""
|
||||||
Get the next usable node id.
|
Get the next usable node id.
|
||||||
|
|
||||||
:return: the next id to be used
|
|
||||||
:rtype: int
|
|
||||||
"""
|
"""
|
||||||
i = 1
|
i = 1
|
||||||
while True:
|
while True:
|
||||||
|
@ -683,15 +684,11 @@ class CoreClient:
|
||||||
i += 1
|
i += 1
|
||||||
return i
|
return i
|
||||||
|
|
||||||
def create_node(self, x, y, node_type, model):
|
def create_node(
|
||||||
|
self, x: int, y: int, node_type: core_pb2.NodeType, model: str
|
||||||
|
) -> core_pb2.Node:
|
||||||
"""
|
"""
|
||||||
Add node, with information filled in, to grpc manager
|
Add node, with information filled in, to grpc manager
|
||||||
|
|
||||||
:param int x: x coord
|
|
||||||
:param int y: y coord
|
|
||||||
:param core_pb2.NodeType node_type: node type
|
|
||||||
:param str model: node model
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
node_id = self.next_node_id()
|
node_id = self.next_node_id()
|
||||||
position = core_pb2.Position(x=x, y=y)
|
position = core_pb2.Position(x=x, y=y)
|
||||||
|
@ -726,13 +723,10 @@ class CoreClient:
|
||||||
)
|
)
|
||||||
return node
|
return node
|
||||||
|
|
||||||
def delete_graph_nodes(self, canvas_nodes):
|
def delete_graph_nodes(self, canvas_nodes: List[core_pb2.Node]):
|
||||||
"""
|
"""
|
||||||
remove the nodes selected by the user and anything related to that node
|
remove the nodes selected by the user and anything related to that node
|
||||||
such as link, configurations, interfaces
|
such as link, configurations, interfaces
|
||||||
|
|
||||||
:param list canvas_nodes: list of nodes to delete
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
edges = set()
|
edges = set()
|
||||||
for canvas_node in canvas_nodes:
|
for canvas_node in canvas_nodes:
|
||||||
|
@ -754,12 +748,9 @@ class CoreClient:
|
||||||
if edge in edges:
|
if edge in edges:
|
||||||
continue
|
continue
|
||||||
edges.add(edge)
|
edges.add(edge)
|
||||||
#
|
|
||||||
# if edge.token not in self.links:
|
|
||||||
# logging.error("unknown edge: %s", edge.token)
|
|
||||||
self.links.pop(edge.token, None)
|
self.links.pop(edge.token, None)
|
||||||
|
|
||||||
def create_interface(self, canvas_node):
|
def create_interface(self, canvas_node: CanvasNode) -> core_pb2.Interface:
|
||||||
node = canvas_node.core_node
|
node = canvas_node.core_node
|
||||||
ip4, ip6, prefix = self.interfaces_manager.get_ips(node.id)
|
ip4, ip6, prefix = self.interfaces_manager.get_ips(node.id)
|
||||||
interface_id = len(canvas_node.interfaces)
|
interface_id = len(canvas_node.interfaces)
|
||||||
|
@ -776,16 +767,12 @@ class CoreClient:
|
||||||
)
|
)
|
||||||
return interface
|
return interface
|
||||||
|
|
||||||
def create_link(self, edge, canvas_src_node, canvas_dst_node):
|
def create_link(
|
||||||
|
self, edge: CanvasEdge, canvas_src_node: CanvasNode, canvas_dst_node: CanvasNode
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Create core link for a pair of canvas nodes, with token referencing
|
Create core link for a pair of canvas nodes, with token referencing
|
||||||
the canvas edge.
|
the canvas edge.
|
||||||
|
|
||||||
:param edge: edge for link
|
|
||||||
:param canvas_src_node: canvas node one
|
|
||||||
:param canvas_dst_node: canvas node two
|
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
src_node = canvas_src_node.core_node
|
src_node = canvas_src_node.core_node
|
||||||
dst_node = canvas_dst_node.core_node
|
dst_node = canvas_dst_node.core_node
|
||||||
|
@ -815,7 +802,7 @@ class CoreClient:
|
||||||
edge.set_link(link)
|
edge.set_link(link)
|
||||||
self.links[edge.token] = edge
|
self.links[edge.token] = edge
|
||||||
|
|
||||||
def get_wlan_configs_proto(self):
|
def get_wlan_configs_proto(self) -> List[core_pb2.WlanConfig]:
|
||||||
configs = []
|
configs = []
|
||||||
for node_id, config in self.wlan_configs.items():
|
for node_id, config in self.wlan_configs.items():
|
||||||
config = {x: config[x].value for x in config}
|
config = {x: config[x].value for x in config}
|
||||||
|
@ -823,7 +810,7 @@ class CoreClient:
|
||||||
configs.append(wlan_config)
|
configs.append(wlan_config)
|
||||||
return configs
|
return configs
|
||||||
|
|
||||||
def get_mobility_configs_proto(self):
|
def get_mobility_configs_proto(self) -> List[core_pb2.MobilityConfig]:
|
||||||
configs = []
|
configs = []
|
||||||
for node_id, config in self.mobility_configs.items():
|
for node_id, config in self.mobility_configs.items():
|
||||||
config = {x: config[x].value for x in config}
|
config = {x: config[x].value for x in config}
|
||||||
|
@ -831,7 +818,7 @@ class CoreClient:
|
||||||
configs.append(mobility_config)
|
configs.append(mobility_config)
|
||||||
return configs
|
return configs
|
||||||
|
|
||||||
def get_emane_model_configs_proto(self):
|
def get_emane_model_configs_proto(self) -> List[core_pb2.EmaneModelConfig]:
|
||||||
configs = []
|
configs = []
|
||||||
for key, config in self.emane_model_configs.items():
|
for key, config in self.emane_model_configs.items():
|
||||||
node_id, model, interface = key
|
node_id, model, interface = key
|
||||||
|
@ -844,7 +831,7 @@ class CoreClient:
|
||||||
configs.append(config_proto)
|
configs.append(config_proto)
|
||||||
return configs
|
return configs
|
||||||
|
|
||||||
def get_service_configs_proto(self):
|
def get_service_configs_proto(self) -> List[core_pb2.ServiceConfig]:
|
||||||
configs = []
|
configs = []
|
||||||
for node_id, services in self.service_configs.items():
|
for node_id, services in self.service_configs.items():
|
||||||
for name, config in services.items():
|
for name, config in services.items():
|
||||||
|
@ -858,7 +845,7 @@ class CoreClient:
|
||||||
configs.append(config_proto)
|
configs.append(config_proto)
|
||||||
return configs
|
return configs
|
||||||
|
|
||||||
def get_service_file_configs_proto(self):
|
def get_service_file_configs_proto(self) -> List[core_pb2.ServiceFileConfig]:
|
||||||
configs = []
|
configs = []
|
||||||
for (node_id, file_configs) in self.file_configs.items():
|
for (node_id, file_configs) in self.file_configs.items():
|
||||||
for service, file_config in file_configs.items():
|
for service, file_config in file_configs.items():
|
||||||
|
@ -869,25 +856,27 @@ class CoreClient:
|
||||||
configs.append(config_proto)
|
configs.append(config_proto)
|
||||||
return configs
|
return configs
|
||||||
|
|
||||||
def run(self, node_id):
|
def run(self, node_id: int) -> str:
|
||||||
logging.info("running node(%s) cmd: %s", node_id, self.observer)
|
logging.info("running node(%s) cmd: %s", node_id, self.observer)
|
||||||
return self.client.node_command(self.session_id, node_id, self.observer).output
|
return self.client.node_command(self.session_id, node_id, self.observer).output
|
||||||
|
|
||||||
def get_wlan_config(self, node_id):
|
def get_wlan_config(self, node_id: int) -> Dict[str, core_pb2.ConfigOption]:
|
||||||
config = self.wlan_configs.get(node_id)
|
config = self.wlan_configs.get(node_id)
|
||||||
if not config:
|
if not config:
|
||||||
response = self.client.get_wlan_config(self.session_id, node_id)
|
response = self.client.get_wlan_config(self.session_id, node_id)
|
||||||
config = response.config
|
config = response.config
|
||||||
return config
|
return config
|
||||||
|
|
||||||
def get_mobility_config(self, node_id):
|
def get_mobility_config(self, node_id: int) -> Dict[str, core_pb2.ConfigOption]:
|
||||||
config = self.mobility_configs.get(node_id)
|
config = self.mobility_configs.get(node_id)
|
||||||
if not config:
|
if not config:
|
||||||
response = self.client.get_mobility_config(self.session_id, node_id)
|
response = self.client.get_mobility_config(self.session_id, node_id)
|
||||||
config = response.config
|
config = response.config
|
||||||
return config
|
return config
|
||||||
|
|
||||||
def get_emane_model_config(self, node_id, model, interface=None):
|
def get_emane_model_config(
|
||||||
|
self, node_id: int, model: str, interface: int = None
|
||||||
|
) -> Dict[str, core_pb2.ConfigOption]:
|
||||||
logging.info("getting emane model config: %s %s %s", node_id, model, interface)
|
logging.info("getting emane model config: %s %s %s", node_id, model, interface)
|
||||||
config = self.emane_model_configs.get((node_id, model, interface))
|
config = self.emane_model_configs.get((node_id, model, interface))
|
||||||
if not config:
|
if not config:
|
||||||
|
@ -899,15 +888,21 @@ class CoreClient:
|
||||||
config = response.config
|
config = response.config
|
||||||
return config
|
return config
|
||||||
|
|
||||||
def set_emane_model_config(self, node_id, model, config, interface=None):
|
def set_emane_model_config(
|
||||||
|
self,
|
||||||
|
node_id: int,
|
||||||
|
model: str,
|
||||||
|
config: Dict[str, core_pb2.ConfigOption],
|
||||||
|
interface: int = None,
|
||||||
|
):
|
||||||
logging.info("setting emane model config: %s %s %s", node_id, model, interface)
|
logging.info("setting emane model config: %s %s %s", node_id, model, interface)
|
||||||
self.emane_model_configs[(node_id, model, interface)] = config
|
self.emane_model_configs[(node_id, model, interface)] = config
|
||||||
|
|
||||||
def copy_node_service(self, _from, _to):
|
def copy_node_service(self, _from: int, _to: int):
|
||||||
services = self.canvas_nodes[_from].core_node.services
|
services = self.canvas_nodes[_from].core_node.services
|
||||||
self.canvas_nodes[_to].core_node.services[:] = services
|
self.canvas_nodes[_to].core_node.services[:] = services
|
||||||
|
|
||||||
def copy_node_config(self, _from, _to):
|
def copy_node_config(self, _from: int, _to: int):
|
||||||
node_type = self.canvas_nodes[_from].core_node.type
|
node_type = self.canvas_nodes[_from].core_node.type
|
||||||
if node_type == core_pb2.NodeType.DEFAULT:
|
if node_type == core_pb2.NodeType.DEFAULT:
|
||||||
services = self.canvas_nodes[_from].core_node.services
|
services = self.canvas_nodes[_from].core_node.services
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from core.gui.dialogs.dialog import Dialog
|
from core.gui.dialogs.dialog import Dialog
|
||||||
from core.gui.widgets import CodeText
|
from core.gui.widgets import CodeText
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
LICENSE = """\
|
LICENSE = """\
|
||||||
Copyright (c) 2005-2020, the Boeing Company.
|
Copyright (c) 2005-2020, the Boeing Company.
|
||||||
|
|
||||||
|
@ -31,7 +35,7 @@ THE POSSIBILITY OF SUCH DAMAGE.\
|
||||||
|
|
||||||
|
|
||||||
class AboutDialog(Dialog):
|
class AboutDialog(Dialog):
|
||||||
def __init__(self, master, app):
|
def __init__(self, master: "Application", app: "Application"):
|
||||||
super().__init__(master, app, "About CORE", modal=True)
|
super().__init__(master, app, "About CORE", modal=True)
|
||||||
self.draw()
|
self.draw()
|
||||||
|
|
||||||
|
|
|
@ -3,15 +3,19 @@ check engine light
|
||||||
"""
|
"""
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from core.api.grpc.core_pb2 import ExceptionLevel
|
from core.api.grpc.core_pb2 import ExceptionLevel
|
||||||
from core.gui.dialogs.dialog import Dialog
|
from core.gui.dialogs.dialog import Dialog
|
||||||
from core.gui.themes import PADX, PADY
|
from core.gui.themes import PADX, PADY
|
||||||
from core.gui.widgets import CodeText
|
from core.gui.widgets import CodeText
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
|
|
||||||
class AlertsDialog(Dialog):
|
class AlertsDialog(Dialog):
|
||||||
def __init__(self, master, app):
|
def __init__(self, master: "Application", app: "Application"):
|
||||||
super().__init__(master, app, "Alerts", modal=True)
|
super().__init__(master, app, "Alerts", modal=True)
|
||||||
self.app = app
|
self.app = app
|
||||||
self.tree = None
|
self.tree = None
|
||||||
|
@ -110,7 +114,7 @@ class AlertsDialog(Dialog):
|
||||||
dialog = DaemonLog(self, self.app)
|
dialog = DaemonLog(self, self.app)
|
||||||
dialog.show()
|
dialog.show()
|
||||||
|
|
||||||
def click_select(self, event):
|
def click_select(self, event: tk.Event):
|
||||||
current = self.tree.selection()[0]
|
current = self.tree.selection()[0]
|
||||||
alarm = self.alarm_map[current]
|
alarm = self.alarm_map[current]
|
||||||
self.codetext.text.config(state=tk.NORMAL)
|
self.codetext.text.config(state=tk.NORMAL)
|
||||||
|
@ -120,7 +124,7 @@ class AlertsDialog(Dialog):
|
||||||
|
|
||||||
|
|
||||||
class DaemonLog(Dialog):
|
class DaemonLog(Dialog):
|
||||||
def __init__(self, master, app):
|
def __init__(self, master: tk.Widget, app: "Application"):
|
||||||
super().__init__(master, app, "core-daemon log", modal=True)
|
super().__init__(master, app, "core-daemon log", modal=True)
|
||||||
self.columnconfigure(0, weight=1)
|
self.columnconfigure(0, weight=1)
|
||||||
self.path = tk.StringVar(value="/var/log/core-daemon.log")
|
self.path = tk.StringVar(value="/var/log/core-daemon.log")
|
||||||
|
|
|
@ -3,19 +3,21 @@ size and scale
|
||||||
"""
|
"""
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import font, ttk
|
from tkinter import font, ttk
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from core.gui.dialogs.dialog import Dialog
|
from core.gui.dialogs.dialog import Dialog
|
||||||
from core.gui.themes import FRAME_PAD, PADX, PADY
|
from core.gui.themes import FRAME_PAD, PADX, PADY
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
PIXEL_SCALE = 100
|
PIXEL_SCALE = 100
|
||||||
|
|
||||||
|
|
||||||
class SizeAndScaleDialog(Dialog):
|
class SizeAndScaleDialog(Dialog):
|
||||||
def __init__(self, master, app):
|
def __init__(self, master: "Application", app: "Application"):
|
||||||
"""
|
"""
|
||||||
create an instance for size and scale object
|
create an instance for size and scale object
|
||||||
|
|
||||||
:param app: main application
|
|
||||||
"""
|
"""
|
||||||
super().__init__(master, app, "Canvas Size and Scale", modal=True)
|
super().__init__(master, app, "Canvas Size and Scale", modal=True)
|
||||||
self.canvas = self.app.canvas
|
self.canvas = self.app.canvas
|
||||||
|
|
|
@ -4,6 +4,7 @@ set wallpaper
|
||||||
import logging
|
import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from core.gui.appconfig import BACKGROUNDS_PATH
|
from core.gui.appconfig import BACKGROUNDS_PATH
|
||||||
from core.gui.dialogs.dialog import Dialog
|
from core.gui.dialogs.dialog import Dialog
|
||||||
|
@ -11,13 +12,14 @@ from core.gui.images import Images
|
||||||
from core.gui.themes import PADX, PADY
|
from core.gui.themes import PADX, PADY
|
||||||
from core.gui.widgets import image_chooser
|
from core.gui.widgets import image_chooser
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
|
|
||||||
class CanvasWallpaperDialog(Dialog):
|
class CanvasWallpaperDialog(Dialog):
|
||||||
def __init__(self, master, app):
|
def __init__(self, master: "Application", app: "Application"):
|
||||||
"""
|
"""
|
||||||
create an instance of CanvasWallpaper object
|
create an instance of CanvasWallpaper object
|
||||||
|
|
||||||
:param coretk.app.Application app: root application
|
|
||||||
"""
|
"""
|
||||||
super().__init__(master, app, "Canvas Background", modal=True)
|
super().__init__(master, app, "Canvas Background", modal=True)
|
||||||
self.canvas = self.app.canvas
|
self.canvas = self.app.canvas
|
||||||
|
@ -140,8 +142,6 @@ class CanvasWallpaperDialog(Dialog):
|
||||||
def click_clear(self):
|
def click_clear(self):
|
||||||
"""
|
"""
|
||||||
delete like shown in image link entry if there is any
|
delete like shown in image link entry if there is any
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
# delete entry
|
# delete entry
|
||||||
self.filename.set("")
|
self.filename.set("")
|
||||||
|
|
|
@ -4,12 +4,16 @@ custom color picker
|
||||||
import logging
|
import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
from core.gui.dialogs.dialog import Dialog
|
from core.gui.dialogs.dialog import Dialog
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
|
|
||||||
class ColorPickerDialog(Dialog):
|
class ColorPickerDialog(Dialog):
|
||||||
def __init__(self, master, app, initcolor="#000000"):
|
def __init__(self, master: Any, app: "Application", initcolor: str = "#000000"):
|
||||||
super().__init__(master, app, "color picker", modal=True)
|
super().__init__(master, app, "color picker", modal=True)
|
||||||
self.red_entry = None
|
self.red_entry = None
|
||||||
self.blue_entry = None
|
self.blue_entry = None
|
||||||
|
@ -31,7 +35,7 @@ class ColorPickerDialog(Dialog):
|
||||||
self.draw()
|
self.draw()
|
||||||
self.set_bindings()
|
self.set_bindings()
|
||||||
|
|
||||||
def askcolor(self):
|
def askcolor(self) -> str:
|
||||||
self.show()
|
self.show()
|
||||||
return self.color
|
return self.color
|
||||||
|
|
||||||
|
@ -175,19 +179,16 @@ class ColorPickerDialog(Dialog):
|
||||||
self.color = self.hex.get()
|
self.color = self.hex.get()
|
||||||
self.destroy()
|
self.destroy()
|
||||||
|
|
||||||
def get_hex(self):
|
def get_hex(self) -> str:
|
||||||
"""
|
"""
|
||||||
convert current RGB values into hex color
|
convert current RGB values into hex color
|
||||||
|
|
||||||
:rtype: str
|
|
||||||
:return: hex color
|
|
||||||
"""
|
"""
|
||||||
red = self.red_entry.get()
|
red = self.red_entry.get()
|
||||||
blue = self.blue_entry.get()
|
blue = self.blue_entry.get()
|
||||||
green = self.green_entry.get()
|
green = self.green_entry.get()
|
||||||
return "#%02x%02x%02x" % (int(red), int(green), int(blue))
|
return "#%02x%02x%02x" % (int(red), int(green), int(blue))
|
||||||
|
|
||||||
def current_focus(self, focus):
|
def current_focus(self, focus: str):
|
||||||
self.focus = focus
|
self.focus = focus
|
||||||
|
|
||||||
def update_color(self, arg1=None, arg2=None, arg3=None):
|
def update_color(self, arg1=None, arg2=None, arg3=None):
|
||||||
|
@ -210,35 +211,31 @@ class ColorPickerDialog(Dialog):
|
||||||
self.set_entry(red, green, blue)
|
self.set_entry(red, green, blue)
|
||||||
self.set_scale(red, green, blue)
|
self.set_scale(red, green, blue)
|
||||||
self.display.config(background=hex_code)
|
self.display.config(background=hex_code)
|
||||||
self.set_label(red, green, blue)
|
self.set_label(str(red), str(green), str(blue))
|
||||||
|
|
||||||
def scale_callback(self, var, color_var):
|
def scale_callback(self, var: tk.IntVar, color_var: tk.IntVar):
|
||||||
color_var.set(var.get())
|
color_var.set(var.get())
|
||||||
self.focus = "rgb"
|
self.focus = "rgb"
|
||||||
self.update_color()
|
self.update_color()
|
||||||
|
|
||||||
def set_scale(self, red, green, blue):
|
def set_scale(self, red: int, green: int, blue: int):
|
||||||
self.red_scale.set(red)
|
self.red_scale.set(red)
|
||||||
self.green_scale.set(green)
|
self.green_scale.set(green)
|
||||||
self.blue_scale.set(blue)
|
self.blue_scale.set(blue)
|
||||||
|
|
||||||
def set_entry(self, red, green, blue):
|
def set_entry(self, red: int, green: int, blue: int):
|
||||||
self.red.set(red)
|
self.red.set(red)
|
||||||
self.green.set(green)
|
self.green.set(green)
|
||||||
self.blue.set(blue)
|
self.blue.set(blue)
|
||||||
|
|
||||||
def set_label(self, red, green, blue):
|
def set_label(self, red: str, green: str, blue: str):
|
||||||
self.red_label.configure(background="#%02x%02x%02x" % (int(red), 0, 0))
|
self.red_label.configure(background="#%02x%02x%02x" % (int(red), 0, 0))
|
||||||
self.green_label.configure(background="#%02x%02x%02x" % (0, int(green), 0))
|
self.green_label.configure(background="#%02x%02x%02x" % (0, int(green), 0))
|
||||||
self.blue_label.configure(background="#%02x%02x%02x" % (0, 0, int(blue)))
|
self.blue_label.configure(background="#%02x%02x%02x" % (0, 0, int(blue)))
|
||||||
|
|
||||||
def get_rgb(self, hex_code):
|
def get_rgb(self, hex_code: str) -> [int, int, int]:
|
||||||
"""
|
"""
|
||||||
convert a valid hex code to RGB values
|
convert a valid hex code to RGB values
|
||||||
|
|
||||||
:param string hex_code: color in hex
|
|
||||||
:rtype: tuple(int, int, int)
|
|
||||||
:return: the RGB values
|
|
||||||
"""
|
"""
|
||||||
if len(hex_code) == 4:
|
if len(hex_code) == 4:
|
||||||
red = hex_code[1]
|
red = hex_code[1]
|
||||||
|
|
|
@ -5,14 +5,18 @@ copy service config dialog
|
||||||
import logging
|
import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING, Any, Tuple
|
||||||
|
|
||||||
from core.gui.dialogs.dialog import Dialog
|
from core.gui.dialogs.dialog import Dialog
|
||||||
from core.gui.themes import FRAME_PAD, PADX
|
from core.gui.themes import FRAME_PAD, PADX
|
||||||
from core.gui.widgets import CodeText
|
from core.gui.widgets import CodeText
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
|
|
||||||
class CopyServiceConfigDialog(Dialog):
|
class CopyServiceConfigDialog(Dialog):
|
||||||
def __init__(self, master, app, node_id):
|
def __init__(self, master: Any, app: "Application", node_id: int):
|
||||||
super().__init__(master, app, f"Copy services to node {node_id}", modal=True)
|
super().__init__(master, app, f"Copy services to node {node_id}", modal=True)
|
||||||
self.parent = master
|
self.parent = master
|
||||||
self.app = app
|
self.app = app
|
||||||
|
@ -128,6 +132,7 @@ class CopyServiceConfigDialog(Dialog):
|
||||||
|
|
||||||
def click_view(self):
|
def click_view(self):
|
||||||
selected = self.tree.selection()
|
selected = self.tree.selection()
|
||||||
|
data = ""
|
||||||
if selected:
|
if selected:
|
||||||
item = self.tree.item(selected[0])
|
item = self.tree.item(selected[0])
|
||||||
if "file" in item["tags"]:
|
if "file" in item["tags"]:
|
||||||
|
@ -157,7 +162,7 @@ class CopyServiceConfigDialog(Dialog):
|
||||||
)
|
)
|
||||||
dialog.show()
|
dialog.show()
|
||||||
|
|
||||||
def get_node_service(self, selected):
|
def get_node_service(self, selected: Tuple[str]) -> [int, str]:
|
||||||
service_tree_id = self.tree.parent(selected[0])
|
service_tree_id = self.tree.parent(selected[0])
|
||||||
service_name = self.tree.item(service_tree_id)["text"]
|
service_name = self.tree.item(service_tree_id)["text"]
|
||||||
node_tree_id = self.tree.parent(service_tree_id)
|
node_tree_id = self.tree.parent(service_tree_id)
|
||||||
|
@ -166,7 +171,14 @@ class CopyServiceConfigDialog(Dialog):
|
||||||
|
|
||||||
|
|
||||||
class ViewConfigDialog(Dialog):
|
class ViewConfigDialog(Dialog):
|
||||||
def __init__(self, master, app, node_id, data, filename=None):
|
def __init__(
|
||||||
|
self,
|
||||||
|
master: Any,
|
||||||
|
app: "Application",
|
||||||
|
node_id: int,
|
||||||
|
data: str,
|
||||||
|
filename: str = None,
|
||||||
|
):
|
||||||
super().__init__(master, app, f"n{node_id} config data", modal=True)
|
super().__init__(master, app, f"n{node_id} config data", modal=True)
|
||||||
self.data = data
|
self.data = data
|
||||||
self.service_data = None
|
self.service_data = None
|
||||||
|
|
|
@ -2,6 +2,7 @@ import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING, Any, Set
|
||||||
|
|
||||||
from core.gui import nodeutils
|
from core.gui import nodeutils
|
||||||
from core.gui.appconfig import ICONS_PATH
|
from core.gui.appconfig import ICONS_PATH
|
||||||
|
@ -11,9 +12,12 @@ from core.gui.nodeutils import NodeDraw
|
||||||
from core.gui.themes import FRAME_PAD, PADX, PADY
|
from core.gui.themes import FRAME_PAD, PADX, PADY
|
||||||
from core.gui.widgets import CheckboxList, ListboxScroll, image_chooser
|
from core.gui.widgets import CheckboxList, ListboxScroll, image_chooser
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
|
|
||||||
class ServicesSelectDialog(Dialog):
|
class ServicesSelectDialog(Dialog):
|
||||||
def __init__(self, master, app, current_services):
|
def __init__(self, master: Any, app: "Application", current_services: Set[str]):
|
||||||
super().__init__(master, app, "Node Services", modal=True)
|
super().__init__(master, app, "Node Services", modal=True)
|
||||||
self.groups = None
|
self.groups = None
|
||||||
self.services = None
|
self.services = None
|
||||||
|
@ -71,7 +75,7 @@ class ServicesSelectDialog(Dialog):
|
||||||
# trigger group change
|
# trigger group change
|
||||||
self.groups.listbox.event_generate("<<ListboxSelect>>")
|
self.groups.listbox.event_generate("<<ListboxSelect>>")
|
||||||
|
|
||||||
def handle_group_change(self, event):
|
def handle_group_change(self, event: tk.Event):
|
||||||
selection = self.groups.listbox.curselection()
|
selection = self.groups.listbox.curselection()
|
||||||
if selection:
|
if selection:
|
||||||
index = selection[0]
|
index = selection[0]
|
||||||
|
@ -81,7 +85,7 @@ class ServicesSelectDialog(Dialog):
|
||||||
checked = name in self.current_services
|
checked = name in self.current_services
|
||||||
self.services.add(name, checked)
|
self.services.add(name, checked)
|
||||||
|
|
||||||
def service_clicked(self, name, var):
|
def service_clicked(self, name: str, var: tk.BooleanVar):
|
||||||
if var.get() and name not in self.current_services:
|
if var.get() and name not in self.current_services:
|
||||||
self.current_services.add(name)
|
self.current_services.add(name)
|
||||||
elif not var.get() and name in self.current_services:
|
elif not var.get() and name in self.current_services:
|
||||||
|
@ -96,7 +100,7 @@ class ServicesSelectDialog(Dialog):
|
||||||
|
|
||||||
|
|
||||||
class CustomNodesDialog(Dialog):
|
class CustomNodesDialog(Dialog):
|
||||||
def __init__(self, master, app):
|
def __init__(self, master: "Application", app: "Application"):
|
||||||
super().__init__(master, app, "Custom Nodes", modal=True)
|
super().__init__(master, app, "Custom Nodes", modal=True)
|
||||||
self.edit_button = None
|
self.edit_button = None
|
||||||
self.delete_button = None
|
self.delete_button = None
|
||||||
|
@ -241,7 +245,7 @@ class CustomNodesDialog(Dialog):
|
||||||
self.nodes_list.listbox.selection_clear(0, tk.END)
|
self.nodes_list.listbox.selection_clear(0, tk.END)
|
||||||
self.nodes_list.listbox.event_generate("<<ListboxSelect>>")
|
self.nodes_list.listbox.event_generate("<<ListboxSelect>>")
|
||||||
|
|
||||||
def handle_node_select(self, event):
|
def handle_node_select(self, event: tk.Event):
|
||||||
selection = self.nodes_list.listbox.curselection()
|
selection = self.nodes_list.listbox.curselection()
|
||||||
if selection:
|
if selection:
|
||||||
self.selected_index = selection[0]
|
self.selected_index = selection[0]
|
||||||
|
|
|
@ -1,12 +1,18 @@
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from core.gui.images import ImageEnum, Images
|
from core.gui.images import ImageEnum, Images
|
||||||
from core.gui.themes import DIALOG_PAD
|
from core.gui.themes import DIALOG_PAD
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
|
|
||||||
class Dialog(tk.Toplevel):
|
class Dialog(tk.Toplevel):
|
||||||
def __init__(self, master, app, title, modal=False):
|
def __init__(
|
||||||
|
self, master: tk.Widget, app: "Application", title: str, modal: bool = False
|
||||||
|
):
|
||||||
super().__init__(master)
|
super().__init__(master)
|
||||||
self.withdraw()
|
self.withdraw()
|
||||||
self.app = app
|
self.app = app
|
||||||
|
@ -30,7 +36,7 @@ class Dialog(tk.Toplevel):
|
||||||
self.grab_set()
|
self.grab_set()
|
||||||
self.wait_window()
|
self.wait_window()
|
||||||
|
|
||||||
def draw_spacer(self, row=None):
|
def draw_spacer(self, row: int = None):
|
||||||
frame = ttk.Frame(self.top)
|
frame = ttk.Frame(self.top)
|
||||||
frame.grid(row=row, sticky="nsew")
|
frame.grid(row=row, sticky="nsew")
|
||||||
frame.rowconfigure(0, weight=1)
|
frame.rowconfigure(0, weight=1)
|
||||||
|
|
|
@ -5,18 +5,24 @@ import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
import webbrowser
|
import webbrowser
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
|
|
||||||
|
from core.api.grpc import core_pb2
|
||||||
from core.gui.dialogs.dialog import Dialog
|
from core.gui.dialogs.dialog import Dialog
|
||||||
from core.gui.errors import show_grpc_error
|
from core.gui.errors import show_grpc_error
|
||||||
from core.gui.images import ImageEnum, Images
|
from core.gui.images import ImageEnum, Images
|
||||||
from core.gui.themes import PADX, PADY
|
from core.gui.themes import PADX, PADY
|
||||||
from core.gui.widgets import ConfigFrame
|
from core.gui.widgets import ConfigFrame
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
from core.gui.graph.node import CanvasNode
|
||||||
|
|
||||||
|
|
||||||
class GlobalEmaneDialog(Dialog):
|
class GlobalEmaneDialog(Dialog):
|
||||||
def __init__(self, master, app):
|
def __init__(self, master: Any, app: "Application"):
|
||||||
super().__init__(master, app, "EMANE Configuration", modal=True)
|
super().__init__(master, app, "EMANE Configuration", modal=True)
|
||||||
self.config_frame = None
|
self.config_frame = None
|
||||||
self.draw()
|
self.draw()
|
||||||
|
@ -47,7 +53,14 @@ class GlobalEmaneDialog(Dialog):
|
||||||
|
|
||||||
|
|
||||||
class EmaneModelDialog(Dialog):
|
class EmaneModelDialog(Dialog):
|
||||||
def __init__(self, master, app, node, model, interface=None):
|
def __init__(
|
||||||
|
self,
|
||||||
|
master: Any,
|
||||||
|
app: "Application",
|
||||||
|
node: core_pb2.Node,
|
||||||
|
model: str,
|
||||||
|
interface: int = None,
|
||||||
|
):
|
||||||
super().__init__(master, app, f"{node.name} {model} Configuration", modal=True)
|
super().__init__(master, app, f"{node.name} {model} Configuration", modal=True)
|
||||||
self.node = node
|
self.node = node
|
||||||
self.model = f"emane_{model}"
|
self.model = f"emane_{model}"
|
||||||
|
@ -91,7 +104,9 @@ class EmaneModelDialog(Dialog):
|
||||||
|
|
||||||
|
|
||||||
class EmaneConfigDialog(Dialog):
|
class EmaneConfigDialog(Dialog):
|
||||||
def __init__(self, master, app, canvas_node):
|
def __init__(
|
||||||
|
self, master: "Application", app: "Application", canvas_node: "CanvasNode"
|
||||||
|
):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
master, app, f"{canvas_node.core_node.name} EMANE Configuration", modal=True
|
master, app, f"{canvas_node.core_node.name} EMANE Configuration", modal=True
|
||||||
)
|
)
|
||||||
|
@ -116,8 +131,6 @@ class EmaneConfigDialog(Dialog):
|
||||||
def draw_emane_configuration(self):
|
def draw_emane_configuration(self):
|
||||||
"""
|
"""
|
||||||
draw the main frame for emane configuration
|
draw the main frame for emane configuration
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
label = ttk.Label(
|
label = ttk.Label(
|
||||||
self.top,
|
self.top,
|
||||||
|
@ -143,8 +156,6 @@ class EmaneConfigDialog(Dialog):
|
||||||
def draw_emane_models(self):
|
def draw_emane_models(self):
|
||||||
"""
|
"""
|
||||||
create a combobox that has all the known emane models
|
create a combobox that has all the known emane models
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
frame = ttk.Frame(self.top)
|
frame = ttk.Frame(self.top)
|
||||||
frame.grid(sticky="ew", pady=PADY)
|
frame.grid(sticky="ew", pady=PADY)
|
||||||
|
@ -210,8 +221,6 @@ class EmaneConfigDialog(Dialog):
|
||||||
def click_model_config(self):
|
def click_model_config(self):
|
||||||
"""
|
"""
|
||||||
draw emane model configuration
|
draw emane model configuration
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
model_name = self.emane_model.get()
|
model_name = self.emane_model.get()
|
||||||
logging.info("configuring emane model: %s", model_name)
|
logging.info("configuring emane model: %s", model_name)
|
||||||
|
@ -220,12 +229,9 @@ class EmaneConfigDialog(Dialog):
|
||||||
)
|
)
|
||||||
dialog.show()
|
dialog.show()
|
||||||
|
|
||||||
def emane_model_change(self, event):
|
def emane_model_change(self, event: tk.Event):
|
||||||
"""
|
"""
|
||||||
update emane model options button
|
update emane model options button
|
||||||
|
|
||||||
:param event:
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
model_name = self.emane_model.get()
|
model_name = self.emane_model.get()
|
||||||
self.emane_model_button.config(text=f"{model_name} options")
|
self.emane_model_button.config(text=f"{model_name} options")
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
from core.api.grpc import core_pb2
|
from core.api.grpc import core_pb2
|
||||||
from core.gui.dialogs.dialog import Dialog
|
from core.gui.dialogs.dialog import Dialog
|
||||||
from core.gui.themes import PADX, PADY
|
from core.gui.themes import PADX, PADY
|
||||||
from core.gui.widgets import CodeText, ListboxScroll
|
from core.gui.widgets import CodeText, ListboxScroll
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
|
|
||||||
class HookDialog(Dialog):
|
class HookDialog(Dialog):
|
||||||
def __init__(self, master, app):
|
def __init__(self, master: Any, app: "Application"):
|
||||||
super().__init__(master, app, "Hook", modal=True)
|
super().__init__(master, app, "Hook", modal=True)
|
||||||
self.name = tk.StringVar()
|
self.name = tk.StringVar()
|
||||||
self.codetext = None
|
self.codetext = None
|
||||||
|
@ -62,11 +66,11 @@ class HookDialog(Dialog):
|
||||||
button = ttk.Button(frame, text="Cancel", command=lambda: self.destroy())
|
button = ttk.Button(frame, text="Cancel", command=lambda: self.destroy())
|
||||||
button.grid(row=0, column=1, sticky="ew")
|
button.grid(row=0, column=1, sticky="ew")
|
||||||
|
|
||||||
def state_change(self, event):
|
def state_change(self, event: tk.Event):
|
||||||
state_name = self.state.get()
|
state_name = self.state.get()
|
||||||
self.name.set(f"{state_name.lower()}_hook.sh")
|
self.name.set(f"{state_name.lower()}_hook.sh")
|
||||||
|
|
||||||
def set(self, hook):
|
def set(self, hook: core_pb2.Hook):
|
||||||
self.hook = hook
|
self.hook = hook
|
||||||
self.name.set(hook.file)
|
self.name.set(hook.file)
|
||||||
self.codetext.text.delete(1.0, tk.END)
|
self.codetext.text.delete(1.0, tk.END)
|
||||||
|
@ -84,7 +88,7 @@ class HookDialog(Dialog):
|
||||||
|
|
||||||
|
|
||||||
class HooksDialog(Dialog):
|
class HooksDialog(Dialog):
|
||||||
def __init__(self, master, app):
|
def __init__(self, master: "Application", app: "Application"):
|
||||||
super().__init__(master, app, "Hooks", modal=True)
|
super().__init__(master, app, "Hooks", modal=True)
|
||||||
self.listbox = None
|
self.listbox = None
|
||||||
self.edit_button = None
|
self.edit_button = None
|
||||||
|
@ -140,7 +144,7 @@ class HooksDialog(Dialog):
|
||||||
self.edit_button.config(state=tk.DISABLED)
|
self.edit_button.config(state=tk.DISABLED)
|
||||||
self.delete_button.config(state=tk.DISABLED)
|
self.delete_button.config(state=tk.DISABLED)
|
||||||
|
|
||||||
def select(self, event):
|
def select(self, event: tk.Event):
|
||||||
if self.listbox.curselection():
|
if self.listbox.curselection():
|
||||||
index = self.listbox.curselection()[0]
|
index = self.listbox.curselection()[0]
|
||||||
self.selected = self.listbox.get(index)
|
self.selected = self.listbox.get(index)
|
||||||
|
|
|
@ -4,14 +4,19 @@ link configuration
|
||||||
import logging
|
import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING, Union
|
||||||
|
|
||||||
from core.api.grpc import core_pb2
|
from core.api.grpc import core_pb2
|
||||||
from core.gui.dialogs.colorpicker import ColorPickerDialog
|
from core.gui.dialogs.colorpicker import ColorPickerDialog
|
||||||
from core.gui.dialogs.dialog import Dialog
|
from core.gui.dialogs.dialog import Dialog
|
||||||
from core.gui.themes import PADX, PADY
|
from core.gui.themes import PADX, PADY
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
from core.gui.graph.graph import CanvasGraph, CanvasEdge
|
||||||
|
|
||||||
def get_int(var):
|
|
||||||
|
def get_int(var: tk.StringVar) -> Union[int, None]:
|
||||||
value = var.get()
|
value = var.get()
|
||||||
if value != "":
|
if value != "":
|
||||||
return int(value)
|
return int(value)
|
||||||
|
@ -19,7 +24,7 @@ def get_int(var):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_float(var):
|
def get_float(var: tk.StringVar) -> Union[float, None]:
|
||||||
value = var.get()
|
value = var.get()
|
||||||
if value != "":
|
if value != "":
|
||||||
return float(value)
|
return float(value)
|
||||||
|
@ -28,7 +33,7 @@ def get_float(var):
|
||||||
|
|
||||||
|
|
||||||
class LinkConfigurationDialog(Dialog):
|
class LinkConfigurationDialog(Dialog):
|
||||||
def __init__(self, master, app, edge):
|
def __init__(self, master: "CanvasGraph", app: "Application", edge: "CanvasEdge"):
|
||||||
super().__init__(master, app, "Link Configuration", modal=True)
|
super().__init__(master, app, "Link Configuration", modal=True)
|
||||||
self.app = app
|
self.app = app
|
||||||
self.edge = edge
|
self.edge = edge
|
||||||
|
@ -103,7 +108,7 @@ class LinkConfigurationDialog(Dialog):
|
||||||
button = ttk.Button(frame, text="Cancel", command=self.destroy)
|
button = ttk.Button(frame, text="Cancel", command=self.destroy)
|
||||||
button.grid(row=0, column=1, sticky="ew")
|
button.grid(row=0, column=1, sticky="ew")
|
||||||
|
|
||||||
def get_frame(self):
|
def get_frame(self) -> ttk.Frame:
|
||||||
frame = ttk.Frame(self.top)
|
frame = ttk.Frame(self.top)
|
||||||
frame.columnconfigure(1, weight=1)
|
frame.columnconfigure(1, weight=1)
|
||||||
if self.is_symmetric:
|
if self.is_symmetric:
|
||||||
|
@ -339,8 +344,6 @@ class LinkConfigurationDialog(Dialog):
|
||||||
def load_link_config(self):
|
def load_link_config(self):
|
||||||
"""
|
"""
|
||||||
populate link config to the table
|
populate link config to the table
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
width = self.app.canvas.itemcget(self.edge.id, "width")
|
width = self.app.canvas.itemcget(self.edge.id, "width")
|
||||||
self.width.set(width)
|
self.width.set(width)
|
||||||
|
|
|
@ -4,15 +4,21 @@ marker dialog
|
||||||
|
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from core.gui.dialogs.colorpicker import ColorPickerDialog
|
from core.gui.dialogs.colorpicker import ColorPickerDialog
|
||||||
from core.gui.dialogs.dialog import Dialog
|
from core.gui.dialogs.dialog import Dialog
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
MARKER_THICKNESS = [3, 5, 8, 10]
|
MARKER_THICKNESS = [3, 5, 8, 10]
|
||||||
|
|
||||||
|
|
||||||
class MarkerDialog(Dialog):
|
class MarkerDialog(Dialog):
|
||||||
def __init__(self, master, app, initcolor="#000000"):
|
def __init__(
|
||||||
|
self, master: "Application", app: "Application", initcolor: str = "#000000"
|
||||||
|
):
|
||||||
super().__init__(master, app, "marker tool", modal=False)
|
super().__init__(master, app, "marker tool", modal=False)
|
||||||
self.app = app
|
self.app = app
|
||||||
self.color = initcolor
|
self.color = initcolor
|
||||||
|
@ -53,13 +59,13 @@ class MarkerDialog(Dialog):
|
||||||
for i in canvas.find_withtag("marker"):
|
for i in canvas.find_withtag("marker"):
|
||||||
canvas.delete(i)
|
canvas.delete(i)
|
||||||
|
|
||||||
def change_color(self, event):
|
def change_color(self, event: tk.Event):
|
||||||
color_picker = ColorPickerDialog(self, self.app, self.color)
|
color_picker = ColorPickerDialog(self, self.app, self.color)
|
||||||
color = color_picker.askcolor()
|
color = color_picker.askcolor()
|
||||||
event.widget.configure(background=color)
|
event.widget.configure(background=color)
|
||||||
self.color = color
|
self.color = color
|
||||||
|
|
||||||
def change_thickness(self, event):
|
def change_thickness(self, event: tk.Event):
|
||||||
self.radius = self.marker_thickness.get()
|
self.radius = self.marker_thickness.get()
|
||||||
|
|
||||||
def show(self):
|
def show(self):
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
mobility configuration
|
mobility configuration
|
||||||
"""
|
"""
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
|
|
||||||
|
@ -10,9 +11,15 @@ from core.gui.errors import show_grpc_error
|
||||||
from core.gui.themes import PADX, PADY
|
from core.gui.themes import PADX, PADY
|
||||||
from core.gui.widgets import ConfigFrame
|
from core.gui.widgets import ConfigFrame
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
from core.gui.graph.node import CanvasNode
|
||||||
|
|
||||||
|
|
||||||
class MobilityConfigDialog(Dialog):
|
class MobilityConfigDialog(Dialog):
|
||||||
def __init__(self, master, app, canvas_node):
|
def __init__(
|
||||||
|
self, master: "Application", app: "Application", canvas_node: "CanvasNode"
|
||||||
|
):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
master,
|
master,
|
||||||
app,
|
app,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
|
|
||||||
|
@ -9,11 +10,21 @@ from core.gui.errors import show_grpc_error
|
||||||
from core.gui.images import ImageEnum, Images
|
from core.gui.images import ImageEnum, Images
|
||||||
from core.gui.themes import PADX, PADY
|
from core.gui.themes import PADX, PADY
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
from core.gui.graph.node import CanvasNode
|
||||||
|
|
||||||
ICON_SIZE = 16
|
ICON_SIZE = 16
|
||||||
|
|
||||||
|
|
||||||
class MobilityPlayer:
|
class MobilityPlayer:
|
||||||
def __init__(self, master, app, canvas_node, config):
|
def __init__(
|
||||||
|
self,
|
||||||
|
master: "Application",
|
||||||
|
app: "Application",
|
||||||
|
canvas_node: "CanvasNode",
|
||||||
|
config,
|
||||||
|
):
|
||||||
self.master = master
|
self.master = master
|
||||||
self.app = app
|
self.app = app
|
||||||
self.canvas_node = canvas_node
|
self.canvas_node = canvas_node
|
||||||
|
@ -57,7 +68,9 @@ class MobilityPlayer:
|
||||||
|
|
||||||
|
|
||||||
class MobilityPlayerDialog(Dialog):
|
class MobilityPlayerDialog(Dialog):
|
||||||
def __init__(self, master, app, canvas_node, config):
|
def __init__(
|
||||||
|
self, master: Any, app: "Application", canvas_node: "CanvasNode", config
|
||||||
|
):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
master, app, f"{canvas_node.core_node.name} Mobility Player", modal=False
|
master, app, f"{canvas_node.core_node.name} Mobility Player", modal=False
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,6 +2,7 @@ import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from core.gui import nodeutils
|
from core.gui import nodeutils
|
||||||
from core.gui.appconfig import ICONS_PATH
|
from core.gui.appconfig import ICONS_PATH
|
||||||
|
@ -12,20 +13,32 @@ from core.gui.nodeutils import NodeUtils
|
||||||
from core.gui.themes import FRAME_PAD, PADX, PADY
|
from core.gui.themes import FRAME_PAD, PADX, PADY
|
||||||
from core.gui.widgets import ListboxScroll, image_chooser
|
from core.gui.widgets import ListboxScroll, image_chooser
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
from core.gui.graph.node import CanvasNode
|
||||||
|
|
||||||
def mac_auto(is_auto, entry):
|
|
||||||
|
def mac_auto(is_auto: tk.BooleanVar, entry: ttk.Entry):
|
||||||
logging.info("mac auto clicked")
|
logging.info("mac auto clicked")
|
||||||
if is_auto.get():
|
if is_auto.get():
|
||||||
logging.info("disabling mac")
|
logging.info("disabling mac")
|
||||||
entry.var.set("")
|
entry.delete(0, tk.END)
|
||||||
|
entry.insert(tk.END, "")
|
||||||
entry.config(state=tk.DISABLED)
|
entry.config(state=tk.DISABLED)
|
||||||
else:
|
else:
|
||||||
entry.var.set("00:00:00:00:00:00")
|
entry.delete(0, tk.END)
|
||||||
|
entry.insert(tk.END, "00:00:00:00:00:00")
|
||||||
entry.config(state=tk.NORMAL)
|
entry.config(state=tk.NORMAL)
|
||||||
|
|
||||||
|
|
||||||
class InterfaceData:
|
class InterfaceData:
|
||||||
def __init__(self, is_auto, mac, ip4, ip6):
|
def __init__(
|
||||||
|
self,
|
||||||
|
is_auto: tk.BooleanVar,
|
||||||
|
mac: tk.StringVar,
|
||||||
|
ip4: tk.StringVar,
|
||||||
|
ip6: tk.StringVar,
|
||||||
|
):
|
||||||
self.is_auto = is_auto
|
self.is_auto = is_auto
|
||||||
self.mac = mac
|
self.mac = mac
|
||||||
self.ip4 = ip4
|
self.ip4 = ip4
|
||||||
|
@ -33,13 +46,11 @@ class InterfaceData:
|
||||||
|
|
||||||
|
|
||||||
class NodeConfigDialog(Dialog):
|
class NodeConfigDialog(Dialog):
|
||||||
def __init__(self, master, app, canvas_node):
|
def __init__(
|
||||||
|
self, master: "Application", app: "Application", canvas_node: "CanvasNode"
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
create an instance of node configuration
|
create an instance of node configuration
|
||||||
|
|
||||||
:param master: dialog master
|
|
||||||
:param coretk.app.Application: main app
|
|
||||||
:param coretk.graph.CanvasNode canvas_node: canvas node object
|
|
||||||
"""
|
"""
|
||||||
super().__init__(
|
super().__init__(
|
||||||
master, app, f"{canvas_node.core_node.name} Configuration", modal=True
|
master, app, f"{canvas_node.core_node.name} Configuration", modal=True
|
||||||
|
@ -217,7 +228,7 @@ class NodeConfigDialog(Dialog):
|
||||||
button = ttk.Button(frame, text="Cancel", command=self.destroy)
|
button = ttk.Button(frame, text="Cancel", command=self.destroy)
|
||||||
button.grid(row=0, column=1, sticky="ew")
|
button.grid(row=0, column=1, sticky="ew")
|
||||||
|
|
||||||
def click_emane_config(self, emane_model, interface_id):
|
def click_emane_config(self, emane_model: str, interface_id: int):
|
||||||
dialog = EmaneModelDialog(self, self.app, self.node, emane_model, interface_id)
|
dialog = EmaneModelDialog(self, self.app, self.node, emane_model, interface_id)
|
||||||
dialog.show()
|
dialog.show()
|
||||||
|
|
||||||
|
@ -248,7 +259,7 @@ class NodeConfigDialog(Dialog):
|
||||||
self.canvas_node.redraw()
|
self.canvas_node.redraw()
|
||||||
self.destroy()
|
self.destroy()
|
||||||
|
|
||||||
def interface_select(self, event):
|
def interface_select(self, event: tk.Event):
|
||||||
listbox = event.widget
|
listbox = event.widget
|
||||||
cur = listbox.curselection()
|
cur = listbox.curselection()
|
||||||
if cur:
|
if cur:
|
||||||
|
|
|
@ -3,15 +3,26 @@ core node services
|
||||||
"""
|
"""
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import messagebox, ttk
|
from tkinter import messagebox, ttk
|
||||||
|
from typing import TYPE_CHECKING, Any, Set
|
||||||
|
|
||||||
from core.gui.dialogs.dialog import Dialog
|
from core.gui.dialogs.dialog import Dialog
|
||||||
from core.gui.dialogs.serviceconfig import ServiceConfigDialog
|
from core.gui.dialogs.serviceconfig import ServiceConfigDialog
|
||||||
from core.gui.themes import FRAME_PAD, PADX, PADY
|
from core.gui.themes import FRAME_PAD, PADX, PADY
|
||||||
from core.gui.widgets import CheckboxList, ListboxScroll
|
from core.gui.widgets import CheckboxList, ListboxScroll
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
from core.gui.graph.node import CanvasNode
|
||||||
|
|
||||||
|
|
||||||
class NodeServiceDialog(Dialog):
|
class NodeServiceDialog(Dialog):
|
||||||
def __init__(self, master, app, canvas_node, services=None):
|
def __init__(
|
||||||
|
self,
|
||||||
|
master: Any,
|
||||||
|
app: "Application",
|
||||||
|
canvas_node: "CanvasNode",
|
||||||
|
services: Set[str] = None,
|
||||||
|
):
|
||||||
title = f"{canvas_node.core_node.name} Services"
|
title = f"{canvas_node.core_node.name} Services"
|
||||||
super().__init__(master, app, title, modal=True)
|
super().__init__(master, app, title, modal=True)
|
||||||
self.app = app
|
self.app = app
|
||||||
|
@ -87,7 +98,7 @@ class NodeServiceDialog(Dialog):
|
||||||
# trigger group change
|
# trigger group change
|
||||||
self.groups.listbox.event_generate("<<ListboxSelect>>")
|
self.groups.listbox.event_generate("<<ListboxSelect>>")
|
||||||
|
|
||||||
def handle_group_change(self, event=None):
|
def handle_group_change(self, event: tk.Event = None):
|
||||||
selection = self.groups.listbox.curselection()
|
selection = self.groups.listbox.curselection()
|
||||||
if selection:
|
if selection:
|
||||||
index = selection[0]
|
index = selection[0]
|
||||||
|
@ -97,7 +108,7 @@ class NodeServiceDialog(Dialog):
|
||||||
checked = name in self.current_services
|
checked = name in self.current_services
|
||||||
self.services.add(name, checked)
|
self.services.add(name, checked)
|
||||||
|
|
||||||
def service_clicked(self, name, var):
|
def service_clicked(self, name: str, var: tk.IntVar):
|
||||||
if var.get() and name not in self.current_services:
|
if var.get() and name not in self.current_services:
|
||||||
self.current_services.add(name)
|
self.current_services.add(name)
|
||||||
elif not var.get() and name in self.current_services:
|
elif not var.get() and name in self.current_services:
|
||||||
|
@ -150,7 +161,7 @@ class NodeServiceDialog(Dialog):
|
||||||
checkbutton.invoke()
|
checkbutton.invoke()
|
||||||
return
|
return
|
||||||
|
|
||||||
def is_custom_service(self, service):
|
def is_custom_service(self, service: str) -> bool:
|
||||||
service_configs = self.app.core.service_configs
|
service_configs = self.app.core.service_configs
|
||||||
file_configs = self.app.core.file_configs
|
file_configs = self.app.core.file_configs
|
||||||
if self.node_id in service_configs and service in service_configs[self.node_id]:
|
if self.node_id in service_configs and service in service_configs[self.node_id]:
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from core.gui.coreclient import Observer
|
from core.gui.coreclient import Observer
|
||||||
from core.gui.dialogs.dialog import Dialog
|
from core.gui.dialogs.dialog import Dialog
|
||||||
from core.gui.themes import PADX, PADY
|
from core.gui.themes import PADX, PADY
|
||||||
from core.gui.widgets import ListboxScroll
|
from core.gui.widgets import ListboxScroll
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
|
|
||||||
class ObserverDialog(Dialog):
|
class ObserverDialog(Dialog):
|
||||||
def __init__(self, master, app):
|
def __init__(self, master: "Application", app: "Application"):
|
||||||
super().__init__(master, app, "Observer Widgets", modal=True)
|
super().__init__(master, app, "Observer Widgets", modal=True)
|
||||||
self.observers = None
|
self.observers = None
|
||||||
self.save_button = None
|
self.save_button = None
|
||||||
|
@ -126,7 +130,7 @@ class ObserverDialog(Dialog):
|
||||||
self.save_button.config(state=tk.DISABLED)
|
self.save_button.config(state=tk.DISABLED)
|
||||||
self.delete_button.config(state=tk.DISABLED)
|
self.delete_button.config(state=tk.DISABLED)
|
||||||
|
|
||||||
def handle_observer_change(self, event):
|
def handle_observer_change(self, event: tk.Event):
|
||||||
selection = self.observers.curselection()
|
selection = self.observers.curselection()
|
||||||
if selection:
|
if selection:
|
||||||
self.selected_index = selection[0]
|
self.selected_index = selection[0]
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
import logging
|
import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from core.gui import appconfig
|
from core.gui import appconfig
|
||||||
from core.gui.dialogs.dialog import Dialog
|
from core.gui.dialogs.dialog import Dialog
|
||||||
from core.gui.themes import FRAME_PAD, PADX, PADY
|
from core.gui.themes import FRAME_PAD, PADX, PADY
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
|
|
||||||
class PreferencesDialog(Dialog):
|
class PreferencesDialog(Dialog):
|
||||||
def __init__(self, master, app):
|
def __init__(self, master: "Application", app: "Application"):
|
||||||
super().__init__(master, app, "Preferences", modal=True)
|
super().__init__(master, app, "Preferences", modal=True)
|
||||||
preferences = self.app.guiconfig["preferences"]
|
preferences = self.app.guiconfig["preferences"]
|
||||||
self.editor = tk.StringVar(value=preferences["editor"])
|
self.editor = tk.StringVar(value=preferences["editor"])
|
||||||
|
@ -72,7 +76,7 @@ class PreferencesDialog(Dialog):
|
||||||
button = ttk.Button(frame, text="Cancel", command=self.destroy)
|
button = ttk.Button(frame, text="Cancel", command=self.destroy)
|
||||||
button.grid(row=0, column=1, sticky="ew")
|
button.grid(row=0, column=1, sticky="ew")
|
||||||
|
|
||||||
def theme_change(self, event):
|
def theme_change(self, event: tk.Event):
|
||||||
theme = self.theme.get()
|
theme = self.theme.get()
|
||||||
logging.info("changing theme: %s", theme)
|
logging.info("changing theme: %s", theme)
|
||||||
self.app.style.theme_use(theme)
|
self.app.style.theme_use(theme)
|
||||||
|
|
|
@ -1,18 +1,22 @@
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from core.gui.coreclient import CoreServer
|
from core.gui.coreclient import CoreServer
|
||||||
from core.gui.dialogs.dialog import Dialog
|
from core.gui.dialogs.dialog import Dialog
|
||||||
from core.gui.themes import FRAME_PAD, PADX, PADY
|
from core.gui.themes import FRAME_PAD, PADX, PADY
|
||||||
from core.gui.widgets import ListboxScroll
|
from core.gui.widgets import ListboxScroll
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
DEFAULT_NAME = "example"
|
DEFAULT_NAME = "example"
|
||||||
DEFAULT_ADDRESS = "127.0.0.1"
|
DEFAULT_ADDRESS = "127.0.0.1"
|
||||||
DEFAULT_PORT = 50051
|
DEFAULT_PORT = 50051
|
||||||
|
|
||||||
|
|
||||||
class ServersDialog(Dialog):
|
class ServersDialog(Dialog):
|
||||||
def __init__(self, master, app):
|
def __init__(self, master: "Application", app: "Application"):
|
||||||
super().__init__(master, app, "CORE Servers", modal=True)
|
super().__init__(master, app, "CORE Servers", modal=True)
|
||||||
self.name = tk.StringVar(value=DEFAULT_NAME)
|
self.name = tk.StringVar(value=DEFAULT_NAME)
|
||||||
self.address = tk.StringVar(value=DEFAULT_ADDRESS)
|
self.address = tk.StringVar(value=DEFAULT_ADDRESS)
|
||||||
|
@ -155,7 +159,7 @@ class ServersDialog(Dialog):
|
||||||
self.save_button.config(state=tk.DISABLED)
|
self.save_button.config(state=tk.DISABLED)
|
||||||
self.delete_button.config(state=tk.DISABLED)
|
self.delete_button.config(state=tk.DISABLED)
|
||||||
|
|
||||||
def handle_server_change(self, event):
|
def handle_server_change(self, event: tk.Event):
|
||||||
selection = self.servers.curselection()
|
selection = self.servers.curselection()
|
||||||
if selection:
|
if selection:
|
||||||
self.selected_index = selection[0]
|
self.selected_index = selection[0]
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
"Service configuration dialog"
|
"""
|
||||||
|
Service configuration dialog
|
||||||
|
"""
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING, Any, List
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
|
|
||||||
|
@ -12,9 +15,14 @@ from core.gui.images import ImageEnum, Images
|
||||||
from core.gui.themes import FRAME_PAD, PADX, PADY
|
from core.gui.themes import FRAME_PAD, PADX, PADY
|
||||||
from core.gui.widgets import CodeText, ListboxScroll
|
from core.gui.widgets import CodeText, ListboxScroll
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
|
|
||||||
class ServiceConfigDialog(Dialog):
|
class ServiceConfigDialog(Dialog):
|
||||||
def __init__(self, master, app, service_name, node_id):
|
def __init__(
|
||||||
|
self, master: Any, app: "Application", service_name: str, node_id: int
|
||||||
|
):
|
||||||
title = f"{service_name} Service"
|
title = f"{service_name} Service"
|
||||||
super().__init__(master, app, title, modal=True)
|
super().__init__(master, app, title, modal=True)
|
||||||
self.master = master
|
self.master = master
|
||||||
|
@ -225,7 +233,7 @@ class ServiceConfigDialog(Dialog):
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
tab.rowconfigure(i, weight=1)
|
tab.rowconfigure(i, weight=1)
|
||||||
self.notebook.add(tab, text="Startup/Shutdown")
|
self.notebook.add(tab, text="Startup/Shutdown")
|
||||||
|
commands = []
|
||||||
# tab 3
|
# tab 3
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
label_frame = None
|
label_frame = None
|
||||||
|
@ -345,7 +353,7 @@ class ServiceConfigDialog(Dialog):
|
||||||
button = ttk.Button(frame, text="Cancel", command=self.destroy)
|
button = ttk.Button(frame, text="Cancel", command=self.destroy)
|
||||||
button.grid(row=0, column=3, sticky="ew")
|
button.grid(row=0, column=3, sticky="ew")
|
||||||
|
|
||||||
def add_filename(self, event):
|
def add_filename(self, event: tk.Event):
|
||||||
# not worry about it for now
|
# not worry about it for now
|
||||||
return
|
return
|
||||||
frame_contains_button = event.widget.master
|
frame_contains_button = event.widget.master
|
||||||
|
@ -354,7 +362,7 @@ class ServiceConfigDialog(Dialog):
|
||||||
if filename not in combobox["values"]:
|
if filename not in combobox["values"]:
|
||||||
combobox["values"] += (filename,)
|
combobox["values"] += (filename,)
|
||||||
|
|
||||||
def delete_filename(self, event):
|
def delete_filename(self, event: tk.Event):
|
||||||
# not worry about it for now
|
# not worry about it for now
|
||||||
return
|
return
|
||||||
frame_comntains_button = event.widget.master
|
frame_comntains_button = event.widget.master
|
||||||
|
@ -364,7 +372,7 @@ class ServiceConfigDialog(Dialog):
|
||||||
combobox["values"] = tuple([x for x in combobox["values"] if x != filename])
|
combobox["values"] = tuple([x for x in combobox["values"] if x != filename])
|
||||||
combobox.set("")
|
combobox.set("")
|
||||||
|
|
||||||
def add_command(self, event):
|
def add_command(self, event: tk.Event):
|
||||||
frame_contains_button = event.widget.master
|
frame_contains_button = event.widget.master
|
||||||
listbox = frame_contains_button.master.grid_slaves(row=1, column=0)[0].listbox
|
listbox = frame_contains_button.master.grid_slaves(row=1, column=0)[0].listbox
|
||||||
command_to_add = frame_contains_button.grid_slaves(row=0, column=0)[0].get()
|
command_to_add = frame_contains_button.grid_slaves(row=0, column=0)[0].get()
|
||||||
|
@ -375,7 +383,7 @@ class ServiceConfigDialog(Dialog):
|
||||||
return
|
return
|
||||||
listbox.insert(tk.END, command_to_add)
|
listbox.insert(tk.END, command_to_add)
|
||||||
|
|
||||||
def update_entry(self, event):
|
def update_entry(self, event: tk.Event):
|
||||||
listbox = event.widget
|
listbox = event.widget
|
||||||
current_selection = listbox.curselection()
|
current_selection = listbox.curselection()
|
||||||
if len(current_selection) > 0:
|
if len(current_selection) > 0:
|
||||||
|
@ -386,7 +394,7 @@ class ServiceConfigDialog(Dialog):
|
||||||
entry.delete(0, "end")
|
entry.delete(0, "end")
|
||||||
entry.insert(0, cmd)
|
entry.insert(0, cmd)
|
||||||
|
|
||||||
def delete_command(self, event):
|
def delete_command(self, event: tk.Event):
|
||||||
button = event.widget
|
button = event.widget
|
||||||
frame_contains_button = button.master
|
frame_contains_button = button.master
|
||||||
listbox = frame_contains_button.master.grid_slaves(row=1, column=0)[0].listbox
|
listbox = frame_contains_button.master.grid_slaves(row=1, column=0)[0].listbox
|
||||||
|
@ -439,13 +447,13 @@ class ServiceConfigDialog(Dialog):
|
||||||
show_grpc_error(e)
|
show_grpc_error(e)
|
||||||
self.destroy()
|
self.destroy()
|
||||||
|
|
||||||
def display_service_file_data(self, event):
|
def display_service_file_data(self, event: tk.Event):
|
||||||
combobox = event.widget
|
combobox = event.widget
|
||||||
filename = combobox.get()
|
filename = combobox.get()
|
||||||
self.service_file_data.text.delete(1.0, "end")
|
self.service_file_data.text.delete(1.0, "end")
|
||||||
self.service_file_data.text.insert("end", self.temp_service_files[filename])
|
self.service_file_data.text.insert("end", self.temp_service_files[filename])
|
||||||
|
|
||||||
def update_temp_service_file_data(self, event):
|
def update_temp_service_file_data(self, event: tk.Event):
|
||||||
scrolledtext = event.widget
|
scrolledtext = event.widget
|
||||||
filename = self.filename_combobox.get()
|
filename = self.filename_combobox.get()
|
||||||
self.temp_service_files[filename] = scrolledtext.get(1.0, "end")
|
self.temp_service_files[filename] = scrolledtext.get(1.0, "end")
|
||||||
|
@ -490,7 +498,9 @@ class ServiceConfigDialog(Dialog):
|
||||||
dialog = CopyServiceConfigDialog(self, self.app, self.node_id)
|
dialog = CopyServiceConfigDialog(self, self.app, self.node_id)
|
||||||
dialog.show()
|
dialog.show()
|
||||||
|
|
||||||
def append_commands(self, commands, listbox, to_add):
|
def append_commands(
|
||||||
|
self, commands: List[str], listbox: tk.Listbox, to_add: List[str]
|
||||||
|
):
|
||||||
for cmd in to_add:
|
for cmd in to_add:
|
||||||
commands.append(cmd)
|
commands.append(cmd)
|
||||||
listbox.insert(tk.END, cmd)
|
listbox.insert(tk.END, cmd)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import logging
|
import logging
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
|
|
||||||
|
@ -8,9 +9,12 @@ from core.gui.errors import show_grpc_error
|
||||||
from core.gui.themes import PADX, PADY
|
from core.gui.themes import PADX, PADY
|
||||||
from core.gui.widgets import ConfigFrame
|
from core.gui.widgets import ConfigFrame
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
|
|
||||||
class SessionOptionsDialog(Dialog):
|
class SessionOptionsDialog(Dialog):
|
||||||
def __init__(self, master, app):
|
def __init__(self, master: "Application", app: "Application"):
|
||||||
super().__init__(master, app, "Session Options", modal=True)
|
super().__init__(master, app, "Session Options", modal=True)
|
||||||
self.config_frame = None
|
self.config_frame = None
|
||||||
self.config = self.get_config()
|
self.config = self.get_config()
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import logging
|
import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING, Iterable
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
|
|
||||||
|
@ -11,9 +12,14 @@ from core.gui.images import ImageEnum, Images
|
||||||
from core.gui.task import BackgroundTask
|
from core.gui.task import BackgroundTask
|
||||||
from core.gui.themes import PADX, PADY
|
from core.gui.themes import PADX, PADY
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
|
|
||||||
class SessionsDialog(Dialog):
|
class SessionsDialog(Dialog):
|
||||||
def __init__(self, master, app, is_start_app=False):
|
def __init__(
|
||||||
|
self, master: "Application", app: "Application", is_start_app: bool = False
|
||||||
|
):
|
||||||
super().__init__(master, app, "Sessions", modal=True)
|
super().__init__(master, app, "Sessions", modal=True)
|
||||||
self.is_start_app = is_start_app
|
self.is_start_app = is_start_app
|
||||||
self.selected = False
|
self.selected = False
|
||||||
|
@ -22,7 +28,7 @@ class SessionsDialog(Dialog):
|
||||||
self.sessions = self.get_sessions()
|
self.sessions = self.get_sessions()
|
||||||
self.draw()
|
self.draw()
|
||||||
|
|
||||||
def get_sessions(self):
|
def get_sessions(self) -> Iterable[core_pb2.SessionSummary]:
|
||||||
try:
|
try:
|
||||||
response = self.app.core.client.get_sessions()
|
response = self.app.core.client.get_sessions()
|
||||||
logging.info("sessions: %s", response)
|
logging.info("sessions: %s", response)
|
||||||
|
@ -41,7 +47,6 @@ class SessionsDialog(Dialog):
|
||||||
def draw_description(self):
|
def draw_description(self):
|
||||||
"""
|
"""
|
||||||
write a short description
|
write a short description
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
label = ttk.Label(
|
label = ttk.Label(
|
||||||
self.top,
|
self.top,
|
||||||
|
@ -154,7 +159,7 @@ class SessionsDialog(Dialog):
|
||||||
self.app.core.create_new_session()
|
self.app.core.create_new_session()
|
||||||
self.destroy()
|
self.destroy()
|
||||||
|
|
||||||
def click_select(self, event):
|
def click_select(self, event: tk.Event):
|
||||||
item = self.tree.selection()
|
item = self.tree.selection()
|
||||||
session_id = int(self.tree.item(item, "text"))
|
session_id = int(self.tree.item(item, "text"))
|
||||||
self.selected = True
|
self.selected = True
|
||||||
|
@ -163,8 +168,6 @@ class SessionsDialog(Dialog):
|
||||||
def click_connect(self):
|
def click_connect(self):
|
||||||
"""
|
"""
|
||||||
if no session is selected yet, create a new one else join that session
|
if no session is selected yet, create a new one else join that session
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
if self.selected and self.selected_id is not None:
|
if self.selected and self.selected_id is not None:
|
||||||
self.join_session(self.selected_id)
|
self.join_session(self.selected_id)
|
||||||
|
@ -177,8 +180,6 @@ class SessionsDialog(Dialog):
|
||||||
"""
|
"""
|
||||||
if no session is currently selected create a new session else shut the selected
|
if no session is currently selected create a new session else shut the selected
|
||||||
session down.
|
session down.
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
if self.selected and self.selected_id is not None:
|
if self.selected and self.selected_id is not None:
|
||||||
self.shutdown_session(self.selected_id)
|
self.shutdown_session(self.selected_id)
|
||||||
|
@ -187,18 +188,18 @@ class SessionsDialog(Dialog):
|
||||||
else:
|
else:
|
||||||
logging.error("querysessiondrawing.py invalid state")
|
logging.error("querysessiondrawing.py invalid state")
|
||||||
|
|
||||||
def join_session(self, session_id):
|
def join_session(self, session_id: int):
|
||||||
self.app.statusbar.progress_bar.start(5)
|
self.app.statusbar.progress_bar.start(5)
|
||||||
task = BackgroundTask(self.app, self.app.core.join_session, args=(session_id,))
|
task = BackgroundTask(self.app, self.app.core.join_session, args=(session_id,))
|
||||||
task.start()
|
task.start()
|
||||||
self.destroy()
|
self.destroy()
|
||||||
|
|
||||||
def on_selected(self, event):
|
def on_selected(self, event: tk.Event):
|
||||||
item = self.tree.selection()
|
item = self.tree.selection()
|
||||||
sid = int(self.tree.item(item, "text"))
|
sid = int(self.tree.item(item, "text"))
|
||||||
self.join_session(sid)
|
self.join_session(sid)
|
||||||
|
|
||||||
def shutdown_session(self, sid):
|
def shutdown_session(self, sid: int):
|
||||||
self.app.core.stop_session(sid)
|
self.app.core.stop_session(sid)
|
||||||
self.click_new()
|
self.click_new()
|
||||||
self.destroy()
|
self.destroy()
|
||||||
|
|
|
@ -3,6 +3,7 @@ shape input dialog
|
||||||
"""
|
"""
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import font, ttk
|
from tkinter import font, ttk
|
||||||
|
from typing import TYPE_CHECKING, List, Union
|
||||||
|
|
||||||
from core.gui.dialogs.colorpicker import ColorPickerDialog
|
from core.gui.dialogs.colorpicker import ColorPickerDialog
|
||||||
from core.gui.dialogs.dialog import Dialog
|
from core.gui.dialogs.dialog import Dialog
|
||||||
|
@ -10,12 +11,16 @@ from core.gui.graph import tags
|
||||||
from core.gui.graph.shapeutils import is_draw_shape, is_shape_text
|
from core.gui.graph.shapeutils import is_draw_shape, is_shape_text
|
||||||
from core.gui.themes import FRAME_PAD, PADX, PADY
|
from core.gui.themes import FRAME_PAD, PADX, PADY
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
from core.gui.graph.shape import Shape
|
||||||
|
|
||||||
FONT_SIZES = [8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72]
|
FONT_SIZES = [8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72]
|
||||||
BORDER_WIDTH = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
BORDER_WIDTH = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||||
|
|
||||||
|
|
||||||
class ShapeDialog(Dialog):
|
class ShapeDialog(Dialog):
|
||||||
def __init__(self, master, app, shape):
|
def __init__(self, master: "Application", app: "Application", shape: "Shape"):
|
||||||
if is_draw_shape(shape.shape_type):
|
if is_draw_shape(shape.shape_type):
|
||||||
title = "Add Shape"
|
title = "Add Shape"
|
||||||
else:
|
else:
|
||||||
|
@ -162,10 +167,9 @@ class ShapeDialog(Dialog):
|
||||||
self.add_text()
|
self.add_text()
|
||||||
self.destroy()
|
self.destroy()
|
||||||
|
|
||||||
def make_font(self):
|
def make_font(self) -> List[Union[int, str]]:
|
||||||
"""
|
"""
|
||||||
create font for text or shape label
|
create font for text or shape label
|
||||||
:return: list(font specifications)
|
|
||||||
"""
|
"""
|
||||||
size = int(self.font_size.get())
|
size = int(self.font_size.get())
|
||||||
text_font = [self.font.get(), size]
|
text_font = [self.font.get(), size]
|
||||||
|
@ -180,8 +184,6 @@ class ShapeDialog(Dialog):
|
||||||
def save_text(self):
|
def save_text(self):
|
||||||
"""
|
"""
|
||||||
save info related to text or shape label
|
save info related to text or shape label
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
data = self.shape.shape_data
|
data = self.shape.shape_data
|
||||||
data.text = self.shape_text.get()
|
data.text = self.shape_text.get()
|
||||||
|
@ -195,8 +197,6 @@ class ShapeDialog(Dialog):
|
||||||
def save_shape(self):
|
def save_shape(self):
|
||||||
"""
|
"""
|
||||||
save info related to shape
|
save info related to shape
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
data = self.shape.shape_data
|
data = self.shape.shape_data
|
||||||
data.fill_color = self.fill_color
|
data.fill_color = self.fill_color
|
||||||
|
@ -206,8 +206,6 @@ class ShapeDialog(Dialog):
|
||||||
def add_text(self):
|
def add_text(self):
|
||||||
"""
|
"""
|
||||||
add text to canvas
|
add text to canvas
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
text = self.shape_text.get()
|
text = self.shape_text.get()
|
||||||
text_font = self.make_font()
|
text_font = self.make_font()
|
||||||
|
|
|
@ -3,14 +3,18 @@ throughput dialog
|
||||||
"""
|
"""
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from core.gui.dialogs.colorpicker import ColorPickerDialog
|
from core.gui.dialogs.colorpicker import ColorPickerDialog
|
||||||
from core.gui.dialogs.dialog import Dialog
|
from core.gui.dialogs.dialog import Dialog
|
||||||
from core.gui.themes import FRAME_PAD, PADX, PADY
|
from core.gui.themes import FRAME_PAD, PADX, PADY
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
|
|
||||||
class ThroughputDialog(Dialog):
|
class ThroughputDialog(Dialog):
|
||||||
def __init__(self, master, app):
|
def __init__(self, master: "Application", app: "Application"):
|
||||||
super().__init__(master, app, "Throughput Config", modal=False)
|
super().__init__(master, app, "Throughput Config", modal=False)
|
||||||
self.app = app
|
self.app = app
|
||||||
self.canvas = app.canvas
|
self.canvas = app.canvas
|
||||||
|
|
|
@ -3,6 +3,7 @@ wlan configuration
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
|
|
||||||
|
@ -11,9 +12,15 @@ from core.gui.errors import show_grpc_error
|
||||||
from core.gui.themes import PADX, PADY
|
from core.gui.themes import PADX, PADY
|
||||||
from core.gui.widgets import ConfigFrame
|
from core.gui.widgets import ConfigFrame
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
from core.gui.graph.node import CanvasNode
|
||||||
|
|
||||||
|
|
||||||
class WlanConfigDialog(Dialog):
|
class WlanConfigDialog(Dialog):
|
||||||
def __init__(self, master, app, canvas_node):
|
def __init__(
|
||||||
|
self, master: "Application", app: "Application", canvas_node: "CanvasNode"
|
||||||
|
):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
master, app, f"{canvas_node.core_node.name} Wlan Configuration", modal=True
|
master, app, f"{canvas_node.core_node.name} Wlan Configuration", modal=True
|
||||||
)
|
)
|
||||||
|
@ -38,8 +45,6 @@ class WlanConfigDialog(Dialog):
|
||||||
def draw_apply_buttons(self):
|
def draw_apply_buttons(self):
|
||||||
"""
|
"""
|
||||||
create node configuration options
|
create node configuration options
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
frame = ttk.Frame(self.top)
|
frame = ttk.Frame(self.top)
|
||||||
frame.grid(sticky="ew")
|
frame.grid(sticky="ew")
|
||||||
|
@ -55,8 +60,6 @@ class WlanConfigDialog(Dialog):
|
||||||
def click_apply(self):
|
def click_apply(self):
|
||||||
"""
|
"""
|
||||||
retrieve user's wlan configuration and store the new configuration values
|
retrieve user's wlan configuration and store the new configuration values
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
config = self.config_frame.parse_config()
|
config = self.config_frame.parse_config()
|
||||||
self.app.core.wlan_configs[self.node.id] = self.config
|
self.app.core.wlan_configs[self.node.id] = self.config
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
from tkinter import messagebox
|
from tkinter import messagebox
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
import grpc
|
||||||
|
|
||||||
|
|
||||||
def show_grpc_error(e):
|
def show_grpc_error(e: "grpc.RpcError"):
|
||||||
title = [x.capitalize() for x in e.code().name.lower().split("_")]
|
title = [x.capitalize() for x in e.code().name.lower().split("_")]
|
||||||
title = " ".join(title)
|
title = " ".join(title)
|
||||||
title = f"GRPC {title}"
|
title = f"GRPC {title}"
|
||||||
|
|
|
@ -1,19 +1,30 @@
|
||||||
import logging
|
import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter.font import Font
|
from tkinter.font import Font
|
||||||
|
from typing import TYPE_CHECKING, Any, Tuple
|
||||||
|
|
||||||
from core.gui import themes
|
from core.gui import themes
|
||||||
from core.gui.dialogs.linkconfig import LinkConfigurationDialog
|
from core.gui.dialogs.linkconfig import LinkConfigurationDialog
|
||||||
from core.gui.graph import tags
|
from core.gui.graph import tags
|
||||||
from core.gui.nodeutils import NodeUtils
|
from core.gui.nodeutils import NodeUtils
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.graph.graph import CanvasGraph
|
||||||
|
|
||||||
TEXT_DISTANCE = 0.30
|
TEXT_DISTANCE = 0.30
|
||||||
EDGE_WIDTH = 3
|
EDGE_WIDTH = 3
|
||||||
EDGE_COLOR = "#ff0000"
|
EDGE_COLOR = "#ff0000"
|
||||||
|
|
||||||
|
|
||||||
class CanvasWirelessEdge:
|
class CanvasWirelessEdge:
|
||||||
def __init__(self, token, position, src, dst, canvas):
|
def __init__(
|
||||||
|
self,
|
||||||
|
token: Tuple[Any, ...],
|
||||||
|
position: Tuple[float, float, float, float],
|
||||||
|
src: int,
|
||||||
|
dst: int,
|
||||||
|
canvas: "CanvasGraph",
|
||||||
|
):
|
||||||
self.token = token
|
self.token = token
|
||||||
self.src = src
|
self.src = src
|
||||||
self.dst = dst
|
self.dst = dst
|
||||||
|
@ -31,15 +42,17 @@ class CanvasEdge:
|
||||||
Canvas edge class
|
Canvas edge class
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, x1, y1, x2, y2, src, canvas):
|
def __init__(
|
||||||
|
self,
|
||||||
|
x1: float,
|
||||||
|
y1: float,
|
||||||
|
x2: float,
|
||||||
|
y2: float,
|
||||||
|
src: int,
|
||||||
|
canvas: "CanvasGraph",
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Create an instance of canvas edge object
|
Create an instance of canvas edge object
|
||||||
:param int x1: source x-coord
|
|
||||||
:param int y1: source y-coord
|
|
||||||
:param int x2: destination x-coord
|
|
||||||
:param int y2: destination y-coord
|
|
||||||
:param int src: source id
|
|
||||||
:param coretk.graph.graph.GraphCanvas canvas: canvas object
|
|
||||||
"""
|
"""
|
||||||
self.src = src
|
self.src = src
|
||||||
self.dst = None
|
self.dst = None
|
||||||
|
@ -66,7 +79,7 @@ class CanvasEdge:
|
||||||
self.link = link
|
self.link = link
|
||||||
self.draw_labels()
|
self.draw_labels()
|
||||||
|
|
||||||
def get_coordinates(self):
|
def get_coordinates(self) -> [float, float, float, float]:
|
||||||
x1, y1, x2, y2 = self.canvas.coords(self.id)
|
x1, y1, x2, y2 = self.canvas.coords(self.id)
|
||||||
v1 = x2 - x1
|
v1 = x2 - x1
|
||||||
v2 = y2 - y1
|
v2 = y2 - y1
|
||||||
|
@ -78,7 +91,7 @@ class CanvasEdge:
|
||||||
y2 = y2 - uy
|
y2 = y2 - uy
|
||||||
return x1, y1, x2, y2
|
return x1, y1, x2, y2
|
||||||
|
|
||||||
def get_midpoint(self):
|
def get_midpoint(self) -> [float, float]:
|
||||||
x1, y1, x2, y2 = self.canvas.coords(self.id)
|
x1, y1, x2, y2 = self.canvas.coords(self.id)
|
||||||
x = (x1 + x2) / 2
|
x = (x1 + x2) / 2
|
||||||
y = (y1 + y2) / 2
|
y = (y1 + y2) / 2
|
||||||
|
@ -118,8 +131,6 @@ class CanvasEdge:
|
||||||
def update_labels(self):
|
def update_labels(self):
|
||||||
"""
|
"""
|
||||||
Move edge labels based on current position.
|
Move edge labels based on current position.
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
x1, y1, x2, y2 = self.get_coordinates()
|
x1, y1, x2, y2 = self.get_coordinates()
|
||||||
self.canvas.coords(self.text_src, x1, y1)
|
self.canvas.coords(self.text_src, x1, y1)
|
||||||
|
@ -128,7 +139,7 @@ class CanvasEdge:
|
||||||
x, y = self.get_midpoint()
|
x, y = self.get_midpoint()
|
||||||
self.canvas.coords(self.text_middle, x, y)
|
self.canvas.coords(self.text_middle, x, y)
|
||||||
|
|
||||||
def set_throughput(self, throughput):
|
def set_throughput(self, throughput: float):
|
||||||
throughput = 0.001 * throughput
|
throughput = 0.001 * throughput
|
||||||
value = f"{throughput:.3f} kbps"
|
value = f"{throughput:.3f} kbps"
|
||||||
if self.text_middle is None:
|
if self.text_middle is None:
|
||||||
|
@ -147,7 +158,7 @@ class CanvasEdge:
|
||||||
width = EDGE_WIDTH
|
width = EDGE_WIDTH
|
||||||
self.canvas.itemconfig(self.id, fill=color, width=width)
|
self.canvas.itemconfig(self.id, fill=color, width=width)
|
||||||
|
|
||||||
def complete(self, dst):
|
def complete(self, dst: int):
|
||||||
self.dst = dst
|
self.dst = dst
|
||||||
self.token = tuple(sorted((self.src, self.dst)))
|
self.token = tuple(sorted((self.src, self.dst)))
|
||||||
x, y = self.canvas.coords(self.dst)
|
x, y = self.canvas.coords(self.dst)
|
||||||
|
@ -157,7 +168,7 @@ class CanvasEdge:
|
||||||
self.canvas.tag_raise(self.src)
|
self.canvas.tag_raise(self.src)
|
||||||
self.canvas.tag_raise(self.dst)
|
self.canvas.tag_raise(self.dst)
|
||||||
|
|
||||||
def is_wireless(self):
|
def is_wireless(self) -> [bool, bool]:
|
||||||
src_node = self.canvas.nodes[self.src]
|
src_node = self.canvas.nodes[self.src]
|
||||||
dst_node = self.canvas.nodes[self.dst]
|
dst_node = self.canvas.nodes[self.dst]
|
||||||
src_node_type = src_node.core_node.type
|
src_node_type = src_node.core_node.type
|
||||||
|
@ -183,7 +194,6 @@ class CanvasEdge:
|
||||||
dst_node.add_antenna()
|
dst_node.add_antenna()
|
||||||
elif not is_src_wireless and is_dst_wireless:
|
elif not is_src_wireless and is_dst_wireless:
|
||||||
src_node.add_antenna()
|
src_node.add_antenna()
|
||||||
# TODO: remove this? dont allow linking wireless nodes?
|
|
||||||
else:
|
else:
|
||||||
src_node.add_antenna()
|
src_node.add_antenna()
|
||||||
|
|
||||||
|
@ -199,7 +209,7 @@ class CanvasEdge:
|
||||||
self.text_middle = None
|
self.text_middle = None
|
||||||
self.canvas.itemconfig(self.id, fill=EDGE_COLOR, width=EDGE_WIDTH)
|
self.canvas.itemconfig(self.id, fill=EDGE_COLOR, width=EDGE_WIDTH)
|
||||||
|
|
||||||
def create_context(self, event):
|
def create_context(self, event: tk.Event):
|
||||||
logging.debug("create link context")
|
logging.debug("create link context")
|
||||||
context = tk.Menu(self.canvas)
|
context = tk.Menu(self.canvas)
|
||||||
themes.style_menu(context)
|
themes.style_menu(context)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import logging
|
import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
|
from typing import TYPE_CHECKING, List, Tuple
|
||||||
|
|
||||||
from PIL import Image, ImageTk
|
from PIL import Image, ImageTk
|
||||||
|
|
||||||
|
@ -15,12 +16,18 @@ from core.gui.graph.shapeutils import ShapeType, is_draw_shape, is_marker
|
||||||
from core.gui.images import Images
|
from core.gui.images import Images
|
||||||
from core.gui.nodeutils import NodeUtils
|
from core.gui.nodeutils import NodeUtils
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
from core.gui.coreclient import CoreClient
|
||||||
|
|
||||||
ZOOM_IN = 1.1
|
ZOOM_IN = 1.1
|
||||||
ZOOM_OUT = 0.9
|
ZOOM_OUT = 0.9
|
||||||
|
|
||||||
|
|
||||||
class CanvasGraph(tk.Canvas):
|
class CanvasGraph(tk.Canvas):
|
||||||
def __init__(self, master, core, width, height):
|
def __init__(
|
||||||
|
self, master: "Application", core: "CoreClient", width: int, height: int
|
||||||
|
):
|
||||||
super().__init__(master, highlightthickness=0, background="#cccccc")
|
super().__init__(master, highlightthickness=0, background="#cccccc")
|
||||||
self.app = master
|
self.app = master
|
||||||
self.core = core
|
self.core = core
|
||||||
|
@ -67,7 +74,7 @@ class CanvasGraph(tk.Canvas):
|
||||||
self.draw_canvas()
|
self.draw_canvas()
|
||||||
self.draw_grid()
|
self.draw_grid()
|
||||||
|
|
||||||
def draw_canvas(self, dimensions=None):
|
def draw_canvas(self, dimensions: Tuple[int, int] = None):
|
||||||
if self.grid is not None:
|
if self.grid is not None:
|
||||||
self.delete(self.grid)
|
self.delete(self.grid)
|
||||||
if not dimensions:
|
if not dimensions:
|
||||||
|
@ -84,13 +91,11 @@ class CanvasGraph(tk.Canvas):
|
||||||
)
|
)
|
||||||
self.configure(scrollregion=self.bbox(tk.ALL))
|
self.configure(scrollregion=self.bbox(tk.ALL))
|
||||||
|
|
||||||
def reset_and_redraw(self, session):
|
def reset_and_redraw(self, session: core_pb2.Session):
|
||||||
"""
|
"""
|
||||||
Reset the private variables CanvasGraph object, redraw nodes given the new grpc
|
Reset the private variables CanvasGraph object, redraw nodes given the new grpc
|
||||||
client.
|
client.
|
||||||
|
|
||||||
:param core.api.grpc.core_pb2.Session session: session to draw
|
:param core.api.grpc.core_pb2.Session session: session to draw
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
# hide context
|
# hide context
|
||||||
self.hide_context()
|
self.hide_context()
|
||||||
|
@ -114,8 +119,6 @@ class CanvasGraph(tk.Canvas):
|
||||||
def setup_bindings(self):
|
def setup_bindings(self):
|
||||||
"""
|
"""
|
||||||
Bind any mouse events or hot keys to the matching action
|
Bind any mouse events or hot keys to the matching action
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
self.bind("<ButtonPress-1>", self.click_press)
|
self.bind("<ButtonPress-1>", self.click_press)
|
||||||
self.bind("<ButtonRelease-1>", self.click_release)
|
self.bind("<ButtonRelease-1>", self.click_release)
|
||||||
|
@ -135,28 +138,28 @@ class CanvasGraph(tk.Canvas):
|
||||||
self.context.unpost()
|
self.context.unpost()
|
||||||
self.context = None
|
self.context = None
|
||||||
|
|
||||||
def get_actual_coords(self, x, y):
|
def get_actual_coords(self, x: float, y: float) -> [float, float]:
|
||||||
actual_x = (x - self.offset[0]) / self.ratio
|
actual_x = (x - self.offset[0]) / self.ratio
|
||||||
actual_y = (y - self.offset[1]) / self.ratio
|
actual_y = (y - self.offset[1]) / self.ratio
|
||||||
return actual_x, actual_y
|
return actual_x, actual_y
|
||||||
|
|
||||||
def get_scaled_coords(self, x, y):
|
def get_scaled_coords(self, x: float, y: float) -> [float, float]:
|
||||||
scaled_x = (x * self.ratio) + self.offset[0]
|
scaled_x = (x * self.ratio) + self.offset[0]
|
||||||
scaled_y = (y * self.ratio) + self.offset[1]
|
scaled_y = (y * self.ratio) + self.offset[1]
|
||||||
return scaled_x, scaled_y
|
return scaled_x, scaled_y
|
||||||
|
|
||||||
def inside_canvas(self, x, y):
|
def inside_canvas(self, x: float, y: float) -> [bool, bool]:
|
||||||
x1, y1, x2, y2 = self.bbox(self.grid)
|
x1, y1, x2, y2 = self.bbox(self.grid)
|
||||||
valid_x = x1 <= x <= x2
|
valid_x = x1 <= x <= x2
|
||||||
valid_y = y1 <= y <= y2
|
valid_y = y1 <= y <= y2
|
||||||
return valid_x and valid_y
|
return valid_x and valid_y
|
||||||
|
|
||||||
def valid_position(self, x1, y1, x2, y2):
|
def valid_position(self, x1: int, y1: int, x2: int, y2: int) -> [bool, bool]:
|
||||||
valid_topleft = self.inside_canvas(x1, y1)
|
valid_topleft = self.inside_canvas(x1, y1)
|
||||||
valid_bottomright = self.inside_canvas(x2, y2)
|
valid_bottomright = self.inside_canvas(x2, y2)
|
||||||
return valid_topleft and valid_bottomright
|
return valid_topleft and valid_bottomright
|
||||||
|
|
||||||
def set_throughputs(self, throughputs_event):
|
def set_throughputs(self, throughputs_event: core_pb2.ThroughputsEvent):
|
||||||
for interface_throughput in throughputs_event.interface_throughputs:
|
for interface_throughput in throughputs_event.interface_throughputs:
|
||||||
node_id = interface_throughput.node_id
|
node_id = interface_throughput.node_id
|
||||||
interface_id = interface_throughput.interface_id
|
interface_id = interface_throughput.interface_id
|
||||||
|
@ -174,8 +177,6 @@ class CanvasGraph(tk.Canvas):
|
||||||
def draw_grid(self):
|
def draw_grid(self):
|
||||||
"""
|
"""
|
||||||
Create grid.
|
Create grid.
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
width, height = self.width_and_height()
|
width, height = self.width_and_height()
|
||||||
width = int(width)
|
width = int(width)
|
||||||
|
@ -187,13 +188,9 @@ class CanvasGraph(tk.Canvas):
|
||||||
self.tag_lower(tags.GRIDLINE)
|
self.tag_lower(tags.GRIDLINE)
|
||||||
self.tag_lower(self.grid)
|
self.tag_lower(self.grid)
|
||||||
|
|
||||||
def add_wireless_edge(self, src, dst):
|
def add_wireless_edge(self, src: CanvasNode, dst: CanvasNode):
|
||||||
"""
|
"""
|
||||||
add a wireless edge between 2 canvas nodes
|
add a wireless edge between 2 canvas nodes
|
||||||
|
|
||||||
:param CanvasNode src: source node
|
|
||||||
:param CanvasNode dst: destination node
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
token = tuple(sorted((src.id, dst.id)))
|
token = tuple(sorted((src.id, dst.id)))
|
||||||
x1, y1 = self.coords(src.id)
|
x1, y1 = self.coords(src.id)
|
||||||
|
@ -206,18 +203,16 @@ class CanvasGraph(tk.Canvas):
|
||||||
self.tag_raise(src.id)
|
self.tag_raise(src.id)
|
||||||
self.tag_raise(dst.id)
|
self.tag_raise(dst.id)
|
||||||
|
|
||||||
def delete_wireless_edge(self, src, dst):
|
def delete_wireless_edge(self, src: CanvasNode, dst: CanvasNode):
|
||||||
token = tuple(sorted((src.id, dst.id)))
|
token = tuple(sorted((src.id, dst.id)))
|
||||||
edge = self.wireless_edges.pop(token)
|
edge = self.wireless_edges.pop(token)
|
||||||
edge.delete()
|
edge.delete()
|
||||||
src.wireless_edges.remove(edge)
|
src.wireless_edges.remove(edge)
|
||||||
dst.wireless_edges.remove(edge)
|
dst.wireless_edges.remove(edge)
|
||||||
|
|
||||||
def draw_session(self, session):
|
def draw_session(self, session: core_pb2.Session):
|
||||||
"""
|
"""
|
||||||
Draw existing session.
|
Draw existing session.
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
# draw existing nodes
|
# draw existing nodes
|
||||||
for core_node in session.nodes:
|
for core_node in session.nodes:
|
||||||
|
@ -296,25 +291,17 @@ class CanvasGraph(tk.Canvas):
|
||||||
for edge in self.edges.values():
|
for edge in self.edges.values():
|
||||||
edge.reset()
|
edge.reset()
|
||||||
|
|
||||||
def canvas_xy(self, event):
|
def canvas_xy(self, event: tk.Event) -> [float, float]:
|
||||||
"""
|
"""
|
||||||
Convert window coordinate to canvas coordinate
|
Convert window coordinate to canvas coordinate
|
||||||
|
|
||||||
:param event:
|
|
||||||
:rtype: (int, int)
|
|
||||||
:return: x, y canvas coordinate
|
|
||||||
"""
|
"""
|
||||||
x = self.canvasx(event.x)
|
x = self.canvasx(event.x)
|
||||||
y = self.canvasy(event.y)
|
y = self.canvasy(event.y)
|
||||||
return x, y
|
return x, y
|
||||||
|
|
||||||
def get_selected(self, event):
|
def get_selected(self, event: tk.Event) -> int:
|
||||||
"""
|
"""
|
||||||
Retrieve the item id that is on the mouse position
|
Retrieve the item id that is on the mouse position
|
||||||
|
|
||||||
:param event: mouse event
|
|
||||||
:rtype: int
|
|
||||||
:return: the item that the mouse point to
|
|
||||||
"""
|
"""
|
||||||
x, y = self.canvas_xy(event)
|
x, y = self.canvas_xy(event)
|
||||||
overlapping = self.find_overlapping(x, y, x, y)
|
overlapping = self.find_overlapping(x, y, x, y)
|
||||||
|
@ -332,12 +319,9 @@ class CanvasGraph(tk.Canvas):
|
||||||
|
|
||||||
return selected
|
return selected
|
||||||
|
|
||||||
def click_release(self, event):
|
def click_release(self, event: tk.Event):
|
||||||
"""
|
"""
|
||||||
Draw a node or finish drawing an edge according to the current graph mode
|
Draw a node or finish drawing an edge according to the current graph mode
|
||||||
|
|
||||||
:param event: mouse event
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
logging.debug("click release")
|
logging.debug("click release")
|
||||||
x, y = self.canvas_xy(event)
|
x, y = self.canvas_xy(event)
|
||||||
|
@ -380,7 +364,7 @@ class CanvasGraph(tk.Canvas):
|
||||||
self.mode = GraphMode.NODE
|
self.mode = GraphMode.NODE
|
||||||
self.selected = None
|
self.selected = None
|
||||||
|
|
||||||
def handle_edge_release(self, event):
|
def handle_edge_release(self, event: tk.Event):
|
||||||
edge = self.drawing_edge
|
edge = self.drawing_edge
|
||||||
self.drawing_edge = None
|
self.drawing_edge = None
|
||||||
|
|
||||||
|
@ -417,7 +401,7 @@ class CanvasGraph(tk.Canvas):
|
||||||
node_dst.edges.add(edge)
|
node_dst.edges.add(edge)
|
||||||
self.core.create_link(edge, node_src, node_dst)
|
self.core.create_link(edge, node_src, node_dst)
|
||||||
|
|
||||||
def select_object(self, object_id, choose_multiple=False):
|
def select_object(self, object_id: int, choose_multiple: bool = False):
|
||||||
"""
|
"""
|
||||||
create a bounding box when a node is selected
|
create a bounding box when a node is selected
|
||||||
"""
|
"""
|
||||||
|
@ -441,19 +425,17 @@ class CanvasGraph(tk.Canvas):
|
||||||
def clear_selection(self):
|
def clear_selection(self):
|
||||||
"""
|
"""
|
||||||
Clear current selection boxes.
|
Clear current selection boxes.
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
for _id in self.selection.values():
|
for _id in self.selection.values():
|
||||||
self.delete(_id)
|
self.delete(_id)
|
||||||
self.selection.clear()
|
self.selection.clear()
|
||||||
|
|
||||||
def move_selection(self, object_id, x_offset, y_offset):
|
def move_selection(self, object_id: int, x_offset: float, y_offset: float):
|
||||||
select_id = self.selection.get(object_id)
|
select_id = self.selection.get(object_id)
|
||||||
if select_id is not None:
|
if select_id is not None:
|
||||||
self.move(select_id, x_offset, y_offset)
|
self.move(select_id, x_offset, y_offset)
|
||||||
|
|
||||||
def delete_selection_objects(self):
|
def delete_selection_objects(self) -> List[CanvasNode]:
|
||||||
edges = set()
|
edges = set()
|
||||||
nodes = []
|
nodes = []
|
||||||
for object_id in self.selection:
|
for object_id in self.selection:
|
||||||
|
@ -499,7 +481,7 @@ class CanvasGraph(tk.Canvas):
|
||||||
self.selection.clear()
|
self.selection.clear()
|
||||||
return nodes
|
return nodes
|
||||||
|
|
||||||
def zoom(self, event, factor=None):
|
def zoom(self, event: tk.Event, factor: float = None):
|
||||||
if not factor:
|
if not factor:
|
||||||
factor = ZOOM_IN if event.delta > 0 else ZOOM_OUT
|
factor = ZOOM_IN if event.delta > 0 else ZOOM_OUT
|
||||||
event.x, event.y = self.canvasx(event.x), self.canvasy(event.y)
|
event.x, event.y = self.canvasx(event.x), self.canvasy(event.y)
|
||||||
|
@ -517,12 +499,9 @@ class CanvasGraph(tk.Canvas):
|
||||||
if self.wallpaper:
|
if self.wallpaper:
|
||||||
self.redraw_wallpaper()
|
self.redraw_wallpaper()
|
||||||
|
|
||||||
def click_press(self, event):
|
def click_press(self, event: tk.Event):
|
||||||
"""
|
"""
|
||||||
Start drawing an edge if mouse click is on a node
|
Start drawing an edge if mouse click is on a node
|
||||||
|
|
||||||
:param event: mouse event
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
x, y = self.canvas_xy(event)
|
x, y = self.canvas_xy(event)
|
||||||
if not self.inside_canvas(x, y):
|
if not self.inside_canvas(x, y):
|
||||||
|
@ -581,7 +560,7 @@ class CanvasGraph(tk.Canvas):
|
||||||
self.select_box = shape
|
self.select_box = shape
|
||||||
self.clear_selection()
|
self.clear_selection()
|
||||||
|
|
||||||
def ctrl_click(self, event):
|
def ctrl_click(self, event: tk.Event):
|
||||||
# update cursor location
|
# update cursor location
|
||||||
x, y = self.canvas_xy(event)
|
x, y = self.canvas_xy(event)
|
||||||
if not self.inside_canvas(x, y):
|
if not self.inside_canvas(x, y):
|
||||||
|
@ -599,12 +578,9 @@ class CanvasGraph(tk.Canvas):
|
||||||
):
|
):
|
||||||
self.select_object(selected, choose_multiple=True)
|
self.select_object(selected, choose_multiple=True)
|
||||||
|
|
||||||
def click_motion(self, event):
|
def click_motion(self, event: tk.Event):
|
||||||
"""
|
"""
|
||||||
Redraw drawing edge according to the current position of the mouse
|
Redraw drawing edge according to the current position of the mouse
|
||||||
|
|
||||||
:param event: mouse event
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
x, y = self.canvas_xy(event)
|
x, y = self.canvas_xy(event)
|
||||||
if not self.inside_canvas(x, y):
|
if not self.inside_canvas(x, y):
|
||||||
|
@ -658,7 +634,7 @@ class CanvasGraph(tk.Canvas):
|
||||||
if self.select_box and self.mode == GraphMode.SELECT:
|
if self.select_box and self.mode == GraphMode.SELECT:
|
||||||
self.select_box.shape_motion(x, y)
|
self.select_box.shape_motion(x, y)
|
||||||
|
|
||||||
def click_context(self, event):
|
def click_context(self, event: tk.Event):
|
||||||
logging.info("context event: %s", self.context)
|
logging.info("context event: %s", self.context)
|
||||||
if not self.context:
|
if not self.context:
|
||||||
selected = self.get_selected(event)
|
selected = self.get_selected(event)
|
||||||
|
@ -670,24 +646,22 @@ class CanvasGraph(tk.Canvas):
|
||||||
else:
|
else:
|
||||||
self.hide_context()
|
self.hide_context()
|
||||||
|
|
||||||
def press_delete(self, event):
|
def press_delete(self, event: tk.Event):
|
||||||
"""
|
"""
|
||||||
delete selected nodes and any data that relates to it
|
delete selected nodes and any data that relates to it
|
||||||
:param event:
|
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
logging.debug("press delete key")
|
logging.debug("press delete key")
|
||||||
nodes = self.delete_selection_objects()
|
nodes = self.delete_selection_objects()
|
||||||
self.core.delete_graph_nodes(nodes)
|
self.core.delete_graph_nodes(nodes)
|
||||||
|
|
||||||
def double_click(self, event):
|
def double_click(self, event: tk.Event):
|
||||||
selected = self.get_selected(event)
|
selected = self.get_selected(event)
|
||||||
if selected is not None and selected in self.shapes:
|
if selected is not None and selected in self.shapes:
|
||||||
shape = self.shapes[selected]
|
shape = self.shapes[selected]
|
||||||
dialog = ShapeDialog(self.app, self.app, shape)
|
dialog = ShapeDialog(self.app, self.app, shape)
|
||||||
dialog.show()
|
dialog.show()
|
||||||
|
|
||||||
def add_node(self, x, y):
|
def add_node(self, x: float, y: float) -> CanvasNode:
|
||||||
if self.selected is None or self.selected in self.shapes:
|
if self.selected is None or self.selected in self.shapes:
|
||||||
actual_x, actual_y = self.get_actual_coords(x, y)
|
actual_x, actual_y = self.get_actual_coords(x, y)
|
||||||
core_node = self.core.create_node(
|
core_node = self.core.create_node(
|
||||||
|
@ -701,26 +675,25 @@ class CanvasGraph(tk.Canvas):
|
||||||
def width_and_height(self):
|
def width_and_height(self):
|
||||||
"""
|
"""
|
||||||
retrieve canvas width and height in pixels
|
retrieve canvas width and height in pixels
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
x0, y0, x1, y1 = self.coords(self.grid)
|
x0, y0, x1, y1 = self.coords(self.grid)
|
||||||
canvas_w = abs(x0 - x1)
|
canvas_w = abs(x0 - x1)
|
||||||
canvas_h = abs(y0 - y1)
|
canvas_h = abs(y0 - y1)
|
||||||
return canvas_w, canvas_h
|
return canvas_w, canvas_h
|
||||||
|
|
||||||
def get_wallpaper_image(self):
|
def get_wallpaper_image(self) -> Image.Image:
|
||||||
width = int(self.wallpaper.width * self.ratio)
|
width = int(self.wallpaper.width * self.ratio)
|
||||||
height = int(self.wallpaper.height * self.ratio)
|
height = int(self.wallpaper.height * self.ratio)
|
||||||
image = self.wallpaper.resize((width, height), Image.ANTIALIAS)
|
image = self.wallpaper.resize((width, height), Image.ANTIALIAS)
|
||||||
return image
|
return image
|
||||||
|
|
||||||
def draw_wallpaper(self, image, x=None, y=None):
|
def draw_wallpaper(
|
||||||
|
self, image: ImageTk.PhotoImage, x: float = None, y: float = None
|
||||||
|
):
|
||||||
if x is None and y is None:
|
if x is None and y is None:
|
||||||
x1, y1, x2, y2 = self.bbox(self.grid)
|
x1, y1, x2, y2 = self.bbox(self.grid)
|
||||||
x = (x1 + x2) / 2
|
x = (x1 + x2) / 2
|
||||||
y = (y1 + y2) / 2
|
y = (y1 + y2) / 2
|
||||||
|
|
||||||
self.wallpaper_id = self.create_image((x, y), image=image, tags=tags.WALLPAPER)
|
self.wallpaper_id = self.create_image((x, y), image=image, tags=tags.WALLPAPER)
|
||||||
self.wallpaper_drawn = image
|
self.wallpaper_drawn = image
|
||||||
|
|
||||||
|
@ -748,8 +721,6 @@ class CanvasGraph(tk.Canvas):
|
||||||
def wallpaper_center(self):
|
def wallpaper_center(self):
|
||||||
"""
|
"""
|
||||||
place the image at the center of canvas
|
place the image at the center of canvas
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
self.delete(self.wallpaper_id)
|
self.delete(self.wallpaper_id)
|
||||||
|
|
||||||
|
@ -773,8 +744,6 @@ class CanvasGraph(tk.Canvas):
|
||||||
def wallpaper_scaled(self):
|
def wallpaper_scaled(self):
|
||||||
"""
|
"""
|
||||||
scale image based on canvas dimension
|
scale image based on canvas dimension
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
self.delete(self.wallpaper_id)
|
self.delete(self.wallpaper_id)
|
||||||
canvas_w, canvas_h = self.width_and_height()
|
canvas_w, canvas_h = self.width_and_height()
|
||||||
|
@ -788,7 +757,7 @@ class CanvasGraph(tk.Canvas):
|
||||||
self.redraw_canvas((image.width(), image.height()))
|
self.redraw_canvas((image.width(), image.height()))
|
||||||
self.draw_wallpaper(image)
|
self.draw_wallpaper(image)
|
||||||
|
|
||||||
def redraw_canvas(self, dimensions=None):
|
def redraw_canvas(self, dimensions: Tuple[int, int] = None):
|
||||||
logging.info("redrawing canvas to dimensions: %s", dimensions)
|
logging.info("redrawing canvas to dimensions: %s", dimensions)
|
||||||
|
|
||||||
# reset scale and move back to original position
|
# reset scale and move back to original position
|
||||||
|
@ -836,7 +805,7 @@ class CanvasGraph(tk.Canvas):
|
||||||
else:
|
else:
|
||||||
self.itemconfig(tags.GRIDLINE, state=tk.HIDDEN)
|
self.itemconfig(tags.GRIDLINE, state=tk.HIDDEN)
|
||||||
|
|
||||||
def set_wallpaper(self, filename):
|
def set_wallpaper(self, filename: str):
|
||||||
logging.info("setting wallpaper: %s", filename)
|
logging.info("setting wallpaper: %s", filename)
|
||||||
if filename:
|
if filename:
|
||||||
img = Image.open(filename)
|
img = Image.open(filename)
|
||||||
|
@ -849,16 +818,12 @@ class CanvasGraph(tk.Canvas):
|
||||||
self.wallpaper = None
|
self.wallpaper = None
|
||||||
self.wallpaper_file = None
|
self.wallpaper_file = None
|
||||||
|
|
||||||
def is_selection_mode(self):
|
def is_selection_mode(self) -> bool:
|
||||||
return self.mode == GraphMode.SELECT
|
return self.mode == GraphMode.SELECT
|
||||||
|
|
||||||
def create_edge(self, source, dest):
|
def create_edge(self, source: CanvasNode, dest: CanvasNode):
|
||||||
"""
|
"""
|
||||||
create an edge between source node and destination node
|
create an edge between source node and destination node
|
||||||
|
|
||||||
:param CanvasNode source: source node
|
|
||||||
:param CanvasNode dest: destination node
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
if (source.id, dest.id) not in self.edges:
|
if (source.id, dest.id) not in self.edges:
|
||||||
pos0 = source.core_node.position
|
pos0 = source.core_node.position
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import font
|
from tkinter import font
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
|
|
||||||
|
@ -16,11 +17,22 @@ from core.gui.graph import tags
|
||||||
from core.gui.graph.tooltip import CanvasTooltip
|
from core.gui.graph.tooltip import CanvasTooltip
|
||||||
from core.gui.nodeutils import NodeUtils
|
from core.gui.nodeutils import NodeUtils
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
from PIL.ImageTk import PhotoImage
|
||||||
|
|
||||||
NODE_TEXT_OFFSET = 5
|
NODE_TEXT_OFFSET = 5
|
||||||
|
|
||||||
|
|
||||||
class CanvasNode:
|
class CanvasNode:
|
||||||
def __init__(self, app, x, y, core_node, image):
|
def __init__(
|
||||||
|
self,
|
||||||
|
app: "Application",
|
||||||
|
x: float,
|
||||||
|
y: float,
|
||||||
|
core_node: core_pb2.Node,
|
||||||
|
image: "PhotoImage",
|
||||||
|
):
|
||||||
self.app = app
|
self.app = app
|
||||||
self.canvas = app.canvas
|
self.canvas = app.canvas
|
||||||
self.image = image
|
self.image = image
|
||||||
|
@ -70,8 +82,6 @@ class CanvasNode:
|
||||||
def delete_antenna(self):
|
def delete_antenna(self):
|
||||||
"""
|
"""
|
||||||
delete one antenna
|
delete one antenna
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
if self.antennae:
|
if self.antennae:
|
||||||
antenna_id = self.antennae.pop()
|
antenna_id = self.antennae.pop()
|
||||||
|
@ -80,8 +90,6 @@ class CanvasNode:
|
||||||
def delete_antennae(self):
|
def delete_antennae(self):
|
||||||
"""
|
"""
|
||||||
delete all antennas
|
delete all antennas
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
for antenna_id in self.antennae:
|
for antenna_id in self.antennae:
|
||||||
self.canvas.delete(antenna_id)
|
self.canvas.delete(antenna_id)
|
||||||
|
@ -95,14 +103,14 @@ class CanvasNode:
|
||||||
image_box = self.canvas.bbox(self.id)
|
image_box = self.canvas.bbox(self.id)
|
||||||
return image_box[3] + NODE_TEXT_OFFSET
|
return image_box[3] + NODE_TEXT_OFFSET
|
||||||
|
|
||||||
def move(self, x, y):
|
def move(self, x: int, y: int):
|
||||||
x, y = self.canvas.get_scaled_coords(x, y)
|
x, y = self.canvas.get_scaled_coords(x, y)
|
||||||
current_x, current_y = self.canvas.coords(self.id)
|
current_x, current_y = self.canvas.coords(self.id)
|
||||||
x_offset = x - current_x
|
x_offset = x - current_x
|
||||||
y_offset = y - current_y
|
y_offset = y - current_y
|
||||||
self.motion(x_offset, y_offset, update=False)
|
self.motion(x_offset, y_offset, update=False)
|
||||||
|
|
||||||
def motion(self, x_offset, y_offset, update=True):
|
def motion(self, x_offset: int, y_offset: int, update: bool = True):
|
||||||
original_position = self.canvas.coords(self.id)
|
original_position = self.canvas.coords(self.id)
|
||||||
self.canvas.move(self.id, x_offset, y_offset)
|
self.canvas.move(self.id, x_offset, y_offset)
|
||||||
x, y = self.canvas.coords(self.id)
|
x, y = self.canvas.coords(self.id)
|
||||||
|
@ -144,7 +152,7 @@ class CanvasNode:
|
||||||
if self.app.core.is_runtime() and update:
|
if self.app.core.is_runtime() and update:
|
||||||
self.app.core.edit_node(self.core_node)
|
self.app.core.edit_node(self.core_node)
|
||||||
|
|
||||||
def on_enter(self, event):
|
def on_enter(self, event: tk.Event):
|
||||||
if self.app.core.is_runtime() and self.app.core.observer:
|
if self.app.core.is_runtime() and self.app.core.observer:
|
||||||
self.tooltip.text.set("waiting...")
|
self.tooltip.text.set("waiting...")
|
||||||
self.tooltip.on_enter(event)
|
self.tooltip.on_enter(event)
|
||||||
|
@ -154,16 +162,16 @@ class CanvasNode:
|
||||||
except grpc.RpcError as e:
|
except grpc.RpcError as e:
|
||||||
show_grpc_error(e)
|
show_grpc_error(e)
|
||||||
|
|
||||||
def on_leave(self, event):
|
def on_leave(self, event: tk.Event):
|
||||||
self.tooltip.on_leave(event)
|
self.tooltip.on_leave(event)
|
||||||
|
|
||||||
def double_click(self, event):
|
def double_click(self, event: tk.Event):
|
||||||
if self.app.core.is_runtime():
|
if self.app.core.is_runtime():
|
||||||
self.canvas.core.launch_terminal(self.core_node.id)
|
self.canvas.core.launch_terminal(self.core_node.id)
|
||||||
else:
|
else:
|
||||||
self.show_config()
|
self.show_config()
|
||||||
|
|
||||||
def create_context(self):
|
def create_context(self) -> tk.Menu:
|
||||||
is_wlan = self.core_node.type == NodeType.WIRELESS_LAN
|
is_wlan = self.core_node.type == NodeType.WIRELESS_LAN
|
||||||
is_emane = self.core_node.type == NodeType.EMANE
|
is_emane = self.core_node.type == NodeType.EMANE
|
||||||
context = tk.Menu(self.canvas)
|
context = tk.Menu(self.canvas)
|
||||||
|
@ -245,7 +253,7 @@ class CanvasNode:
|
||||||
dialog = NodeServiceDialog(self.app.master, self.app, self)
|
dialog = NodeServiceDialog(self.app.master, self.app, self)
|
||||||
dialog.show()
|
dialog.show()
|
||||||
|
|
||||||
def has_emane_link(self, interface_id):
|
def has_emane_link(self, interface_id: int) -> core_pb2.Node:
|
||||||
result = None
|
result = None
|
||||||
for edge in self.edges:
|
for edge in self.edges:
|
||||||
if self.id == edge.src:
|
if self.id == edge.src:
|
||||||
|
|
|
@ -1,23 +1,28 @@
|
||||||
import logging
|
import logging
|
||||||
|
from typing import TYPE_CHECKING, Dict, List, Union
|
||||||
|
|
||||||
from core.gui.dialogs.shapemod import ShapeDialog
|
from core.gui.dialogs.shapemod import ShapeDialog
|
||||||
from core.gui.graph import tags
|
from core.gui.graph import tags
|
||||||
from core.gui.graph.shapeutils import ShapeType
|
from core.gui.graph.shapeutils import ShapeType
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
from core.gui.graph.graph import CanvasGraph
|
||||||
|
|
||||||
|
|
||||||
class AnnotationData:
|
class AnnotationData:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
text="",
|
text: str = "",
|
||||||
font="Arial",
|
font: str = "Arial",
|
||||||
font_size=12,
|
font_size: int = 12,
|
||||||
text_color="#000000",
|
text_color: str = "#000000",
|
||||||
fill_color="",
|
fill_color: str = "",
|
||||||
border_color="#000000",
|
border_color: str = "#000000",
|
||||||
border_width=1,
|
border_width: int = 1,
|
||||||
bold=False,
|
bold: bool = False,
|
||||||
italic=False,
|
italic: bool = False,
|
||||||
underline=False,
|
underline: bool = False,
|
||||||
):
|
):
|
||||||
self.text = text
|
self.text = text
|
||||||
self.font = font
|
self.font = font
|
||||||
|
@ -32,7 +37,17 @@ class AnnotationData:
|
||||||
|
|
||||||
|
|
||||||
class Shape:
|
class Shape:
|
||||||
def __init__(self, app, canvas, shape_type, x1, y1, x2=None, y2=None, data=None):
|
def __init__(
|
||||||
|
self,
|
||||||
|
app: "Application",
|
||||||
|
canvas: "CanvasGraph",
|
||||||
|
shape_type: ShapeType,
|
||||||
|
x1: float,
|
||||||
|
y1: float,
|
||||||
|
x2: float = None,
|
||||||
|
y2: float = None,
|
||||||
|
data: AnnotationData = None,
|
||||||
|
):
|
||||||
self.app = app
|
self.app = app
|
||||||
self.canvas = canvas
|
self.canvas = canvas
|
||||||
self.shape_type = shape_type
|
self.shape_type = shape_type
|
||||||
|
@ -99,7 +114,7 @@ class Shape:
|
||||||
logging.error("unknown shape type: %s", self.shape_type)
|
logging.error("unknown shape type: %s", self.shape_type)
|
||||||
self.created = True
|
self.created = True
|
||||||
|
|
||||||
def get_font(self):
|
def get_font(self) -> List[Union[int, str]]:
|
||||||
font = [self.shape_data.font, self.shape_data.font_size]
|
font = [self.shape_data.font, self.shape_data.font_size]
|
||||||
if self.shape_data.bold:
|
if self.shape_data.bold:
|
||||||
font.append("bold")
|
font.append("bold")
|
||||||
|
@ -123,10 +138,10 @@ class Shape:
|
||||||
font=font,
|
font=font,
|
||||||
)
|
)
|
||||||
|
|
||||||
def shape_motion(self, x1, y1):
|
def shape_motion(self, x1: float, y1: float):
|
||||||
self.canvas.coords(self.id, self.x1, self.y1, x1, y1)
|
self.canvas.coords(self.id, self.x1, self.y1, x1, y1)
|
||||||
|
|
||||||
def shape_complete(self, x, y):
|
def shape_complete(self, x: float, y: float):
|
||||||
for component in tags.ABOVE_SHAPE:
|
for component in tags.ABOVE_SHAPE:
|
||||||
self.canvas.tag_raise(component)
|
self.canvas.tag_raise(component)
|
||||||
s = ShapeDialog(self.app, self.app, self)
|
s = ShapeDialog(self.app, self.app, self)
|
||||||
|
@ -135,7 +150,7 @@ class Shape:
|
||||||
def disappear(self):
|
def disappear(self):
|
||||||
self.canvas.delete(self.id)
|
self.canvas.delete(self.id)
|
||||||
|
|
||||||
def motion(self, x_offset, y_offset):
|
def motion(self, x_offset: float, y_offset: float):
|
||||||
original_position = self.canvas.coords(self.id)
|
original_position = self.canvas.coords(self.id)
|
||||||
self.canvas.move(self.id, x_offset, y_offset)
|
self.canvas.move(self.id, x_offset, y_offset)
|
||||||
coords = self.canvas.coords(self.id)
|
coords = self.canvas.coords(self.id)
|
||||||
|
@ -151,7 +166,7 @@ class Shape:
|
||||||
self.canvas.delete(self.id)
|
self.canvas.delete(self.id)
|
||||||
self.canvas.delete(self.text_id)
|
self.canvas.delete(self.text_id)
|
||||||
|
|
||||||
def metadata(self):
|
def metadata(self) -> Dict[str, Union[str, int, bool]]:
|
||||||
coords = self.canvas.coords(self.id)
|
coords = self.canvas.coords(self.id)
|
||||||
# update coords to actual positions
|
# update coords to actual positions
|
||||||
if len(coords) == 4:
|
if len(coords) == 4:
|
||||||
|
|
|
@ -11,13 +11,13 @@ class ShapeType(enum.Enum):
|
||||||
SHAPES = {ShapeType.OVAL, ShapeType.RECTANGLE}
|
SHAPES = {ShapeType.OVAL, ShapeType.RECTANGLE}
|
||||||
|
|
||||||
|
|
||||||
def is_draw_shape(shape_type):
|
def is_draw_shape(shape_type: ShapeType) -> bool:
|
||||||
return shape_type in SHAPES
|
return shape_type in SHAPES
|
||||||
|
|
||||||
|
|
||||||
def is_shape_text(shape_type):
|
def is_shape_text(shape_type: ShapeType) -> bool:
|
||||||
return shape_type == ShapeType.TEXT
|
return shape_type == ShapeType.TEXT
|
||||||
|
|
||||||
|
|
||||||
def is_marker(shape_type):
|
def is_marker(shape_type: ShapeType) -> bool:
|
||||||
return shape_type == ShapeType.MARKER
|
return shape_type == ShapeType.MARKER
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from core.gui.themes import Styles
|
from core.gui.themes import Styles
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.graph.graph import CanvasGraph
|
||||||
|
|
||||||
|
|
||||||
class CanvasTooltip:
|
class CanvasTooltip:
|
||||||
"""
|
"""
|
||||||
|
@ -19,7 +23,14 @@ class CanvasTooltip:
|
||||||
Alberto Vassena on 2016.12.10.
|
Alberto Vassena on 2016.12.10.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, canvas, *, pad=(5, 3, 5, 3), waittime=400, wraplength=600):
|
def __init__(
|
||||||
|
self,
|
||||||
|
canvas: "CanvasGraph",
|
||||||
|
*,
|
||||||
|
pad=(5, 3, 5, 3),
|
||||||
|
waittime: int = 400,
|
||||||
|
wraplength: int = 600
|
||||||
|
):
|
||||||
# in miliseconds, originally 500
|
# in miliseconds, originally 500
|
||||||
self.waittime = waittime
|
self.waittime = waittime
|
||||||
# in pixels, originally 180
|
# in pixels, originally 180
|
||||||
|
@ -30,10 +41,10 @@ class CanvasTooltip:
|
||||||
self.id = None
|
self.id = None
|
||||||
self.tw = None
|
self.tw = None
|
||||||
|
|
||||||
def on_enter(self, event=None):
|
def on_enter(self, event: tk.Event = None):
|
||||||
self.schedule()
|
self.schedule()
|
||||||
|
|
||||||
def on_leave(self, event=None):
|
def on_leave(self, event: tk.Event = None):
|
||||||
self.unschedule()
|
self.unschedule()
|
||||||
self.hide()
|
self.hide()
|
||||||
|
|
||||||
|
@ -47,7 +58,7 @@ class CanvasTooltip:
|
||||||
if id_:
|
if id_:
|
||||||
self.canvas.after_cancel(id_)
|
self.canvas.after_cancel(id_)
|
||||||
|
|
||||||
def show(self, event=None):
|
def show(self, event: tk.Event = None):
|
||||||
def tip_pos_calculator(canvas, label, *, tip_delta=(10, 5), pad=(5, 3, 5, 3)):
|
def tip_pos_calculator(canvas, label, *, tip_delta=(10, 5), pad=(5, 3, 5, 3)):
|
||||||
c = canvas
|
c = canvas
|
||||||
s_width, s_height = c.winfo_screenwidth(), c.winfo_screenheight()
|
s_width, s_height = c.winfo_screenwidth(), c.winfo_screenheight()
|
||||||
|
|
|
@ -9,7 +9,7 @@ class Images:
|
||||||
images = {}
|
images = {}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, file_path, width, height=None):
|
def create(cls, file_path: str, width: int, height: int = None):
|
||||||
if height is None:
|
if height is None:
|
||||||
height = width
|
height = width
|
||||||
image = Image.open(file_path)
|
image = Image.open(file_path)
|
||||||
|
@ -22,12 +22,12 @@ class Images:
|
||||||
cls.images[image.stem] = str(image)
|
cls.images[image.stem] = str(image)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get(cls, image_enum, width, height=None):
|
def get(cls, image_enum: Enum, width: int, height: int = None):
|
||||||
file_path = cls.images[image_enum.value]
|
file_path = cls.images[image_enum.value]
|
||||||
return cls.create(file_path, width, height)
|
return cls.create(file_path, width, height)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_custom(cls, name, width, height=None):
|
def get_custom(cls, name: str, width: int, height: int = None):
|
||||||
file_path = cls.images[name]
|
file_path = cls.images[name]
|
||||||
return cls.create(file_path, width, height)
|
return cls.create(file_path, width, height)
|
||||||
|
|
||||||
|
|
|
@ -1,24 +1,30 @@
|
||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
|
from typing import TYPE_CHECKING, Set, Union
|
||||||
|
|
||||||
from netaddr import IPNetwork
|
from netaddr import IPNetwork
|
||||||
|
|
||||||
from core.gui.nodeutils import NodeUtils
|
from core.gui.nodeutils import NodeUtils
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
from core.api.grpc import core_pb2
|
||||||
|
from core.gui.graph.node import CanvasNode
|
||||||
|
|
||||||
|
|
||||||
def random_mac():
|
def random_mac():
|
||||||
return ("{:02x}" * 6).format(*[random.randrange(256) for _ in range(6)])
|
return ("{:02x}" * 6).format(*[random.randrange(256) for _ in range(6)])
|
||||||
|
|
||||||
|
|
||||||
class InterfaceManager:
|
class InterfaceManager:
|
||||||
def __init__(self, app, address="10.0.0.0", mask=24):
|
def __init__(self, app: "Application", address: str = "10.0.0.0", mask: int = 24):
|
||||||
self.app = app
|
self.app = app
|
||||||
self.mask = mask
|
self.mask = mask
|
||||||
self.base_prefix = max(self.mask - 8, 0)
|
self.base_prefix = max(self.mask - 8, 0)
|
||||||
self.subnets = IPNetwork(f"{address}/{self.base_prefix}")
|
self.subnets = IPNetwork(f"{address}/{self.base_prefix}")
|
||||||
self.current_subnet = None
|
self.current_subnet = None
|
||||||
|
|
||||||
def next_subnet(self):
|
def next_subnet(self) -> IPNetwork:
|
||||||
# define currently used subnets
|
# define currently used subnets
|
||||||
used_subnets = set()
|
used_subnets = set()
|
||||||
for edge in self.app.core.links.values():
|
for edge in self.app.core.links.values():
|
||||||
|
@ -38,17 +44,19 @@ class InterfaceManager:
|
||||||
def reset(self):
|
def reset(self):
|
||||||
self.current_subnet = None
|
self.current_subnet = None
|
||||||
|
|
||||||
def get_ips(self, node_id):
|
def get_ips(self, node_id: int) -> [str, str, int]:
|
||||||
ip4 = self.current_subnet[node_id]
|
ip4 = self.current_subnet[node_id]
|
||||||
ip6 = ip4.ipv6()
|
ip6 = ip4.ipv6()
|
||||||
prefix = self.current_subnet.prefixlen
|
prefix = self.current_subnet.prefixlen
|
||||||
return str(ip4), str(ip6), prefix
|
return str(ip4), str(ip6), prefix
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_subnet(cls, interface):
|
def get_subnet(cls, interface: "core_pb2.Interface") -> IPNetwork:
|
||||||
return IPNetwork(f"{interface.ip4}/{interface.ip4mask}").cidr
|
return IPNetwork(f"{interface.ip4}/{interface.ip4mask}").cidr
|
||||||
|
|
||||||
def determine_subnet(self, canvas_src_node, canvas_dst_node):
|
def determine_subnet(
|
||||||
|
self, canvas_src_node: "CanvasNode", canvas_dst_node: "CanvasNode"
|
||||||
|
):
|
||||||
src_node = canvas_src_node.core_node
|
src_node = canvas_src_node.core_node
|
||||||
dst_node = canvas_dst_node.core_node
|
dst_node = canvas_dst_node.core_node
|
||||||
is_src_container = NodeUtils.is_container_node(src_node.type)
|
is_src_container = NodeUtils.is_container_node(src_node.type)
|
||||||
|
@ -70,7 +78,9 @@ class InterfaceManager:
|
||||||
else:
|
else:
|
||||||
logging.info("ignoring subnet change for link between network nodes")
|
logging.info("ignoring subnet change for link between network nodes")
|
||||||
|
|
||||||
def find_subnet(self, canvas_node, visited=None):
|
def find_subnet(
|
||||||
|
self, canvas_node: "CanvasNode", visited: Set[int] = None
|
||||||
|
) -> Union[IPNetwork, None]:
|
||||||
logging.info("finding subnet for node: %s", canvas_node.core_node.name)
|
logging.info("finding subnet for node: %s", canvas_node.core_node.name)
|
||||||
canvas = self.app.canvas
|
canvas = self.app.canvas
|
||||||
cidr = None
|
cidr = None
|
||||||
|
|
|
@ -3,8 +3,10 @@ The actions taken when each menubar option is clicked
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import tkinter as tk
|
||||||
import webbrowser
|
import webbrowser
|
||||||
from tkinter import filedialog, messagebox
|
from tkinter import filedialog, messagebox
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from core.gui.appconfig import XMLS_PATH
|
from core.gui.appconfig import XMLS_PATH
|
||||||
from core.gui.dialogs.about import AboutDialog
|
from core.gui.dialogs.about import AboutDialog
|
||||||
|
@ -19,29 +21,24 @@ from core.gui.dialogs.sessions import SessionsDialog
|
||||||
from core.gui.dialogs.throughput import ThroughputDialog
|
from core.gui.dialogs.throughput import ThroughputDialog
|
||||||
from core.gui.task import BackgroundTask
|
from core.gui.task import BackgroundTask
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
|
|
||||||
class MenuAction:
|
class MenuAction:
|
||||||
"""
|
def __init__(self, app: "Application", master: tk.Tk):
|
||||||
Actions performed when choosing menu items
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, app, master):
|
|
||||||
self.master = master
|
self.master = master
|
||||||
self.app = app
|
self.app = app
|
||||||
self.canvas = app.canvas
|
self.canvas = app.canvas
|
||||||
|
|
||||||
def cleanup_old_session(self, quitapp=False):
|
def cleanup_old_session(self):
|
||||||
logging.info("cleaning up old session")
|
logging.info("cleaning up old session")
|
||||||
self.app.core.stop_session()
|
self.app.core.stop_session()
|
||||||
self.app.core.delete_session()
|
self.app.core.delete_session()
|
||||||
# if quitapp:
|
|
||||||
# self.app.quit()
|
|
||||||
|
|
||||||
def prompt_save_running_session(self, quitapp=False):
|
def prompt_save_running_session(self, quitapp: bool = False):
|
||||||
"""
|
"""
|
||||||
Prompt use to stop running session before application is closed
|
Prompt use to stop running session before application is closed
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
result = True
|
result = True
|
||||||
if self.app.core.is_runtime():
|
if self.app.core.is_runtime():
|
||||||
|
@ -56,15 +53,13 @@ class MenuAction:
|
||||||
elif quitapp:
|
elif quitapp:
|
||||||
self.app.quit()
|
self.app.quit()
|
||||||
|
|
||||||
def on_quit(self, event=None):
|
def on_quit(self, event: tk.Event = None):
|
||||||
"""
|
"""
|
||||||
Prompt user whether so save running session, and then close the application
|
Prompt user whether so save running session, and then close the application
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
self.prompt_save_running_session(quitapp=True)
|
self.prompt_save_running_session(quitapp=True)
|
||||||
|
|
||||||
def file_save_as_xml(self, event=None):
|
def file_save_as_xml(self, event: tk.Event = None):
|
||||||
logging.info("menuaction.py file_save_as_xml()")
|
logging.info("menuaction.py file_save_as_xml()")
|
||||||
file_path = filedialog.asksaveasfilename(
|
file_path = filedialog.asksaveasfilename(
|
||||||
initialdir=str(XMLS_PATH),
|
initialdir=str(XMLS_PATH),
|
||||||
|
@ -75,7 +70,7 @@ class MenuAction:
|
||||||
if file_path:
|
if file_path:
|
||||||
self.app.core.save_xml(file_path)
|
self.app.core.save_xml(file_path)
|
||||||
|
|
||||||
def file_open_xml(self, event=None):
|
def file_open_xml(self, event: tk.Event = None):
|
||||||
logging.info("menuaction.py file_open_xml()")
|
logging.info("menuaction.py file_open_xml()")
|
||||||
file_path = filedialog.askopenfilename(
|
file_path = filedialog.askopenfilename(
|
||||||
initialdir=str(XMLS_PATH),
|
initialdir=str(XMLS_PATH),
|
||||||
|
@ -141,11 +136,11 @@ class MenuAction:
|
||||||
else:
|
else:
|
||||||
self.app.core.cancel_throughputs()
|
self.app.core.cancel_throughputs()
|
||||||
|
|
||||||
def copy(self, event=None):
|
def copy(self, event: tk.Event = None):
|
||||||
logging.debug("copy")
|
logging.debug("copy")
|
||||||
self.app.canvas.copy()
|
self.app.canvas.copy()
|
||||||
|
|
||||||
def paste(self, event=None):
|
def paste(self, event: tk.Event = None):
|
||||||
logging.debug("paste")
|
logging.debug("paste")
|
||||||
self.app.canvas.paste()
|
self.app.canvas.paste()
|
||||||
|
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import core.gui.menuaction as action
|
import core.gui.menuaction as action
|
||||||
from core.gui.coreclient import OBSERVERS
|
from core.gui.coreclient import OBSERVERS
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
|
|
||||||
class Menubar(tk.Menu):
|
class Menubar(tk.Menu):
|
||||||
"""
|
"""
|
||||||
Core menubar
|
Core menubar
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, master, app, cnf={}, **kwargs):
|
def __init__(self, master: tk.Tk, app: "Application", cnf={}, **kwargs):
|
||||||
"""
|
"""
|
||||||
Create a CoreMenubar instance
|
Create a CoreMenubar instance
|
||||||
|
|
||||||
:param master:
|
|
||||||
:param tkinter.Menu menubar: menubar object
|
|
||||||
:param coretk.app.Application app: application object
|
|
||||||
"""
|
"""
|
||||||
super().__init__(master, cnf, **kwargs)
|
super().__init__(master, cnf, **kwargs)
|
||||||
self.master.config(menu=self)
|
self.master.config(menu=self)
|
||||||
|
@ -27,8 +27,6 @@ class Menubar(tk.Menu):
|
||||||
def draw(self):
|
def draw(self):
|
||||||
"""
|
"""
|
||||||
Create core menubar and bind the hot keys to their matching command
|
Create core menubar and bind the hot keys to their matching command
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
self.draw_file_menu()
|
self.draw_file_menu()
|
||||||
self.draw_edit_menu()
|
self.draw_edit_menu()
|
||||||
|
@ -42,8 +40,6 @@ class Menubar(tk.Menu):
|
||||||
def draw_file_menu(self):
|
def draw_file_menu(self):
|
||||||
"""
|
"""
|
||||||
Create file menu
|
Create file menu
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
menu = tk.Menu(self)
|
menu = tk.Menu(self)
|
||||||
menu.add_command(
|
menu.add_command(
|
||||||
|
@ -81,8 +77,6 @@ class Menubar(tk.Menu):
|
||||||
def draw_edit_menu(self):
|
def draw_edit_menu(self):
|
||||||
"""
|
"""
|
||||||
Create edit menu
|
Create edit menu
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
menu = tk.Menu(self)
|
menu = tk.Menu(self)
|
||||||
menu.add_command(label="Preferences", command=self.menuaction.gui_preferences)
|
menu.add_command(label="Preferences", command=self.menuaction.gui_preferences)
|
||||||
|
@ -112,8 +106,6 @@ class Menubar(tk.Menu):
|
||||||
def draw_canvas_menu(self):
|
def draw_canvas_menu(self):
|
||||||
"""
|
"""
|
||||||
Create canvas menu
|
Create canvas menu
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
menu = tk.Menu(self)
|
menu = tk.Menu(self)
|
||||||
menu.add_command(
|
menu.add_command(
|
||||||
|
@ -136,8 +128,6 @@ class Menubar(tk.Menu):
|
||||||
def draw_view_menu(self):
|
def draw_view_menu(self):
|
||||||
"""
|
"""
|
||||||
Create view menu
|
Create view menu
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
view_menu = tk.Menu(self)
|
view_menu = tk.Menu(self)
|
||||||
self.create_show_menu(view_menu)
|
self.create_show_menu(view_menu)
|
||||||
|
@ -149,12 +139,9 @@ class Menubar(tk.Menu):
|
||||||
view_menu.add_command(label="Zoom out", accelerator="-", state=tk.DISABLED)
|
view_menu.add_command(label="Zoom out", accelerator="-", state=tk.DISABLED)
|
||||||
self.add_cascade(label="View", menu=view_menu)
|
self.add_cascade(label="View", menu=view_menu)
|
||||||
|
|
||||||
def create_show_menu(self, view_menu):
|
def create_show_menu(self, view_menu: tk.Menu):
|
||||||
"""
|
"""
|
||||||
Create the menu items in View/Show
|
Create the menu items in View/Show
|
||||||
|
|
||||||
:param tkinter.Menu view_menu: the view menu
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
menu = tk.Menu(view_menu)
|
menu = tk.Menu(view_menu)
|
||||||
menu.add_command(label="All", state=tk.DISABLED)
|
menu.add_command(label="All", state=tk.DISABLED)
|
||||||
|
@ -169,12 +156,9 @@ class Menubar(tk.Menu):
|
||||||
menu.add_command(label="API Messages", state=tk.DISABLED)
|
menu.add_command(label="API Messages", state=tk.DISABLED)
|
||||||
view_menu.add_cascade(label="Show", menu=menu)
|
view_menu.add_cascade(label="Show", menu=menu)
|
||||||
|
|
||||||
def create_experimental_menu(self, tools_menu):
|
def create_experimental_menu(self, tools_menu: tk.Menu):
|
||||||
"""
|
"""
|
||||||
Create experimental menu item and the sub menu items inside
|
Create experimental menu item and the sub menu items inside
|
||||||
|
|
||||||
:param tkinter.Menu tools_menu: tools menu
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
menu = tk.Menu(tools_menu)
|
menu = tk.Menu(tools_menu)
|
||||||
menu.add_command(label="Plugins...", state=tk.DISABLED)
|
menu.add_command(label="Plugins...", state=tk.DISABLED)
|
||||||
|
@ -182,12 +166,9 @@ class Menubar(tk.Menu):
|
||||||
menu.add_command(label="Topology partitioning...", state=tk.DISABLED)
|
menu.add_command(label="Topology partitioning...", state=tk.DISABLED)
|
||||||
tools_menu.add_cascade(label="Experimental", menu=menu)
|
tools_menu.add_cascade(label="Experimental", menu=menu)
|
||||||
|
|
||||||
def create_random_menu(self, topology_generator_menu):
|
def create_random_menu(self, topology_generator_menu: tk.Menu):
|
||||||
"""
|
"""
|
||||||
Create random menu item and the sub menu items inside
|
Create random menu item and the sub menu items inside
|
||||||
|
|
||||||
:param tkinter.Menu topology_generator_menu: topology generator menu
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
menu = tk.Menu(topology_generator_menu)
|
menu = tk.Menu(topology_generator_menu)
|
||||||
# list of number of random nodes to create
|
# list of number of random nodes to create
|
||||||
|
@ -197,12 +178,9 @@ class Menubar(tk.Menu):
|
||||||
menu.add_command(label=label, state=tk.DISABLED)
|
menu.add_command(label=label, state=tk.DISABLED)
|
||||||
topology_generator_menu.add_cascade(label="Random", menu=menu)
|
topology_generator_menu.add_cascade(label="Random", menu=menu)
|
||||||
|
|
||||||
def create_grid_menu(self, topology_generator_menu):
|
def create_grid_menu(self, topology_generator_menu: tk.Menu):
|
||||||
"""
|
"""
|
||||||
Create grid menu item and the sub menu items inside
|
Create grid menu item and the sub menu items inside
|
||||||
|
|
||||||
:param tkinter.Menu topology_generator_menu: topology_generator_menu
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
menu = tk.Menu(topology_generator_menu)
|
menu = tk.Menu(topology_generator_menu)
|
||||||
# list of number of nodes to create
|
# list of number of nodes to create
|
||||||
|
@ -212,12 +190,9 @@ class Menubar(tk.Menu):
|
||||||
menu.add_command(label=label, state=tk.DISABLED)
|
menu.add_command(label=label, state=tk.DISABLED)
|
||||||
topology_generator_menu.add_cascade(label="Grid", menu=menu)
|
topology_generator_menu.add_cascade(label="Grid", menu=menu)
|
||||||
|
|
||||||
def create_connected_grid_menu(self, topology_generator_menu):
|
def create_connected_grid_menu(self, topology_generator_menu: tk.Menu):
|
||||||
"""
|
"""
|
||||||
Create connected grid menu items and the sub menu items inside
|
Create connected grid menu items and the sub menu items inside
|
||||||
|
|
||||||
:param tkinter.Menu topology_generator_menu: topology generator menu
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
menu = tk.Menu(topology_generator_menu)
|
menu = tk.Menu(topology_generator_menu)
|
||||||
for i in range(1, 11, 1):
|
for i in range(1, 11, 1):
|
||||||
|
@ -229,12 +204,9 @@ class Menubar(tk.Menu):
|
||||||
menu.add_cascade(label=label, menu=submenu)
|
menu.add_cascade(label=label, menu=submenu)
|
||||||
topology_generator_menu.add_cascade(label="Connected Grid", menu=menu)
|
topology_generator_menu.add_cascade(label="Connected Grid", menu=menu)
|
||||||
|
|
||||||
def create_chain_menu(self, topology_generator_menu):
|
def create_chain_menu(self, topology_generator_menu: tk.Menu):
|
||||||
"""
|
"""
|
||||||
Create chain menu item and the sub menu items inside
|
Create chain menu item and the sub menu items inside
|
||||||
|
|
||||||
:param tkinter.Menu topology_generator_menu: topology generator menu
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
menu = tk.Menu(topology_generator_menu)
|
menu = tk.Menu(topology_generator_menu)
|
||||||
# number of nodes to create
|
# number of nodes to create
|
||||||
|
@ -244,12 +216,9 @@ class Menubar(tk.Menu):
|
||||||
menu.add_command(label=label, state=tk.DISABLED)
|
menu.add_command(label=label, state=tk.DISABLED)
|
||||||
topology_generator_menu.add_cascade(label="Chain", menu=menu)
|
topology_generator_menu.add_cascade(label="Chain", menu=menu)
|
||||||
|
|
||||||
def create_star_menu(self, topology_generator_menu):
|
def create_star_menu(self, topology_generator_menu: tk.Menu):
|
||||||
"""
|
"""
|
||||||
Create star menu item and the sub menu items inside
|
Create star menu item and the sub menu items inside
|
||||||
|
|
||||||
:param tkinter.Menu topology_generator_menu: topology generator menu
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
menu = tk.Menu(topology_generator_menu)
|
menu = tk.Menu(topology_generator_menu)
|
||||||
for i in range(3, 26, 1):
|
for i in range(3, 26, 1):
|
||||||
|
@ -257,12 +226,9 @@ class Menubar(tk.Menu):
|
||||||
menu.add_command(label=label, state=tk.DISABLED)
|
menu.add_command(label=label, state=tk.DISABLED)
|
||||||
topology_generator_menu.add_cascade(label="Star", menu=menu)
|
topology_generator_menu.add_cascade(label="Star", menu=menu)
|
||||||
|
|
||||||
def create_cycle_menu(self, topology_generator_menu):
|
def create_cycle_menu(self, topology_generator_menu: tk.Menu):
|
||||||
"""
|
"""
|
||||||
Create cycle menu item and the sub items inside
|
Create cycle menu item and the sub items inside
|
||||||
|
|
||||||
:param tkinter.Menu topology_generator_menu: topology generator menu
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
menu = tk.Menu(topology_generator_menu)
|
menu = tk.Menu(topology_generator_menu)
|
||||||
for i in range(3, 25, 1):
|
for i in range(3, 25, 1):
|
||||||
|
@ -270,12 +236,9 @@ class Menubar(tk.Menu):
|
||||||
menu.add_command(label=label, state=tk.DISABLED)
|
menu.add_command(label=label, state=tk.DISABLED)
|
||||||
topology_generator_menu.add_cascade(label="Cycle", menu=menu)
|
topology_generator_menu.add_cascade(label="Cycle", menu=menu)
|
||||||
|
|
||||||
def create_wheel_menu(self, topology_generator_menu):
|
def create_wheel_menu(self, topology_generator_menu: tk.Menu):
|
||||||
"""
|
"""
|
||||||
Create wheel menu item and the sub menu items inside
|
Create wheel menu item and the sub menu items inside
|
||||||
|
|
||||||
:param tkinter.Menu topology_generator_menu: topology generator menu
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
menu = tk.Menu(topology_generator_menu)
|
menu = tk.Menu(topology_generator_menu)
|
||||||
for i in range(4, 26, 1):
|
for i in range(4, 26, 1):
|
||||||
|
@ -283,12 +246,9 @@ class Menubar(tk.Menu):
|
||||||
menu.add_command(label=label, state=tk.DISABLED)
|
menu.add_command(label=label, state=tk.DISABLED)
|
||||||
topology_generator_menu.add_cascade(label="Wheel", menu=menu)
|
topology_generator_menu.add_cascade(label="Wheel", menu=menu)
|
||||||
|
|
||||||
def create_cube_menu(self, topology_generator_menu):
|
def create_cube_menu(self, topology_generator_menu: tk.Menu):
|
||||||
"""
|
"""
|
||||||
Create cube menu item and the sub menu items inside
|
Create cube menu item and the sub menu items inside
|
||||||
|
|
||||||
:param tkinter.Menu topology_generator_menu: topology generator menu
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
menu = tk.Menu(topology_generator_menu)
|
menu = tk.Menu(topology_generator_menu)
|
||||||
for i in range(2, 7, 1):
|
for i in range(2, 7, 1):
|
||||||
|
@ -296,12 +256,9 @@ class Menubar(tk.Menu):
|
||||||
menu.add_command(label=label, state=tk.DISABLED)
|
menu.add_command(label=label, state=tk.DISABLED)
|
||||||
topology_generator_menu.add_cascade(label="Cube", menu=menu)
|
topology_generator_menu.add_cascade(label="Cube", menu=menu)
|
||||||
|
|
||||||
def create_clique_menu(self, topology_generator_menu):
|
def create_clique_menu(self, topology_generator_menu: tk.Menu):
|
||||||
"""
|
"""
|
||||||
Create clique menu item and the sub menu items inside
|
Create clique menu item and the sub menu items inside
|
||||||
|
|
||||||
:param tkinter.Menu topology_generator_menu: topology generator menu
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
menu = tk.Menu(topology_generator_menu)
|
menu = tk.Menu(topology_generator_menu)
|
||||||
for i in range(3, 25, 1):
|
for i in range(3, 25, 1):
|
||||||
|
@ -309,12 +266,9 @@ class Menubar(tk.Menu):
|
||||||
menu.add_command(label=label, state=tk.DISABLED)
|
menu.add_command(label=label, state=tk.DISABLED)
|
||||||
topology_generator_menu.add_cascade(label="Clique", menu=menu)
|
topology_generator_menu.add_cascade(label="Clique", menu=menu)
|
||||||
|
|
||||||
def create_bipartite_menu(self, topology_generator_menu):
|
def create_bipartite_menu(self, topology_generator_menu: tk.Menu):
|
||||||
"""
|
"""
|
||||||
Create bipartite menu item and the sub menu items inside
|
Create bipartite menu item and the sub menu items inside
|
||||||
|
|
||||||
:param tkinter.Menu topology_generator_menu: topology_generator_menu
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
menu = tk.Menu(topology_generator_menu)
|
menu = tk.Menu(topology_generator_menu)
|
||||||
temp = 24
|
temp = 24
|
||||||
|
@ -328,13 +282,9 @@ class Menubar(tk.Menu):
|
||||||
temp = temp - 1
|
temp = temp - 1
|
||||||
topology_generator_menu.add_cascade(label="Bipartite", menu=menu)
|
topology_generator_menu.add_cascade(label="Bipartite", menu=menu)
|
||||||
|
|
||||||
def create_topology_generator_menu(self, tools_menu):
|
def create_topology_generator_menu(self, tools_menu: tk.Menu):
|
||||||
"""
|
"""
|
||||||
Create topology menu item and its sub menu items
|
Create topology menu item and its sub menu items
|
||||||
|
|
||||||
:param tkinter.Menu tools_menu: tools menu
|
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
menu = tk.Menu(tools_menu)
|
menu = tk.Menu(tools_menu)
|
||||||
self.create_random_menu(menu)
|
self.create_random_menu(menu)
|
||||||
|
@ -352,8 +302,6 @@ class Menubar(tk.Menu):
|
||||||
def draw_tools_menu(self):
|
def draw_tools_menu(self):
|
||||||
"""
|
"""
|
||||||
Create tools menu
|
Create tools menu
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
menu = tk.Menu(self)
|
menu = tk.Menu(self)
|
||||||
menu.add_command(label="Auto rearrange all", state=tk.DISABLED)
|
menu.add_command(label="Auto rearrange all", state=tk.DISABLED)
|
||||||
|
@ -371,12 +319,9 @@ class Menubar(tk.Menu):
|
||||||
menu.add_command(label="Debugger...", state=tk.DISABLED)
|
menu.add_command(label="Debugger...", state=tk.DISABLED)
|
||||||
self.add_cascade(label="Tools", menu=menu)
|
self.add_cascade(label="Tools", menu=menu)
|
||||||
|
|
||||||
def create_observer_widgets_menu(self, widget_menu):
|
def create_observer_widgets_menu(self, widget_menu: tk.Menu):
|
||||||
"""
|
"""
|
||||||
Create observer widget menu item and create the sub menu items inside
|
Create observer widget menu item and create the sub menu items inside
|
||||||
|
|
||||||
:param tkinter.Menu widget_menu: widget_menu
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
var = tk.StringVar(value="none")
|
var = tk.StringVar(value="none")
|
||||||
menu = tk.Menu(widget_menu)
|
menu = tk.Menu(widget_menu)
|
||||||
|
@ -409,12 +354,9 @@ class Menubar(tk.Menu):
|
||||||
)
|
)
|
||||||
widget_menu.add_cascade(label="Observer Widgets", menu=menu)
|
widget_menu.add_cascade(label="Observer Widgets", menu=menu)
|
||||||
|
|
||||||
def create_adjacency_menu(self, widget_menu):
|
def create_adjacency_menu(self, widget_menu: tk.Menu):
|
||||||
"""
|
"""
|
||||||
Create adjacency menu item and the sub menu items inside
|
Create adjacency menu item and the sub menu items inside
|
||||||
|
|
||||||
:param tkinter.Menu widget_menu: widget menu
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
menu = tk.Menu(widget_menu)
|
menu = tk.Menu(widget_menu)
|
||||||
menu.add_command(label="OSPFv2", state=tk.DISABLED)
|
menu.add_command(label="OSPFv2", state=tk.DISABLED)
|
||||||
|
@ -426,8 +368,6 @@ class Menubar(tk.Menu):
|
||||||
def draw_widgets_menu(self):
|
def draw_widgets_menu(self):
|
||||||
"""
|
"""
|
||||||
Create widget menu
|
Create widget menu
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
menu = tk.Menu(self)
|
menu = tk.Menu(self)
|
||||||
self.create_observer_widgets_menu(menu)
|
self.create_observer_widgets_menu(menu)
|
||||||
|
@ -443,8 +383,6 @@ class Menubar(tk.Menu):
|
||||||
def draw_session_menu(self):
|
def draw_session_menu(self):
|
||||||
"""
|
"""
|
||||||
Create session menu
|
Create session menu
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
menu = tk.Menu(self)
|
menu = tk.Menu(self)
|
||||||
menu.add_command(
|
menu.add_command(
|
||||||
|
@ -461,8 +399,6 @@ class Menubar(tk.Menu):
|
||||||
def draw_help_menu(self):
|
def draw_help_menu(self):
|
||||||
"""
|
"""
|
||||||
Create help menu
|
Create help menu
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
menu = tk.Menu(self)
|
menu = tk.Menu(self)
|
||||||
menu.add_command(
|
menu.add_command(
|
||||||
|
|
|
@ -1,22 +1,34 @@
|
||||||
|
from typing import TYPE_CHECKING, Optional, Set
|
||||||
|
|
||||||
from core.api.grpc.core_pb2 import NodeType
|
from core.api.grpc.core_pb2 import NodeType
|
||||||
from core.gui.images import ImageEnum, Images
|
from core.gui.images import ImageEnum, Images
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.api.grpc import core_pb2
|
||||||
|
|
||||||
ICON_SIZE = 48
|
ICON_SIZE = 48
|
||||||
ANTENNA_SIZE = 32
|
ANTENNA_SIZE = 32
|
||||||
|
|
||||||
|
|
||||||
class NodeDraw:
|
class NodeDraw:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.custom = False
|
self.custom: bool = False
|
||||||
self.image = None
|
self.image = None
|
||||||
self.image_enum = None
|
self.image_enum: Optional[ImageEnum] = None
|
||||||
self.image_file = None
|
self.image_file = None
|
||||||
self.node_type = None
|
self.node_type: core_pb2.NodeType = None
|
||||||
self.model = None
|
self.model: Optional[str] = None
|
||||||
self.services = set()
|
self.services: Set[str] = set()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_setup(cls, image_enum, node_type, label, model=None, tooltip=None):
|
def from_setup(
|
||||||
|
cls,
|
||||||
|
image_enum: ImageEnum,
|
||||||
|
node_type: "core_pb2.NodeType",
|
||||||
|
label: str,
|
||||||
|
model: str = None,
|
||||||
|
tooltip=None,
|
||||||
|
):
|
||||||
node_draw = NodeDraw()
|
node_draw = NodeDraw()
|
||||||
node_draw.image_enum = image_enum
|
node_draw.image_enum = image_enum
|
||||||
node_draw.image = Images.get(image_enum, ICON_SIZE)
|
node_draw.image = Images.get(image_enum, ICON_SIZE)
|
||||||
|
@ -27,7 +39,7 @@ class NodeDraw:
|
||||||
return node_draw
|
return node_draw
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_custom(cls, name, image_file, services):
|
def from_custom(cls, name: str, image_file: str, services: Set[str]):
|
||||||
node_draw = NodeDraw()
|
node_draw = NodeDraw()
|
||||||
node_draw.custom = True
|
node_draw.custom = True
|
||||||
node_draw.image_file = image_file
|
node_draw.image_file = image_file
|
||||||
|
@ -53,31 +65,31 @@ class NodeUtils:
|
||||||
ANTENNA_ICON = None
|
ANTENNA_ICON = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_ignore_node(cls, node_type):
|
def is_ignore_node(cls, node_type: NodeType) -> bool:
|
||||||
return node_type in cls.IGNORE_NODES
|
return node_type in cls.IGNORE_NODES
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_container_node(cls, node_type):
|
def is_container_node(cls, node_type: NodeType) -> bool:
|
||||||
return node_type in cls.CONTAINER_NODES
|
return node_type in cls.CONTAINER_NODES
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_model_node(cls, node_type):
|
def is_model_node(cls, node_type: NodeType) -> bool:
|
||||||
return node_type == NodeType.DEFAULT
|
return node_type == NodeType.DEFAULT
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_image_node(cls, node_type):
|
def is_image_node(cls, node_type: NodeType) -> bool:
|
||||||
return node_type in cls.IMAGE_NODES
|
return node_type in cls.IMAGE_NODES
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_wireless_node(cls, node_type):
|
def is_wireless_node(cls, node_type: NodeType) -> bool:
|
||||||
return node_type in cls.WIRELESS_NODES
|
return node_type in cls.WIRELESS_NODES
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_rj45_node(cls, node_type):
|
def is_rj45_node(cls, node_type: NodeType) -> bool:
|
||||||
return node_type in cls.RJ45_NODES
|
return node_type in cls.RJ45_NODES
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def node_icon(cls, node_type, model):
|
def node_icon(cls, node_type: NodeType, model: str) -> bool:
|
||||||
if model == "":
|
if model == "":
|
||||||
model = None
|
model = None
|
||||||
return cls.NODE_ICONS[(node_type, model)]
|
return cls.NODE_ICONS[(node_type, model)]
|
||||||
|
|
|
@ -1,13 +1,19 @@
|
||||||
"status bar"
|
"""
|
||||||
|
status bar
|
||||||
|
"""
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from core.gui.dialogs.alerts import AlertsDialog
|
from core.gui.dialogs.alerts import AlertsDialog
|
||||||
from core.gui.themes import Styles
|
from core.gui.themes import Styles
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
|
|
||||||
class StatusBar(ttk.Frame):
|
class StatusBar(ttk.Frame):
|
||||||
def __init__(self, master, app, **kwargs):
|
def __init__(self, master: "Application", app: "Application", **kwargs):
|
||||||
super().__init__(master, **kwargs)
|
super().__init__(master, **kwargs)
|
||||||
self.app = app
|
self.app = app
|
||||||
self.status = None
|
self.status = None
|
||||||
|
@ -68,9 +74,5 @@ class StatusBar(ttk.Frame):
|
||||||
dialog = AlertsDialog(self.app, self.app)
|
dialog = AlertsDialog(self.app, self.app)
|
||||||
dialog.show()
|
dialog.show()
|
||||||
|
|
||||||
def set_status(self, message):
|
def set_status(self, message: str):
|
||||||
self.statusvar.set(message)
|
self.statusvar.set(message)
|
||||||
|
|
||||||
def stop_session_callback(self, cleanup_time):
|
|
||||||
self.progress_bar.stop()
|
|
||||||
self.statusvar.set(f"Stopped in {cleanup_time:.3f} seconds")
|
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
|
from typing import Any, Callable
|
||||||
|
|
||||||
|
|
||||||
class BackgroundTask:
|
class BackgroundTask:
|
||||||
def __init__(self, master, task, callback=None, args=()):
|
def __init__(self, master: Any, task: Callable, callback: Callable = None, args=()):
|
||||||
self.master = master
|
self.master = master
|
||||||
self.args = args
|
self.args = args
|
||||||
self.task = task
|
self.task = task
|
||||||
|
|
|
@ -33,7 +33,7 @@ class Colors:
|
||||||
listboxbg = "#f2f1f0"
|
listboxbg = "#f2f1f0"
|
||||||
|
|
||||||
|
|
||||||
def load(style):
|
def load(style: ttk.Style):
|
||||||
style.theme_create(
|
style.theme_create(
|
||||||
THEME_DARK,
|
THEME_DARK,
|
||||||
"clam",
|
"clam",
|
||||||
|
@ -139,13 +139,13 @@ def load(style):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def theme_change_menu(event):
|
def theme_change_menu(event: tk.Event):
|
||||||
if not isinstance(event.widget, tk.Menu):
|
if not isinstance(event.widget, tk.Menu):
|
||||||
return
|
return
|
||||||
style_menu(event.widget)
|
style_menu(event.widget)
|
||||||
|
|
||||||
|
|
||||||
def style_menu(widget):
|
def style_menu(widget: tk.Widget):
|
||||||
style = ttk.Style()
|
style = ttk.Style()
|
||||||
bg = style.lookup(".", "background")
|
bg = style.lookup(".", "background")
|
||||||
fg = style.lookup(".", "foreground")
|
fg = style.lookup(".", "foreground")
|
||||||
|
@ -157,7 +157,7 @@ def style_menu(widget):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def style_listbox(widget):
|
def style_listbox(widget: tk.Widget):
|
||||||
style = ttk.Style()
|
style = ttk.Style()
|
||||||
bg = style.lookup(".", "background")
|
bg = style.lookup(".", "background")
|
||||||
fg = style.lookup(".", "foreground")
|
fg = style.lookup(".", "foreground")
|
||||||
|
@ -174,7 +174,7 @@ def style_listbox(widget):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def theme_change(event):
|
def theme_change(event: tk.Event):
|
||||||
style = ttk.Style()
|
style = ttk.Style()
|
||||||
style.configure(Styles.picker_button, font=("TkDefaultFont", 8, "normal"))
|
style.configure(Styles.picker_button, font=("TkDefaultFont", 8, "normal"))
|
||||||
style.configure(
|
style.configure(
|
||||||
|
|
|
@ -4,17 +4,23 @@ import tkinter as tk
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from tkinter import messagebox, ttk
|
from tkinter import messagebox, ttk
|
||||||
from tkinter.font import Font
|
from tkinter.font import Font
|
||||||
|
from typing import TYPE_CHECKING, Callable
|
||||||
|
|
||||||
|
from core.api.grpc import core_pb2
|
||||||
from core.gui.dialogs.customnodes import CustomNodesDialog
|
from core.gui.dialogs.customnodes import CustomNodesDialog
|
||||||
from core.gui.dialogs.marker import MarkerDialog
|
from core.gui.dialogs.marker import MarkerDialog
|
||||||
from core.gui.graph.enums import GraphMode
|
from core.gui.graph.enums import GraphMode
|
||||||
from core.gui.graph.shapeutils import ShapeType, is_marker
|
from core.gui.graph.shapeutils import ShapeType, is_marker
|
||||||
from core.gui.images import ImageEnum, Images
|
from core.gui.images import ImageEnum, Images
|
||||||
from core.gui.nodeutils import NodeUtils
|
from core.gui.nodeutils import NodeDraw, NodeUtils
|
||||||
from core.gui.task import BackgroundTask
|
from core.gui.task import BackgroundTask
|
||||||
from core.gui.themes import Styles
|
from core.gui.themes import Styles
|
||||||
from core.gui.tooltip import Tooltip
|
from core.gui.tooltip import Tooltip
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
from PIL import ImageTk
|
||||||
|
|
||||||
TOOLBAR_SIZE = 32
|
TOOLBAR_SIZE = 32
|
||||||
PICKER_SIZE = 24
|
PICKER_SIZE = 24
|
||||||
|
|
||||||
|
@ -28,11 +34,9 @@ class Toolbar(ttk.Frame):
|
||||||
Core toolbar class
|
Core toolbar class
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, master, app, **kwargs):
|
def __init__(self, master: "Application", app: "Application", **kwargs):
|
||||||
"""
|
"""
|
||||||
Create a CoreToolbar instance
|
Create a CoreToolbar instance
|
||||||
|
|
||||||
:param tkinter.Frame edit_frame: edit frame
|
|
||||||
"""
|
"""
|
||||||
super().__init__(master, **kwargs)
|
super().__init__(master, **kwargs)
|
||||||
self.app = app
|
self.app = app
|
||||||
|
@ -100,7 +104,7 @@ class Toolbar(ttk.Frame):
|
||||||
self.create_network_button()
|
self.create_network_button()
|
||||||
self.create_annotation_button()
|
self.create_annotation_button()
|
||||||
|
|
||||||
def design_select(self, button):
|
def design_select(self, button: ttk.Button):
|
||||||
logging.info("selecting design button: %s", button)
|
logging.info("selecting design button: %s", button)
|
||||||
self.select_button.state(["!pressed"])
|
self.select_button.state(["!pressed"])
|
||||||
self.link_button.state(["!pressed"])
|
self.link_button.state(["!pressed"])
|
||||||
|
@ -109,7 +113,7 @@ class Toolbar(ttk.Frame):
|
||||||
self.annotation_button.state(["!pressed"])
|
self.annotation_button.state(["!pressed"])
|
||||||
button.state(["pressed"])
|
button.state(["pressed"])
|
||||||
|
|
||||||
def runtime_select(self, button):
|
def runtime_select(self, button: ttk.Button):
|
||||||
logging.info("selecting runtime button: %s", button)
|
logging.info("selecting runtime button: %s", button)
|
||||||
self.runtime_select_button.state(["!pressed"])
|
self.runtime_select_button.state(["!pressed"])
|
||||||
self.stop_button.state(["!pressed"])
|
self.stop_button.state(["!pressed"])
|
||||||
|
@ -185,7 +189,7 @@ class Toolbar(ttk.Frame):
|
||||||
0, lambda: self.show_picker(self.node_button, self.node_picker)
|
0, lambda: self.show_picker(self.node_button, self.node_picker)
|
||||||
)
|
)
|
||||||
|
|
||||||
def show_picker(self, button, picker):
|
def show_picker(self, button: ttk.Button, picker: ttk.Frame):
|
||||||
x = self.winfo_width() + 1
|
x = self.winfo_width() + 1
|
||||||
y = button.winfo_rooty() - picker.master.winfo_rooty() - 1
|
y = button.winfo_rooty() - picker.master.winfo_rooty() - 1
|
||||||
picker.place(x=x, y=y)
|
picker.place(x=x, y=y)
|
||||||
|
@ -195,7 +199,9 @@ class Toolbar(ttk.Frame):
|
||||||
self.wait_window(picker)
|
self.wait_window(picker)
|
||||||
self.app.unbind_all("<ButtonRelease-1>")
|
self.app.unbind_all("<ButtonRelease-1>")
|
||||||
|
|
||||||
def create_picker_button(self, image, func, frame, label):
|
def create_picker_button(
|
||||||
|
self, image: "ImageTk.PhotoImage", func: Callable, frame: ttk.Frame, label: str
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Create button and put it on the frame
|
Create button and put it on the frame
|
||||||
|
|
||||||
|
@ -203,7 +209,6 @@ class Toolbar(ttk.Frame):
|
||||||
:param func: the command that is executed when button is clicked
|
:param func: the command that is executed when button is clicked
|
||||||
:param tkinter.Frame frame: frame that contains the button
|
:param tkinter.Frame frame: frame that contains the button
|
||||||
:param str label: button label
|
:param str label: button label
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
button = ttk.Button(
|
button = ttk.Button(
|
||||||
frame, image=image, text=label, compound=tk.TOP, style=Styles.picker_button
|
frame, image=image, text=label, compound=tk.TOP, style=Styles.picker_button
|
||||||
|
@ -212,7 +217,13 @@ class Toolbar(ttk.Frame):
|
||||||
button.bind("<ButtonRelease-1>", lambda e: func())
|
button.bind("<ButtonRelease-1>", lambda e: func())
|
||||||
button.grid(pady=1)
|
button.grid(pady=1)
|
||||||
|
|
||||||
def create_button(self, frame, image, func, tooltip):
|
def create_button(
|
||||||
|
self,
|
||||||
|
frame: ttk.Frame,
|
||||||
|
image: "ImageTk.PhotoImage",
|
||||||
|
func: Callable,
|
||||||
|
tooltip: str,
|
||||||
|
):
|
||||||
button = ttk.Button(frame, image=image, command=func)
|
button = ttk.Button(frame, image=image, command=func)
|
||||||
button.image = image
|
button.image = image
|
||||||
button.grid(sticky="ew")
|
button.grid(sticky="ew")
|
||||||
|
@ -233,8 +244,6 @@ class Toolbar(ttk.Frame):
|
||||||
"""
|
"""
|
||||||
Start session handler redraw buttons, send node and link messages to grpc
|
Start session handler redraw buttons, send node and link messages to grpc
|
||||||
server.
|
server.
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
self.app.canvas.hide_context()
|
self.app.canvas.hide_context()
|
||||||
self.app.statusbar.progress_bar.start(5)
|
self.app.statusbar.progress_bar.start(5)
|
||||||
|
@ -243,7 +252,7 @@ class Toolbar(ttk.Frame):
|
||||||
task = BackgroundTask(self, self.app.core.start_session, self.start_callback)
|
task = BackgroundTask(self, self.app.core.start_session, self.start_callback)
|
||||||
task.start()
|
task.start()
|
||||||
|
|
||||||
def start_callback(self, response):
|
def start_callback(self, response: core_pb2.StartSessionResponse):
|
||||||
self.app.statusbar.progress_bar.stop()
|
self.app.statusbar.progress_bar.stop()
|
||||||
total = time.perf_counter() - self.time
|
total = time.perf_counter() - self.time
|
||||||
message = f"Start ran for {total:.3f} seconds"
|
message = f"Start ran for {total:.3f} seconds"
|
||||||
|
@ -275,7 +284,7 @@ class Toolbar(ttk.Frame):
|
||||||
dialog = CustomNodesDialog(self.app, self.app)
|
dialog = CustomNodesDialog(self.app, self.app)
|
||||||
dialog.show()
|
dialog.show()
|
||||||
|
|
||||||
def update_button(self, button, image, node_draw):
|
def update_button(self, button: ttk.Button, image: "ImageTk", node_draw: NodeDraw):
|
||||||
logging.info("update button(%s): %s", button, node_draw)
|
logging.info("update button(%s): %s", button, node_draw)
|
||||||
self.hide_pickers()
|
self.hide_pickers()
|
||||||
button.configure(image=image)
|
button.configure(image=image)
|
||||||
|
@ -298,8 +307,6 @@ class Toolbar(ttk.Frame):
|
||||||
def create_node_button(self):
|
def create_node_button(self):
|
||||||
"""
|
"""
|
||||||
Create network layer button
|
Create network layer button
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
image = icon(ImageEnum.ROUTER)
|
image = icon(ImageEnum.ROUTER)
|
||||||
self.node_button = ttk.Button(
|
self.node_button = ttk.Button(
|
||||||
|
@ -312,8 +319,6 @@ class Toolbar(ttk.Frame):
|
||||||
def draw_network_picker(self):
|
def draw_network_picker(self):
|
||||||
"""
|
"""
|
||||||
Draw the options for link-layer button.
|
Draw the options for link-layer button.
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
self.hide_pickers()
|
self.hide_pickers()
|
||||||
self.network_picker = ttk.Frame(self.master)
|
self.network_picker = ttk.Frame(self.master)
|
||||||
|
@ -337,8 +342,6 @@ class Toolbar(ttk.Frame):
|
||||||
"""
|
"""
|
||||||
Create link-layer node button and the options that represent different
|
Create link-layer node button and the options that represent different
|
||||||
link-layer node types.
|
link-layer node types.
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
image = icon(ImageEnum.HUB)
|
image = icon(ImageEnum.HUB)
|
||||||
self.network_button = ttk.Button(
|
self.network_button = ttk.Button(
|
||||||
|
@ -351,8 +354,6 @@ class Toolbar(ttk.Frame):
|
||||||
def draw_annotation_picker(self):
|
def draw_annotation_picker(self):
|
||||||
"""
|
"""
|
||||||
Draw the options for marker button.
|
Draw the options for marker button.
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
self.hide_pickers()
|
self.hide_pickers()
|
||||||
self.annotation_picker = ttk.Frame(self.master)
|
self.annotation_picker = ttk.Frame(self.master)
|
||||||
|
@ -379,8 +380,6 @@ class Toolbar(ttk.Frame):
|
||||||
def create_annotation_button(self):
|
def create_annotation_button(self):
|
||||||
"""
|
"""
|
||||||
Create marker button and options that represent different marker types
|
Create marker button and options that represent different marker types
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
image = icon(ImageEnum.MARKER)
|
image = icon(ImageEnum.MARKER)
|
||||||
self.annotation_button = ttk.Button(
|
self.annotation_button = ttk.Button(
|
||||||
|
@ -417,8 +416,6 @@ class Toolbar(ttk.Frame):
|
||||||
def click_stop(self):
|
def click_stop(self):
|
||||||
"""
|
"""
|
||||||
redraw buttons on the toolbar, send node and link messages to grpc server
|
redraw buttons on the toolbar, send node and link messages to grpc server
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
self.app.canvas.hide_context()
|
self.app.canvas.hide_context()
|
||||||
self.app.statusbar.progress_bar.start(5)
|
self.app.statusbar.progress_bar.start(5)
|
||||||
|
@ -426,7 +423,7 @@ class Toolbar(ttk.Frame):
|
||||||
task = BackgroundTask(self, self.app.core.stop_session, self.stop_callback)
|
task = BackgroundTask(self, self.app.core.stop_session, self.stop_callback)
|
||||||
task.start()
|
task.start()
|
||||||
|
|
||||||
def stop_callback(self, response):
|
def stop_callback(self, response: core_pb2.StopSessionResponse):
|
||||||
self.app.statusbar.progress_bar.stop()
|
self.app.statusbar.progress_bar.stop()
|
||||||
self.set_design()
|
self.set_design()
|
||||||
total = time.perf_counter() - self.time
|
total = time.perf_counter() - self.time
|
||||||
|
@ -436,7 +433,7 @@ class Toolbar(ttk.Frame):
|
||||||
if not response.result:
|
if not response.result:
|
||||||
messagebox.showerror("Stop Error", "Errors stopping session")
|
messagebox.showerror("Stop Error", "Errors stopping session")
|
||||||
|
|
||||||
def update_annotation(self, image, shape_type):
|
def update_annotation(self, image: "ImageTk.PhotoImage", shape_type: ShapeType):
|
||||||
logging.info("clicked annotation: ")
|
logging.info("clicked annotation: ")
|
||||||
self.hide_pickers()
|
self.hide_pickers()
|
||||||
self.annotation_button.configure(image=image)
|
self.annotation_button.configure(image=image)
|
||||||
|
@ -446,7 +443,7 @@ class Toolbar(ttk.Frame):
|
||||||
if is_marker(shape_type):
|
if is_marker(shape_type):
|
||||||
if self.marker_tool:
|
if self.marker_tool:
|
||||||
self.marker_tool.destroy()
|
self.marker_tool.destroy()
|
||||||
self.marker_tool = MarkerDialog(self.master, self.app)
|
self.marker_tool = MarkerDialog(self.app, self.app)
|
||||||
self.marker_tool.show()
|
self.marker_tool.show()
|
||||||
|
|
||||||
def click_run_button(self):
|
def click_run_button(self):
|
||||||
|
@ -462,7 +459,7 @@ class Toolbar(ttk.Frame):
|
||||||
self.app.canvas.annotation_type = ShapeType.MARKER
|
self.app.canvas.annotation_type = ShapeType.MARKER
|
||||||
if self.marker_tool:
|
if self.marker_tool:
|
||||||
self.marker_tool.destroy()
|
self.marker_tool.destroy()
|
||||||
self.marker_tool = MarkerDialog(self.master, self.app)
|
self.marker_tool = MarkerDialog(self.app, self.app)
|
||||||
self.marker_tool.show()
|
self.marker_tool.show()
|
||||||
|
|
||||||
def click_two_node_button(self):
|
def click_two_node_button(self):
|
||||||
|
|
|
@ -9,7 +9,7 @@ class Tooltip(object):
|
||||||
Create tool tip for a given widget
|
Create tool tip for a given widget
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, widget, text="widget info"):
|
def __init__(self, widget: tk.Widget, text: str = "widget info"):
|
||||||
self.widget = widget
|
self.widget = widget
|
||||||
self.text = text
|
self.text = text
|
||||||
self.widget.bind("<Enter>", self.on_enter)
|
self.widget.bind("<Enter>", self.on_enter)
|
||||||
|
@ -18,10 +18,10 @@ class Tooltip(object):
|
||||||
self.id = None
|
self.id = None
|
||||||
self.tw = None
|
self.tw = None
|
||||||
|
|
||||||
def on_enter(self, event=None):
|
def on_enter(self, event: tk.Event = None):
|
||||||
self.schedule()
|
self.schedule()
|
||||||
|
|
||||||
def on_leave(self, event=None):
|
def on_leave(self, event: tk.Event = None):
|
||||||
self.unschedule()
|
self.unschedule()
|
||||||
self.close(event)
|
self.close(event)
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ class Tooltip(object):
|
||||||
if id_:
|
if id_:
|
||||||
self.widget.after_cancel(id_)
|
self.widget.after_cancel(id_)
|
||||||
|
|
||||||
def enter(self, event=None):
|
def enter(self, event: tk.Event = None):
|
||||||
x, y, cx, cy = self.widget.bbox("insert")
|
x, y, cx, cy = self.widget.bbox("insert")
|
||||||
x += self.widget.winfo_rootx()
|
x += self.widget.winfo_rootx()
|
||||||
y += self.widget.winfo_rooty() + 32
|
y += self.widget.winfo_rooty() + 32
|
||||||
|
@ -50,6 +50,6 @@ class Tooltip(object):
|
||||||
label = ttk.Label(frame, text=self.text, style=Styles.tooltip)
|
label = ttk.Label(frame, text=self.text, style=Styles.tooltip)
|
||||||
label.grid()
|
label.grid()
|
||||||
|
|
||||||
def close(self, event=None):
|
def close(self, event: tk.Event = None):
|
||||||
if self.tw:
|
if self.tw:
|
||||||
self.tw.destroy()
|
self.tw.destroy()
|
||||||
|
|
|
@ -3,13 +3,17 @@ input validation
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import netaddr
|
import netaddr
|
||||||
from netaddr import IPNetwork
|
from netaddr import IPNetwork
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
|
|
||||||
class InputValidation:
|
class InputValidation:
|
||||||
def __init__(self, app):
|
def __init__(self, app: "Application"):
|
||||||
self.master = app.master
|
self.master = app.master
|
||||||
self.positive_int = None
|
self.positive_int = None
|
||||||
self.positive_float = None
|
self.positive_float = None
|
||||||
|
@ -27,7 +31,7 @@ class InputValidation:
|
||||||
self.rgb = self.master.register(self.check_rbg)
|
self.rgb = self.master.register(self.check_rbg)
|
||||||
self.hex = self.master.register(self.check_hex)
|
self.hex = self.master.register(self.check_hex)
|
||||||
|
|
||||||
def ip_focus_out(self, event):
|
def ip_focus_out(self, event: tk.Event):
|
||||||
value = event.widget.get()
|
value = event.widget.get()
|
||||||
try:
|
try:
|
||||||
IPNetwork(value)
|
IPNetwork(value)
|
||||||
|
@ -35,12 +39,12 @@ class InputValidation:
|
||||||
event.widget.delete(0, tk.END)
|
event.widget.delete(0, tk.END)
|
||||||
event.widget.insert(tk.END, "invalid")
|
event.widget.insert(tk.END, "invalid")
|
||||||
|
|
||||||
def focus_out(self, event, default):
|
def focus_out(self, event: tk.Event, default: str):
|
||||||
value = event.widget.get()
|
value = event.widget.get()
|
||||||
if value == "":
|
if value == "":
|
||||||
event.widget.insert(tk.END, default)
|
event.widget.insert(tk.END, default)
|
||||||
|
|
||||||
def check_positive_int(self, s):
|
def check_positive_int(self, s: str) -> bool:
|
||||||
if len(s) == 0:
|
if len(s) == 0:
|
||||||
return True
|
return True
|
||||||
try:
|
try:
|
||||||
|
@ -51,7 +55,7 @@ class InputValidation:
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def check_positive_float(self, s):
|
def check_positive_float(self, s: str) -> bool:
|
||||||
if len(s) == 0:
|
if len(s) == 0:
|
||||||
return True
|
return True
|
||||||
try:
|
try:
|
||||||
|
@ -62,7 +66,7 @@ class InputValidation:
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def check_node_name(self, s):
|
def check_node_name(self, s: str) -> bool:
|
||||||
if len(s) < 0:
|
if len(s) < 0:
|
||||||
return False
|
return False
|
||||||
if len(s) == 0:
|
if len(s) == 0:
|
||||||
|
@ -72,7 +76,7 @@ class InputValidation:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def check_canvas_int(sefl, s):
|
def check_canvas_int(self, s: str) -> bool:
|
||||||
if len(s) == 0:
|
if len(s) == 0:
|
||||||
return True
|
return True
|
||||||
try:
|
try:
|
||||||
|
@ -83,7 +87,7 @@ class InputValidation:
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def check_canvas_float(self, s):
|
def check_canvas_float(self, s: str) -> bool:
|
||||||
if not s:
|
if not s:
|
||||||
return True
|
return True
|
||||||
try:
|
try:
|
||||||
|
@ -94,7 +98,7 @@ class InputValidation:
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def check_ip4(self, s):
|
def check_ip4(self, s: str) -> bool:
|
||||||
if not s:
|
if not s:
|
||||||
return True
|
return True
|
||||||
pat = re.compile("^([0-9]+[.])*[0-9]*$")
|
pat = re.compile("^([0-9]+[.])*[0-9]*$")
|
||||||
|
@ -113,7 +117,7 @@ class InputValidation:
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def check_rbg(self, s):
|
def check_rbg(self, s: str) -> bool:
|
||||||
if not s:
|
if not s:
|
||||||
return True
|
return True
|
||||||
if s.startswith("0") and len(s) >= 2:
|
if s.startswith("0") and len(s) >= 2:
|
||||||
|
@ -127,7 +131,7 @@ class InputValidation:
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def check_hex(self, s):
|
def check_hex(self, s: str) -> bool:
|
||||||
if not s:
|
if not s:
|
||||||
return True
|
return True
|
||||||
pat = re.compile("^([#]([0-9]|[a-f])+)$|^[#]$")
|
pat = re.compile("^([#]([0-9]|[a-f])+)$|^[#]$")
|
||||||
|
|
|
@ -1,12 +1,18 @@
|
||||||
import logging
|
import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
from pathlib import PosixPath
|
||||||
from tkinter import filedialog, font, ttk
|
from tkinter import filedialog, font, ttk
|
||||||
|
from typing import TYPE_CHECKING, Dict
|
||||||
|
|
||||||
from core.api.grpc import core_pb2
|
from core.api.grpc import core_pb2
|
||||||
from core.gui import themes
|
from core.gui import themes
|
||||||
from core.gui.themes import FRAME_PAD, PADX, PADY
|
from core.gui.themes import FRAME_PAD, PADX, PADY
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
from core.gui.dialogs.dialog import Dialog
|
||||||
|
|
||||||
INT_TYPES = {
|
INT_TYPES = {
|
||||||
core_pb2.ConfigOptionType.UINT8,
|
core_pb2.ConfigOptionType.UINT8,
|
||||||
core_pb2.ConfigOptionType.UINT16,
|
core_pb2.ConfigOptionType.UINT16,
|
||||||
|
@ -19,14 +25,14 @@ INT_TYPES = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def file_button_click(value, parent):
|
def file_button_click(value: tk.StringVar, parent: tk.Widget):
|
||||||
file_path = filedialog.askopenfilename(title="Select File", parent=parent)
|
file_path = filedialog.askopenfilename(title="Select File", parent=parent)
|
||||||
if file_path:
|
if file_path:
|
||||||
value.set(file_path)
|
value.set(file_path)
|
||||||
|
|
||||||
|
|
||||||
class FrameScroll(ttk.Frame):
|
class FrameScroll(ttk.Frame):
|
||||||
def __init__(self, master, app, _cls=ttk.Frame, **kw):
|
def __init__(self, master: tk.Widget, app: "Application", _cls=ttk.Frame, **kw):
|
||||||
super().__init__(master, **kw)
|
super().__init__(master, **kw)
|
||||||
self.app = app
|
self.app = app
|
||||||
self.rowconfigure(0, weight=1)
|
self.rowconfigure(0, weight=1)
|
||||||
|
@ -49,13 +55,13 @@ class FrameScroll(ttk.Frame):
|
||||||
self.frame.bind("<Configure>", self._configure_frame)
|
self.frame.bind("<Configure>", self._configure_frame)
|
||||||
self.canvas.bind("<Configure>", self._configure_canvas)
|
self.canvas.bind("<Configure>", self._configure_canvas)
|
||||||
|
|
||||||
def _configure_frame(self, event):
|
def _configure_frame(self, event: tk.Event):
|
||||||
req_width = self.frame.winfo_reqwidth()
|
req_width = self.frame.winfo_reqwidth()
|
||||||
if req_width != self.canvas.winfo_reqwidth():
|
if req_width != self.canvas.winfo_reqwidth():
|
||||||
self.canvas.configure(width=req_width)
|
self.canvas.configure(width=req_width)
|
||||||
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
|
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
|
||||||
|
|
||||||
def _configure_canvas(self, event):
|
def _configure_canvas(self, event: tk.Event):
|
||||||
self.canvas.itemconfig(self.frame_id, width=event.width)
|
self.canvas.itemconfig(self.frame_id, width=event.width)
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
|
@ -64,7 +70,13 @@ class FrameScroll(ttk.Frame):
|
||||||
|
|
||||||
|
|
||||||
class ConfigFrame(ttk.Notebook):
|
class ConfigFrame(ttk.Notebook):
|
||||||
def __init__(self, master, app, config, **kw):
|
def __init__(
|
||||||
|
self,
|
||||||
|
master: tk.Widget,
|
||||||
|
app: "Application",
|
||||||
|
config: Dict[str, core_pb2.ConfigOption],
|
||||||
|
**kw
|
||||||
|
):
|
||||||
super().__init__(master, **kw)
|
super().__init__(master, **kw)
|
||||||
self.app = app
|
self.app = app
|
||||||
self.config = config
|
self.config = config
|
||||||
|
@ -174,7 +186,7 @@ class ConfigFrame(ttk.Notebook):
|
||||||
|
|
||||||
|
|
||||||
class ListboxScroll(ttk.Frame):
|
class ListboxScroll(ttk.Frame):
|
||||||
def __init__(self, master=None, **kw):
|
def __init__(self, master: tk.Widget = None, **kw):
|
||||||
super().__init__(master, **kw)
|
super().__init__(master, **kw)
|
||||||
self.columnconfigure(0, weight=1)
|
self.columnconfigure(0, weight=1)
|
||||||
self.rowconfigure(0, weight=1)
|
self.rowconfigure(0, weight=1)
|
||||||
|
@ -189,12 +201,12 @@ class ListboxScroll(ttk.Frame):
|
||||||
|
|
||||||
|
|
||||||
class CheckboxList(FrameScroll):
|
class CheckboxList(FrameScroll):
|
||||||
def __init__(self, master, app, clicked=None, **kw):
|
def __init__(self, master: ttk.Widget, app: "Application", clicked=None, **kw):
|
||||||
super().__init__(master, app, **kw)
|
super().__init__(master, app, **kw)
|
||||||
self.clicked = clicked
|
self.clicked = clicked
|
||||||
self.frame.columnconfigure(0, weight=1)
|
self.frame.columnconfigure(0, weight=1)
|
||||||
|
|
||||||
def add(self, name, checked):
|
def add(self, name: str, checked: bool):
|
||||||
var = tk.BooleanVar(value=checked)
|
var = tk.BooleanVar(value=checked)
|
||||||
func = partial(self.clicked, name, var)
|
func = partial(self.clicked, name, var)
|
||||||
checkbox = ttk.Checkbutton(self.frame, text=name, variable=var, command=func)
|
checkbox = ttk.Checkbutton(self.frame, text=name, variable=var, command=func)
|
||||||
|
@ -207,7 +219,7 @@ class CodeFont(font.Font):
|
||||||
|
|
||||||
|
|
||||||
class CodeText(ttk.Frame):
|
class CodeText(ttk.Frame):
|
||||||
def __init__(self, master, **kwargs):
|
def __init__(self, master: tk.Widget, **kwargs):
|
||||||
super().__init__(master, **kwargs)
|
super().__init__(master, **kwargs)
|
||||||
self.rowconfigure(0, weight=1)
|
self.rowconfigure(0, weight=1)
|
||||||
self.columnconfigure(0, weight=1)
|
self.columnconfigure(0, weight=1)
|
||||||
|
@ -231,14 +243,14 @@ class CodeText(ttk.Frame):
|
||||||
|
|
||||||
|
|
||||||
class Spinbox(ttk.Entry):
|
class Spinbox(ttk.Entry):
|
||||||
def __init__(self, master=None, **kwargs):
|
def __init__(self, master: tk.Widget = None, **kwargs):
|
||||||
super().__init__(master, "ttk::spinbox", **kwargs)
|
super().__init__(master, "ttk::spinbox", **kwargs)
|
||||||
|
|
||||||
def set(self, value):
|
def set(self, value):
|
||||||
self.tk.call(self._w, "set", value)
|
self.tk.call(self._w, "set", value)
|
||||||
|
|
||||||
|
|
||||||
def image_chooser(parent, path):
|
def image_chooser(parent: "Dialog", path: PosixPath):
|
||||||
return filedialog.askopenfilename(
|
return filedialog.askopenfilename(
|
||||||
parent=parent,
|
parent=parent,
|
||||||
initialdir=str(path),
|
initialdir=str(path),
|
||||||
|
|
|
@ -6,6 +6,7 @@ https://pypi.python.org/pypi/utm (version 0.3.0).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Optional, Tuple
|
||||||
|
|
||||||
from core.emulator.enumerations import RegisterTlvs
|
from core.emulator.enumerations import RegisterTlvs
|
||||||
from core.location import utm
|
from core.location import utm
|
||||||
|
@ -21,7 +22,7 @@ class CoreLocation:
|
||||||
name = "location"
|
name = "location"
|
||||||
config_type = RegisterTlvs.UTILITY.value
|
config_type = RegisterTlvs.UTILITY.value
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
"""
|
"""
|
||||||
Creates a MobilityManager instance.
|
Creates a MobilityManager instance.
|
||||||
|
|
||||||
|
@ -37,7 +38,7 @@ class CoreLocation:
|
||||||
for n, l in utm.ZONE_LETTERS:
|
for n, l in utm.ZONE_LETTERS:
|
||||||
self.zonemap[l] = n
|
self.zonemap[l] = n
|
||||||
|
|
||||||
def reset(self):
|
def reset(self) -> None:
|
||||||
"""
|
"""
|
||||||
Reset to initial state.
|
Reset to initial state.
|
||||||
"""
|
"""
|
||||||
|
@ -50,7 +51,7 @@ class CoreLocation:
|
||||||
# cached distance to refpt in other zones
|
# cached distance to refpt in other zones
|
||||||
self.zoneshifts = {}
|
self.zoneshifts = {}
|
||||||
|
|
||||||
def px2m(self, val):
|
def px2m(self, val: float) -> float:
|
||||||
"""
|
"""
|
||||||
Convert the specified value in pixels to meters using the
|
Convert the specified value in pixels to meters using the
|
||||||
configured scale. The scale is given as s, where
|
configured scale. The scale is given as s, where
|
||||||
|
@ -61,7 +62,7 @@ class CoreLocation:
|
||||||
"""
|
"""
|
||||||
return (val / 100.0) * self.refscale
|
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
|
Convert the specified value in meters to pixels using the
|
||||||
configured scale. The scale is given as s, where
|
configured scale. The scale is given as s, where
|
||||||
|
@ -74,7 +75,7 @@ class CoreLocation:
|
||||||
return 0.0
|
return 0.0
|
||||||
return 100.0 * (val / self.refscale)
|
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)
|
Record the geographical reference point decimal (lat, lon, alt)
|
||||||
and convert and store its UTM equivalent for later use.
|
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)
|
e, n, zonen, zonel = utm.from_latlon(lat, lon)
|
||||||
self.refutm = ((zonen, zonel), e, n, alt)
|
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,
|
Given (x, y, z) Cartesian coordinates, convert them to latitude,
|
||||||
longitude, and altitude based on the configured reference point
|
longitude, and altitude based on the configured reference point
|
||||||
|
@ -130,7 +131,7 @@ class CoreLocation:
|
||||||
lat, lon = self.refgeo[:2]
|
lat, lon = self.refgeo[:2]
|
||||||
return lat, lon, alt
|
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
|
Given latitude, longitude, and altitude location data, convert them
|
||||||
to (x, y, z) Cartesian coordinates based on the configured
|
to (x, y, z) Cartesian coordinates based on the configured
|
||||||
|
@ -165,7 +166,7 @@ class CoreLocation:
|
||||||
z = self.m2px(zm) + self.refxyz[2]
|
z = self.m2px(zm) + self.refxyz[2]
|
||||||
return x, y, z
|
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
|
If the lat, lon coordinates being converted are located in a
|
||||||
different UTM zone than the canvas reference point, the UTM meters
|
different UTM zone than the canvas reference point, the UTM meters
|
||||||
|
@ -201,7 +202,7 @@ class CoreLocation:
|
||||||
self.zoneshifts[z] = (xshift, yshift)
|
self.zoneshifts[z] = (xshift, yshift)
|
||||||
return xshift
|
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
|
If the lat, lon coordinates being converted are located in a
|
||||||
different UTM zone than the canvas reference point, the UTM meters
|
different UTM zone than the canvas reference point, the UTM meters
|
||||||
|
@ -238,7 +239,9 @@ class CoreLocation:
|
||||||
self.zoneshifts[z] = (xshift, yshift)
|
self.zoneshifts[z] = (xshift, yshift)
|
||||||
return 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
|
Given UTM easting and northing values, check if they fall outside
|
||||||
the reference point's zone boundary. Return the UTM coordinates in a
|
the reference point's zone boundary. Return the UTM coordinates in a
|
||||||
|
|
|
@ -6,6 +6,7 @@ import heapq
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from functools import total_ordering
|
from functools import total_ordering
|
||||||
|
from typing import Any, Callable
|
||||||
|
|
||||||
|
|
||||||
class Timer(threading.Thread):
|
class Timer(threading.Thread):
|
||||||
|
@ -14,7 +15,9 @@ class Timer(threading.Thread):
|
||||||
already running.
|
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.
|
Create a Timer instance.
|
||||||
|
|
||||||
|
@ -42,7 +45,7 @@ class Timer(threading.Thread):
|
||||||
else:
|
else:
|
||||||
self.kwargs = {}
|
self.kwargs = {}
|
||||||
|
|
||||||
def cancel(self):
|
def cancel(self) -> bool:
|
||||||
"""
|
"""
|
||||||
Stop the timer if it hasn't finished yet. Return False if
|
Stop the timer if it hasn't finished yet. Return False if
|
||||||
the timer was already running.
|
the timer was already running.
|
||||||
|
@ -56,7 +59,7 @@ class Timer(threading.Thread):
|
||||||
self._running.release()
|
self._running.release()
|
||||||
return locked
|
return locked
|
||||||
|
|
||||||
def run(self):
|
def run(self) -> None:
|
||||||
"""
|
"""
|
||||||
Run the timer.
|
Run the timer.
|
||||||
|
|
||||||
|
@ -75,7 +78,9 @@ class Event:
|
||||||
Provides event objects that can be used within the EventLoop class.
|
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.
|
Create an Event instance.
|
||||||
|
|
||||||
|
@ -92,13 +97,13 @@ class Event:
|
||||||
self.kwds = kwds
|
self.kwds = kwds
|
||||||
self.canceled = False
|
self.canceled = False
|
||||||
|
|
||||||
def __lt__(self, other):
|
def __lt__(self, other: "Event") -> bool:
|
||||||
result = self.time < other.time
|
result = self.time < other.time
|
||||||
if result:
|
if result:
|
||||||
result = self.eventnum < other.eventnum
|
result = self.eventnum < other.eventnum
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def run(self):
|
def run(self) -> None:
|
||||||
"""
|
"""
|
||||||
Run an event.
|
Run an event.
|
||||||
|
|
||||||
|
@ -108,7 +113,7 @@ class Event:
|
||||||
return
|
return
|
||||||
self.func(*self.args, **self.kwds)
|
self.func(*self.args, **self.kwds)
|
||||||
|
|
||||||
def cancel(self):
|
def cancel(self) -> None:
|
||||||
"""
|
"""
|
||||||
Cancel event.
|
Cancel event.
|
||||||
|
|
||||||
|
@ -123,7 +128,7 @@ class EventLoop:
|
||||||
Provides an event loop for running events.
|
Provides an event loop for running events.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
"""
|
"""
|
||||||
Creates a EventLoop instance.
|
Creates a EventLoop instance.
|
||||||
"""
|
"""
|
||||||
|
@ -134,7 +139,7 @@ class EventLoop:
|
||||||
self.running = False
|
self.running = False
|
||||||
self.start = None
|
self.start = None
|
||||||
|
|
||||||
def __run_events(self):
|
def __run_events(self) -> None:
|
||||||
"""
|
"""
|
||||||
Run events.
|
Run events.
|
||||||
|
|
||||||
|
@ -159,7 +164,7 @@ class EventLoop:
|
||||||
if schedule:
|
if schedule:
|
||||||
self.__schedule_event()
|
self.__schedule_event()
|
||||||
|
|
||||||
def __schedule_event(self):
|
def __schedule_event(self) -> None:
|
||||||
"""
|
"""
|
||||||
Schedule event.
|
Schedule event.
|
||||||
|
|
||||||
|
@ -177,7 +182,7 @@ class EventLoop:
|
||||||
self.timer.daemon = True
|
self.timer.daemon = True
|
||||||
self.timer.start()
|
self.timer.start()
|
||||||
|
|
||||||
def run(self):
|
def run(self) -> None:
|
||||||
"""
|
"""
|
||||||
Start event loop.
|
Start event loop.
|
||||||
|
|
||||||
|
@ -192,7 +197,7 @@ class EventLoop:
|
||||||
event.time += self.start
|
event.time += self.start
|
||||||
self.__schedule_event()
|
self.__schedule_event()
|
||||||
|
|
||||||
def stop(self):
|
def stop(self) -> None:
|
||||||
"""
|
"""
|
||||||
Stop event loop.
|
Stop event loop.
|
||||||
|
|
||||||
|
@ -209,7 +214,7 @@ class EventLoop:
|
||||||
self.running = False
|
self.running = False
|
||||||
self.start = None
|
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.
|
Add an event to the event loop.
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import os
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from functools import total_ordering
|
from functools import total_ordering
|
||||||
|
from typing import TYPE_CHECKING, Dict, List, Tuple
|
||||||
|
|
||||||
from core import utils
|
from core import utils
|
||||||
from core.config import ConfigGroup, ConfigurableOptions, Configuration, ModelManager
|
from core.config import ConfigGroup, ConfigurableOptions, Configuration, ModelManager
|
||||||
|
@ -21,6 +22,11 @@ from core.emulator.enumerations import (
|
||||||
RegisterTlvs,
|
RegisterTlvs,
|
||||||
)
|
)
|
||||||
from core.errors import CoreError
|
from core.errors import CoreError
|
||||||
|
from core.nodes.base import CoreNode, NodeBase
|
||||||
|
from core.nodes.interface import CoreInterface
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.emulator.session import Session
|
||||||
|
|
||||||
|
|
||||||
class MobilityManager(ModelManager):
|
class MobilityManager(ModelManager):
|
||||||
|
@ -32,7 +38,7 @@ class MobilityManager(ModelManager):
|
||||||
name = "MobilityManager"
|
name = "MobilityManager"
|
||||||
config_type = RegisterTlvs.WIRELESS.value
|
config_type = RegisterTlvs.WIRELESS.value
|
||||||
|
|
||||||
def __init__(self, session):
|
def __init__(self, session: "Session") -> None:
|
||||||
"""
|
"""
|
||||||
Creates a MobilityManager instance.
|
Creates a MobilityManager instance.
|
||||||
|
|
||||||
|
@ -43,7 +49,7 @@ class MobilityManager(ModelManager):
|
||||||
self.models[BasicRangeModel.name] = BasicRangeModel
|
self.models[BasicRangeModel.name] = BasicRangeModel
|
||||||
self.models[Ns2ScriptedMobility.name] = Ns2ScriptedMobility
|
self.models[Ns2ScriptedMobility.name] = Ns2ScriptedMobility
|
||||||
|
|
||||||
def reset(self):
|
def reset(self) -> None:
|
||||||
"""
|
"""
|
||||||
Clear out all current configurations.
|
Clear out all current configurations.
|
||||||
|
|
||||||
|
@ -51,7 +57,7 @@ class MobilityManager(ModelManager):
|
||||||
"""
|
"""
|
||||||
self.config_reset()
|
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.
|
Session is transitioning from instantiation to runtime state.
|
||||||
Instantiate any mobility models that have been configured for a WLAN.
|
Instantiate any mobility models that have been configured for a WLAN.
|
||||||
|
@ -86,7 +92,7 @@ class MobilityManager(ModelManager):
|
||||||
if node.mobility:
|
if node.mobility:
|
||||||
self.session.event_loop.add_event(0.0, node.mobility.startup)
|
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
|
Handle an Event Message used to start, stop, or pause
|
||||||
mobility scripts for a given WlanNode.
|
mobility scripts for a given WlanNode.
|
||||||
|
@ -149,7 +155,7 @@ class MobilityManager(ModelManager):
|
||||||
if event_type == EventTypes.PAUSE.value:
|
if event_type == EventTypes.PAUSE.value:
|
||||||
model.pause()
|
model.pause()
|
||||||
|
|
||||||
def sendevent(self, model):
|
def sendevent(self, model: "WayPointMobility") -> None:
|
||||||
"""
|
"""
|
||||||
Send an event message on behalf of a mobility model.
|
Send an event message on behalf of a mobility model.
|
||||||
This communicates the current and end (max) times to the GUI.
|
This communicates the current and end (max) times to the GUI.
|
||||||
|
@ -179,7 +185,9 @@ class MobilityManager(ModelManager):
|
||||||
|
|
||||||
self.session.broadcast_event(event_data)
|
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.
|
A mobility script has caused nodes in the 'moved' list to move.
|
||||||
Update every WlanNode. This saves range calculations if the model
|
Update every WlanNode. This saves range calculations if the model
|
||||||
|
@ -208,7 +216,7 @@ class WirelessModel(ConfigurableOptions):
|
||||||
bitmap = None
|
bitmap = None
|
||||||
position_callback = None
|
position_callback = None
|
||||||
|
|
||||||
def __init__(self, session, _id):
|
def __init__(self, session: "Session", _id: int):
|
||||||
"""
|
"""
|
||||||
Create a WirelessModel instance.
|
Create a WirelessModel instance.
|
||||||
|
|
||||||
|
@ -218,7 +226,7 @@ class WirelessModel(ConfigurableOptions):
|
||||||
self.session = session
|
self.session = session
|
||||||
self.id = _id
|
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)
|
May be used if the model can populate the GUI with wireless (green)
|
||||||
link lines.
|
link lines.
|
||||||
|
@ -229,7 +237,7 @@ class WirelessModel(ConfigurableOptions):
|
||||||
"""
|
"""
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def update(self, moved, moved_netifs):
|
def update(self, moved: bool, moved_netifs: List[CoreInterface]) -> None:
|
||||||
"""
|
"""
|
||||||
Update this wireless model.
|
Update this wireless model.
|
||||||
|
|
||||||
|
@ -239,10 +247,10 @@ class WirelessModel(ConfigurableOptions):
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
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
|
For run-time updates of model config. Returns True when position callback and
|
||||||
parameters should be invoked.
|
set link parameters should be invoked.
|
||||||
|
|
||||||
:param dict config: configuration values to update
|
:param dict config: configuration values to update
|
||||||
:return: nothing
|
:return: nothing
|
||||||
|
@ -295,7 +303,7 @@ class BasicRangeModel(WirelessModel):
|
||||||
def config_groups(cls):
|
def config_groups(cls):
|
||||||
return [ConfigGroup("Basic Range Parameters", 1, len(cls.configurations()))]
|
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.
|
Create a BasicRangeModel instance.
|
||||||
|
|
||||||
|
@ -314,7 +322,7 @@ class BasicRangeModel(WirelessModel):
|
||||||
self.loss = None
|
self.loss = None
|
||||||
self.jitter = 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.
|
Values to convert to link parameters.
|
||||||
|
|
||||||
|
@ -340,7 +348,7 @@ class BasicRangeModel(WirelessModel):
|
||||||
if self.jitter == 0:
|
if self.jitter == 0:
|
||||||
self.jitter = None
|
self.jitter = None
|
||||||
|
|
||||||
def setlinkparams(self):
|
def setlinkparams(self) -> None:
|
||||||
"""
|
"""
|
||||||
Apply link parameters to all interfaces. This is invoked from
|
Apply link parameters to all interfaces. This is invoked from
|
||||||
WlanNode.setmodel() after the position callback has been set.
|
WlanNode.setmodel() after the position callback has been set.
|
||||||
|
@ -356,7 +364,7 @@ class BasicRangeModel(WirelessModel):
|
||||||
jitter=self.jitter,
|
jitter=self.jitter,
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_position(self, netif):
|
def get_position(self, netif: CoreInterface) -> Tuple[float, float, float]:
|
||||||
"""
|
"""
|
||||||
Retrieve network interface position.
|
Retrieve network interface position.
|
||||||
|
|
||||||
|
@ -366,7 +374,9 @@ class BasicRangeModel(WirelessModel):
|
||||||
with self._netifslock:
|
with self._netifslock:
|
||||||
return self._netifs[netif]
|
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
|
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
|
been set; calculate the new distance between other nodes and link or
|
||||||
|
@ -389,7 +399,7 @@ class BasicRangeModel(WirelessModel):
|
||||||
|
|
||||||
position_callback = set_position
|
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 positions have changed without recalc. Update positions from
|
||||||
node.position, then re-calculate links for those that have moved.
|
node.position, then re-calculate links for those that have moved.
|
||||||
|
@ -411,7 +421,7 @@ class BasicRangeModel(WirelessModel):
|
||||||
continue
|
continue
|
||||||
self.calclink(netif, netif2)
|
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
|
Helper used by set_position() and update() to
|
||||||
calculate distance between two interfaces and perform
|
calculate distance between two interfaces and perform
|
||||||
|
@ -455,7 +465,9 @@ class BasicRangeModel(WirelessModel):
|
||||||
logging.exception("error getting interfaces during calclinkS")
|
logging.exception("error getting interfaces during calclinkS")
|
||||||
|
|
||||||
@staticmethod
|
@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.
|
Calculate the distance between two three-dimensional points.
|
||||||
|
|
||||||
|
@ -471,7 +483,7 @@ class BasicRangeModel(WirelessModel):
|
||||||
c = p1[2] - p2[2]
|
c = p1[2] - p2[2]
|
||||||
return math.hypot(math.hypot(a, b), c)
|
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.
|
Configuration has changed during runtime.
|
||||||
|
|
||||||
|
@ -482,12 +494,14 @@ class BasicRangeModel(WirelessModel):
|
||||||
self.setlinkparams()
|
self.setlinkparams()
|
||||||
return True
|
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.
|
Create a wireless link/unlink data message.
|
||||||
|
|
||||||
:param core.coreobj.PyCoreNetIf interface1: interface one
|
:param core.nodes.interface.CoreInterface interface1: interface one
|
||||||
:param core.coreobj.PyCoreNetIf interface2: interface two
|
:param core.nodes.interface.CoreInterface interface2: interface two
|
||||||
:param message_type: link message type
|
:param message_type: link message type
|
||||||
:return: link data
|
:return: link data
|
||||||
:rtype: LinkData
|
:rtype: LinkData
|
||||||
|
@ -500,7 +514,9 @@ class BasicRangeModel(WirelessModel):
|
||||||
link_type=LinkTypes.WIRELESS.value,
|
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.
|
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)
|
link_data = self.create_link_data(netif, netif2, message_type)
|
||||||
self.session.broadcast_link(link_data)
|
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.
|
Return a list of wireless link messages for when the GUI reconnects.
|
||||||
|
|
||||||
|
@ -540,7 +556,7 @@ class WayPoint:
|
||||||
Maintains information regarding waypoints.
|
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.
|
Creates a WayPoint instance.
|
||||||
|
|
||||||
|
@ -554,13 +570,13 @@ class WayPoint:
|
||||||
self.coords = coords
|
self.coords = coords
|
||||||
self.speed = speed
|
self.speed = speed
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other: "WayPoint") -> bool:
|
||||||
return (self.time, self.nodenum) == (other.time, other.nodedum)
|
return (self.time, self.nodenum) == (other.time, other.nodenum)
|
||||||
|
|
||||||
def __ne__(self, other):
|
def __ne__(self, other: "WayPoint") -> bool:
|
||||||
return not self == other
|
return not self == other
|
||||||
|
|
||||||
def __lt__(self, other):
|
def __lt__(self, other: "WayPoint") -> bool:
|
||||||
result = self.time < other.time
|
result = self.time < other.time
|
||||||
if result:
|
if result:
|
||||||
result = self.nodenum < other.nodenum
|
result = self.nodenum < other.nodenum
|
||||||
|
@ -579,7 +595,7 @@ class WayPointMobility(WirelessModel):
|
||||||
STATE_RUNNING = 1
|
STATE_RUNNING = 1
|
||||||
STATE_PAUSED = 2
|
STATE_PAUSED = 2
|
||||||
|
|
||||||
def __init__(self, session, _id):
|
def __init__(self, session: "Session", _id: int) -> None:
|
||||||
"""
|
"""
|
||||||
Create a WayPointMobility instance.
|
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)
|
# (ns-3 sets this to False as new waypoints may be added from trace)
|
||||||
self.empty_queue_stop = True
|
self.empty_queue_stop = True
|
||||||
|
|
||||||
def runround(self):
|
def runround(self) -> None:
|
||||||
"""
|
"""
|
||||||
Advance script time and move nodes.
|
Advance script time and move nodes.
|
||||||
|
|
||||||
|
@ -657,7 +673,7 @@ class WayPointMobility(WirelessModel):
|
||||||
# TODO: check session state
|
# TODO: check session state
|
||||||
self.session.event_loop.add_event(0.001 * self.refresh_ms, self.runround)
|
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.
|
Run the waypoint mobility scenario.
|
||||||
|
|
||||||
|
@ -670,7 +686,7 @@ class WayPointMobility(WirelessModel):
|
||||||
self.runround()
|
self.runround()
|
||||||
self.session.mobility.sendevent(self)
|
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.
|
Calculate next node location and update its coordinates.
|
||||||
Returns True if the node's position has changed.
|
Returns True if the node's position has changed.
|
||||||
|
@ -723,7 +739,7 @@ class WayPointMobility(WirelessModel):
|
||||||
self.setnodeposition(node, x1 + dx, y1 + dy, z1)
|
self.setnodeposition(node, x1 + dx, y1 + dy, z1)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def movenodesinitial(self):
|
def movenodesinitial(self) -> None:
|
||||||
"""
|
"""
|
||||||
Move nodes to their initial positions. Then calculate the ranges.
|
Move nodes to their initial positions. Then calculate the ranges.
|
||||||
|
|
||||||
|
@ -741,11 +757,13 @@ class WayPointMobility(WirelessModel):
|
||||||
moved_netifs.append(netif)
|
moved_netifs.append(netif)
|
||||||
self.session.mobility.updatewlans(moved, moved_netifs)
|
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.
|
Waypoints are pushed to a heapq, sorted by time.
|
||||||
|
|
||||||
:param time: waypoint time
|
:param _time: waypoint time
|
||||||
:param int nodenum: node id
|
:param int nodenum: node id
|
||||||
:param x: x position
|
:param x: x position
|
||||||
:param y: y position
|
:param y: y position
|
||||||
|
@ -753,10 +771,10 @@ class WayPointMobility(WirelessModel):
|
||||||
:param speed: speed
|
:param speed: speed
|
||||||
:return: nothing
|
: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)
|
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.
|
Record initial position in a dict.
|
||||||
|
|
||||||
|
@ -769,11 +787,11 @@ class WayPointMobility(WirelessModel):
|
||||||
wp = WayPoint(0, nodenum, coords=(x, y, z), speed=0)
|
wp = WayPoint(0, nodenum, coords=(x, y, z), speed=0)
|
||||||
self.initial[nodenum] = wp
|
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.
|
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
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
while len(self.queue):
|
while len(self.queue):
|
||||||
|
@ -782,7 +800,7 @@ class WayPointMobility(WirelessModel):
|
||||||
wp = heapq.heappop(self.queue)
|
wp = heapq.heappop(self.queue)
|
||||||
self.points[wp.nodenum] = wp
|
self.points[wp.nodenum] = wp
|
||||||
|
|
||||||
def copywaypoints(self):
|
def copywaypoints(self) -> None:
|
||||||
"""
|
"""
|
||||||
Store backup copy of waypoints for looping and stopping.
|
Store backup copy of waypoints for looping and stopping.
|
||||||
|
|
||||||
|
@ -790,7 +808,7 @@ class WayPointMobility(WirelessModel):
|
||||||
"""
|
"""
|
||||||
self.queue_copy = list(self.queue)
|
self.queue_copy = list(self.queue)
|
||||||
|
|
||||||
def loopwaypoints(self):
|
def loopwaypoints(self) -> None:
|
||||||
"""
|
"""
|
||||||
Restore backup copy of waypoints when looping.
|
Restore backup copy of waypoints when looping.
|
||||||
|
|
||||||
|
@ -799,13 +817,13 @@ class WayPointMobility(WirelessModel):
|
||||||
self.queue = list(self.queue_copy)
|
self.queue = list(self.queue_copy)
|
||||||
return self.loop
|
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),
|
Helper to move a node, notify any GUI (connected session handlers),
|
||||||
without invoking the interface poshook callback that may perform
|
without invoking the interface poshook callback that may perform
|
||||||
range calculation.
|
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 x: x position
|
||||||
:param y: y position
|
:param y: y position
|
||||||
:param z: z position
|
:param z: z position
|
||||||
|
@ -815,7 +833,7 @@ class WayPointMobility(WirelessModel):
|
||||||
node_data = node.data(message_type=0)
|
node_data = node.data(message_type=0)
|
||||||
self.session.broadcast_node(node_data)
|
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
|
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
|
waypoints. This is just an estimate. The endtime will later be
|
||||||
|
@ -829,7 +847,7 @@ class WayPointMobility(WirelessModel):
|
||||||
except IndexError:
|
except IndexError:
|
||||||
self.endtime = 0
|
self.endtime = 0
|
||||||
|
|
||||||
def start(self):
|
def start(self) -> None:
|
||||||
"""
|
"""
|
||||||
Run the script from the beginning or unpause from where it
|
Run the script from the beginning or unpause from where it
|
||||||
was before.
|
was before.
|
||||||
|
@ -849,11 +867,12 @@ class WayPointMobility(WirelessModel):
|
||||||
self.lasttime = now - (0.001 * self.refresh_ms)
|
self.lasttime = now - (0.001 * self.refresh_ms)
|
||||||
self.runround()
|
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.
|
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
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
self.state = self.STATE_STOPPED
|
self.state = self.STATE_STOPPED
|
||||||
|
@ -864,7 +883,7 @@ class WayPointMobility(WirelessModel):
|
||||||
self.movenodesinitial()
|
self.movenodesinitial()
|
||||||
self.session.mobility.sendevent(self)
|
self.session.mobility.sendevent(self)
|
||||||
|
|
||||||
def pause(self):
|
def pause(self) -> None:
|
||||||
"""
|
"""
|
||||||
Pause the script; pause time is stored to self.lasttime.
|
Pause the script; pause time is stored to self.lasttime.
|
||||||
|
|
||||||
|
@ -926,12 +945,12 @@ class Ns2ScriptedMobility(WayPointMobility):
|
||||||
]
|
]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def config_groups(cls):
|
def config_groups(cls) -> List[ConfigGroup]:
|
||||||
return [
|
return [
|
||||||
ConfigGroup("ns-2 Mobility Script Parameters", 1, len(cls.configurations()))
|
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.
|
Creates a Ns2ScriptedMobility instance.
|
||||||
|
|
||||||
|
@ -951,7 +970,7 @@ class Ns2ScriptedMobility(WayPointMobility):
|
||||||
self.script_pause = None
|
self.script_pause = None
|
||||||
self.script_stop = None
|
self.script_stop = None
|
||||||
|
|
||||||
def update_config(self, config):
|
def update_config(self, config: Dict[str, str]) -> None:
|
||||||
self.file = config["file"]
|
self.file = config["file"]
|
||||||
logging.info(
|
logging.info(
|
||||||
"ns-2 scripted mobility configured for WLAN %d using file: %s",
|
"ns-2 scripted mobility configured for WLAN %d using file: %s",
|
||||||
|
@ -969,7 +988,7 @@ class Ns2ScriptedMobility(WayPointMobility):
|
||||||
self.copywaypoints()
|
self.copywaypoints()
|
||||||
self.setendtime()
|
self.setendtime()
|
||||||
|
|
||||||
def readscriptfile(self):
|
def readscriptfile(self) -> None:
|
||||||
"""
|
"""
|
||||||
Read in mobility script from a file. This adds waypoints to a
|
Read in mobility script from a file. This adds waypoints to a
|
||||||
priority queue, sorted by waypoint time. Initial waypoints are
|
priority queue, sorted by waypoint time. Initial waypoints are
|
||||||
|
@ -1012,7 +1031,6 @@ class Ns2ScriptedMobility(WayPointMobility):
|
||||||
# initial position (time=0, speed=0):
|
# initial position (time=0, speed=0):
|
||||||
# $node_(6) set X_ 780.0
|
# $node_(6) set X_ 780.0
|
||||||
parts = line.split()
|
parts = line.split()
|
||||||
time = 0.0
|
|
||||||
nodenum = parts[0][1 + parts[0].index("(") : parts[0].index(")")]
|
nodenum = parts[0][1 + parts[0].index("(") : parts[0].index(")")]
|
||||||
if parts[2] == "X_":
|
if parts[2] == "X_":
|
||||||
if ix is not None and iy is not None:
|
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:
|
if ix is not None and iy is not None:
|
||||||
self.addinitial(self.map(inodenum), ix, iy, iz)
|
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
|
Locate a script file. If the specified file doesn't exist, look in the
|
||||||
same directory as the scenario file, or in the default
|
same directory as the scenario file, or in the default
|
||||||
|
@ -1065,7 +1083,7 @@ class Ns2ScriptedMobility(WayPointMobility):
|
||||||
|
|
||||||
return file_name
|
return file_name
|
||||||
|
|
||||||
def parsemap(self, mapstr):
|
def parsemap(self, mapstr: str) -> None:
|
||||||
"""
|
"""
|
||||||
Parse a node mapping string, given as a configuration parameter.
|
Parse a node mapping string, given as a configuration parameter.
|
||||||
|
|
||||||
|
@ -1085,18 +1103,18 @@ class Ns2ScriptedMobility(WayPointMobility):
|
||||||
except ValueError:
|
except ValueError:
|
||||||
logging.exception("ns-2 mobility node map error")
|
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.
|
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
|
:return: mapped value or the node id itself
|
||||||
:rtype: int
|
:rtype: int
|
||||||
"""
|
"""
|
||||||
nodenum = int(nodenum)
|
nodenum = int(nodenum)
|
||||||
return self.nodemap.get(nodenum, nodenum)
|
return self.nodemap.get(nodenum, nodenum)
|
||||||
|
|
||||||
def startup(self):
|
def startup(self) -> None:
|
||||||
"""
|
"""
|
||||||
Start running the script if autostart is enabled.
|
Start running the script if autostart is enabled.
|
||||||
Move node to initial positions when any autostart time is specified.
|
Move node to initial positions when any autostart time is specified.
|
||||||
|
@ -1122,7 +1140,7 @@ class Ns2ScriptedMobility(WayPointMobility):
|
||||||
self.state = self.STATE_RUNNING
|
self.state = self.STATE_RUNNING
|
||||||
self.session.event_loop.add_event(t, self.run)
|
self.session.event_loop.add_event(t, self.run)
|
||||||
|
|
||||||
def start(self):
|
def start(self) -> None:
|
||||||
"""
|
"""
|
||||||
Handle the case when un-paused.
|
Handle the case when un-paused.
|
||||||
|
|
||||||
|
@ -1134,7 +1152,7 @@ class Ns2ScriptedMobility(WayPointMobility):
|
||||||
if laststate == self.STATE_PAUSED:
|
if laststate == self.STATE_PAUSED:
|
||||||
self.statescript("unpause")
|
self.statescript("unpause")
|
||||||
|
|
||||||
def run(self):
|
def run(self) -> None:
|
||||||
"""
|
"""
|
||||||
Start is pressed or autostart is triggered.
|
Start is pressed or autostart is triggered.
|
||||||
|
|
||||||
|
@ -1143,7 +1161,7 @@ class Ns2ScriptedMobility(WayPointMobility):
|
||||||
super().run()
|
super().run()
|
||||||
self.statescript("run")
|
self.statescript("run")
|
||||||
|
|
||||||
def pause(self):
|
def pause(self) -> None:
|
||||||
"""
|
"""
|
||||||
Pause the mobility script.
|
Pause the mobility script.
|
||||||
|
|
||||||
|
@ -1152,17 +1170,18 @@ class Ns2ScriptedMobility(WayPointMobility):
|
||||||
super().pause()
|
super().pause()
|
||||||
self.statescript("pause")
|
self.statescript("pause")
|
||||||
|
|
||||||
def stop(self, move_initial=True):
|
def stop(self, move_initial: bool = True) -> None:
|
||||||
"""
|
"""
|
||||||
Stop the mobility script.
|
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
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
super().stop(move_initial=move_initial)
|
super().stop(move_initial=move_initial)
|
||||||
self.statescript("stop")
|
self.statescript("stop")
|
||||||
|
|
||||||
def statescript(self, typestr):
|
def statescript(self, typestr: str) -> None:
|
||||||
"""
|
"""
|
||||||
State of the mobility script.
|
State of the mobility script.
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import logging
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import threading
|
import threading
|
||||||
|
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
import netaddr
|
import netaddr
|
||||||
|
|
||||||
|
@ -15,8 +16,12 @@ from core.emulator.data import LinkData, NodeData
|
||||||
from core.emulator.enumerations import LinkTypes, NodeTypes
|
from core.emulator.enumerations import LinkTypes, NodeTypes
|
||||||
from core.errors import CoreCommandError
|
from core.errors import CoreCommandError
|
||||||
from core.nodes import client
|
from core.nodes import client
|
||||||
from core.nodes.interface import TunTap, Veth
|
from core.nodes.interface import CoreInterface, TunTap, Veth
|
||||||
from core.nodes.netclient import get_net_client
|
from core.nodes.netclient import LinuxNetClient, get_net_client
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.emulator.distributed import DistributedServer
|
||||||
|
from core.emulator.session import Session
|
||||||
|
|
||||||
_DEFAULT_MTU = 1500
|
_DEFAULT_MTU = 1500
|
||||||
|
|
||||||
|
@ -29,9 +34,16 @@ class NodeBase:
|
||||||
apitype = None
|
apitype = None
|
||||||
|
|
||||||
# TODO: appears start has no usage, verify and remove
|
# 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 core.emulator.session.Session session: CORE session object
|
||||||
:param int _id: id
|
:param int _id: id
|
||||||
|
@ -63,7 +75,7 @@ class NodeBase:
|
||||||
use_ovs = session.options.get_config("ovs") == "True"
|
use_ovs = session.options.get_config("ovs") == "True"
|
||||||
self.net_client = get_net_client(use_ovs, self.host_cmd)
|
self.net_client = get_net_client(use_ovs, self.host_cmd)
|
||||||
|
|
||||||
def startup(self):
|
def startup(self) -> None:
|
||||||
"""
|
"""
|
||||||
Each object implements its own startup method.
|
Each object implements its own startup method.
|
||||||
|
|
||||||
|
@ -71,7 +83,7 @@ class NodeBase:
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self) -> None:
|
||||||
"""
|
"""
|
||||||
Each object implements its own shutdown method.
|
Each object implements its own shutdown method.
|
||||||
|
|
||||||
|
@ -79,7 +91,14 @@ class NodeBase:
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
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.
|
Runs a command on the host system or distributed server.
|
||||||
|
|
||||||
|
@ -97,7 +116,7 @@ class NodeBase:
|
||||||
else:
|
else:
|
||||||
return self.server.remote_cmd(args, env, cwd, wait)
|
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.
|
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)
|
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.
|
Return an (x,y,z) tuple representing this object's position.
|
||||||
|
|
||||||
|
@ -118,7 +137,7 @@ class NodeBase:
|
||||||
"""
|
"""
|
||||||
return self.position.get()
|
return self.position.get()
|
||||||
|
|
||||||
def ifname(self, ifindex):
|
def ifname(self, ifindex: int) -> str:
|
||||||
"""
|
"""
|
||||||
Retrieve interface name for index.
|
Retrieve interface name for index.
|
||||||
|
|
||||||
|
@ -128,7 +147,7 @@ class NodeBase:
|
||||||
"""
|
"""
|
||||||
return self._netif[ifindex].name
|
return self._netif[ifindex].name
|
||||||
|
|
||||||
def netifs(self, sort=False):
|
def netifs(self, sort: bool = False) -> List[CoreInterface]:
|
||||||
"""
|
"""
|
||||||
Retrieve network interfaces, sorted if desired.
|
Retrieve network interfaces, sorted if desired.
|
||||||
|
|
||||||
|
@ -141,7 +160,7 @@ class NodeBase:
|
||||||
else:
|
else:
|
||||||
return list(self._netif.values())
|
return list(self._netif.values())
|
||||||
|
|
||||||
def numnetif(self):
|
def numnetif(self) -> int:
|
||||||
"""
|
"""
|
||||||
Return the attached interface count.
|
Return the attached interface count.
|
||||||
|
|
||||||
|
@ -150,7 +169,7 @@ class NodeBase:
|
||||||
"""
|
"""
|
||||||
return len(self._netif)
|
return len(self._netif)
|
||||||
|
|
||||||
def getifindex(self, netif):
|
def getifindex(self, netif: CoreInterface) -> int:
|
||||||
"""
|
"""
|
||||||
Retrieve index for an interface.
|
Retrieve index for an interface.
|
||||||
|
|
||||||
|
@ -163,7 +182,7 @@ class NodeBase:
|
||||||
return ifindex
|
return ifindex
|
||||||
return -1
|
return -1
|
||||||
|
|
||||||
def newifindex(self):
|
def newifindex(self) -> int:
|
||||||
"""
|
"""
|
||||||
Create a new interface index.
|
Create a new interface index.
|
||||||
|
|
||||||
|
@ -176,7 +195,14 @@ class NodeBase:
|
||||||
self.ifindex += 1
|
self.ifindex += 1
|
||||||
return ifindex
|
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.
|
Build a data object for this node.
|
||||||
|
|
||||||
|
@ -223,7 +249,7 @@ class NodeBase:
|
||||||
|
|
||||||
return node_data
|
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
|
Build CORE Link data for this object. There is no default
|
||||||
method for PyCoreObjs as PyCoreNodes do not implement this but
|
method for PyCoreObjs as PyCoreNodes do not implement this but
|
||||||
|
@ -241,7 +267,14 @@ class CoreNodeBase(NodeBase):
|
||||||
Base class for CORE nodes.
|
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.
|
Create a CoreNodeBase instance.
|
||||||
|
|
||||||
|
@ -257,7 +290,7 @@ class CoreNodeBase(NodeBase):
|
||||||
self.nodedir = None
|
self.nodedir = None
|
||||||
self.tmpnodedir = False
|
self.tmpnodedir = False
|
||||||
|
|
||||||
def makenodedir(self):
|
def makenodedir(self) -> None:
|
||||||
"""
|
"""
|
||||||
Create the node directory.
|
Create the node directory.
|
||||||
|
|
||||||
|
@ -270,7 +303,7 @@ class CoreNodeBase(NodeBase):
|
||||||
else:
|
else:
|
||||||
self.tmpnodedir = False
|
self.tmpnodedir = False
|
||||||
|
|
||||||
def rmnodedir(self):
|
def rmnodedir(self) -> None:
|
||||||
"""
|
"""
|
||||||
Remove the node directory, unless preserve directory has been set.
|
Remove the node directory, unless preserve directory has been set.
|
||||||
|
|
||||||
|
@ -283,7 +316,7 @@ class CoreNodeBase(NodeBase):
|
||||||
if self.tmpnodedir:
|
if self.tmpnodedir:
|
||||||
self.host_cmd(f"rm -rf {self.nodedir}")
|
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.
|
Add network interface to node and set the network interface index if successful.
|
||||||
|
|
||||||
|
@ -296,7 +329,7 @@ class CoreNodeBase(NodeBase):
|
||||||
self._netif[ifindex] = netif
|
self._netif[ifindex] = netif
|
||||||
netif.netindex = ifindex
|
netif.netindex = ifindex
|
||||||
|
|
||||||
def delnetif(self, ifindex):
|
def delnetif(self, ifindex: int) -> None:
|
||||||
"""
|
"""
|
||||||
Delete a network interface
|
Delete a network interface
|
||||||
|
|
||||||
|
@ -309,7 +342,7 @@ class CoreNodeBase(NodeBase):
|
||||||
netif.shutdown()
|
netif.shutdown()
|
||||||
del netif
|
del netif
|
||||||
|
|
||||||
def netif(self, ifindex):
|
def netif(self, ifindex: int) -> Optional[CoreInterface]:
|
||||||
"""
|
"""
|
||||||
Retrieve network interface.
|
Retrieve network interface.
|
||||||
|
|
||||||
|
@ -322,7 +355,7 @@ class CoreNodeBase(NodeBase):
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def attachnet(self, ifindex, net):
|
def attachnet(self, ifindex: int, net: "CoreNetworkBase") -> None:
|
||||||
"""
|
"""
|
||||||
Attach a network.
|
Attach a network.
|
||||||
|
|
||||||
|
@ -334,7 +367,7 @@ class CoreNodeBase(NodeBase):
|
||||||
raise ValueError(f"ifindex {ifindex} does not exist")
|
raise ValueError(f"ifindex {ifindex} does not exist")
|
||||||
self._netif[ifindex].attachnet(net)
|
self._netif[ifindex].attachnet(net)
|
||||||
|
|
||||||
def detachnet(self, ifindex):
|
def detachnet(self, ifindex: int) -> None:
|
||||||
"""
|
"""
|
||||||
Detach network interface.
|
Detach network interface.
|
||||||
|
|
||||||
|
@ -345,7 +378,7 @@ class CoreNodeBase(NodeBase):
|
||||||
raise ValueError(f"ifindex {ifindex} does not exist")
|
raise ValueError(f"ifindex {ifindex} does not exist")
|
||||||
self._netif[ifindex].detachnet()
|
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.
|
Set position.
|
||||||
|
|
||||||
|
@ -359,7 +392,9 @@ class CoreNodeBase(NodeBase):
|
||||||
for netif in self.netifs(sort=True):
|
for netif in self.netifs(sort=True):
|
||||||
netif.setposition(x, y, z)
|
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
|
Given another node or net object, return common networks between
|
||||||
this node and that object. A list of tuples is returned, with each tuple
|
this node and that object. A list of tuples is returned, with each tuple
|
||||||
|
@ -377,10 +412,9 @@ class CoreNodeBase(NodeBase):
|
||||||
for netif2 in obj.netifs():
|
for netif2 in obj.netifs():
|
||||||
if netif1.net == netif2.net:
|
if netif1.net == netif2.net:
|
||||||
common.append((netif1.net, netif1, netif2))
|
common.append((netif1.net, netif1, netif2))
|
||||||
|
|
||||||
return common
|
return common
|
||||||
|
|
||||||
def 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.
|
Runs a command within a node container.
|
||||||
|
|
||||||
|
@ -393,7 +427,7 @@ class CoreNodeBase(NodeBase):
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def termcmdstring(self, sh):
|
def termcmdstring(self, sh: str) -> str:
|
||||||
"""
|
"""
|
||||||
Create a terminal command string.
|
Create a terminal command string.
|
||||||
|
|
||||||
|
@ -413,14 +447,14 @@ class CoreNode(CoreNodeBase):
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
session,
|
session: "Session",
|
||||||
_id=None,
|
_id: int = None,
|
||||||
name=None,
|
name: str = None,
|
||||||
nodedir=None,
|
nodedir: str = None,
|
||||||
bootsh="boot.sh",
|
bootsh: str = "boot.sh",
|
||||||
start=True,
|
start: bool = True,
|
||||||
server=None,
|
server: "DistributedServer" = None,
|
||||||
):
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Create a CoreNode instance.
|
Create a CoreNode instance.
|
||||||
|
|
||||||
|
@ -451,17 +485,17 @@ class CoreNode(CoreNodeBase):
|
||||||
if start:
|
if start:
|
||||||
self.startup()
|
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
|
Create node network client for running network commands within the nodes
|
||||||
container.
|
container.
|
||||||
|
|
||||||
:param bool use_ovs: True for OVS bridges, False for Linux bridges
|
: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)
|
return get_net_client(use_ovs, self.cmd)
|
||||||
|
|
||||||
def alive(self):
|
def alive(self) -> bool:
|
||||||
"""
|
"""
|
||||||
Check if the node is alive.
|
Check if the node is alive.
|
||||||
|
|
||||||
|
@ -475,7 +509,7 @@ class CoreNode(CoreNodeBase):
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def startup(self):
|
def startup(self) -> None:
|
||||||
"""
|
"""
|
||||||
Start a new namespace node by invoking the vnoded process that
|
Start a new namespace node by invoking the vnoded process that
|
||||||
allocates a new namespace. Bring up the loopback device and set
|
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/run")
|
||||||
self.privatedir("/var/log")
|
self.privatedir("/var/log")
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self) -> None:
|
||||||
"""
|
"""
|
||||||
Shutdown logic for simple lxc nodes.
|
Shutdown logic for simple lxc nodes.
|
||||||
|
|
||||||
|
@ -562,7 +596,7 @@ class CoreNode(CoreNodeBase):
|
||||||
finally:
|
finally:
|
||||||
self.rmnodedir()
|
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
|
Runs a command that is used to configure and setup the network within a
|
||||||
node.
|
node.
|
||||||
|
@ -580,7 +614,7 @@ class CoreNode(CoreNodeBase):
|
||||||
args = self.client.create_cmd(args)
|
args = self.client.create_cmd(args)
|
||||||
return self.server.remote_cmd(args, wait=wait)
|
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.
|
Create a terminal command string.
|
||||||
|
|
||||||
|
@ -593,7 +627,7 @@ class CoreNode(CoreNodeBase):
|
||||||
else:
|
else:
|
||||||
return f"ssh -X -f {self.server.host} xterm -e {terminal}"
|
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.
|
Create a private directory.
|
||||||
|
|
||||||
|
@ -608,7 +642,7 @@ class CoreNode(CoreNodeBase):
|
||||||
self.host_cmd(f"mkdir -p {hostpath}")
|
self.host_cmd(f"mkdir -p {hostpath}")
|
||||||
self.mount(hostpath, path)
|
self.mount(hostpath, path)
|
||||||
|
|
||||||
def mount(self, source, target):
|
def mount(self, source: str, target: str) -> None:
|
||||||
"""
|
"""
|
||||||
Create and mount a directory.
|
Create and mount a directory.
|
||||||
|
|
||||||
|
@ -623,7 +657,7 @@ class CoreNode(CoreNodeBase):
|
||||||
self.cmd(f"{MOUNT_BIN} -n --bind {source} {target}")
|
self.cmd(f"{MOUNT_BIN} -n --bind {source} {target}")
|
||||||
self._mounts.append((source, target))
|
self._mounts.append((source, target))
|
||||||
|
|
||||||
def newifindex(self):
|
def newifindex(self) -> int:
|
||||||
"""
|
"""
|
||||||
Retrieve a new interface index.
|
Retrieve a new interface index.
|
||||||
|
|
||||||
|
@ -633,7 +667,7 @@ class CoreNode(CoreNodeBase):
|
||||||
with self.lock:
|
with self.lock:
|
||||||
return super().newifindex()
|
return super().newifindex()
|
||||||
|
|
||||||
def newveth(self, ifindex=None, ifname=None):
|
def newveth(self, ifindex: int = None, ifname: str = None) -> int:
|
||||||
"""
|
"""
|
||||||
Create a new interface.
|
Create a new interface.
|
||||||
|
|
||||||
|
@ -690,7 +724,7 @@ class CoreNode(CoreNodeBase):
|
||||||
|
|
||||||
return ifindex
|
return ifindex
|
||||||
|
|
||||||
def newtuntap(self, ifindex=None, ifname=None):
|
def newtuntap(self, ifindex: int = None, ifname: str = None) -> int:
|
||||||
"""
|
"""
|
||||||
Create a new tunnel tap.
|
Create a new tunnel tap.
|
||||||
|
|
||||||
|
@ -720,7 +754,7 @@ class CoreNode(CoreNodeBase):
|
||||||
|
|
||||||
return ifindex
|
return ifindex
|
||||||
|
|
||||||
def sethwaddr(self, ifindex, addr):
|
def sethwaddr(self, ifindex: int, addr: str) -> None:
|
||||||
"""
|
"""
|
||||||
Set hardware addres for an interface.
|
Set hardware addres for an interface.
|
||||||
|
|
||||||
|
@ -735,7 +769,7 @@ class CoreNode(CoreNodeBase):
|
||||||
if self.up:
|
if self.up:
|
||||||
self.node_net_client.device_mac(interface.name, addr)
|
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.
|
Add interface address.
|
||||||
|
|
||||||
|
@ -753,7 +787,7 @@ class CoreNode(CoreNodeBase):
|
||||||
broadcast = "+"
|
broadcast = "+"
|
||||||
self.node_net_client.create_address(interface.name, addr, 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.
|
Delete address from an interface.
|
||||||
|
|
||||||
|
@ -772,7 +806,7 @@ class CoreNode(CoreNodeBase):
|
||||||
if self.up:
|
if self.up:
|
||||||
self.node_net_client.delete_address(interface.name, addr)
|
self.node_net_client.delete_address(interface.name, addr)
|
||||||
|
|
||||||
def ifup(self, ifindex):
|
def ifup(self, ifindex: int) -> None:
|
||||||
"""
|
"""
|
||||||
Bring an interface up.
|
Bring an interface up.
|
||||||
|
|
||||||
|
@ -783,7 +817,14 @@ class CoreNode(CoreNodeBase):
|
||||||
interface_name = self.ifname(ifindex)
|
interface_name = self.ifname(ifindex)
|
||||||
self.node_net_client.device_up(interface_name)
|
self.node_net_client.device_up(interface_name)
|
||||||
|
|
||||||
def newnetif(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.
|
Create a new network interface.
|
||||||
|
|
||||||
|
@ -827,7 +868,7 @@ class CoreNode(CoreNodeBase):
|
||||||
self.ifup(ifindex)
|
self.ifup(ifindex)
|
||||||
return ifindex
|
return ifindex
|
||||||
|
|
||||||
def addfile(self, srcname, filename):
|
def addfile(self, srcname: str, filename: str) -> None:
|
||||||
"""
|
"""
|
||||||
Add a file.
|
Add a file.
|
||||||
|
|
||||||
|
@ -846,7 +887,7 @@ class CoreNode(CoreNodeBase):
|
||||||
self.host_cmd(f"mkdir -p {directory}")
|
self.host_cmd(f"mkdir -p {directory}")
|
||||||
self.server.remote_put(srcname, filename)
|
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.
|
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)
|
dirname = os.path.join(self.nodedir, dirname)
|
||||||
return os.path.join(dirname, basename)
|
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.
|
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
|
"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.
|
Copy a file to a node, following symlinks and preserving metadata.
|
||||||
Change file mode if specified.
|
Change file mode if specified.
|
||||||
|
@ -917,7 +958,14 @@ class CoreNetworkBase(NodeBase):
|
||||||
linktype = LinkTypes.WIRED.value
|
linktype = LinkTypes.WIRED.value
|
||||||
is_emane = False
|
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.
|
Create a CoreNetworkBase instance.
|
||||||
|
|
||||||
|
@ -932,7 +980,7 @@ class CoreNetworkBase(NodeBase):
|
||||||
self._linked = {}
|
self._linked = {}
|
||||||
self._linked_lock = threading.Lock()
|
self._linked_lock = threading.Lock()
|
||||||
|
|
||||||
def startup(self):
|
def startup(self) -> None:
|
||||||
"""
|
"""
|
||||||
Each object implements its own startup method.
|
Each object implements its own startup method.
|
||||||
|
|
||||||
|
@ -940,7 +988,7 @@ class CoreNetworkBase(NodeBase):
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self) -> None:
|
||||||
"""
|
"""
|
||||||
Each object implements its own shutdown method.
|
Each object implements its own shutdown method.
|
||||||
|
|
||||||
|
@ -948,7 +996,30 @@ class CoreNetworkBase(NodeBase):
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
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.
|
Attach network interface.
|
||||||
|
|
||||||
|
@ -961,7 +1032,7 @@ class CoreNetworkBase(NodeBase):
|
||||||
with self._linked_lock:
|
with self._linked_lock:
|
||||||
self._linked[netif] = {}
|
self._linked[netif] = {}
|
||||||
|
|
||||||
def detach(self, netif):
|
def detach(self, netif: CoreInterface) -> None:
|
||||||
"""
|
"""
|
||||||
Detach network interface.
|
Detach network interface.
|
||||||
|
|
||||||
|
@ -973,7 +1044,7 @@ class CoreNetworkBase(NodeBase):
|
||||||
with self._linked_lock:
|
with self._linked_lock:
|
||||||
del self._linked[netif]
|
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
|
Build link data objects for this network. Each link object describes a link
|
||||||
between this network and a node.
|
between this network and a node.
|
||||||
|
@ -981,7 +1052,6 @@ class CoreNetworkBase(NodeBase):
|
||||||
:param int flags: message type
|
:param int flags: message type
|
||||||
:return: list of link data
|
:return: list of link data
|
||||||
:rtype: list[core.data.LinkData]
|
:rtype: list[core.data.LinkData]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
all_links = []
|
all_links = []
|
||||||
|
|
||||||
|
@ -1072,7 +1142,7 @@ class Position:
|
||||||
Helper class for Cartesian coordinate 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.
|
Creates a Position instance.
|
||||||
|
|
||||||
|
@ -1085,7 +1155,7 @@ class Position:
|
||||||
self.y = y
|
self.y = y
|
||||||
self.z = z
|
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.
|
Returns True if the position has actually changed.
|
||||||
|
|
||||||
|
@ -1102,7 +1172,7 @@ class Position:
|
||||||
self.z = z
|
self.z = z
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def get(self):
|
def get(self) -> Tuple[float, float, float]:
|
||||||
"""
|
"""
|
||||||
Retrieve x,y,z position.
|
Retrieve x,y,z position.
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ class VnodeClient:
|
||||||
Provides client functionality for interacting with a virtual node.
|
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.
|
Create a VnodeClient instance.
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ class VnodeClient:
|
||||||
self.name = name
|
self.name = name
|
||||||
self.ctrlchnlname = ctrlchnlname
|
self.ctrlchnlname = ctrlchnlname
|
||||||
|
|
||||||
def _verify_connection(self):
|
def _verify_connection(self) -> None:
|
||||||
"""
|
"""
|
||||||
Checks that the vcmd client is properly connected.
|
Checks that the vcmd client is properly connected.
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ class VnodeClient:
|
||||||
if not self.connected():
|
if not self.connected():
|
||||||
raise IOError("vcmd not connected")
|
raise IOError("vcmd not connected")
|
||||||
|
|
||||||
def connected(self):
|
def connected(self) -> bool:
|
||||||
"""
|
"""
|
||||||
Check if node is connected or not.
|
Check if node is connected or not.
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ class VnodeClient:
|
||||||
"""
|
"""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def close(self):
|
def close(self) -> None:
|
||||||
"""
|
"""
|
||||||
Close the client connection.
|
Close the client connection.
|
||||||
|
|
||||||
|
@ -50,10 +50,10 @@ class VnodeClient:
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def create_cmd(self, args):
|
def create_cmd(self, args: str) -> str:
|
||||||
return f"{VCMD_BIN} -c {self.ctrlchnlname} -- {args}"
|
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.
|
Run command and return exit status and combined stdout and stderr.
|
||||||
|
|
||||||
|
|
|
@ -2,22 +2,27 @@ import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from tempfile import NamedTemporaryFile
|
from tempfile import NamedTemporaryFile
|
||||||
|
from typing import TYPE_CHECKING, Callable, Dict
|
||||||
|
|
||||||
from core import utils
|
from core import utils
|
||||||
|
from core.emulator.distributed import DistributedServer
|
||||||
from core.emulator.enumerations import NodeTypes
|
from core.emulator.enumerations import NodeTypes
|
||||||
from core.errors import CoreCommandError
|
from core.errors import CoreCommandError
|
||||||
from core.nodes.base import CoreNode
|
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:
|
class DockerClient:
|
||||||
def __init__(self, name, image, run):
|
def __init__(self, name: str, image: str, run: Callable[..., str]) -> None:
|
||||||
self.name = name
|
self.name = name
|
||||||
self.image = image
|
self.image = image
|
||||||
self.run = run
|
self.run = run
|
||||||
self.pid = None
|
self.pid = None
|
||||||
|
|
||||||
def create_container(self):
|
def create_container(self) -> str:
|
||||||
self.run(
|
self.run(
|
||||||
f"docker run -td --init --net=none --hostname {self.name} --name {self.name} "
|
f"docker run -td --init --net=none --hostname {self.name} --name {self.name} "
|
||||||
f"--sysctl net.ipv6.conf.all.disable_ipv6=0 {self.image} /bin/bash"
|
f"--sysctl net.ipv6.conf.all.disable_ipv6=0 {self.image} /bin/bash"
|
||||||
|
@ -25,7 +30,7 @@ class DockerClient:
|
||||||
self.pid = self.get_pid()
|
self.pid = self.get_pid()
|
||||||
return self.pid
|
return self.pid
|
||||||
|
|
||||||
def get_info(self):
|
def get_info(self) -> Dict:
|
||||||
args = f"docker inspect {self.name}"
|
args = f"docker inspect {self.name}"
|
||||||
output = self.run(args)
|
output = self.run(args)
|
||||||
data = json.loads(output)
|
data = json.loads(output)
|
||||||
|
@ -33,35 +38,35 @@ class DockerClient:
|
||||||
raise CoreCommandError(-1, args, f"docker({self.name}) not present")
|
raise CoreCommandError(-1, args, f"docker({self.name}) not present")
|
||||||
return data[0]
|
return data[0]
|
||||||
|
|
||||||
def is_alive(self):
|
def is_alive(self) -> bool:
|
||||||
try:
|
try:
|
||||||
data = self.get_info()
|
data = self.get_info()
|
||||||
return data["State"]["Running"]
|
return data["State"]["Running"]
|
||||||
except CoreCommandError:
|
except CoreCommandError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def stop_container(self):
|
def stop_container(self) -> None:
|
||||||
self.run(f"docker rm -f {self.name}")
|
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)
|
logging.info("docker cmd output: %s", cmd)
|
||||||
return utils.cmd(f"docker exec {self.name} {cmd}", wait=wait, shell=shell)
|
return utils.cmd(f"docker exec {self.name} {cmd}", wait=wait, shell=shell)
|
||||||
|
|
||||||
def create_ns_cmd(self, cmd):
|
def create_ns_cmd(self, cmd: str) -> str:
|
||||||
return f"nsenter -t {self.pid} -u -i -p -n {cmd}"
|
return f"nsenter -t {self.pid} -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}"
|
args = f"nsenter -t {self.pid} -u -i -p -n {cmd}"
|
||||||
return utils.cmd(args, wait=wait)
|
return utils.cmd(args, wait=wait)
|
||||||
|
|
||||||
def get_pid(self):
|
def get_pid(self) -> str:
|
||||||
args = f"docker inspect -f '{{{{.State.Pid}}}}' {self.name}"
|
args = f"docker inspect -f '{{{{.State.Pid}}}}' {self.name}"
|
||||||
output = self.run(args)
|
output = self.run(args)
|
||||||
self.pid = output
|
self.pid = output
|
||||||
logging.debug("node(%s) pid: %s", self.name, self.pid)
|
logging.debug("node(%s) pid: %s", self.name, self.pid)
|
||||||
return output
|
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}"
|
args = f"docker cp {source} {self.name}:{destination}"
|
||||||
return self.run(args)
|
return self.run(args)
|
||||||
|
|
||||||
|
@ -71,15 +76,15 @@ class DockerNode(CoreNode):
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
session,
|
session: "Session",
|
||||||
_id=None,
|
_id: int = None,
|
||||||
name=None,
|
name: str = None,
|
||||||
nodedir=None,
|
nodedir: str = None,
|
||||||
bootsh="boot.sh",
|
bootsh: str = "boot.sh",
|
||||||
start=True,
|
start: bool = True,
|
||||||
server=None,
|
server: DistributedServer = None,
|
||||||
image=None
|
image: str = None
|
||||||
):
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Create a DockerNode instance.
|
Create a DockerNode instance.
|
||||||
|
|
||||||
|
@ -98,7 +103,7 @@ class DockerNode(CoreNode):
|
||||||
self.image = image
|
self.image = image
|
||||||
super().__init__(session, _id, name, nodedir, bootsh, start, server)
|
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
|
Create node network client for running network commands within the nodes
|
||||||
container.
|
container.
|
||||||
|
@ -108,7 +113,7 @@ class DockerNode(CoreNode):
|
||||||
"""
|
"""
|
||||||
return get_net_client(use_ovs, self.nsenter_cmd)
|
return get_net_client(use_ovs, self.nsenter_cmd)
|
||||||
|
|
||||||
def alive(self):
|
def alive(self) -> bool:
|
||||||
"""
|
"""
|
||||||
Check if the node is alive.
|
Check if the node is alive.
|
||||||
|
|
||||||
|
@ -117,7 +122,7 @@ class DockerNode(CoreNode):
|
||||||
"""
|
"""
|
||||||
return self.client.is_alive()
|
return self.client.is_alive()
|
||||||
|
|
||||||
def startup(self):
|
def startup(self) -> None:
|
||||||
"""
|
"""
|
||||||
Start a new namespace node by invoking the vnoded process that
|
Start a new namespace node by invoking the vnoded process that
|
||||||
allocates a new namespace. Bring up the loopback device and set
|
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.pid = self.client.create_container()
|
||||||
self.up = True
|
self.up = True
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self) -> None:
|
||||||
"""
|
"""
|
||||||
Shutdown logic.
|
Shutdown logic.
|
||||||
|
|
||||||
|
@ -148,7 +153,7 @@ class DockerNode(CoreNode):
|
||||||
self.client.stop_container()
|
self.client.stop_container()
|
||||||
self.up = False
|
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:
|
if self.server is None:
|
||||||
args = self.client.create_ns_cmd(args)
|
args = self.client.create_ns_cmd(args)
|
||||||
return utils.cmd(args, wait=wait, shell=shell)
|
return utils.cmd(args, wait=wait, shell=shell)
|
||||||
|
@ -156,7 +161,7 @@ class DockerNode(CoreNode):
|
||||||
args = self.client.create_ns_cmd(args)
|
args = self.client.create_ns_cmd(args)
|
||||||
return self.server.remote_cmd(args, wait=wait)
|
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.
|
Create a terminal command string.
|
||||||
|
|
||||||
|
@ -165,7 +170,7 @@ class DockerNode(CoreNode):
|
||||||
"""
|
"""
|
||||||
return f"docker exec -it {self.name} bash"
|
return f"docker exec -it {self.name} bash"
|
||||||
|
|
||||||
def privatedir(self, path):
|
def privatedir(self, path: str) -> None:
|
||||||
"""
|
"""
|
||||||
Create a private directory.
|
Create a private directory.
|
||||||
|
|
||||||
|
@ -176,7 +181,7 @@ class DockerNode(CoreNode):
|
||||||
args = f"mkdir -p {path}"
|
args = f"mkdir -p {path}"
|
||||||
self.cmd(args)
|
self.cmd(args)
|
||||||
|
|
||||||
def mount(self, source, target):
|
def mount(self, source: str, target: str) -> None:
|
||||||
"""
|
"""
|
||||||
Create and mount a directory.
|
Create and mount a directory.
|
||||||
|
|
||||||
|
@ -188,7 +193,7 @@ class DockerNode(CoreNode):
|
||||||
logging.debug("mounting source(%s) target(%s)", source, target)
|
logging.debug("mounting source(%s) target(%s)", source, target)
|
||||||
raise Exception("not supported")
|
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.
|
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
|
"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.
|
Copy a file to a node, following symlinks and preserving metadata.
|
||||||
Change file mode if specified.
|
Change file mode if specified.
|
||||||
|
|
|
@ -4,18 +4,31 @@ virtual ethernet classes that implement the interfaces available under Linux.
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
|
from typing import TYPE_CHECKING, Callable, Dict, List, Tuple
|
||||||
|
|
||||||
from core import utils
|
from core import utils
|
||||||
from core.errors import CoreCommandError
|
from core.errors import CoreCommandError
|
||||||
from core.nodes.netclient import get_net_client
|
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:
|
class CoreInterface:
|
||||||
"""
|
"""
|
||||||
Base class for network interfaces.
|
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.
|
Creates a CoreInterface instance.
|
||||||
|
|
||||||
|
@ -50,7 +63,14 @@ class CoreInterface:
|
||||||
use_ovs = session.options.get_config("ovs") == "True"
|
use_ovs = session.options.get_config("ovs") == "True"
|
||||||
self.net_client = get_net_client(use_ovs, self.host_cmd)
|
self.net_client = 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.
|
Runs a command on the host system or distributed server.
|
||||||
|
|
||||||
|
@ -68,7 +88,7 @@ class CoreInterface:
|
||||||
else:
|
else:
|
||||||
return self.server.remote_cmd(args, env, cwd, wait)
|
return self.server.remote_cmd(args, env, cwd, wait)
|
||||||
|
|
||||||
def startup(self):
|
def startup(self) -> None:
|
||||||
"""
|
"""
|
||||||
Startup method for the interface.
|
Startup method for the interface.
|
||||||
|
|
||||||
|
@ -76,7 +96,7 @@ class CoreInterface:
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self) -> None:
|
||||||
"""
|
"""
|
||||||
Shutdown method for the interface.
|
Shutdown method for the interface.
|
||||||
|
|
||||||
|
@ -84,7 +104,7 @@ class CoreInterface:
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def attachnet(self, net):
|
def attachnet(self, net: "CoreNetworkBase") -> None:
|
||||||
"""
|
"""
|
||||||
Attach network.
|
Attach network.
|
||||||
|
|
||||||
|
@ -98,7 +118,7 @@ class CoreInterface:
|
||||||
net.attach(self)
|
net.attach(self)
|
||||||
self.net = net
|
self.net = net
|
||||||
|
|
||||||
def detachnet(self):
|
def detachnet(self) -> None:
|
||||||
"""
|
"""
|
||||||
Detach from a network.
|
Detach from a network.
|
||||||
|
|
||||||
|
@ -107,7 +127,7 @@ class CoreInterface:
|
||||||
if self.net is not None:
|
if self.net is not None:
|
||||||
self.net.detach(self)
|
self.net.detach(self)
|
||||||
|
|
||||||
def addaddr(self, addr):
|
def addaddr(self, addr: str) -> None:
|
||||||
"""
|
"""
|
||||||
Add address.
|
Add address.
|
||||||
|
|
||||||
|
@ -117,7 +137,7 @@ class CoreInterface:
|
||||||
addr = utils.validate_ip(addr)
|
addr = utils.validate_ip(addr)
|
||||||
self.addrlist.append(addr)
|
self.addrlist.append(addr)
|
||||||
|
|
||||||
def deladdr(self, addr):
|
def deladdr(self, addr: str) -> None:
|
||||||
"""
|
"""
|
||||||
Delete address.
|
Delete address.
|
||||||
|
|
||||||
|
@ -126,17 +146,18 @@ class CoreInterface:
|
||||||
"""
|
"""
|
||||||
self.addrlist.remove(addr)
|
self.addrlist.remove(addr)
|
||||||
|
|
||||||
def sethwaddr(self, addr):
|
def sethwaddr(self, addr: str) -> None:
|
||||||
"""
|
"""
|
||||||
Set hardware address.
|
Set hardware address.
|
||||||
|
|
||||||
:param str addr: hardware address to set to.
|
:param str addr: hardware address to set to.
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
addr = utils.validate_mac(addr)
|
if addr is not None:
|
||||||
|
addr = utils.validate_mac(addr)
|
||||||
self.hwaddr = 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.
|
Retrieve a parameter from the, or None if the parameter does not exist.
|
||||||
|
|
||||||
|
@ -145,7 +166,7 @@ class CoreInterface:
|
||||||
"""
|
"""
|
||||||
return self._params.get(key)
|
return self._params.get(key)
|
||||||
|
|
||||||
def getparams(self):
|
def getparams(self) -> List[Tuple[str, float]]:
|
||||||
"""
|
"""
|
||||||
Return (key, value) pairs for parameters.
|
Return (key, value) pairs for parameters.
|
||||||
"""
|
"""
|
||||||
|
@ -154,7 +175,7 @@ class CoreInterface:
|
||||||
parameters.append((k, self._params[k]))
|
parameters.append((k, self._params[k]))
|
||||||
return parameters
|
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.
|
Set a parameter value, returns True if the parameter has changed.
|
||||||
|
|
||||||
|
@ -174,7 +195,7 @@ class CoreInterface:
|
||||||
self._params[key] = value
|
self._params[key] = value
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def swapparams(self, name):
|
def swapparams(self, name: str) -> None:
|
||||||
"""
|
"""
|
||||||
Swap out parameters dict for name. If name does not exist,
|
Swap out parameters dict for name. If name does not exist,
|
||||||
intialize it. This is for supporting separate upstream/downstream
|
intialize it. This is for supporting separate upstream/downstream
|
||||||
|
@ -189,7 +210,7 @@ class CoreInterface:
|
||||||
self._params = getattr(self, name)
|
self._params = getattr(self, name)
|
||||||
setattr(self, name, tmp)
|
setattr(self, name, tmp)
|
||||||
|
|
||||||
def setposition(self, x, y, z):
|
def setposition(self, x: float, y: float, z: float) -> None:
|
||||||
"""
|
"""
|
||||||
Dispatch position hook handler.
|
Dispatch position hook handler.
|
||||||
|
|
||||||
|
@ -200,7 +221,7 @@ class CoreInterface:
|
||||||
"""
|
"""
|
||||||
self.poshook(self, x, y, z)
|
self.poshook(self, x, y, z)
|
||||||
|
|
||||||
def __lt__(self, other):
|
def __lt__(self, other: "CoreInterface") -> bool:
|
||||||
"""
|
"""
|
||||||
Used for comparisons of this object.
|
Used for comparisons of this object.
|
||||||
|
|
||||||
|
@ -217,8 +238,15 @@ class Veth(CoreInterface):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
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.
|
Creates a VEth instance.
|
||||||
|
|
||||||
|
@ -239,7 +267,7 @@ class Veth(CoreInterface):
|
||||||
if start:
|
if start:
|
||||||
self.startup()
|
self.startup()
|
||||||
|
|
||||||
def startup(self):
|
def startup(self) -> None:
|
||||||
"""
|
"""
|
||||||
Interface startup logic.
|
Interface startup logic.
|
||||||
|
|
||||||
|
@ -250,7 +278,7 @@ class Veth(CoreInterface):
|
||||||
self.net_client.device_up(self.localname)
|
self.net_client.device_up(self.localname)
|
||||||
self.up = True
|
self.up = True
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self) -> None:
|
||||||
"""
|
"""
|
||||||
Interface shutdown logic.
|
Interface shutdown logic.
|
||||||
|
|
||||||
|
@ -280,8 +308,15 @@ class TunTap(CoreInterface):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
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.
|
Create a TunTap instance.
|
||||||
|
|
||||||
|
@ -301,7 +336,7 @@ class TunTap(CoreInterface):
|
||||||
if start:
|
if start:
|
||||||
self.startup()
|
self.startup()
|
||||||
|
|
||||||
def startup(self):
|
def startup(self) -> None:
|
||||||
"""
|
"""
|
||||||
Startup logic for a tunnel tap.
|
Startup logic for a tunnel tap.
|
||||||
|
|
||||||
|
@ -315,7 +350,7 @@ class TunTap(CoreInterface):
|
||||||
# self.install()
|
# self.install()
|
||||||
self.up = True
|
self.up = True
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self) -> None:
|
||||||
"""
|
"""
|
||||||
Shutdown functionality for a tunnel tap.
|
Shutdown functionality for a tunnel tap.
|
||||||
|
|
||||||
|
@ -331,7 +366,9 @@ class TunTap(CoreInterface):
|
||||||
|
|
||||||
self.up = False
|
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.
|
Wait for func() to return zero with exponential backoff.
|
||||||
|
|
||||||
|
@ -362,7 +399,7 @@ class TunTap(CoreInterface):
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def waitfordevicelocal(self):
|
def waitfordevicelocal(self) -> None:
|
||||||
"""
|
"""
|
||||||
Check for presence of a local device - tap device may not
|
Check for presence of a local device - tap device may not
|
||||||
appear right away waits
|
appear right away waits
|
||||||
|
@ -381,7 +418,7 @@ class TunTap(CoreInterface):
|
||||||
|
|
||||||
self.waitfor(localdevexists)
|
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.
|
Check for presence of a node device - tap device may not appear right away waits.
|
||||||
|
|
||||||
|
@ -412,7 +449,7 @@ class TunTap(CoreInterface):
|
||||||
else:
|
else:
|
||||||
raise RuntimeError("node device failed to exist")
|
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
|
Install this TAP into its namespace. This is not done from the
|
||||||
startup() method but called at a later time when a userspace
|
startup() method but called at a later time when a userspace
|
||||||
|
@ -428,7 +465,7 @@ class TunTap(CoreInterface):
|
||||||
self.node.node_net_client.device_name(self.localname, self.name)
|
self.node.node_net_client.device_name(self.localname, self.name)
|
||||||
self.node.node_net_client.device_up(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.
|
Set interface addresses based on self.addrlist.
|
||||||
|
|
||||||
|
@ -448,18 +485,18 @@ class GreTap(CoreInterface):
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
node=None,
|
node: "CoreNode" = None,
|
||||||
name=None,
|
name: str = None,
|
||||||
session=None,
|
session: "Session" = None,
|
||||||
mtu=1458,
|
mtu: int = 1458,
|
||||||
remoteip=None,
|
remoteip: str = None,
|
||||||
_id=None,
|
_id: int = None,
|
||||||
localip=None,
|
localip: str = None,
|
||||||
ttl=255,
|
ttl: int = 255,
|
||||||
key=None,
|
key: int = None,
|
||||||
start=True,
|
start: bool = True,
|
||||||
server=None,
|
server: "DistributedServer" = None,
|
||||||
):
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Creates a GreTap instance.
|
Creates a GreTap instance.
|
||||||
|
|
||||||
|
@ -497,7 +534,7 @@ class GreTap(CoreInterface):
|
||||||
self.net_client.device_up(self.localname)
|
self.net_client.device_up(self.localname)
|
||||||
self.up = True
|
self.up = True
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self) -> None:
|
||||||
"""
|
"""
|
||||||
Shutdown logic for a GreTap.
|
Shutdown logic for a GreTap.
|
||||||
|
|
||||||
|
@ -512,7 +549,7 @@ class GreTap(CoreInterface):
|
||||||
|
|
||||||
self.localname = None
|
self.localname = None
|
||||||
|
|
||||||
def data(self, message_type):
|
def data(self, message_type: int) -> None:
|
||||||
"""
|
"""
|
||||||
Data for a gre tap.
|
Data for a gre tap.
|
||||||
|
|
||||||
|
@ -521,7 +558,7 @@ class GreTap(CoreInterface):
|
||||||
"""
|
"""
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def all_link_data(self, flags):
|
def all_link_data(self, flags: int) -> List:
|
||||||
"""
|
"""
|
||||||
Retrieve link data.
|
Retrieve link data.
|
||||||
|
|
||||||
|
|
|
@ -3,27 +3,33 @@ import logging
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
from tempfile import NamedTemporaryFile
|
from tempfile import NamedTemporaryFile
|
||||||
|
from typing import TYPE_CHECKING, Callable, Dict
|
||||||
|
|
||||||
from core import utils
|
from core import utils
|
||||||
|
from core.emulator.distributed import DistributedServer
|
||||||
from core.emulator.enumerations import NodeTypes
|
from core.emulator.enumerations import NodeTypes
|
||||||
from core.errors import CoreCommandError
|
from core.errors import CoreCommandError
|
||||||
from core.nodes.base import CoreNode
|
from core.nodes.base import CoreNode
|
||||||
|
from core.nodes.interface import CoreInterface
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.emulator.session import Session
|
||||||
|
|
||||||
|
|
||||||
class LxdClient:
|
class LxdClient:
|
||||||
def __init__(self, name, image, run):
|
def __init__(self, name: str, image: str, run: Callable[..., str]) -> None:
|
||||||
self.name = name
|
self.name = name
|
||||||
self.image = image
|
self.image = image
|
||||||
self.run = run
|
self.run = run
|
||||||
self.pid = None
|
self.pid = None
|
||||||
|
|
||||||
def create_container(self):
|
def create_container(self) -> int:
|
||||||
self.run(f"lxc launch {self.image} {self.name}")
|
self.run(f"lxc launch {self.image} {self.name}")
|
||||||
data = self.get_info()
|
data = self.get_info()
|
||||||
self.pid = data["state"]["pid"]
|
self.pid = data["state"]["pid"]
|
||||||
return self.pid
|
return self.pid
|
||||||
|
|
||||||
def get_info(self):
|
def get_info(self) -> Dict:
|
||||||
args = f"lxc list {self.name} --format json"
|
args = f"lxc list {self.name} --format json"
|
||||||
output = self.run(args)
|
output = self.run(args)
|
||||||
data = json.loads(output)
|
data = json.loads(output)
|
||||||
|
@ -31,27 +37,27 @@ class LxdClient:
|
||||||
raise CoreCommandError(-1, args, f"LXC({self.name}) not present")
|
raise CoreCommandError(-1, args, f"LXC({self.name}) not present")
|
||||||
return data[0]
|
return data[0]
|
||||||
|
|
||||||
def is_alive(self):
|
def is_alive(self) -> bool:
|
||||||
try:
|
try:
|
||||||
data = self.get_info()
|
data = self.get_info()
|
||||||
return data["state"]["status"] == "Running"
|
return data["state"]["status"] == "Running"
|
||||||
except CoreCommandError:
|
except CoreCommandError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def stop_container(self):
|
def stop_container(self) -> None:
|
||||||
self.run(f"lxc delete --force {self.name}")
|
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}"
|
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}"
|
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)
|
args = self.create_cmd(cmd)
|
||||||
return utils.cmd(args, wait=wait, shell=shell)
|
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] != "/":
|
if destination[0] != "/":
|
||||||
destination = os.path.join("/root/", destination)
|
destination = os.path.join("/root/", destination)
|
||||||
|
|
||||||
|
@ -64,15 +70,15 @@ class LxcNode(CoreNode):
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
session,
|
session: "Session",
|
||||||
_id=None,
|
_id: int = None,
|
||||||
name=None,
|
name: str = None,
|
||||||
nodedir=None,
|
nodedir: str = None,
|
||||||
bootsh="boot.sh",
|
bootsh: str = "boot.sh",
|
||||||
start=True,
|
start: bool = True,
|
||||||
server=None,
|
server: DistributedServer = None,
|
||||||
image=None,
|
image: str = None,
|
||||||
):
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Create a LxcNode instance.
|
Create a LxcNode instance.
|
||||||
|
|
||||||
|
@ -91,7 +97,7 @@ class LxcNode(CoreNode):
|
||||||
self.image = image
|
self.image = image
|
||||||
super().__init__(session, _id, name, nodedir, bootsh, start, server)
|
super().__init__(session, _id, name, nodedir, bootsh, start, server)
|
||||||
|
|
||||||
def alive(self):
|
def alive(self) -> bool:
|
||||||
"""
|
"""
|
||||||
Check if the node is alive.
|
Check if the node is alive.
|
||||||
|
|
||||||
|
@ -100,7 +106,7 @@ class LxcNode(CoreNode):
|
||||||
"""
|
"""
|
||||||
return self.client.is_alive()
|
return self.client.is_alive()
|
||||||
|
|
||||||
def startup(self):
|
def startup(self) -> None:
|
||||||
"""
|
"""
|
||||||
Startup logic.
|
Startup logic.
|
||||||
|
|
||||||
|
@ -114,7 +120,7 @@ class LxcNode(CoreNode):
|
||||||
self.pid = self.client.create_container()
|
self.pid = self.client.create_container()
|
||||||
self.up = True
|
self.up = True
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self) -> None:
|
||||||
"""
|
"""
|
||||||
Shutdown logic.
|
Shutdown logic.
|
||||||
|
|
||||||
|
@ -129,7 +135,7 @@ class LxcNode(CoreNode):
|
||||||
self.client.stop_container()
|
self.client.stop_container()
|
||||||
self.up = False
|
self.up = False
|
||||||
|
|
||||||
def termcmdstring(self, sh="/bin/sh"):
|
def termcmdstring(self, sh: str = "/bin/sh") -> str:
|
||||||
"""
|
"""
|
||||||
Create a terminal command string.
|
Create a terminal command string.
|
||||||
|
|
||||||
|
@ -138,7 +144,7 @@ class LxcNode(CoreNode):
|
||||||
"""
|
"""
|
||||||
return f"lxc exec {self.name} -- {sh}"
|
return f"lxc exec {self.name} -- {sh}"
|
||||||
|
|
||||||
def privatedir(self, path):
|
def privatedir(self, path: str) -> None:
|
||||||
"""
|
"""
|
||||||
Create a private directory.
|
Create a private directory.
|
||||||
|
|
||||||
|
@ -147,9 +153,9 @@ class LxcNode(CoreNode):
|
||||||
"""
|
"""
|
||||||
logging.info("creating node dir: %s", path)
|
logging.info("creating node dir: %s", path)
|
||||||
args = f"mkdir -p {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.
|
Create and mount a directory.
|
||||||
|
|
||||||
|
@ -161,7 +167,7 @@ class LxcNode(CoreNode):
|
||||||
logging.debug("mounting source(%s) target(%s)", source, target)
|
logging.debug("mounting source(%s) target(%s)", source, target)
|
||||||
raise Exception("not supported")
|
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.
|
Create a node file with a given mode.
|
||||||
|
|
||||||
|
@ -188,7 +194,7 @@ class LxcNode(CoreNode):
|
||||||
os.unlink(temp.name)
|
os.unlink(temp.name)
|
||||||
logging.debug("node(%s) added file: %s; mode: 0%o", self.name, filename, mode)
|
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.
|
Copy a file to a node, following symlinks and preserving metadata.
|
||||||
Change file mode if specified.
|
Change file mode if specified.
|
||||||
|
@ -214,7 +220,7 @@ class LxcNode(CoreNode):
|
||||||
self.client.copy_file(source, filename)
|
self.client.copy_file(source, filename)
|
||||||
self.cmd(f"chmod {mode:o} {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)
|
super().addnetif(netif, ifindex)
|
||||||
# adding small delay to allow time for adding addresses to work correctly
|
# adding small delay to allow time for adding addresses to work correctly
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
|
|
|
@ -2,30 +2,17 @@
|
||||||
Clients for dealing with bridge/interface commands.
|
Clients for dealing with bridge/interface commands.
|
||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
from core.constants import ETHTOOL_BIN, IP_BIN, OVS_BIN, TC_BIN
|
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:
|
class LinuxNetClient:
|
||||||
"""
|
"""
|
||||||
Client for creating Linux bridges and ip interfaces for nodes.
|
Client for creating Linux bridges and ip interfaces for nodes.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, run):
|
def __init__(self, run: Callable[..., str]) -> None:
|
||||||
"""
|
"""
|
||||||
Create LinuxNetClient instance.
|
Create LinuxNetClient instance.
|
||||||
|
|
||||||
|
@ -33,7 +20,7 @@ class LinuxNetClient:
|
||||||
"""
|
"""
|
||||||
self.run = run
|
self.run = run
|
||||||
|
|
||||||
def set_hostname(self, name):
|
def set_hostname(self, name: str) -> None:
|
||||||
"""
|
"""
|
||||||
Set network hostname.
|
Set network hostname.
|
||||||
|
|
||||||
|
@ -42,7 +29,7 @@ class LinuxNetClient:
|
||||||
"""
|
"""
|
||||||
self.run(f"hostname {name}")
|
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.
|
Create a new route for a device.
|
||||||
|
|
||||||
|
@ -52,7 +39,7 @@ class LinuxNetClient:
|
||||||
"""
|
"""
|
||||||
self.run(f"{IP_BIN} route add {route} dev {device}")
|
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.
|
Bring a device up.
|
||||||
|
|
||||||
|
@ -61,7 +48,7 @@ class LinuxNetClient:
|
||||||
"""
|
"""
|
||||||
self.run(f"{IP_BIN} link set {device} up")
|
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.
|
Bring a device down.
|
||||||
|
|
||||||
|
@ -70,7 +57,7 @@ class LinuxNetClient:
|
||||||
"""
|
"""
|
||||||
self.run(f"{IP_BIN} link set {device} down")
|
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.
|
Set a device name.
|
||||||
|
|
||||||
|
@ -80,7 +67,7 @@ class LinuxNetClient:
|
||||||
"""
|
"""
|
||||||
self.run(f"{IP_BIN} link set {device} name {name}")
|
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.
|
Show information for a device.
|
||||||
|
|
||||||
|
@ -90,7 +77,7 @@ class LinuxNetClient:
|
||||||
"""
|
"""
|
||||||
return self.run(f"{IP_BIN} link show {device}")
|
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.
|
Retrieve MAC address for a given device.
|
||||||
|
|
||||||
|
@ -100,7 +87,7 @@ class LinuxNetClient:
|
||||||
"""
|
"""
|
||||||
return self.run(f"cat /sys/class/net/{device}/address")
|
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.
|
Retrieve ifindex for a given device.
|
||||||
|
|
||||||
|
@ -110,7 +97,7 @@ class LinuxNetClient:
|
||||||
"""
|
"""
|
||||||
return self.run(f"cat /sys/class/net/{device}/ifindex")
|
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.
|
Set netns for a device.
|
||||||
|
|
||||||
|
@ -120,7 +107,7 @@ class LinuxNetClient:
|
||||||
"""
|
"""
|
||||||
self.run(f"{IP_BIN} link set {device} netns {namespace}")
|
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.
|
Flush device addresses.
|
||||||
|
|
||||||
|
@ -132,7 +119,7 @@ class LinuxNetClient:
|
||||||
shell=True,
|
shell=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
def device_mac(self, device, mac):
|
def device_mac(self, device: str, mac: str) -> None:
|
||||||
"""
|
"""
|
||||||
Set MAC address for a device.
|
Set MAC address for a device.
|
||||||
|
|
||||||
|
@ -142,7 +129,7 @@ class LinuxNetClient:
|
||||||
"""
|
"""
|
||||||
self.run(f"{IP_BIN} link set dev {device} address {mac}")
|
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.
|
Delete device.
|
||||||
|
|
||||||
|
@ -151,7 +138,7 @@ class LinuxNetClient:
|
||||||
"""
|
"""
|
||||||
self.run(f"{IP_BIN} link delete {device}")
|
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.
|
Remove traffic control settings for a device.
|
||||||
|
|
||||||
|
@ -160,7 +147,7 @@ class LinuxNetClient:
|
||||||
"""
|
"""
|
||||||
self.run(f"{TC_BIN} qdisc delete dev {device} root")
|
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.
|
Turns interface checksums off.
|
||||||
|
|
||||||
|
@ -169,7 +156,7 @@ class LinuxNetClient:
|
||||||
"""
|
"""
|
||||||
self.run(f"{ETHTOOL_BIN} -K {interface_name} rx off tx off")
|
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.
|
Create address for a device.
|
||||||
|
|
||||||
|
@ -185,7 +172,7 @@ class LinuxNetClient:
|
||||||
else:
|
else:
|
||||||
self.run(f"{IP_BIN} address add {address} dev {device}")
|
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.
|
Delete an address from a device.
|
||||||
|
|
||||||
|
@ -195,7 +182,7 @@ class LinuxNetClient:
|
||||||
"""
|
"""
|
||||||
self.run(f"{IP_BIN} address delete {address} dev {device}")
|
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.
|
Create a veth pair.
|
||||||
|
|
||||||
|
@ -205,7 +192,9 @@ class LinuxNetClient:
|
||||||
"""
|
"""
|
||||||
self.run(f"{IP_BIN} link add name {name} type veth peer name {peer}")
|
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.
|
Create a GRE tap on a device.
|
||||||
|
|
||||||
|
@ -225,7 +214,7 @@ class LinuxNetClient:
|
||||||
cmd += f" key {key}"
|
cmd += f" key {key}"
|
||||||
self.run(cmd)
|
self.run(cmd)
|
||||||
|
|
||||||
def create_bridge(self, name):
|
def create_bridge(self, name: str) -> None:
|
||||||
"""
|
"""
|
||||||
Create a Linux bridge and bring it up.
|
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.run(f"{IP_BIN} link set {name} type bridge mcast_snooping 0")
|
||||||
self.device_up(name)
|
self.device_up(name)
|
||||||
|
|
||||||
def delete_bridge(self, name):
|
def delete_bridge(self, name: str) -> None:
|
||||||
"""
|
"""
|
||||||
Bring down and delete a Linux bridge.
|
Bring down and delete a Linux bridge.
|
||||||
|
|
||||||
|
@ -248,7 +237,7 @@ class LinuxNetClient:
|
||||||
self.device_down(name)
|
self.device_down(name)
|
||||||
self.run(f"{IP_BIN} link delete {name} type bridge")
|
self.run(f"{IP_BIN} link delete {name} type bridge")
|
||||||
|
|
||||||
def create_interface(self, bridge_name, interface_name):
|
def create_interface(self, bridge_name: str, interface_name: str) -> None:
|
||||||
"""
|
"""
|
||||||
Create an interface associated with a Linux bridge.
|
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.run(f"{IP_BIN} link set dev {interface_name} master {bridge_name}")
|
||||||
self.device_up(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 Linux bridge.
|
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")
|
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.
|
Checks if there are any existing Linux bridges for a node.
|
||||||
|
|
||||||
:param _id: node id to check bridges for
|
:param _id: node id to check bridges for
|
||||||
|
:return: True if there are existing bridges, False otherwise
|
||||||
"""
|
"""
|
||||||
output = self.run(f"{IP_BIN} -j link show type bridge")
|
output = self.run(f"{IP_BIN} -j link show type bridge")
|
||||||
bridges = json.loads(output)
|
bridges = json.loads(output)
|
||||||
|
@ -286,7 +276,7 @@ class LinuxNetClient:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def disable_mac_learning(self, name):
|
def disable_mac_learning(self, name: str) -> None:
|
||||||
"""
|
"""
|
||||||
Disable mac learning for a Linux bridge.
|
Disable mac learning for a Linux bridge.
|
||||||
|
|
||||||
|
@ -301,7 +291,7 @@ class OvsNetClient(LinuxNetClient):
|
||||||
Client for creating OVS bridges and ip interfaces for nodes.
|
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.
|
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.run(f"{OVS_BIN} set bridge {name} other_config:stp-forward-delay=4")
|
||||||
self.device_up(name)
|
self.device_up(name)
|
||||||
|
|
||||||
def delete_bridge(self, name):
|
def delete_bridge(self, name: str) -> None:
|
||||||
"""
|
"""
|
||||||
Bring down and delete a OVS bridge.
|
Bring down and delete a OVS bridge.
|
||||||
|
|
||||||
|
@ -324,7 +314,7 @@ class OvsNetClient(LinuxNetClient):
|
||||||
self.device_down(name)
|
self.device_down(name)
|
||||||
self.run(f"{OVS_BIN} del-br {name}")
|
self.run(f"{OVS_BIN} del-br {name}")
|
||||||
|
|
||||||
def create_interface(self, bridge_name, interface_name):
|
def create_interface(self, bridge_name: str, interface_name: str) -> None:
|
||||||
"""
|
"""
|
||||||
Create an interface associated with a network bridge.
|
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.run(f"{OVS_BIN} add-port {bridge_name} {interface_name}")
|
||||||
self.device_up(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.
|
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}")
|
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.
|
Checks if there are any existing OVS bridges for a node.
|
||||||
|
|
||||||
:param _id: node id to check bridges for
|
:param _id: node id to check bridges for
|
||||||
|
:return: True if there are existing bridges, False otherwise
|
||||||
"""
|
"""
|
||||||
output = self.run(f"{OVS_BIN} list-br")
|
output = self.run(f"{OVS_BIN} list-br")
|
||||||
if output:
|
if output:
|
||||||
|
@ -359,7 +350,7 @@ class OvsNetClient(LinuxNetClient):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def disable_mac_learning(self, name):
|
def disable_mac_learning(self, name: str) -> None:
|
||||||
"""
|
"""
|
||||||
Disable mac learning for a OVS bridge.
|
Disable mac learning for a OVS bridge.
|
||||||
|
|
||||||
|
@ -367,3 +358,17 @@ class OvsNetClient(LinuxNetClient):
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
self.run(f"{OVS_BIN} set bridge {name} other_config:mac-aging-time=0")
|
self.run(f"{OVS_BIN} set bridge {name} other_config:mac-aging-time=0")
|
||||||
|
|
||||||
|
|
||||||
|
def get_net_client(use_ovs: bool, run: Callable[..., str]) -> LinuxNetClient:
|
||||||
|
"""
|
||||||
|
Retrieve desired net client for running network commands.
|
||||||
|
|
||||||
|
:param bool use_ovs: True for OVS bridges, False for Linux bridges
|
||||||
|
:param func run: function used to run net client commands
|
||||||
|
:return: net client class
|
||||||
|
"""
|
||||||
|
if use_ovs:
|
||||||
|
return OvsNetClient(run)
|
||||||
|
else:
|
||||||
|
return LinuxNetClient(run)
|
||||||
|
|
|
@ -5,18 +5,26 @@ Defines network nodes used within core.
|
||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Type
|
||||||
|
|
||||||
import netaddr
|
import netaddr
|
||||||
|
|
||||||
from core import utils
|
from core import utils
|
||||||
from core.constants import EBTABLES_BIN, TC_BIN
|
from core.constants import EBTABLES_BIN, TC_BIN
|
||||||
from core.emulator.data import LinkData
|
from core.emulator.data import LinkData, NodeData
|
||||||
from core.emulator.enumerations import LinkTypes, NodeTypes, RegisterTlvs
|
from core.emulator.enumerations import LinkTypes, NodeTypes, RegisterTlvs
|
||||||
from core.errors import CoreCommandError, CoreError
|
from core.errors import CoreCommandError, CoreError
|
||||||
from core.nodes.base import CoreNetworkBase
|
from core.nodes.base import CoreNetworkBase
|
||||||
from core.nodes.interface import GreTap, Veth
|
from core.nodes.interface import CoreInterface, GreTap, Veth
|
||||||
from core.nodes.netclient import get_net_client
|
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()
|
ebtables_lock = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,7 +40,7 @@ class EbtablesQueue:
|
||||||
# ebtables
|
# ebtables
|
||||||
atomic_file = "/tmp/pycore.ebtables.atomic"
|
atomic_file = "/tmp/pycore.ebtables.atomic"
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
"""
|
"""
|
||||||
Initialize the helper class, but don't start the update thread
|
Initialize the helper class, but don't start the update thread
|
||||||
until a WLAN is instantiated.
|
until a WLAN is instantiated.
|
||||||
|
@ -49,7 +57,7 @@ class EbtablesQueue:
|
||||||
# using this queue
|
# using this queue
|
||||||
self.last_update_time = {}
|
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.
|
Kick off the update loop; only needs to be invoked once.
|
||||||
|
|
||||||
|
@ -66,7 +74,7 @@ class EbtablesQueue:
|
||||||
self.updatethread.daemon = True
|
self.updatethread.daemon = True
|
||||||
self.updatethread.start()
|
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.
|
Kill the update loop thread if there are no more WLANs using it.
|
||||||
|
|
||||||
|
@ -88,17 +96,17 @@ class EbtablesQueue:
|
||||||
self.updatethread.join()
|
self.updatethread.join()
|
||||||
self.updatethread = None
|
self.updatethread = None
|
||||||
|
|
||||||
def ebatomiccmd(self, cmd):
|
def ebatomiccmd(self, cmd: str) -> str:
|
||||||
"""
|
"""
|
||||||
Helper for building ebtables atomic file command list.
|
Helper for building ebtables atomic file command list.
|
||||||
|
|
||||||
:param str cmd: ebtable command
|
:param str cmd: ebtable command
|
||||||
:return: ebtable atomic command
|
:return: ebtable atomic command
|
||||||
:rtype: list[str]
|
:rtype: str
|
||||||
"""
|
"""
|
||||||
return f"{EBTABLES_BIN} --atomic-file {self.atomic_file} {cmd}"
|
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.
|
Return the time elapsed since this WLAN was last updated.
|
||||||
|
|
||||||
|
@ -114,7 +122,7 @@ class EbtablesQueue:
|
||||||
|
|
||||||
return elapsed
|
return elapsed
|
||||||
|
|
||||||
def updated(self, wlan):
|
def updated(self, wlan: "CoreNetwork") -> None:
|
||||||
"""
|
"""
|
||||||
Keep track of when this WLAN was last updated.
|
Keep track of when this WLAN was last updated.
|
||||||
|
|
||||||
|
@ -124,7 +132,7 @@ class EbtablesQueue:
|
||||||
self.last_update_time[wlan] = time.monotonic()
|
self.last_update_time[wlan] = time.monotonic()
|
||||||
self.updates.remove(wlan)
|
self.updates.remove(wlan)
|
||||||
|
|
||||||
def updateloop(self):
|
def updateloop(self) -> None:
|
||||||
"""
|
"""
|
||||||
Thread target that looks for WLANs needing update, and
|
Thread target that looks for WLANs needing update, and
|
||||||
rate limits the amount of ebtables activity. Only one userspace program
|
rate limits the amount of ebtables activity. Only one userspace program
|
||||||
|
@ -153,7 +161,7 @@ class EbtablesQueue:
|
||||||
|
|
||||||
time.sleep(self.rate)
|
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.
|
Perform ebtables atomic commit using commands built in the self.cmds list.
|
||||||
|
|
||||||
|
@ -178,7 +186,7 @@ class EbtablesQueue:
|
||||||
except CoreCommandError:
|
except CoreCommandError:
|
||||||
logging.exception("error removing atomic file: %s", self.atomic_file)
|
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
|
Flag a change to the given WLAN's _linked dict, so the ebtables
|
||||||
chain will be rebuilt at the next interval.
|
chain will be rebuilt at the next interval.
|
||||||
|
@ -189,7 +197,7 @@ class EbtablesQueue:
|
||||||
if wlan not in self.updates:
|
if wlan not in self.updates:
|
||||||
self.updates.append(wlan)
|
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.
|
Inspect a _linked dict from a wlan, and rebuild the ebtables chain for that WLAN.
|
||||||
|
|
||||||
|
@ -231,7 +239,7 @@ class EbtablesQueue:
|
||||||
ebq = EbtablesQueue()
|
ebq = EbtablesQueue()
|
||||||
|
|
||||||
|
|
||||||
def ebtablescmds(call, cmds):
|
def ebtablescmds(call: Callable[..., str], cmds: List[str]) -> None:
|
||||||
"""
|
"""
|
||||||
Run ebtable commands.
|
Run ebtable commands.
|
||||||
|
|
||||||
|
@ -252,8 +260,14 @@ class CoreNetwork(CoreNetworkBase):
|
||||||
policy = "DROP"
|
policy = "DROP"
|
||||||
|
|
||||||
def __init__(
|
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.
|
Creates a LxBrNet instance.
|
||||||
|
|
||||||
|
@ -279,7 +293,14 @@ class CoreNetwork(CoreNetworkBase):
|
||||||
self.startup()
|
self.startup()
|
||||||
ebq.startupdateloop(self)
|
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
|
Runs a command that is used to configure and setup the network on the host
|
||||||
system and all configured distributed servers.
|
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))
|
self.session.distributed.execute(lambda x: x.remote_cmd(args, env, cwd, wait))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def startup(self):
|
def startup(self) -> None:
|
||||||
"""
|
"""
|
||||||
Linux bridge starup logic.
|
Linux bridge starup logic.
|
||||||
|
|
||||||
|
@ -309,7 +330,7 @@ class CoreNetwork(CoreNetworkBase):
|
||||||
self.has_ebtables_chain = False
|
self.has_ebtables_chain = False
|
||||||
self.up = True
|
self.up = True
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self) -> None:
|
||||||
"""
|
"""
|
||||||
Linux bridge shutdown logic.
|
Linux bridge shutdown logic.
|
||||||
|
|
||||||
|
@ -340,18 +361,18 @@ class CoreNetwork(CoreNetworkBase):
|
||||||
del self.session
|
del self.session
|
||||||
self.up = False
|
self.up = False
|
||||||
|
|
||||||
def attach(self, netif):
|
def attach(self, netif: CoreInterface) -> None:
|
||||||
"""
|
"""
|
||||||
Attach a network interface.
|
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
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
if self.up:
|
if self.up:
|
||||||
netif.net_client.create_interface(self.brname, netif.localname)
|
netif.net_client.create_interface(self.brname, netif.localname)
|
||||||
super().attach(netif)
|
super().attach(netif)
|
||||||
|
|
||||||
def detach(self, netif):
|
def detach(self, netif: CoreInterface) -> None:
|
||||||
"""
|
"""
|
||||||
Detach a network interface.
|
Detach a network interface.
|
||||||
|
|
||||||
|
@ -362,7 +383,7 @@ class CoreNetwork(CoreNetworkBase):
|
||||||
netif.net_client.delete_interface(self.brname, netif.localname)
|
netif.net_client.delete_interface(self.brname, netif.localname)
|
||||||
super().detach(netif)
|
super().detach(netif)
|
||||||
|
|
||||||
def linked(self, netif1, netif2):
|
def linked(self, netif1: CoreInterface, netif2: CoreInterface) -> bool:
|
||||||
"""
|
"""
|
||||||
Determine if the provided network interfaces are linked.
|
Determine if the provided network interfaces are linked.
|
||||||
|
|
||||||
|
@ -391,9 +412,9 @@ class CoreNetwork(CoreNetworkBase):
|
||||||
|
|
||||||
return linked
|
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.
|
filtering rules.
|
||||||
|
|
||||||
:param core.nodes.interface.CoreInterface netif1: interface one
|
:param core.nodes.interface.CoreInterface netif1: interface one
|
||||||
|
@ -407,9 +428,9 @@ class CoreNetwork(CoreNetworkBase):
|
||||||
|
|
||||||
ebq.ebchange(self)
|
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.
|
ebtables filtering rules.
|
||||||
|
|
||||||
:param core.nodes.interface.CoreInterface netif1: interface one
|
:param core.nodes.interface.CoreInterface netif1: interface one
|
||||||
|
@ -425,19 +446,19 @@ class CoreNetwork(CoreNetworkBase):
|
||||||
|
|
||||||
def linkconfig(
|
def linkconfig(
|
||||||
self,
|
self,
|
||||||
netif,
|
netif: CoreInterface,
|
||||||
bw=None,
|
bw: float = None,
|
||||||
delay=None,
|
delay: float = None,
|
||||||
loss=None,
|
loss: float = None,
|
||||||
duplicate=None,
|
duplicate: float = None,
|
||||||
jitter=None,
|
jitter: float = None,
|
||||||
netif2=None,
|
netif2: float = None,
|
||||||
devname=None,
|
devname: str = None,
|
||||||
):
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Configure link parameters by applying tc queuing disciplines on the interface.
|
Configure link parameters by applying tc queuing disciplines on the interface.
|
||||||
|
|
||||||
:param core.nodes.interface.Veth netif: interface one
|
:param core.nodes.interface.CoreInterface netif: interface one
|
||||||
:param bw: bandwidth to set to
|
:param bw: bandwidth to set to
|
||||||
:param delay: packet delay to set to
|
:param delay: packet delay to set to
|
||||||
:param loss: packet loss to set to
|
:param loss: packet loss to set to
|
||||||
|
@ -520,14 +541,14 @@ class CoreNetwork(CoreNetworkBase):
|
||||||
netif.host_cmd(cmd)
|
netif.host_cmd(cmd)
|
||||||
netif.setparam("has_netem", True)
|
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
|
Link this bridge with another by creating a veth pair and installing
|
||||||
each device into each bridge.
|
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
|
:return: created interface
|
||||||
:rtype: Veth
|
:rtype: core.nodes.interface.CoreInterface
|
||||||
"""
|
"""
|
||||||
sessionid = self.session.short_session_id()
|
sessionid = self.session.short_session_id()
|
||||||
try:
|
try:
|
||||||
|
@ -561,7 +582,7 @@ class CoreNetwork(CoreNetworkBase):
|
||||||
netif.othernet = net
|
netif.othernet = net
|
||||||
return netif
|
return netif
|
||||||
|
|
||||||
def getlinknetif(self, net):
|
def getlinknetif(self, net: CoreNetworkBase) -> Optional[CoreInterface]:
|
||||||
"""
|
"""
|
||||||
Return the interface of that links this net with another net
|
Return the interface of that links this net with another net
|
||||||
(that were linked using linknet()).
|
(that were linked using linknet()).
|
||||||
|
@ -573,10 +594,9 @@ class CoreNetwork(CoreNetworkBase):
|
||||||
for netif in self.netifs():
|
for netif in self.netifs():
|
||||||
if hasattr(netif, "othernet") and netif.othernet == net:
|
if hasattr(netif, "othernet") and netif.othernet == net:
|
||||||
return netif
|
return netif
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def addrconfig(self, addrlist):
|
def addrconfig(self, addrlist: List[str]) -> None:
|
||||||
"""
|
"""
|
||||||
Set addresses on the bridge.
|
Set addresses on the bridge.
|
||||||
|
|
||||||
|
@ -598,17 +618,17 @@ class GreTapBridge(CoreNetwork):
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
session,
|
session: "Session",
|
||||||
remoteip=None,
|
remoteip: str = None,
|
||||||
_id=None,
|
_id: int = None,
|
||||||
name=None,
|
name: str = None,
|
||||||
policy="ACCEPT",
|
policy: str = "ACCEPT",
|
||||||
localip=None,
|
localip: str = None,
|
||||||
ttl=255,
|
ttl: int = 255,
|
||||||
key=None,
|
key: int = None,
|
||||||
start=True,
|
start: bool = True,
|
||||||
server=None,
|
server: "DistributedServer" = None,
|
||||||
):
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Create a GreTapBridge instance.
|
Create a GreTapBridge instance.
|
||||||
|
|
||||||
|
@ -647,7 +667,7 @@ class GreTapBridge(CoreNetwork):
|
||||||
if start:
|
if start:
|
||||||
self.startup()
|
self.startup()
|
||||||
|
|
||||||
def startup(self):
|
def startup(self) -> None:
|
||||||
"""
|
"""
|
||||||
Creates a bridge and adds the gretap device to it.
|
Creates a bridge and adds the gretap device to it.
|
||||||
|
|
||||||
|
@ -657,7 +677,7 @@ class GreTapBridge(CoreNetwork):
|
||||||
if self.gretap:
|
if self.gretap:
|
||||||
self.attach(self.gretap)
|
self.attach(self.gretap)
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self) -> None:
|
||||||
"""
|
"""
|
||||||
Detach the gretap device and remove the bridge.
|
Detach the gretap device and remove the bridge.
|
||||||
|
|
||||||
|
@ -669,7 +689,7 @@ class GreTapBridge(CoreNetwork):
|
||||||
self.gretap = None
|
self.gretap = None
|
||||||
super().shutdown()
|
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
|
Set the remote tunnel endpoint. This is a one-time method for
|
||||||
creating the GreTap device, which requires the remoteip at startup.
|
creating the GreTap device, which requires the remoteip at startup.
|
||||||
|
@ -694,7 +714,7 @@ class GreTapBridge(CoreNetwork):
|
||||||
)
|
)
|
||||||
self.attach(self.gretap)
|
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
|
Set the GRE key used for the GreTap device. This needs to be set
|
||||||
prior to instantiating the GreTap device (before addrconfig).
|
prior to instantiating the GreTap device (before addrconfig).
|
||||||
|
@ -722,17 +742,17 @@ class CtrlNet(CoreNetwork):
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
session,
|
session: "Session",
|
||||||
_id=None,
|
_id: int = None,
|
||||||
name=None,
|
name: str = None,
|
||||||
prefix=None,
|
prefix: str = None,
|
||||||
hostid=None,
|
hostid: int = None,
|
||||||
start=True,
|
start: bool = True,
|
||||||
server=None,
|
server: "DistributedServer" = None,
|
||||||
assign_address=True,
|
assign_address: bool = True,
|
||||||
updown_script=None,
|
updown_script: str = None,
|
||||||
serverintf=None,
|
serverintf: CoreInterface = None,
|
||||||
):
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Creates a CtrlNet instance.
|
Creates a CtrlNet instance.
|
||||||
|
|
||||||
|
@ -756,7 +776,7 @@ class CtrlNet(CoreNetwork):
|
||||||
self.serverintf = serverintf
|
self.serverintf = serverintf
|
||||||
super().__init__(session, _id, name, start, server)
|
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,
|
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 = get_net_client(use_ovs, server.remote_cmd)
|
||||||
net_client.create_address(self.brname, current)
|
net_client.create_address(self.brname, current)
|
||||||
|
|
||||||
def startup(self):
|
def startup(self) -> None:
|
||||||
"""
|
"""
|
||||||
Startup functionality for the control network.
|
Startup functionality for the control network.
|
||||||
|
|
||||||
|
@ -806,7 +826,7 @@ class CtrlNet(CoreNetwork):
|
||||||
if self.serverintf:
|
if self.serverintf:
|
||||||
self.net_client.create_interface(self.brname, self.serverintf)
|
self.net_client.create_interface(self.brname, self.serverintf)
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self) -> None:
|
||||||
"""
|
"""
|
||||||
Control network shutdown.
|
Control network shutdown.
|
||||||
|
|
||||||
|
@ -835,7 +855,7 @@ class CtrlNet(CoreNetwork):
|
||||||
|
|
||||||
super().shutdown()
|
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.
|
Do not include CtrlNet in link messages describing this session.
|
||||||
|
|
||||||
|
@ -853,11 +873,11 @@ class PtpNet(CoreNetwork):
|
||||||
|
|
||||||
policy = "ACCEPT"
|
policy = "ACCEPT"
|
||||||
|
|
||||||
def attach(self, netif):
|
def attach(self, netif: CoreInterface) -> None:
|
||||||
"""
|
"""
|
||||||
Attach a network interface, but limit attachment to two interfaces.
|
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
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
if len(self._netif) >= 2:
|
if len(self._netif) >= 2:
|
||||||
|
@ -866,7 +886,14 @@ class PtpNet(CoreNetwork):
|
||||||
)
|
)
|
||||||
super().attach(netif)
|
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
|
Do not generate a Node Message for point-to-point links. They are
|
||||||
built using a link message instead.
|
built using a link message instead.
|
||||||
|
@ -875,12 +902,13 @@ class PtpNet(CoreNetwork):
|
||||||
:param float lat: latitude
|
:param float lat: latitude
|
||||||
:param float lon: longitude
|
:param float lon: longitude
|
||||||
:param float alt: altitude
|
:param float alt: altitude
|
||||||
|
:param str source: source of node data
|
||||||
:return: node data object
|
:return: node data object
|
||||||
:rtype: core.emulator.data.NodeData
|
:rtype: core.emulator.data.NodeData
|
||||||
"""
|
"""
|
||||||
return None
|
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
|
Build CORE API TLVs for a point-to-point link. One Link message
|
||||||
describes this network.
|
describes this network.
|
||||||
|
@ -997,7 +1025,7 @@ class HubNode(CoreNetwork):
|
||||||
policy = "ACCEPT"
|
policy = "ACCEPT"
|
||||||
type = "hub"
|
type = "hub"
|
||||||
|
|
||||||
def startup(self):
|
def startup(self) -> None:
|
||||||
"""
|
"""
|
||||||
Startup for a hub node, that disables mac learning after normal startup.
|
Startup for a hub node, that disables mac learning after normal startup.
|
||||||
|
|
||||||
|
@ -1018,8 +1046,14 @@ class WlanNode(CoreNetwork):
|
||||||
type = "wlan"
|
type = "wlan"
|
||||||
|
|
||||||
def __init__(
|
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.
|
Create a WlanNode instance.
|
||||||
|
|
||||||
|
@ -1036,7 +1070,7 @@ class WlanNode(CoreNetwork):
|
||||||
self.model = None
|
self.model = None
|
||||||
self.mobility = None
|
self.mobility = None
|
||||||
|
|
||||||
def startup(self):
|
def startup(self) -> None:
|
||||||
"""
|
"""
|
||||||
Startup for a wlan node, that disables mac learning after normal startup.
|
Startup for a wlan node, that disables mac learning after normal startup.
|
||||||
|
|
||||||
|
@ -1045,11 +1079,11 @@ class WlanNode(CoreNetwork):
|
||||||
super().startup()
|
super().startup()
|
||||||
self.net_client.disable_mac_learning(self.brname)
|
self.net_client.disable_mac_learning(self.brname)
|
||||||
|
|
||||||
def attach(self, netif):
|
def attach(self, netif: CoreInterface) -> None:
|
||||||
"""
|
"""
|
||||||
Attach a network interface.
|
Attach a network interface.
|
||||||
|
|
||||||
:param core.nodes.interface.Veth netif: network interface
|
:param core.nodes.interface.CoreInterface netif: network interface
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
super().attach(netif)
|
super().attach(netif)
|
||||||
|
@ -1061,7 +1095,7 @@ class WlanNode(CoreNetwork):
|
||||||
# invokes any netif.poshook
|
# invokes any netif.poshook
|
||||||
netif.setposition(x, y, z)
|
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.
|
Sets the mobility and wireless model.
|
||||||
|
|
||||||
|
@ -1082,12 +1116,12 @@ class WlanNode(CoreNetwork):
|
||||||
self.mobility = model(session=self.session, _id=self.id)
|
self.mobility = model(session=self.session, _id=self.id)
|
||||||
self.mobility.update_config(config)
|
self.mobility.update_config(config)
|
||||||
|
|
||||||
def update_mobility(self, config):
|
def update_mobility(self, config: Dict[str, str]) -> None:
|
||||||
if not self.mobility:
|
if not self.mobility:
|
||||||
raise ValueError(f"no mobility set to update for node({self.id})")
|
raise ValueError(f"no mobility set to update for node({self.id})")
|
||||||
self.mobility.update_config(config)
|
self.mobility.update_config(config)
|
||||||
|
|
||||||
def updatemodel(self, config):
|
def updatemodel(self, config: Dict[str, str]) -> None:
|
||||||
if not self.model:
|
if not self.model:
|
||||||
raise ValueError(f"no model set to update for node({self.id})")
|
raise ValueError(f"no model set to update for node({self.id})")
|
||||||
logging.debug(
|
logging.debug(
|
||||||
|
@ -1099,7 +1133,7 @@ class WlanNode(CoreNetwork):
|
||||||
x, y, z = netif.node.position.get()
|
x, y, z = netif.node.position.get()
|
||||||
netif.poshook(netif, x, y, z)
|
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.
|
Retrieve all link data.
|
||||||
|
|
||||||
|
|
|
@ -5,20 +5,31 @@ PhysicalNode class for including real systems in the emulated network.
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
|
from typing import IO, TYPE_CHECKING, List, Optional
|
||||||
|
|
||||||
from core import utils
|
from core import utils
|
||||||
from core.constants import MOUNT_BIN, UMOUNT_BIN
|
from core.constants import MOUNT_BIN, UMOUNT_BIN
|
||||||
|
from core.emulator.distributed import DistributedServer
|
||||||
from core.emulator.enumerations import NodeTypes
|
from core.emulator.enumerations import NodeTypes
|
||||||
from core.errors import CoreCommandError, CoreError
|
from core.errors import CoreCommandError, CoreError
|
||||||
from core.nodes.base import CoreNodeBase
|
from core.nodes.base import CoreNetworkBase, CoreNodeBase
|
||||||
from core.nodes.interface import CoreInterface
|
from core.nodes.interface import CoreInterface, Veth
|
||||||
from core.nodes.network import CoreNetwork, GreTap
|
from core.nodes.network import CoreNetwork, GreTap
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.emulator.session import Session
|
||||||
|
|
||||||
|
|
||||||
class PhysicalNode(CoreNodeBase):
|
class PhysicalNode(CoreNodeBase):
|
||||||
def __init__(
|
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)
|
super().__init__(session, _id, name, start, server)
|
||||||
if not self.server:
|
if not self.server:
|
||||||
raise CoreError("physical nodes must be assigned to a remote server")
|
raise CoreError("physical nodes must be assigned to a remote server")
|
||||||
|
@ -29,11 +40,11 @@ class PhysicalNode(CoreNodeBase):
|
||||||
if start:
|
if start:
|
||||||
self.startup()
|
self.startup()
|
||||||
|
|
||||||
def startup(self):
|
def startup(self) -> None:
|
||||||
with self.lock:
|
with self.lock:
|
||||||
self.makenodedir()
|
self.makenodedir()
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self) -> None:
|
||||||
if not self.up:
|
if not self.up:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -47,7 +58,7 @@ class PhysicalNode(CoreNodeBase):
|
||||||
|
|
||||||
self.rmnodedir()
|
self.rmnodedir()
|
||||||
|
|
||||||
def termcmdstring(self, sh="/bin/sh"):
|
def termcmdstring(self, sh: str = "/bin/sh") -> str:
|
||||||
"""
|
"""
|
||||||
Create a terminal command string.
|
Create a terminal command string.
|
||||||
|
|
||||||
|
@ -56,7 +67,7 @@ class PhysicalNode(CoreNodeBase):
|
||||||
"""
|
"""
|
||||||
return sh
|
return sh
|
||||||
|
|
||||||
def sethwaddr(self, ifindex, addr):
|
def sethwaddr(self, ifindex: int, addr: str) -> None:
|
||||||
"""
|
"""
|
||||||
Set hardware address for an interface.
|
Set hardware address for an interface.
|
||||||
|
|
||||||
|
@ -71,7 +82,7 @@ class PhysicalNode(CoreNodeBase):
|
||||||
if self.up:
|
if self.up:
|
||||||
self.net_client.device_mac(interface.name, addr)
|
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.
|
Add an address to an interface.
|
||||||
|
|
||||||
|
@ -85,9 +96,13 @@ class PhysicalNode(CoreNodeBase):
|
||||||
self.net_client.create_address(interface.name, addr)
|
self.net_client.create_address(interface.name, addr)
|
||||||
interface.addaddr(addr)
|
interface.addaddr(addr)
|
||||||
|
|
||||||
def deladdr(self, ifindex, addr):
|
def deladdr(self, ifindex: int, addr: str) -> None:
|
||||||
"""
|
"""
|
||||||
Delete an address from an interface.
|
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]
|
interface = self._netif[ifindex]
|
||||||
|
|
||||||
|
@ -99,7 +114,9 @@ class PhysicalNode(CoreNodeBase):
|
||||||
if self.up:
|
if self.up:
|
||||||
self.net_client.delete_address(interface.name, str(addr))
|
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
|
When a link message is received linking this node to another part of
|
||||||
the emulation, no new interface is created; instead, adopt the
|
the emulation, no new interface is created; instead, adopt the
|
||||||
|
@ -127,18 +144,17 @@ class PhysicalNode(CoreNodeBase):
|
||||||
|
|
||||||
def linkconfig(
|
def linkconfig(
|
||||||
self,
|
self,
|
||||||
netif,
|
netif: CoreInterface,
|
||||||
bw=None,
|
bw: float = None,
|
||||||
delay=None,
|
delay: float = None,
|
||||||
loss=None,
|
loss: float = None,
|
||||||
duplicate=None,
|
duplicate: float = None,
|
||||||
jitter=None,
|
jitter: float = None,
|
||||||
netif2=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 = CoreNetwork(session=self.session, start=False)
|
||||||
linux_bridge.up = True
|
linux_bridge.up = True
|
||||||
linux_bridge.linkconfig(
|
linux_bridge.linkconfig(
|
||||||
|
@ -152,7 +168,7 @@ class PhysicalNode(CoreNodeBase):
|
||||||
)
|
)
|
||||||
del linux_bridge
|
del linux_bridge
|
||||||
|
|
||||||
def newifindex(self):
|
def newifindex(self) -> int:
|
||||||
with self.lock:
|
with self.lock:
|
||||||
while self.ifindex in self._netif:
|
while self.ifindex in self._netif:
|
||||||
self.ifindex += 1
|
self.ifindex += 1
|
||||||
|
@ -160,7 +176,14 @@ class PhysicalNode(CoreNodeBase):
|
||||||
self.ifindex += 1
|
self.ifindex += 1
|
||||||
return ifindex
|
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")
|
logging.info("creating interface")
|
||||||
if not addrlist:
|
if not addrlist:
|
||||||
addrlist = []
|
addrlist = []
|
||||||
|
@ -186,7 +209,7 @@ class PhysicalNode(CoreNodeBase):
|
||||||
self.adoptnetif(netif, ifindex, hwaddr, addrlist)
|
self.adoptnetif(netif, ifindex, hwaddr, addrlist)
|
||||||
return ifindex
|
return ifindex
|
||||||
|
|
||||||
def privatedir(self, path):
|
def privatedir(self, path: str) -> None:
|
||||||
if path[0] != "/":
|
if path[0] != "/":
|
||||||
raise ValueError(f"path not fully qualified: {path}")
|
raise ValueError(f"path not fully qualified: {path}")
|
||||||
hostpath = os.path.join(
|
hostpath = os.path.join(
|
||||||
|
@ -195,21 +218,21 @@ class PhysicalNode(CoreNodeBase):
|
||||||
os.mkdir(hostpath)
|
os.mkdir(hostpath)
|
||||||
self.mount(hostpath, path)
|
self.mount(hostpath, path)
|
||||||
|
|
||||||
def mount(self, source, target):
|
def mount(self, source: str, target: str) -> None:
|
||||||
source = os.path.abspath(source)
|
source = os.path.abspath(source)
|
||||||
logging.info("mounting %s at %s", source, target)
|
logging.info("mounting %s at %s", source, target)
|
||||||
os.makedirs(target)
|
os.makedirs(target)
|
||||||
self.host_cmd(f"{MOUNT_BIN} --bind {source} {target}", cwd=self.nodedir)
|
self.host_cmd(f"{MOUNT_BIN} --bind {source} {target}", cwd=self.nodedir)
|
||||||
self._mounts.append((source, target))
|
self._mounts.append((source, target))
|
||||||
|
|
||||||
def umount(self, target):
|
def umount(self, target: str) -> None:
|
||||||
logging.info("unmounting '%s'", target)
|
logging.info("unmounting '%s'", target)
|
||||||
try:
|
try:
|
||||||
self.host_cmd(f"{UMOUNT_BIN} -l {target}", cwd=self.nodedir)
|
self.host_cmd(f"{UMOUNT_BIN} -l {target}", cwd=self.nodedir)
|
||||||
except CoreCommandError:
|
except CoreCommandError:
|
||||||
logging.exception("unmounting failed for %s", target)
|
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)
|
dirname, basename = os.path.split(filename)
|
||||||
if not basename:
|
if not basename:
|
||||||
raise ValueError("no basename for filename: " + filename)
|
raise ValueError("no basename for filename: " + filename)
|
||||||
|
@ -225,13 +248,13 @@ class PhysicalNode(CoreNodeBase):
|
||||||
hostfilename = os.path.join(dirname, basename)
|
hostfilename = os.path.join(dirname, basename)
|
||||||
return open(hostfilename, mode)
|
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:
|
with self.opennodefile(filename, "w") as node_file:
|
||||||
node_file.write(contents)
|
node_file.write(contents)
|
||||||
os.chmod(node_file.name, mode)
|
os.chmod(node_file.name, mode)
|
||||||
logging.info("created nodefile: '%s'; mode: 0%o", 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)
|
return self.host_cmd(args, wait=wait)
|
||||||
|
|
||||||
|
|
||||||
|
@ -244,7 +267,15 @@ class Rj45Node(CoreNodeBase, CoreInterface):
|
||||||
apitype = NodeTypes.RJ45.value
|
apitype = NodeTypes.RJ45.value
|
||||||
type = "rj45"
|
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.
|
Create an RJ45Node instance.
|
||||||
|
|
||||||
|
@ -270,7 +301,7 @@ class Rj45Node(CoreNodeBase, CoreInterface):
|
||||||
if start:
|
if start:
|
||||||
self.startup()
|
self.startup()
|
||||||
|
|
||||||
def startup(self):
|
def startup(self) -> None:
|
||||||
"""
|
"""
|
||||||
Set the interface in the up state.
|
Set the interface in the up state.
|
||||||
|
|
||||||
|
@ -282,7 +313,7 @@ class Rj45Node(CoreNodeBase, CoreInterface):
|
||||||
self.net_client.device_up(self.localname)
|
self.net_client.device_up(self.localname)
|
||||||
self.up = True
|
self.up = True
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self) -> None:
|
||||||
"""
|
"""
|
||||||
Bring the interface down. Remove any addresses and queuing
|
Bring the interface down. Remove any addresses and queuing
|
||||||
disciplines.
|
disciplines.
|
||||||
|
@ -304,18 +335,18 @@ class Rj45Node(CoreNodeBase, CoreInterface):
|
||||||
|
|
||||||
# TODO: issue in that both classes inherited from provide the same method with
|
# TODO: issue in that both classes inherited from provide the same method with
|
||||||
# different signatures
|
# different signatures
|
||||||
def attachnet(self, net):
|
def attachnet(self, net: CoreNetworkBase) -> None:
|
||||||
"""
|
"""
|
||||||
Attach a network.
|
Attach a network.
|
||||||
|
|
||||||
:param core.coreobj.PyCoreNet net: network to attach
|
:param core.nodes.base.CoreNetworkBase net: network to attach
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
CoreInterface.attachnet(self, net)
|
CoreInterface.attachnet(self, net)
|
||||||
|
|
||||||
# TODO: issue in that both classes inherited from provide the same method with
|
# TODO: issue in that both classes inherited from provide the same method with
|
||||||
# different signatures
|
# different signatures
|
||||||
def detachnet(self):
|
def detachnet(self) -> None:
|
||||||
"""
|
"""
|
||||||
Detach a network.
|
Detach a network.
|
||||||
|
|
||||||
|
@ -323,7 +354,14 @@ class Rj45Node(CoreNodeBase, CoreInterface):
|
||||||
"""
|
"""
|
||||||
CoreInterface.detachnet(self)
|
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
|
This is called when linking with another node. Since this node
|
||||||
represents an interface, we do not create another object here,
|
represents an interface, we do not create another object here,
|
||||||
|
@ -359,7 +397,7 @@ class Rj45Node(CoreNodeBase, CoreInterface):
|
||||||
|
|
||||||
return ifindex
|
return ifindex
|
||||||
|
|
||||||
def delnetif(self, ifindex):
|
def delnetif(self, ifindex: int) -> None:
|
||||||
"""
|
"""
|
||||||
Delete a network interface.
|
Delete a network interface.
|
||||||
|
|
||||||
|
@ -376,7 +414,9 @@ class Rj45Node(CoreNodeBase, CoreInterface):
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"ifindex {ifindex} does not exist")
|
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
|
This object is considered the network interface, so we only
|
||||||
return self here. This keeps the RJ45Node compatible with
|
return self here. This keeps the RJ45Node compatible with
|
||||||
|
@ -398,20 +438,20 @@ class Rj45Node(CoreNodeBase, CoreInterface):
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def getifindex(self, netif):
|
def getifindex(self, netif: CoreInterface) -> Optional[int]:
|
||||||
"""
|
"""
|
||||||
Retrieve network interface index.
|
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
|
:return: interface index, None otherwise
|
||||||
:rtype: int
|
:rtype: int
|
||||||
"""
|
"""
|
||||||
if netif != self:
|
if netif != self:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return self.ifindex
|
return self.ifindex
|
||||||
|
|
||||||
def addaddr(self, addr):
|
def addaddr(self, addr: str) -> None:
|
||||||
"""
|
"""
|
||||||
Add address to to network interface.
|
Add address to to network interface.
|
||||||
|
|
||||||
|
@ -424,7 +464,7 @@ class Rj45Node(CoreNodeBase, CoreInterface):
|
||||||
self.net_client.create_address(self.name, addr)
|
self.net_client.create_address(self.name, addr)
|
||||||
CoreInterface.addaddr(self, addr)
|
CoreInterface.addaddr(self, addr)
|
||||||
|
|
||||||
def deladdr(self, addr):
|
def deladdr(self, addr: str) -> None:
|
||||||
"""
|
"""
|
||||||
Delete address from network interface.
|
Delete address from network interface.
|
||||||
|
|
||||||
|
@ -434,10 +474,9 @@ class Rj45Node(CoreNodeBase, CoreInterface):
|
||||||
"""
|
"""
|
||||||
if self.up:
|
if self.up:
|
||||||
self.net_client.delete_address(self.name, str(addr))
|
self.net_client.delete_address(self.name, str(addr))
|
||||||
|
|
||||||
CoreInterface.deladdr(self, addr)
|
CoreInterface.deladdr(self, addr)
|
||||||
|
|
||||||
def savestate(self):
|
def savestate(self) -> None:
|
||||||
"""
|
"""
|
||||||
Save the addresses and other interface state before using the
|
Save the addresses and other interface state before using the
|
||||||
interface for emulation purposes. TODO: save/restore the PROMISC flag
|
interface for emulation purposes. TODO: save/restore the PROMISC flag
|
||||||
|
@ -464,7 +503,7 @@ class Rj45Node(CoreNodeBase, CoreInterface):
|
||||||
continue
|
continue
|
||||||
self.old_addrs.append((items[1], None))
|
self.old_addrs.append((items[1], None))
|
||||||
|
|
||||||
def restorestate(self):
|
def restorestate(self) -> None:
|
||||||
"""
|
"""
|
||||||
Restore the addresses and other interface state after using it.
|
Restore the addresses and other interface state after using it.
|
||||||
|
|
||||||
|
@ -482,7 +521,7 @@ class Rj45Node(CoreNodeBase, CoreInterface):
|
||||||
if self.old_up:
|
if self.old_up:
|
||||||
self.net_client.device_up(self.localname)
|
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.
|
Uses setposition from both parent classes.
|
||||||
|
|
||||||
|
@ -496,7 +535,7 @@ class Rj45Node(CoreNodeBase, CoreInterface):
|
||||||
CoreInterface.setposition(self, x, y, z)
|
CoreInterface.setposition(self, x, y, z)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def termcmdstring(self, sh):
|
def termcmdstring(self, sh: str) -> str:
|
||||||
"""
|
"""
|
||||||
Create a terminal command string.
|
Create a terminal command string.
|
||||||
|
|
||||||
|
|
|
@ -4,17 +4,19 @@ sdt.py: Scripted Display Tool (SDT3D) helper
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import socket
|
import socket
|
||||||
|
from typing import TYPE_CHECKING, Any, Optional
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
from core import constants
|
from core import constants
|
||||||
|
from core.api.tlv.coreapi import CoreLinkMessage, CoreMessage, CoreNodeMessage
|
||||||
from core.constants import CORE_DATA_DIR
|
from core.constants import CORE_DATA_DIR
|
||||||
from core.emane.nodes import EmaneNet
|
from core.emane.nodes import EmaneNet
|
||||||
|
from core.emulator.data import LinkData, NodeData
|
||||||
from core.emulator.enumerations import (
|
from core.emulator.enumerations import (
|
||||||
EventTypes,
|
EventTypes,
|
||||||
LinkTlvs,
|
LinkTlvs,
|
||||||
LinkTypes,
|
LinkTypes,
|
||||||
MessageFlags,
|
MessageFlags,
|
||||||
MessageTypes,
|
|
||||||
NodeTlvs,
|
NodeTlvs,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
)
|
)
|
||||||
|
@ -22,6 +24,9 @@ from core.errors import CoreError
|
||||||
from core.nodes.base import CoreNetworkBase, NodeBase
|
from core.nodes.base import CoreNetworkBase, NodeBase
|
||||||
from core.nodes.network import WlanNode
|
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
|
# TODO: A named tuple may be more appropriate, than abusing a class dict like this
|
||||||
class Bunch:
|
class Bunch:
|
||||||
|
@ -29,7 +34,7 @@ class Bunch:
|
||||||
Helper class for recording a collection of attributes.
|
Helper class for recording a collection of attributes.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs: Any) -> None:
|
||||||
"""
|
"""
|
||||||
Create a Bunch instance.
|
Create a Bunch instance.
|
||||||
|
|
||||||
|
@ -62,7 +67,7 @@ class Sdt:
|
||||||
("tunnel", "tunnel.gif"),
|
("tunnel", "tunnel.gif"),
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, session):
|
def __init__(self, session: "Session") -> None:
|
||||||
"""
|
"""
|
||||||
Creates a Sdt instance.
|
Creates a Sdt instance.
|
||||||
|
|
||||||
|
@ -83,7 +88,7 @@ class Sdt:
|
||||||
# add handler for link updates
|
# add handler for link updates
|
||||||
self.session.link_handlers.append(self.handle_link_update)
|
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.
|
Handler for node updates, specifically for updating their location.
|
||||||
|
|
||||||
|
@ -108,7 +113,7 @@ class Sdt:
|
||||||
# TODO: z is not currently supported by node messages
|
# TODO: z is not currently supported by node messages
|
||||||
self.updatenode(node_data.id, 0, x, y, 0)
|
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.
|
Handler for link updates, checking for wireless link/unlink messages.
|
||||||
|
|
||||||
|
@ -123,7 +128,7 @@ class Sdt:
|
||||||
wireless=True,
|
wireless=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
def is_enabled(self):
|
def is_enabled(self) -> bool:
|
||||||
"""
|
"""
|
||||||
Check for "enablesdt" session option. Return False by default if
|
Check for "enablesdt" session option. Return False by default if
|
||||||
the option is missing.
|
the option is missing.
|
||||||
|
@ -133,7 +138,7 @@ class Sdt:
|
||||||
"""
|
"""
|
||||||
return self.session.options.get_config("enablesdt") == "1"
|
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.
|
Read "sdturl" from session options, or use the default value.
|
||||||
Set self.url, self.address, self.protocol
|
Set self.url, self.address, self.protocol
|
||||||
|
@ -147,7 +152,7 @@ class Sdt:
|
||||||
self.address = (self.url.hostname, self.url.port)
|
self.address = (self.url.hostname, self.url.port)
|
||||||
self.protocol = self.url.scheme
|
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.
|
Connect to the SDT address/port if enabled.
|
||||||
|
|
||||||
|
@ -185,7 +190,7 @@ class Sdt:
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self) -> bool:
|
||||||
"""
|
"""
|
||||||
Load icon sprites, and fly to the reference point location on
|
Load icon sprites, and fly to the reference point location on
|
||||||
the virtual globe.
|
the virtual globe.
|
||||||
|
@ -202,7 +207,7 @@ class Sdt:
|
||||||
lat, long = self.session.location.refgeo[:2]
|
lat, long = self.session.location.refgeo[:2]
|
||||||
return self.cmd(f"flyto {long:.6f},{lat:.6f},{self.DEFAULT_ALT}")
|
return self.cmd(f"flyto {long:.6f},{lat:.6f},{self.DEFAULT_ALT}")
|
||||||
|
|
||||||
def disconnect(self):
|
def disconnect(self) -> None:
|
||||||
"""
|
"""
|
||||||
Disconnect from SDT.
|
Disconnect from SDT.
|
||||||
|
|
||||||
|
@ -218,7 +223,7 @@ class Sdt:
|
||||||
|
|
||||||
self.connected = False
|
self.connected = False
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self) -> None:
|
||||||
"""
|
"""
|
||||||
Invoked from Session.shutdown() and Session.checkshutdown().
|
Invoked from Session.shutdown() and Session.checkshutdown().
|
||||||
|
|
||||||
|
@ -228,7 +233,7 @@ class Sdt:
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
self.showerror = True
|
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
|
Send an SDT command over a UDP socket. socket.sendall() is used
|
||||||
as opposed to socket.sendto() because an exception is raised when
|
as opposed to socket.sendto() because an exception is raised when
|
||||||
|
@ -250,7 +255,17 @@ class Sdt:
|
||||||
self.connected = False
|
self.connected = False
|
||||||
return 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.
|
Node is updated from a Node Message or mobility script.
|
||||||
|
|
||||||
|
@ -283,13 +298,13 @@ class Sdt:
|
||||||
else:
|
else:
|
||||||
self.cmd(f"node {nodenum} {pos}")
|
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.
|
Node is updated upon receiving an EMANE Location Event.
|
||||||
|
|
||||||
:param int nodenum: node id to update geospatial for
|
:param int nodenum: node id to update geospatial for
|
||||||
:param lat: latitude
|
:param lat: latitude
|
||||||
:param long: longitude
|
:param lon: longitude
|
||||||
:param alt: altitude
|
:param alt: altitude
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
|
@ -297,10 +312,12 @@ class Sdt:
|
||||||
# TODO: received Node Message with lat/long/alt.
|
# TODO: received Node Message with lat/long/alt.
|
||||||
if not self.connect():
|
if not self.connect():
|
||||||
return
|
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}")
|
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.
|
Link is updated from a Link Message or by a wireless model.
|
||||||
|
|
||||||
|
@ -323,7 +340,7 @@ class Sdt:
|
||||||
attr = " line red,2"
|
attr = " line red,2"
|
||||||
self.cmd(f"link {node1num},{node2num}{attr}")
|
self.cmd(f"link {node1num},{node2num}{attr}")
|
||||||
|
|
||||||
def sendobjs(self):
|
def sendobjs(self) -> None:
|
||||||
"""
|
"""
|
||||||
Session has already started, and the SDT3D GUI later connects.
|
Session has already started, and the SDT3D GUI later connects.
|
||||||
Send all node and link objects for display. Otherwise, nodes and
|
Send all node and link objects for display. Otherwise, nodes and
|
||||||
|
@ -379,21 +396,21 @@ class Sdt:
|
||||||
for n2num, wireless_link in r.links:
|
for n2num, wireless_link in r.links:
|
||||||
self.updatelink(n1num, n2num, MessageFlags.ADD.value, wireless_link)
|
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
|
Broker handler for processing CORE API messages as they are
|
||||||
received. This is used to snoop the Node messages and update
|
received. This is used to snoop the Node messages and update
|
||||||
node positions.
|
node positions.
|
||||||
|
|
||||||
:param message: message to handle
|
:param message: message to handle
|
||||||
:return: replies
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
if message.message_type == MessageTypes.LINK.value:
|
if isinstance(message, CoreLinkMessage):
|
||||||
return self.handlelinkmsg(message)
|
self.handlelinkmsg(message)
|
||||||
elif message.message_type == MessageTypes.NODE.value:
|
elif isinstance(message, CoreNodeMessage):
|
||||||
return self.handlenodemsg(message)
|
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
|
Process a Node Message to add/delete or move a node on
|
||||||
the SDT display. Node properties are found in a session or
|
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
|
# for distributed sessions to work properly, the SDT option should be
|
||||||
# enabled prior to starting the session
|
# enabled prior to starting the session
|
||||||
if not self.is_enabled():
|
if not self.is_enabled():
|
||||||
return False
|
return
|
||||||
# node.(_id, type, icon, name) are used.
|
# node.(_id, type, icon, name) are used.
|
||||||
nodenum = msg.get_tlv(NodeTlvs.NUMBER.value)
|
nodenum = msg.get_tlv(NodeTlvs.NUMBER.value)
|
||||||
if not nodenum:
|
if not nodenum:
|
||||||
|
@ -461,7 +478,7 @@ class Sdt:
|
||||||
remote.pos = (x, y, z)
|
remote.pos = (x, y, z)
|
||||||
self.updatenode(nodenum, msg.flags, x, y, z, name, nodetype, icon)
|
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.
|
Process a Link Message to add/remove links on the SDT display.
|
||||||
Links are recorded in the remotes[nodenum1].links set for updating
|
Links are recorded in the remotes[nodenum1].links set for updating
|
||||||
|
@ -471,7 +488,7 @@ class Sdt:
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
if not self.is_enabled():
|
if not self.is_enabled():
|
||||||
return False
|
return
|
||||||
nodenum1 = msg.get_tlv(LinkTlvs.N1_NUMBER.value)
|
nodenum1 = msg.get_tlv(LinkTlvs.N1_NUMBER.value)
|
||||||
nodenum2 = msg.get_tlv(LinkTlvs.N2_NUMBER.value)
|
nodenum2 = msg.get_tlv(LinkTlvs.N2_NUMBER.value)
|
||||||
link_msg_type = msg.get_tlv(LinkTlvs.TYPE.value)
|
link_msg_type = msg.get_tlv(LinkTlvs.TYPE.value)
|
||||||
|
@ -488,7 +505,7 @@ class Sdt:
|
||||||
r.links.add((nodenum2, wl))
|
r.links.add((nodenum2, wl))
|
||||||
self.updatelink(nodenum1, nodenum2, msg.flags, wireless=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.
|
Helper returns True if a node number corresponds to a WLAN or EMANE node.
|
||||||
|
|
||||||
|
|
|
@ -10,12 +10,17 @@ services.
|
||||||
import enum
|
import enum
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
|
from typing import TYPE_CHECKING, Iterable, List, Tuple, Type
|
||||||
|
|
||||||
from core import utils
|
from core import utils
|
||||||
from core.constants import which
|
from core.constants import which
|
||||||
from core.emulator.data import FileData
|
from core.emulator.data import FileData
|
||||||
from core.emulator.enumerations import ExceptionLevels, MessageFlags, RegisterTlvs
|
from core.emulator.enumerations import ExceptionLevels, MessageFlags, RegisterTlvs
|
||||||
from core.errors import CoreCommandError
|
from core.errors import CoreCommandError
|
||||||
|
from core.nodes.base import CoreNode
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.emulator.session import Session
|
||||||
|
|
||||||
|
|
||||||
class ServiceBootError(Exception):
|
class ServiceBootError(Exception):
|
||||||
|
@ -34,7 +39,7 @@ class ServiceDependencies:
|
||||||
that all services will be booted and that all dependencies exist within the services provided.
|
that all services will be booted and that all dependencies exist within the services provided.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, services):
|
def __init__(self, services: List["CoreService"]) -> None:
|
||||||
# helpers to check validity
|
# helpers to check validity
|
||||||
self.dependents = {}
|
self.dependents = {}
|
||||||
self.booted = set()
|
self.booted = set()
|
||||||
|
@ -50,7 +55,7 @@ class ServiceDependencies:
|
||||||
self.visited = set()
|
self.visited = set()
|
||||||
self.visiting = 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.
|
Generates the boot paths for the services provided to the class.
|
||||||
|
|
||||||
|
@ -78,17 +83,17 @@ class ServiceDependencies:
|
||||||
|
|
||||||
return paths
|
return paths
|
||||||
|
|
||||||
def _reset(self):
|
def _reset(self) -> None:
|
||||||
self.path = []
|
self.path = []
|
||||||
self.visited.clear()
|
self.visited.clear()
|
||||||
self.visiting.clear()
|
self.visiting.clear()
|
||||||
|
|
||||||
def _start(self, service):
|
def _start(self, service: "CoreService") -> List["CoreService"]:
|
||||||
logging.debug("starting service dependency check: %s", service.name)
|
logging.debug("starting service dependency check: %s", service.name)
|
||||||
self._reset()
|
self._reset()
|
||||||
return self._visit(service)
|
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)
|
logging.debug("visiting service(%s): %s", current_service.name, self.path)
|
||||||
self.visited.add(current_service.name)
|
self.visited.add(current_service.name)
|
||||||
self.visiting.add(current_service.name)
|
self.visiting.add(current_service.name)
|
||||||
|
@ -139,7 +144,7 @@ class ServiceShim:
|
||||||
]
|
]
|
||||||
|
|
||||||
@classmethod
|
@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,
|
Convert service properties into a string list of key=value pairs,
|
||||||
separated by "|".
|
separated by "|".
|
||||||
|
@ -168,7 +173,7 @@ class ServiceShim:
|
||||||
return "|".join(vals)
|
return "|".join(vals)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def fromvaluelist(cls, service, values):
|
def fromvaluelist(cls, service: "CoreService", values: None):
|
||||||
"""
|
"""
|
||||||
Convert list of values into properties for this instantiated
|
Convert list of values into properties for this instantiated
|
||||||
(customized) service.
|
(customized) service.
|
||||||
|
@ -186,7 +191,7 @@ class ServiceShim:
|
||||||
logging.exception("error indexing into key")
|
logging.exception("error indexing into key")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setvalue(cls, service, key, value):
|
def setvalue(cls, service: "CoreService", key: str, value: str) -> None:
|
||||||
"""
|
"""
|
||||||
Set values for this service.
|
Set values for this service.
|
||||||
|
|
||||||
|
@ -220,7 +225,7 @@ class ServiceShim:
|
||||||
service.meta = value
|
service.meta = value
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def servicesfromopaque(cls, opaque):
|
def servicesfromopaque(cls, opaque: str) -> List[str]:
|
||||||
"""
|
"""
|
||||||
Build a list of services from an opaque data string.
|
Build a list of services from an opaque data string.
|
||||||
|
|
||||||
|
@ -242,7 +247,7 @@ class ServiceManager:
|
||||||
services = {}
|
services = {}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def add(cls, service):
|
def add(cls, service: "CoreService") -> None:
|
||||||
"""
|
"""
|
||||||
Add a service to manager.
|
Add a service to manager.
|
||||||
|
|
||||||
|
@ -272,7 +277,7 @@ class ServiceManager:
|
||||||
cls.services[name] = service
|
cls.services[name] = service
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get(cls, name):
|
def get(cls, name: str) -> Type["CoreService"]:
|
||||||
"""
|
"""
|
||||||
Retrieve a service from the manager.
|
Retrieve a service from the manager.
|
||||||
|
|
||||||
|
@ -283,7 +288,7 @@ class ServiceManager:
|
||||||
return cls.services.get(name)
|
return cls.services.get(name)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def add_services(cls, path):
|
def add_services(cls, path: str) -> List[str]:
|
||||||
"""
|
"""
|
||||||
Method for retrieving all CoreServices from a given path.
|
Method for retrieving all CoreServices from a given path.
|
||||||
|
|
||||||
|
@ -317,7 +322,7 @@ class CoreServices:
|
||||||
name = "services"
|
name = "services"
|
||||||
config_type = RegisterTlvs.UTILITY.value
|
config_type = RegisterTlvs.UTILITY.value
|
||||||
|
|
||||||
def __init__(self, session):
|
def __init__(self, session: "Session") -> None:
|
||||||
"""
|
"""
|
||||||
Creates a CoreServices instance.
|
Creates a CoreServices instance.
|
||||||
|
|
||||||
|
@ -329,13 +334,13 @@ class CoreServices:
|
||||||
# dict of node ids to dict of custom services by name
|
# dict of node ids to dict of custom services by name
|
||||||
self.custom_services = {}
|
self.custom_services = {}
|
||||||
|
|
||||||
def reset(self):
|
def reset(self) -> None:
|
||||||
"""
|
"""
|
||||||
Called when config message with reset flag is received
|
Called when config message with reset flag is received
|
||||||
"""
|
"""
|
||||||
self.custom_services.clear()
|
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
|
Get the list of default services that should be enabled for a
|
||||||
node for the given node type.
|
node for the given node type.
|
||||||
|
@ -356,16 +361,18 @@ class CoreServices:
|
||||||
results.append(service)
|
results.append(service)
|
||||||
return results
|
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.
|
Get any custom service configured for the given node that matches the specified
|
||||||
If no custom service is found, return the specified service.
|
service name. If no custom service is found, return the specified service.
|
||||||
|
|
||||||
:param int node_id: object id to get service from
|
:param int node_id: object id to get service from
|
||||||
:param str service_name: name of service to retrieve
|
: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
|
:return: custom service from the node
|
||||||
:rtype: CoreService
|
|
||||||
"""
|
"""
|
||||||
node_services = self.custom_services.setdefault(node_id, {})
|
node_services = self.custom_services.setdefault(node_id, {})
|
||||||
default = None
|
default = None
|
||||||
|
@ -373,7 +380,7 @@ class CoreServices:
|
||||||
default = ServiceManager.get(service_name)
|
default = ServiceManager.get(service_name)
|
||||||
return node_services.get(service_name, default)
|
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
|
Store service customizations in an instantiated service object
|
||||||
using a list of values that came from a config message.
|
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 = self.custom_services.setdefault(node_id, {})
|
||||||
node_services[service.name] = service
|
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.
|
Add services to a node.
|
||||||
|
|
||||||
|
@ -417,10 +426,10 @@ class CoreServices:
|
||||||
continue
|
continue
|
||||||
node.services.append(service)
|
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
|
Return (node_id, service) tuples for all stored configs. Used when reconnecting
|
||||||
session or opening XML.
|
to a session or opening XML.
|
||||||
|
|
||||||
:return: list of tuples of node ids and services
|
:return: list of tuples of node ids and services
|
||||||
:rtype: list[tuple]
|
:rtype: list[tuple]
|
||||||
|
@ -433,7 +442,7 @@ class CoreServices:
|
||||||
configs.append((node_id, service))
|
configs.append((node_id, service))
|
||||||
return configs
|
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.
|
Return all customized files stored with a service.
|
||||||
Used when reconnecting to a session or opening XML.
|
Used when reconnecting to a session or opening XML.
|
||||||
|
@ -454,7 +463,7 @@ class CoreServices:
|
||||||
|
|
||||||
return files
|
return files
|
||||||
|
|
||||||
def boot_services(self, node):
|
def boot_services(self, node: CoreNode) -> None:
|
||||||
"""
|
"""
|
||||||
Start all services on a node.
|
Start all services on a node.
|
||||||
|
|
||||||
|
@ -470,7 +479,7 @@ class CoreServices:
|
||||||
if exceptions:
|
if exceptions:
|
||||||
raise ServiceBootError(*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.
|
Start all service boot paths found, based on dependencies.
|
||||||
|
|
||||||
|
@ -491,7 +500,7 @@ class CoreServices:
|
||||||
logging.exception("exception booting service: %s", service.name)
|
logging.exception("exception booting service: %s", service.name)
|
||||||
raise
|
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
|
Start a service on a node. Create private dirs, generate config
|
||||||
files, and execute startup commands.
|
files, and execute startup commands.
|
||||||
|
@ -555,7 +564,7 @@ class CoreServices:
|
||||||
"node(%s) service(%s) failed validation" % (node.name, service.name)
|
"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
|
Given a configured service filename and config, determine if the
|
||||||
config references an existing file that should be copied.
|
config references an existing file that should be copied.
|
||||||
|
@ -576,7 +585,7 @@ class CoreServices:
|
||||||
return True
|
return True
|
||||||
return False
|
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.
|
Run the validation command(s) for a service.
|
||||||
|
|
||||||
|
@ -605,7 +614,7 @@ class CoreServices:
|
||||||
|
|
||||||
return status
|
return status
|
||||||
|
|
||||||
def stop_services(self, node):
|
def stop_services(self, node: CoreNode) -> None:
|
||||||
"""
|
"""
|
||||||
Stop all services on a node.
|
Stop all services on a node.
|
||||||
|
|
||||||
|
@ -615,14 +624,13 @@ class CoreServices:
|
||||||
for service in node.services:
|
for service in node.services:
|
||||||
self.stop_service(node, service)
|
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.
|
Stop a service on a node.
|
||||||
|
|
||||||
:param core.nodes.base.CoreNode node: node to stop a service on
|
:param core.nodes.base.CoreNode node: node to stop a service on
|
||||||
:param CoreService service: service to stop
|
:param CoreService service: service to stop
|
||||||
:return: status for stopping the services
|
:return: status for stopping the services
|
||||||
:rtype: str
|
|
||||||
"""
|
"""
|
||||||
status = 0
|
status = 0
|
||||||
for args in service.shutdown:
|
for args in service.shutdown:
|
||||||
|
@ -639,7 +647,7 @@ class CoreServices:
|
||||||
status = -1
|
status = -1
|
||||||
return status
|
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.
|
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.
|
The file data is either auto-generated or comes from an existing config.
|
||||||
|
@ -681,7 +689,9 @@ class CoreServices:
|
||||||
data=data,
|
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
|
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
|
in the service config. The filename must match one from the list of
|
||||||
|
@ -713,7 +723,9 @@ class CoreServices:
|
||||||
# set custom service file data
|
# set custom service file data
|
||||||
service.config_data[file_name] = 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.
|
Startup a node service.
|
||||||
|
|
||||||
|
@ -737,7 +749,7 @@ class CoreServices:
|
||||||
status = -1
|
status = -1
|
||||||
return status
|
return status
|
||||||
|
|
||||||
def create_service_files(self, node, service):
|
def create_service_files(self, node: CoreNode, service: "CoreService") -> None:
|
||||||
"""
|
"""
|
||||||
Creates node service files.
|
Creates node service files.
|
||||||
|
|
||||||
|
@ -771,7 +783,7 @@ class CoreServices:
|
||||||
|
|
||||||
node.nodefile(file_name, cfg)
|
node.nodefile(file_name, cfg)
|
||||||
|
|
||||||
def service_reconfigure(self, node, service):
|
def service_reconfigure(self, node: CoreNode, service: "CoreService") -> None:
|
||||||
"""
|
"""
|
||||||
Reconfigure a node service.
|
Reconfigure a node service.
|
||||||
|
|
||||||
|
@ -846,7 +858,7 @@ class CoreService:
|
||||||
custom = False
|
custom = False
|
||||||
custom_needed = False
|
custom_needed = False
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
"""
|
"""
|
||||||
Services are not necessarily instantiated. Classmethods may be used
|
Services are not necessarily instantiated. Classmethods may be used
|
||||||
against their config. Services are instantiated when a custom
|
against their config. Services are instantiated when a custom
|
||||||
|
@ -856,11 +868,11 @@ class CoreService:
|
||||||
self.config_data = self.__class__.config_data.copy()
|
self.config_data = self.__class__.config_data.copy()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def on_load(cls):
|
def on_load(cls) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_configs(cls, node):
|
def get_configs(cls, node: CoreNode) -> Iterable[str]:
|
||||||
"""
|
"""
|
||||||
Return the tuple of configuration file filenames. This default method
|
Return the tuple of configuration file filenames. This default method
|
||||||
returns the cls._configs tuple, but this method may be overriden to
|
returns the cls._configs tuple, but this method may be overriden to
|
||||||
|
@ -873,7 +885,7 @@ class CoreService:
|
||||||
return cls.configs
|
return cls.configs
|
||||||
|
|
||||||
@classmethod
|
@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
|
Generate configuration file given a node object. The filename is
|
||||||
provided to allow for multiple config files.
|
provided to allow for multiple config files.
|
||||||
|
@ -887,7 +899,7 @@ class CoreService:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_startup(cls, node):
|
def get_startup(cls, node: CoreNode) -> Iterable[str]:
|
||||||
"""
|
"""
|
||||||
Return the tuple of startup commands. This default method
|
Return the tuple of startup commands. This default method
|
||||||
returns the cls.startup tuple, but this method may be
|
returns the cls.startup tuple, but this method may be
|
||||||
|
@ -901,7 +913,7 @@ class CoreService:
|
||||||
return cls.startup
|
return cls.startup
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_validate(cls, node):
|
def get_validate(cls, node: CoreNode) -> Iterable[str]:
|
||||||
"""
|
"""
|
||||||
Return the tuple of validate commands. This default method
|
Return the tuple of validate commands. This default method
|
||||||
returns the cls.validate tuple, but this method may be
|
returns the cls.validate tuple, but this method may be
|
||||||
|
|
|
@ -15,15 +15,36 @@ import random
|
||||||
import shlex
|
import shlex
|
||||||
import sys
|
import sys
|
||||||
from subprocess import PIPE, STDOUT, Popen
|
from subprocess import PIPE, STDOUT, Popen
|
||||||
|
from typing import (
|
||||||
|
TYPE_CHECKING,
|
||||||
|
Any,
|
||||||
|
Callable,
|
||||||
|
Dict,
|
||||||
|
Generic,
|
||||||
|
Iterable,
|
||||||
|
List,
|
||||||
|
Optional,
|
||||||
|
Tuple,
|
||||||
|
Type,
|
||||||
|
TypeVar,
|
||||||
|
Union,
|
||||||
|
)
|
||||||
|
|
||||||
import netaddr
|
import netaddr
|
||||||
|
|
||||||
from core.errors import CoreCommandError, CoreError
|
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")
|
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
|
Provides an alternative way to run execfile to be compatible for
|
||||||
both python2/3.
|
both python2/3.
|
||||||
|
@ -41,7 +62,7 @@ def execute_file(path, exec_globals=None, exec_locals=None):
|
||||||
exec(data, exec_globals, exec_locals)
|
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
|
Provide a consistent hash that can be used in place
|
||||||
of the builtin hash, that no longer behaves consistently
|
of the builtin hash, that no longer behaves consistently
|
||||||
|
@ -57,7 +78,7 @@ def hashkey(value):
|
||||||
return int(hashlib.sha256(value).hexdigest(), 16)
|
return int(hashlib.sha256(value).hexdigest(), 16)
|
||||||
|
|
||||||
|
|
||||||
def _detach_init():
|
def _detach_init() -> None:
|
||||||
"""
|
"""
|
||||||
Fork a child process and exit.
|
Fork a child process and exit.
|
||||||
|
|
||||||
|
@ -69,7 +90,7 @@ def _detach_init():
|
||||||
os.setsid()
|
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.
|
Check if file is a valid python module.
|
||||||
|
|
||||||
|
@ -91,7 +112,7 @@ def _valid_module(path, file_name):
|
||||||
return True
|
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.
|
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
|
return True
|
||||||
|
|
||||||
|
|
||||||
def close_onexec(fd):
|
def close_onexec(fd: int) -> None:
|
||||||
"""
|
"""
|
||||||
Close on execution of a shell process.
|
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)
|
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.
|
Find location of desired executable within current PATH.
|
||||||
|
|
||||||
|
@ -146,7 +167,7 @@ def which(command, required):
|
||||||
return found_path
|
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.
|
Create a tuple from an object, or return the object itself.
|
||||||
|
|
||||||
|
@ -160,7 +181,7 @@ def make_tuple(obj):
|
||||||
return (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.
|
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)
|
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.
|
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
|
:param dict kwargs: keyword arguments for the command
|
||||||
:return: process id of the command
|
:return: process id of the command
|
||||||
:rtype: int
|
:rtype: int
|
||||||
|
@ -195,7 +216,13 @@ def mute_detach(args, **kwargs):
|
||||||
return Popen(args, **kwargs).pid
|
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
|
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.
|
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)
|
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.
|
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")
|
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.
|
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))
|
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.
|
Expand a file path given session information.
|
||||||
|
|
||||||
|
@ -296,7 +325,7 @@ def expand_corepath(pathname, session=None, node=None):
|
||||||
return pathname
|
return pathname
|
||||||
|
|
||||||
|
|
||||||
def sysctl_devname(devname):
|
def sysctl_devname(devname: str) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
Translate a device name to the name used with sysctl.
|
Translate a device name to the name used with sysctl.
|
||||||
|
|
||||||
|
@ -309,7 +338,7 @@ def sysctl_devname(devname):
|
||||||
return devname.replace(".", "/")
|
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
|
Read key=value pairs from a file, into a dict. Skip comments; strip newline
|
||||||
characters and spacing.
|
characters and spacing.
|
||||||
|
@ -332,7 +361,7 @@ def load_config(filename, d):
|
||||||
logging.exception("error reading file to dict: %s", filename)
|
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.
|
Dynamically load classes for use within CORE.
|
||||||
|
|
||||||
|
@ -375,7 +404,7 @@ def load_classes(path, clazz):
|
||||||
return classes
|
return classes
|
||||||
|
|
||||||
|
|
||||||
def load_logging_config(config_path):
|
def load_logging_config(config_path: str) -> None:
|
||||||
"""
|
"""
|
||||||
Load CORE logging configuration file.
|
Load CORE logging configuration file.
|
||||||
|
|
||||||
|
@ -387,7 +416,9 @@ def load_logging_config(config_path):
|
||||||
logging.config.dictConfig(log_config)
|
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
|
Run provided functions, arguments, and keywords within a threadpool
|
||||||
collecting results and exceptions.
|
collecting results and exceptions.
|
||||||
|
@ -409,11 +440,12 @@ def threadpool(funcs, workers=10):
|
||||||
result = future.result()
|
result = future.result()
|
||||||
results.append(result)
|
results.append(result)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
logging.exception("thread pool exception")
|
||||||
exceptions.append(e)
|
exceptions.append(e)
|
||||||
return results, exceptions
|
return results, exceptions
|
||||||
|
|
||||||
|
|
||||||
def random_mac():
|
def random_mac() -> str:
|
||||||
"""
|
"""
|
||||||
Create a random mac address using Xen OID 00:16:3E.
|
Create a random mac address using Xen OID 00:16:3E.
|
||||||
|
|
||||||
|
@ -427,7 +459,7 @@ def random_mac():
|
||||||
return str(mac)
|
return str(mac)
|
||||||
|
|
||||||
|
|
||||||
def validate_mac(value):
|
def validate_mac(value: str) -> str:
|
||||||
"""
|
"""
|
||||||
Validate mac and return unix formatted version.
|
Validate mac and return unix formatted version.
|
||||||
|
|
||||||
|
@ -443,7 +475,7 @@ def validate_mac(value):
|
||||||
raise CoreError(f"invalid mac address {value}: {e}")
|
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.
|
Validate ip address with prefix and return formatted version.
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,30 @@
|
||||||
import logging
|
import logging
|
||||||
|
from typing import TYPE_CHECKING, Any, Dict, Generic, List, Optional, Type, TypeVar
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
import core.nodes.base
|
import core.nodes.base
|
||||||
import core.nodes.physical
|
import core.nodes.physical
|
||||||
from core.emane.nodes import EmaneNet
|
from core.emane.nodes import EmaneNet
|
||||||
|
from core.emulator.data import LinkData
|
||||||
from core.emulator.emudata import InterfaceData, LinkOptions, NodeOptions
|
from core.emulator.emudata import InterfaceData, LinkOptions, NodeOptions
|
||||||
from core.emulator.enumerations import NodeTypes
|
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.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_data = etree.tostring(
|
||||||
xml_element,
|
xml_element,
|
||||||
xml_declaration=True,
|
xml_declaration=True,
|
||||||
|
@ -23,27 +36,27 @@ def write_xml_file(xml_element, file_path, doctype=None):
|
||||||
xml_file.write(xml_data)
|
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)
|
value = element.get(name)
|
||||||
if value is not None:
|
if value is not None:
|
||||||
value = _type(value)
|
value = _type(value)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
def get_float(element, name):
|
def get_float(element: etree.Element, name: str) -> float:
|
||||||
return get_type(element, name, 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)
|
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:
|
if value is not None:
|
||||||
element.set(name, str(value))
|
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"))
|
interface_id = int(interface_element.get("id"))
|
||||||
name = interface_element.get("name")
|
name = interface_element.get("name")
|
||||||
mac = interface_element.get("mac")
|
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)
|
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")
|
emane_configuration = etree.Element("emane_configuration")
|
||||||
add_attribute(emane_configuration, "node", node_id)
|
add_attribute(emane_configuration, "node", node_id)
|
||||||
add_attribute(emane_configuration, "model", "emane")
|
add_attribute(emane_configuration, "model", "emane")
|
||||||
|
@ -72,7 +87,9 @@ def create_emane_config(node_id, emane_config, config):
|
||||||
return emane_configuration
|
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")
|
emane_element = etree.Element("emane_configuration")
|
||||||
add_attribute(emane_element, "node", node_id)
|
add_attribute(emane_element, "node", node_id)
|
||||||
add_attribute(emane_element, "model", model.name)
|
add_attribute(emane_element, "model", model.name)
|
||||||
|
@ -95,14 +112,14 @@ def create_emane_model_config(node_id, model, config):
|
||||||
return emane_element
|
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")
|
config_element = etree.SubElement(parent, "configuration")
|
||||||
add_attribute(config_element, "name", name)
|
add_attribute(config_element, "name", name)
|
||||||
add_attribute(config_element, "value", value)
|
add_attribute(config_element, "value", value)
|
||||||
|
|
||||||
|
|
||||||
class NodeElement:
|
class NodeElement:
|
||||||
def __init__(self, session, node, element_name):
|
def __init__(self, session: "Session", node: NodeBase, element_name: str) -> None:
|
||||||
self.session = session
|
self.session = session
|
||||||
self.node = node
|
self.node = node
|
||||||
self.element = etree.Element(element_name)
|
self.element = etree.Element(element_name)
|
||||||
|
@ -112,7 +129,7 @@ class NodeElement:
|
||||||
add_attribute(self.element, "canvas", node.canvas)
|
add_attribute(self.element, "canvas", node.canvas)
|
||||||
self.add_position()
|
self.add_position()
|
||||||
|
|
||||||
def add_position(self):
|
def add_position(self) -> None:
|
||||||
x = self.node.position.x
|
x = self.node.position.x
|
||||||
y = self.node.position.y
|
y = self.node.position.y
|
||||||
z = self.node.position.z
|
z = self.node.position.z
|
||||||
|
@ -129,7 +146,7 @@ class NodeElement:
|
||||||
|
|
||||||
|
|
||||||
class ServiceElement:
|
class ServiceElement:
|
||||||
def __init__(self, service):
|
def __init__(self, service: Type[CoreService]) -> None:
|
||||||
self.service = service
|
self.service = service
|
||||||
self.element = etree.Element("service")
|
self.element = etree.Element("service")
|
||||||
add_attribute(self.element, "name", service.name)
|
add_attribute(self.element, "name", service.name)
|
||||||
|
@ -139,7 +156,7 @@ class ServiceElement:
|
||||||
self.add_shutdown()
|
self.add_shutdown()
|
||||||
self.add_files()
|
self.add_files()
|
||||||
|
|
||||||
def add_directories(self):
|
def add_directories(self) -> None:
|
||||||
# get custom directories
|
# get custom directories
|
||||||
directories = etree.Element("directories")
|
directories = etree.Element("directories")
|
||||||
for directory in self.service.dirs:
|
for directory in self.service.dirs:
|
||||||
|
@ -149,7 +166,7 @@ class ServiceElement:
|
||||||
if directories.getchildren():
|
if directories.getchildren():
|
||||||
self.element.append(directories)
|
self.element.append(directories)
|
||||||
|
|
||||||
def add_files(self):
|
def add_files(self) -> None:
|
||||||
# get custom files
|
# get custom files
|
||||||
file_elements = etree.Element("files")
|
file_elements = etree.Element("files")
|
||||||
for file_name in self.service.config_data:
|
for file_name in self.service.config_data:
|
||||||
|
@ -161,7 +178,7 @@ class ServiceElement:
|
||||||
if file_elements.getchildren():
|
if file_elements.getchildren():
|
||||||
self.element.append(file_elements)
|
self.element.append(file_elements)
|
||||||
|
|
||||||
def add_startup(self):
|
def add_startup(self) -> None:
|
||||||
# get custom startup
|
# get custom startup
|
||||||
startup_elements = etree.Element("startups")
|
startup_elements = etree.Element("startups")
|
||||||
for startup in self.service.startup:
|
for startup in self.service.startup:
|
||||||
|
@ -171,7 +188,7 @@ class ServiceElement:
|
||||||
if startup_elements.getchildren():
|
if startup_elements.getchildren():
|
||||||
self.element.append(startup_elements)
|
self.element.append(startup_elements)
|
||||||
|
|
||||||
def add_validate(self):
|
def add_validate(self) -> None:
|
||||||
# get custom validate
|
# get custom validate
|
||||||
validate_elements = etree.Element("validates")
|
validate_elements = etree.Element("validates")
|
||||||
for validate in self.service.validate:
|
for validate in self.service.validate:
|
||||||
|
@ -181,7 +198,7 @@ class ServiceElement:
|
||||||
if validate_elements.getchildren():
|
if validate_elements.getchildren():
|
||||||
self.element.append(validate_elements)
|
self.element.append(validate_elements)
|
||||||
|
|
||||||
def add_shutdown(self):
|
def add_shutdown(self) -> None:
|
||||||
# get custom shutdown
|
# get custom shutdown
|
||||||
shutdown_elements = etree.Element("shutdowns")
|
shutdown_elements = etree.Element("shutdowns")
|
||||||
for shutdown in self.service.shutdown:
|
for shutdown in self.service.shutdown:
|
||||||
|
@ -193,12 +210,12 @@ class ServiceElement:
|
||||||
|
|
||||||
|
|
||||||
class DeviceElement(NodeElement):
|
class DeviceElement(NodeElement):
|
||||||
def __init__(self, session, node):
|
def __init__(self, session: "Session", node: NodeBase) -> None:
|
||||||
super().__init__(session, node, "device")
|
super().__init__(session, node, "device")
|
||||||
add_attribute(self.element, "type", node.type)
|
add_attribute(self.element, "type", node.type)
|
||||||
self.add_services()
|
self.add_services()
|
||||||
|
|
||||||
def add_services(self):
|
def add_services(self) -> None:
|
||||||
service_elements = etree.Element("services")
|
service_elements = etree.Element("services")
|
||||||
for service in self.node.services:
|
for service in self.node.services:
|
||||||
etree.SubElement(service_elements, "service", name=service.name)
|
etree.SubElement(service_elements, "service", name=service.name)
|
||||||
|
@ -208,7 +225,7 @@ class DeviceElement(NodeElement):
|
||||||
|
|
||||||
|
|
||||||
class NetworkElement(NodeElement):
|
class NetworkElement(NodeElement):
|
||||||
def __init__(self, session, node):
|
def __init__(self, session: "Session", node: NodeBase) -> None:
|
||||||
super().__init__(session, node, "network")
|
super().__init__(session, node, "network")
|
||||||
model = getattr(self.node, "model", None)
|
model = getattr(self.node, "model", None)
|
||||||
if model:
|
if model:
|
||||||
|
@ -221,7 +238,7 @@ class NetworkElement(NodeElement):
|
||||||
add_attribute(self.element, "grekey", grekey)
|
add_attribute(self.element, "grekey", grekey)
|
||||||
self.add_type()
|
self.add_type()
|
||||||
|
|
||||||
def add_type(self):
|
def add_type(self) -> None:
|
||||||
if self.node.apitype:
|
if self.node.apitype:
|
||||||
node_type = NodeTypes(self.node.apitype).name
|
node_type = NodeTypes(self.node.apitype).name
|
||||||
else:
|
else:
|
||||||
|
@ -230,14 +247,14 @@ class NetworkElement(NodeElement):
|
||||||
|
|
||||||
|
|
||||||
class CoreXmlWriter:
|
class CoreXmlWriter:
|
||||||
def __init__(self, session):
|
def __init__(self, session: "Session") -> None:
|
||||||
self.session = session
|
self.session = session
|
||||||
self.scenario = etree.Element("scenario")
|
self.scenario = etree.Element("scenario")
|
||||||
self.networks = None
|
self.networks = None
|
||||||
self.devices = None
|
self.devices = None
|
||||||
self.write_session()
|
self.write_session()
|
||||||
|
|
||||||
def write_session(self):
|
def write_session(self) -> None:
|
||||||
# generate xml content
|
# generate xml content
|
||||||
links = self.write_nodes()
|
links = self.write_nodes()
|
||||||
self.write_links(links)
|
self.write_links(links)
|
||||||
|
@ -250,7 +267,7 @@ class CoreXmlWriter:
|
||||||
self.write_session_metadata()
|
self.write_session_metadata()
|
||||||
self.write_default_services()
|
self.write_default_services()
|
||||||
|
|
||||||
def write(self, file_name):
|
def write(self, file_name: str) -> None:
|
||||||
self.scenario.set("name", file_name)
|
self.scenario.set("name", file_name)
|
||||||
|
|
||||||
# write out generated xml
|
# write out generated xml
|
||||||
|
@ -259,7 +276,7 @@ class CoreXmlWriter:
|
||||||
file_name, xml_declaration=True, pretty_print=True, encoding="UTF-8"
|
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
|
# origin: geolocation of cartesian coordinate 0,0,0
|
||||||
lat, lon, alt = self.session.location.refgeo
|
lat, lon, alt = self.session.location.refgeo
|
||||||
origin = etree.Element("session_origin")
|
origin = etree.Element("session_origin")
|
||||||
|
@ -279,7 +296,7 @@ class CoreXmlWriter:
|
||||||
add_attribute(origin, "y", y)
|
add_attribute(origin, "y", y)
|
||||||
add_attribute(origin, "z", z)
|
add_attribute(origin, "z", z)
|
||||||
|
|
||||||
def write_session_hooks(self):
|
def write_session_hooks(self) -> None:
|
||||||
# hook scripts
|
# hook scripts
|
||||||
hooks = etree.Element("session_hooks")
|
hooks = etree.Element("session_hooks")
|
||||||
for state in sorted(self.session._hooks.keys()):
|
for state in sorted(self.session._hooks.keys()):
|
||||||
|
@ -292,7 +309,7 @@ class CoreXmlWriter:
|
||||||
if hooks.getchildren():
|
if hooks.getchildren():
|
||||||
self.scenario.append(hooks)
|
self.scenario.append(hooks)
|
||||||
|
|
||||||
def write_session_options(self):
|
def write_session_options(self) -> None:
|
||||||
option_elements = etree.Element("session_options")
|
option_elements = etree.Element("session_options")
|
||||||
options_config = self.session.options.get_configs()
|
options_config = self.session.options.get_configs()
|
||||||
if not options_config:
|
if not options_config:
|
||||||
|
@ -307,7 +324,7 @@ class CoreXmlWriter:
|
||||||
if option_elements.getchildren():
|
if option_elements.getchildren():
|
||||||
self.scenario.append(option_elements)
|
self.scenario.append(option_elements)
|
||||||
|
|
||||||
def write_session_metadata(self):
|
def write_session_metadata(self) -> None:
|
||||||
# metadata
|
# metadata
|
||||||
metadata_elements = etree.Element("session_metadata")
|
metadata_elements = etree.Element("session_metadata")
|
||||||
config = self.session.metadata
|
config = self.session.metadata
|
||||||
|
@ -321,7 +338,7 @@ class CoreXmlWriter:
|
||||||
if metadata_elements.getchildren():
|
if metadata_elements.getchildren():
|
||||||
self.scenario.append(metadata_elements)
|
self.scenario.append(metadata_elements)
|
||||||
|
|
||||||
def write_emane_configs(self):
|
def write_emane_configs(self) -> None:
|
||||||
emane_configurations = etree.Element("emane_configurations")
|
emane_configurations = etree.Element("emane_configurations")
|
||||||
for node_id in self.session.emane.nodes():
|
for node_id in self.session.emane.nodes():
|
||||||
all_configs = self.session.emane.get_all_configs(node_id)
|
all_configs = self.session.emane.get_all_configs(node_id)
|
||||||
|
@ -347,7 +364,7 @@ class CoreXmlWriter:
|
||||||
if emane_configurations.getchildren():
|
if emane_configurations.getchildren():
|
||||||
self.scenario.append(emane_configurations)
|
self.scenario.append(emane_configurations)
|
||||||
|
|
||||||
def write_mobility_configs(self):
|
def write_mobility_configs(self) -> None:
|
||||||
mobility_configurations = etree.Element("mobility_configurations")
|
mobility_configurations = etree.Element("mobility_configurations")
|
||||||
for node_id in self.session.mobility.nodes():
|
for node_id in self.session.mobility.nodes():
|
||||||
all_configs = self.session.mobility.get_all_configs(node_id)
|
all_configs = self.session.mobility.get_all_configs(node_id)
|
||||||
|
@ -371,7 +388,7 @@ class CoreXmlWriter:
|
||||||
if mobility_configurations.getchildren():
|
if mobility_configurations.getchildren():
|
||||||
self.scenario.append(mobility_configurations)
|
self.scenario.append(mobility_configurations)
|
||||||
|
|
||||||
def write_service_configs(self):
|
def write_service_configs(self) -> None:
|
||||||
service_configurations = etree.Element("service_configurations")
|
service_configurations = etree.Element("service_configurations")
|
||||||
service_configs = self.session.services.all_configs()
|
service_configs = self.session.services.all_configs()
|
||||||
for node_id, service in service_configs:
|
for node_id, service in service_configs:
|
||||||
|
@ -382,7 +399,7 @@ class CoreXmlWriter:
|
||||||
if service_configurations.getchildren():
|
if service_configurations.getchildren():
|
||||||
self.scenario.append(service_configurations)
|
self.scenario.append(service_configurations)
|
||||||
|
|
||||||
def write_default_services(self):
|
def write_default_services(self) -> None:
|
||||||
node_types = etree.Element("default_services")
|
node_types = etree.Element("default_services")
|
||||||
for node_type in self.session.services.default_services:
|
for node_type in self.session.services.default_services:
|
||||||
services = self.session.services.default_services[node_type]
|
services = self.session.services.default_services[node_type]
|
||||||
|
@ -393,7 +410,7 @@ class CoreXmlWriter:
|
||||||
if node_types.getchildren():
|
if node_types.getchildren():
|
||||||
self.scenario.append(node_types)
|
self.scenario.append(node_types)
|
||||||
|
|
||||||
def write_nodes(self):
|
def write_nodes(self) -> List[LinkData]:
|
||||||
self.networks = etree.SubElement(self.scenario, "networks")
|
self.networks = etree.SubElement(self.scenario, "networks")
|
||||||
self.devices = etree.SubElement(self.scenario, "devices")
|
self.devices = etree.SubElement(self.scenario, "devices")
|
||||||
|
|
||||||
|
@ -416,7 +433,7 @@ class CoreXmlWriter:
|
||||||
|
|
||||||
return links
|
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
|
# ignore p2p and other nodes that are not part of the api
|
||||||
if not node.apitype:
|
if not node.apitype:
|
||||||
return
|
return
|
||||||
|
@ -424,7 +441,7 @@ class CoreXmlWriter:
|
||||||
network = NetworkElement(self.session, node)
|
network = NetworkElement(self.session, node)
|
||||||
self.networks.append(network.element)
|
self.networks.append(network.element)
|
||||||
|
|
||||||
def write_links(self, links):
|
def write_links(self, links: List[LinkData]) -> None:
|
||||||
link_elements = etree.Element("links")
|
link_elements = etree.Element("links")
|
||||||
# add link data
|
# add link data
|
||||||
for link_data in links:
|
for link_data in links:
|
||||||
|
@ -438,13 +455,21 @@ class CoreXmlWriter:
|
||||||
if link_elements.getchildren():
|
if link_elements.getchildren():
|
||||||
self.scenario.append(link_elements)
|
self.scenario.append(link_elements)
|
||||||
|
|
||||||
def write_device(self, node):
|
def write_device(self, node: NodeBase) -> None:
|
||||||
device = DeviceElement(self.session, node)
|
device = DeviceElement(self.session, node)
|
||||||
self.devices.append(device.element)
|
self.devices.append(device.element)
|
||||||
|
|
||||||
def create_interface_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)
|
interface = etree.Element(element_name)
|
||||||
node = self.session.get_node(node_id)
|
node = self.session.get_node(node_id)
|
||||||
interface_name = None
|
interface_name = None
|
||||||
|
@ -467,7 +492,7 @@ class CoreXmlWriter:
|
||||||
|
|
||||||
return interface
|
return interface
|
||||||
|
|
||||||
def create_link_element(self, link_data):
|
def create_link_element(self, link_data: LinkData) -> etree.Element:
|
||||||
link_element = etree.Element("link")
|
link_element = etree.Element("link")
|
||||||
add_attribute(link_element, "node_one", link_data.node1_id)
|
add_attribute(link_element, "node_one", link_data.node1_id)
|
||||||
add_attribute(link_element, "node_two", link_data.node2_id)
|
add_attribute(link_element, "node_two", link_data.node2_id)
|
||||||
|
@ -525,11 +550,11 @@ class CoreXmlWriter:
|
||||||
|
|
||||||
|
|
||||||
class CoreXmlReader:
|
class CoreXmlReader:
|
||||||
def __init__(self, session):
|
def __init__(self, session: "Session") -> None:
|
||||||
self.session = session
|
self.session = session
|
||||||
self.scenario = None
|
self.scenario = None
|
||||||
|
|
||||||
def read(self, file_name):
|
def read(self, file_name: str) -> None:
|
||||||
xml_tree = etree.parse(file_name)
|
xml_tree = etree.parse(file_name)
|
||||||
self.scenario = xml_tree.getroot()
|
self.scenario = xml_tree.getroot()
|
||||||
|
|
||||||
|
@ -545,7 +570,7 @@ class CoreXmlReader:
|
||||||
self.read_nodes()
|
self.read_nodes()
|
||||||
self.read_links()
|
self.read_links()
|
||||||
|
|
||||||
def read_default_services(self):
|
def read_default_services(self) -> None:
|
||||||
default_services = self.scenario.find("default_services")
|
default_services = self.scenario.find("default_services")
|
||||||
if default_services is None:
|
if default_services is None:
|
||||||
return
|
return
|
||||||
|
@ -560,7 +585,7 @@ class CoreXmlReader:
|
||||||
)
|
)
|
||||||
self.session.services.default_services[node_type] = services
|
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")
|
session_metadata = self.scenario.find("session_metadata")
|
||||||
if session_metadata is None:
|
if session_metadata is None:
|
||||||
return
|
return
|
||||||
|
@ -573,7 +598,7 @@ class CoreXmlReader:
|
||||||
logging.info("reading session metadata: %s", configs)
|
logging.info("reading session metadata: %s", configs)
|
||||||
self.session.metadata = configs
|
self.session.metadata = configs
|
||||||
|
|
||||||
def read_session_options(self):
|
def read_session_options(self) -> None:
|
||||||
session_options = self.scenario.find("session_options")
|
session_options = self.scenario.find("session_options")
|
||||||
if session_options is None:
|
if session_options is None:
|
||||||
return
|
return
|
||||||
|
@ -586,7 +611,7 @@ class CoreXmlReader:
|
||||||
logging.info("reading session options: %s", configs)
|
logging.info("reading session options: %s", configs)
|
||||||
self.session.options.set_configs(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")
|
session_hooks = self.scenario.find("session_hooks")
|
||||||
if session_hooks is None:
|
if session_hooks is None:
|
||||||
return
|
return
|
||||||
|
@ -601,7 +626,7 @@ class CoreXmlReader:
|
||||||
hook_type, file_name=name, source_name=None, data=data
|
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")
|
session_origin = self.scenario.find("session_origin")
|
||||||
if session_origin is None:
|
if session_origin is None:
|
||||||
return
|
return
|
||||||
|
@ -625,7 +650,7 @@ class CoreXmlReader:
|
||||||
logging.info("reading session reference xyz: %s, %s, %s", x, y, z)
|
logging.info("reading session reference xyz: %s, %s, %s", x, y, z)
|
||||||
self.session.location.refxyz = (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")
|
service_configurations = self.scenario.find("service_configurations")
|
||||||
if service_configurations is None:
|
if service_configurations is None:
|
||||||
return
|
return
|
||||||
|
@ -669,7 +694,7 @@ class CoreXmlReader:
|
||||||
files.add(name)
|
files.add(name)
|
||||||
service.configs = tuple(files)
|
service.configs = tuple(files)
|
||||||
|
|
||||||
def read_emane_configs(self):
|
def read_emane_configs(self) -> None:
|
||||||
emane_configurations = self.scenario.find("emane_configurations")
|
emane_configurations = self.scenario.find("emane_configurations")
|
||||||
if emane_configurations is None:
|
if emane_configurations is None:
|
||||||
return
|
return
|
||||||
|
@ -702,7 +727,7 @@ class CoreXmlReader:
|
||||||
)
|
)
|
||||||
self.session.emane.set_model_config(node_id, model_name, configs)
|
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")
|
mobility_configurations = self.scenario.find("mobility_configurations")
|
||||||
if mobility_configurations is None:
|
if mobility_configurations is None:
|
||||||
return
|
return
|
||||||
|
@ -722,7 +747,7 @@ class CoreXmlReader:
|
||||||
)
|
)
|
||||||
self.session.mobility.set_model_config(node_id, model_name, configs)
|
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")
|
device_elements = self.scenario.find("devices")
|
||||||
if device_elements is not None:
|
if device_elements is not None:
|
||||||
for device_element in device_elements.iterchildren():
|
for device_element in device_elements.iterchildren():
|
||||||
|
@ -733,7 +758,7 @@ class CoreXmlReader:
|
||||||
for network_element in network_elements.iterchildren():
|
for network_element in network_elements.iterchildren():
|
||||||
self.read_network(network_element)
|
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")
|
node_id = get_int(device_element, "id")
|
||||||
name = device_element.get("name")
|
name = device_element.get("name")
|
||||||
model = device_element.get("type")
|
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)
|
logging.info("reading node id(%s) model(%s) name(%s)", node_id, model, name)
|
||||||
self.session.add_node(_id=node_id, options=options)
|
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")
|
node_id = get_int(network_element, "id")
|
||||||
name = network_element.get("name")
|
name = network_element.get("name")
|
||||||
node_type = NodeTypes[network_element.get("type")]
|
node_type = NodeTypes[network_element.get("type")]
|
||||||
|
@ -783,7 +808,7 @@ class CoreXmlReader:
|
||||||
)
|
)
|
||||||
self.session.add_node(_type=node_type, _id=node_id, options=options)
|
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")
|
link_elements = self.scenario.find("links")
|
||||||
if link_elements is None:
|
if link_elements is None:
|
||||||
return
|
return
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import os
|
import os
|
||||||
import socket
|
import socket
|
||||||
|
from typing import TYPE_CHECKING, List, Tuple
|
||||||
|
|
||||||
import netaddr
|
import netaddr
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
@ -7,26 +8,40 @@ from lxml import etree
|
||||||
from core import utils
|
from core import utils
|
||||||
from core.constants import IP_BIN
|
from core.constants import IP_BIN
|
||||||
from core.emane.nodes import EmaneNet
|
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 = etree.SubElement(parent_element, "type")
|
||||||
type_element.text = name
|
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 = etree.SubElement(parent_element, "address", type=address_type)
|
||||||
address_element.text = address
|
address_element.text = address
|
||||||
if interface_name is not None:
|
if interface_name is not None:
|
||||||
address_element.set("iface", interface_name)
|
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)
|
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]
|
nem_id = netif.net.nemidmap[netif]
|
||||||
host_id = host_element.get("id")
|
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
|
return platform_element
|
||||||
|
|
||||||
|
|
||||||
def get_address_type(address):
|
def get_address_type(address: str) -> str:
|
||||||
addr, _slash, _prefixlen = address.partition("/")
|
addr, _slash, _prefixlen = address.partition("/")
|
||||||
if netaddr.valid_ipv4(addr):
|
if netaddr.valid_ipv4(addr):
|
||||||
address_type = "IPv4"
|
address_type = "IPv4"
|
||||||
|
@ -65,7 +80,7 @@ def get_address_type(address):
|
||||||
return address_type
|
return address_type
|
||||||
|
|
||||||
|
|
||||||
def get_ipv4_addresses(hostname):
|
def get_ipv4_addresses(hostname: str) -> List[Tuple[str, str]]:
|
||||||
if hostname == "localhost":
|
if hostname == "localhost":
|
||||||
addresses = []
|
addresses = []
|
||||||
args = f"{IP_BIN} -o -f inet address show"
|
args = f"{IP_BIN} -o -f inet address show"
|
||||||
|
@ -85,7 +100,7 @@ def get_ipv4_addresses(hostname):
|
||||||
|
|
||||||
|
|
||||||
class CoreXmlDeployment:
|
class CoreXmlDeployment:
|
||||||
def __init__(self, session, scenario):
|
def __init__(self, session: "Session", scenario: etree.Element) -> None:
|
||||||
self.session = session
|
self.session = session
|
||||||
self.scenario = scenario
|
self.scenario = scenario
|
||||||
self.root = etree.SubElement(
|
self.root = etree.SubElement(
|
||||||
|
@ -93,17 +108,17 @@ class CoreXmlDeployment:
|
||||||
)
|
)
|
||||||
self.add_deployment()
|
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}']")
|
device = self.scenario.find(f"devices/device[@name='{name}']")
|
||||||
return device
|
return device
|
||||||
|
|
||||||
def find_interface(self, device, name):
|
def find_interface(self, device: NodeBase, name: str) -> etree.Element:
|
||||||
interface = self.scenario.find(
|
interface = self.scenario.find(
|
||||||
f"devices/device[@name='{device.name}']/interfaces/interface[@name='{name}']"
|
f"devices/device[@name='{device.name}']/interfaces/interface[@name='{name}']"
|
||||||
)
|
)
|
||||||
return interface
|
return interface
|
||||||
|
|
||||||
def add_deployment(self):
|
def add_deployment(self) -> None:
|
||||||
physical_host = self.add_physical_host(socket.gethostname())
|
physical_host = self.add_physical_host(socket.gethostname())
|
||||||
|
|
||||||
for node_id in self.session.nodes:
|
for node_id in self.session.nodes:
|
||||||
|
@ -111,7 +126,7 @@ class CoreXmlDeployment:
|
||||||
if isinstance(node, CoreNodeBase):
|
if isinstance(node, CoreNodeBase):
|
||||||
self.add_virtual_host(physical_host, node)
|
self.add_virtual_host(physical_host, node)
|
||||||
|
|
||||||
def add_physical_host(self, name):
|
def add_physical_host(self, name: str) -> etree.Element:
|
||||||
# add host
|
# add host
|
||||||
root_id = self.root.get("id")
|
root_id = self.root.get("id")
|
||||||
host_id = f"{root_id}/{name}"
|
host_id = f"{root_id}/{name}"
|
||||||
|
@ -126,7 +141,7 @@ class CoreXmlDeployment:
|
||||||
|
|
||||||
return host_element
|
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):
|
if not isinstance(node, CoreNodeBase):
|
||||||
raise TypeError(f"invalid node type: {node}")
|
raise TypeError(f"invalid node type: {node}")
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,26 @@
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from tempfile import NamedTemporaryFile
|
from tempfile import NamedTemporaryFile
|
||||||
|
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
from core import utils
|
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
|
from core.xml import corexml
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.emane.emanemanager import EmaneManager
|
||||||
|
from core.emane.emanemodel import EmaneModel
|
||||||
|
|
||||||
_hwaddr_prefix = "02:02"
|
_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.
|
Checks if the configuration is for an external transport.
|
||||||
|
|
||||||
|
@ -21,7 +31,7 @@ def is_external(config):
|
||||||
return config.get("external") == "1"
|
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.
|
Helper to convert a parameter to a parameter tuple.
|
||||||
|
|
||||||
|
@ -44,7 +54,12 @@ def _value_to_params(value):
|
||||||
return None
|
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.
|
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)
|
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.
|
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)
|
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.
|
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)
|
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.
|
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:
|
if node.model is None:
|
||||||
logging.warning("warning: EMANE network %s has no associated model", node.name)
|
logging.warning("warning: EMANE network %s has no associated model", node.name)
|
||||||
return nem_entries
|
return nem_id
|
||||||
|
|
||||||
for netif in node.netifs():
|
for netif in node.netifs():
|
||||||
logging.debug(
|
logging.debug(
|
||||||
|
@ -228,7 +254,7 @@ def build_node_platform_xml(emane_manager, control_net, node, nem_id, platform_x
|
||||||
return nem_id
|
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.
|
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)
|
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.
|
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.
|
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.
|
Create the mac xml document.
|
||||||
|
|
||||||
|
@ -376,14 +414,14 @@ def create_mac_xml(emane_model, config, file_path, server):
|
||||||
|
|
||||||
|
|
||||||
def create_nem_xml(
|
def create_nem_xml(
|
||||||
emane_model,
|
emane_model: "EmaneModel",
|
||||||
config,
|
config: Dict[str, str],
|
||||||
nem_file,
|
nem_file: str,
|
||||||
transport_definition,
|
transport_definition: str,
|
||||||
mac_definition,
|
mac_definition: str,
|
||||||
phy_definition,
|
phy_definition: str,
|
||||||
server,
|
server: DistributedServer,
|
||||||
):
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Create the nem xml document.
|
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.
|
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)
|
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.
|
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"
|
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.
|
Create name that is leveraged for configuration file creation.
|
||||||
|
|
||||||
|
:param emane_model: emane model to create name for
|
||||||
:param interface: interface for this model
|
:param interface: interface for this model
|
||||||
:return: basename used for file creation
|
:return: basename used for file creation
|
||||||
:rtype: str
|
:rtype: str
|
||||||
|
@ -469,7 +514,7 @@ def _basename(emane_model, interface=None):
|
||||||
return f"{name}{emane_model.name}"
|
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"
|
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"
|
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"
|
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"
|
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"
|
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"
|
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"
|
Return the string name for the PHY XML file, e.g. "n3rfpipephy.xml"
|
||||||
|
|
||||||
|
|
9
ns3/build/lib/corens3/__init__.py
Normal file
9
ns3/build/lib/corens3/__init__.py
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
"""
|
||||||
|
corens3
|
||||||
|
|
||||||
|
Python package containing CORE components for use
|
||||||
|
with the ns-3 simulator.
|
||||||
|
|
||||||
|
See http://code.google.com/p/coreemu/
|
||||||
|
for more information on CORE.
|
||||||
|
"""
|
550
ns3/build/lib/corens3/obj.py
Normal file
550
ns3/build/lib/corens3/obj.py
Normal file
|
@ -0,0 +1,550 @@
|
||||||
|
"""
|
||||||
|
ns3.py: defines classes for running emulations with ns-3 simulated networks.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import subprocess
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
import ns.core
|
||||||
|
import ns.internet
|
||||||
|
import ns.lte
|
||||||
|
import ns.mobility
|
||||||
|
import ns.network
|
||||||
|
import ns.tap_bridge
|
||||||
|
import ns.wifi
|
||||||
|
import ns.wimax
|
||||||
|
|
||||||
|
from core import constants
|
||||||
|
from core.emulator.enumerations import EventTypes
|
||||||
|
from core.emulator.enumerations import LinkTypes
|
||||||
|
from core.emulator.enumerations import NodeTypes
|
||||||
|
from core.utils import make_tuple
|
||||||
|
from core.location.mobility import WayPointMobility
|
||||||
|
from core.nodes.base import CoreNode, CoreNetworkBase
|
||||||
|
from core.emulator.session import Session
|
||||||
|
|
||||||
|
ns.core.GlobalValue.Bind(
|
||||||
|
"SimulatorImplementationType",
|
||||||
|
ns.core.StringValue("ns3::RealtimeSimulatorImpl")
|
||||||
|
)
|
||||||
|
ns.core.GlobalValue.Bind(
|
||||||
|
"ChecksumEnabled",
|
||||||
|
ns.core.BooleanValue("true")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CoreNs3Node(CoreNode, ns.network.Node):
|
||||||
|
"""
|
||||||
|
The CoreNs3Node is both a CoreNode backed by a network namespace and
|
||||||
|
an ns-3 Node simulator object. When linked to simulated networks, the TunTap
|
||||||
|
device will be used.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwds):
|
||||||
|
ns.network.Node.__init__(self)
|
||||||
|
# ns-3 ID starts at 0, CORE uses 1
|
||||||
|
_id = self.GetId() + 1
|
||||||
|
if '_id' not in kwds:
|
||||||
|
kwds['_id'] = _id
|
||||||
|
CoreNode.__init__(self, *args, **kwds)
|
||||||
|
|
||||||
|
def newnetif(self, net=None, addrlist=None, hwaddr=None, ifindex=None, ifname=None):
|
||||||
|
"""
|
||||||
|
Add a network interface. If we are attaching to a CoreNs3Net, this
|
||||||
|
will be a TunTap. Otherwise dispatch to CoreNode.newnetif().
|
||||||
|
"""
|
||||||
|
if not addrlist:
|
||||||
|
addrlist = []
|
||||||
|
|
||||||
|
if not isinstance(net, CoreNs3Net):
|
||||||
|
return CoreNode.newnetif(self, net, addrlist, hwaddr, ifindex, ifname)
|
||||||
|
ifindex = self.newtuntap(ifindex, ifname)
|
||||||
|
self.attachnet(ifindex, net)
|
||||||
|
netif = self.netif(ifindex)
|
||||||
|
netif.sethwaddr(hwaddr)
|
||||||
|
for addr in make_tuple(addrlist):
|
||||||
|
netif.addaddr(addr)
|
||||||
|
|
||||||
|
addrstr = netif.addrlist[0]
|
||||||
|
addr, mask = addrstr.split('/')
|
||||||
|
tap = net._tapdevs[netif]
|
||||||
|
tap.SetAttribute(
|
||||||
|
"IpAddress",
|
||||||
|
ns.network.Ipv4AddressValue(ns.network.Ipv4Address(addr))
|
||||||
|
)
|
||||||
|
tap.SetAttribute(
|
||||||
|
"Netmask",
|
||||||
|
ns.network.Ipv4MaskValue(ns.network.Ipv4Mask(f"/{mask}"))
|
||||||
|
)
|
||||||
|
ns.core.Simulator.Schedule(ns.core.Time("0"), netif.install)
|
||||||
|
return ifindex
|
||||||
|
|
||||||
|
def getns3position(self):
|
||||||
|
"""
|
||||||
|
Return the ns-3 (x, y, z) position of a node.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
mm = self.GetObject(ns.mobility.MobilityModel.GetTypeId())
|
||||||
|
pos = mm.GetPosition()
|
||||||
|
return pos.x, pos.y, pos.z
|
||||||
|
except AttributeError:
|
||||||
|
self.warn("ns-3 mobility model not found")
|
||||||
|
return 0, 0, 0
|
||||||
|
|
||||||
|
def setns3position(self, x, y, z):
|
||||||
|
"""
|
||||||
|
Set the ns-3 (x, y, z) position of a node.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
mm = self.GetObject(ns.mobility.MobilityModel.GetTypeId())
|
||||||
|
if z is None:
|
||||||
|
z = 0.0
|
||||||
|
mm.SetPosition(ns.core.Vector(x, y, z))
|
||||||
|
except AttributeError:
|
||||||
|
self.warn("ns-3 mobility model not found, not setting position")
|
||||||
|
|
||||||
|
|
||||||
|
class CoreNs3Net(CoreNetworkBase):
|
||||||
|
"""
|
||||||
|
The CoreNs3Net is a helper PyCoreNet object. Networks are represented
|
||||||
|
entirely in simulation with the TunTap device bridging the emulated and
|
||||||
|
simulated worlds.
|
||||||
|
"""
|
||||||
|
apitype = NodeTypes.WIRELESS_LAN.value
|
||||||
|
linktype = LinkTypes.WIRELESS.value
|
||||||
|
# icon used
|
||||||
|
type = "wlan"
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, session, _id=None, name=None, start=True, server=None
|
||||||
|
):
|
||||||
|
CoreNetworkBase.__init__(self, session, _id, name, start, server)
|
||||||
|
self.tapbridge = ns.tap_bridge.TapBridgeHelper()
|
||||||
|
self._ns3devs = {}
|
||||||
|
self._tapdevs = {}
|
||||||
|
|
||||||
|
def attach(self, netif):
|
||||||
|
"""
|
||||||
|
Invoked from netif.attach(). Create a TAP device using the TapBridge
|
||||||
|
object. Call getns3dev() to get model-specific device.
|
||||||
|
"""
|
||||||
|
self._netif[netif] = netif
|
||||||
|
self._linked[netif] = {}
|
||||||
|
ns3dev = self.getns3dev(netif.node)
|
||||||
|
tap = self.tapbridge.Install(netif.node, ns3dev)
|
||||||
|
tap.SetMode(ns.tap_bridge.TapBridge.CONFIGURE_LOCAL)
|
||||||
|
tap.SetAttribute(
|
||||||
|
"DeviceName",
|
||||||
|
ns.core.StringValue(netif.localname)
|
||||||
|
)
|
||||||
|
self._ns3devs[netif] = ns3dev
|
||||||
|
self._tapdevs[netif] = tap
|
||||||
|
|
||||||
|
def getns3dev(self, node):
|
||||||
|
"""
|
||||||
|
Implement depending on network helper. Install this network onto
|
||||||
|
the given node and return the device. Register the ns3 device into
|
||||||
|
self._ns3devs
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def findns3dev(self, node):
|
||||||
|
"""
|
||||||
|
Given a node, return the interface and ns3 device associated with
|
||||||
|
this network.
|
||||||
|
"""
|
||||||
|
for netif in node.netifs():
|
||||||
|
if netif in self._ns3devs:
|
||||||
|
return netif, self._ns3devs[netif]
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
"""
|
||||||
|
Session.shutdown() will invoke this.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def usecorepositions(self):
|
||||||
|
"""
|
||||||
|
Set position callbacks for interfaces on this net so the CORE GUI
|
||||||
|
can update the ns-3 node position when moved with the mouse.
|
||||||
|
"""
|
||||||
|
for netif in self.netifs():
|
||||||
|
netif.poshook = self.setns3position
|
||||||
|
|
||||||
|
def setns3position(self, netif, x, y, z):
|
||||||
|
logging.info("setns3position: %s (%s, %s, %s)", netif.node.name, x, y, z)
|
||||||
|
netif.node.setns3position(x, y, z)
|
||||||
|
|
||||||
|
|
||||||
|
class Ns3LteNet(CoreNs3Net):
|
||||||
|
def __init__(self, *args, **kwds):
|
||||||
|
"""
|
||||||
|
Uses a LteHelper to create an ns-3 based LTE network.
|
||||||
|
"""
|
||||||
|
CoreNs3Net.__init__(self, *args, **kwds)
|
||||||
|
self.lte = ns.lte.LteHelper()
|
||||||
|
# enhanced NodeB node list
|
||||||
|
self.enbnodes = []
|
||||||
|
self.dlsubchannels = None
|
||||||
|
self.ulsubchannels = None
|
||||||
|
|
||||||
|
def setsubchannels(self, downlink, uplink):
|
||||||
|
"""
|
||||||
|
Set the downlink/uplink subchannels, which are a list of ints.
|
||||||
|
These should be set prior to using CoreNs3Node.newnetif().
|
||||||
|
"""
|
||||||
|
self.dlsubchannels = downlink
|
||||||
|
self.ulsubchannels = uplink
|
||||||
|
|
||||||
|
def setnodeb(self, node):
|
||||||
|
"""
|
||||||
|
Mark the given node as a nodeb (base transceiver station)
|
||||||
|
"""
|
||||||
|
self.enbnodes.append(node)
|
||||||
|
|
||||||
|
def linknodeb(self, node, nodeb, mob, mobb):
|
||||||
|
"""
|
||||||
|
Register user equipment with a nodeb.
|
||||||
|
Optionally install mobility model while we have the ns-3 devs handy.
|
||||||
|
"""
|
||||||
|
_tmp, nodebdev = self.findns3dev(nodeb)
|
||||||
|
_tmp, dev = self.findns3dev(node)
|
||||||
|
if nodebdev is None or dev is None:
|
||||||
|
raise KeyError("ns-3 device for node not found")
|
||||||
|
self.lte.RegisterUeToTheEnb(dev, nodebdev)
|
||||||
|
if mob:
|
||||||
|
self.lte.AddMobility(dev.GetPhy(), mob)
|
||||||
|
if mobb:
|
||||||
|
self.lte.AddDownlinkChannelRealization(mobb, mob, dev.GetPhy())
|
||||||
|
|
||||||
|
def getns3dev(self, node):
|
||||||
|
"""
|
||||||
|
Get the ns3 NetDevice using the LteHelper.
|
||||||
|
"""
|
||||||
|
if node in self.enbnodes:
|
||||||
|
devtype = ns.lte.LteHelper.DEVICE_TYPE_ENODEB
|
||||||
|
else:
|
||||||
|
devtype = ns.lte.LteHelper.DEVICE_TYPE_USER_EQUIPMENT
|
||||||
|
nodes = ns.network.NodeContainer(node)
|
||||||
|
devs = self.lte.Install(nodes, devtype)
|
||||||
|
devs.Get(0).GetPhy().SetDownlinkSubChannels(self.dlsubchannels)
|
||||||
|
devs.Get(0).GetPhy().SetUplinkSubChannels(self.ulsubchannels)
|
||||||
|
return devs.Get(0)
|
||||||
|
|
||||||
|
def attach(self, netif):
|
||||||
|
"""
|
||||||
|
Invoked from netif.attach(). Create a TAP device using the TapBridge
|
||||||
|
object. Call getns3dev() to get model-specific device.
|
||||||
|
"""
|
||||||
|
self._netif[netif] = netif
|
||||||
|
self._linked[netif] = {}
|
||||||
|
ns3dev = self.getns3dev(netif.node)
|
||||||
|
self.tapbridge.SetAttribute("Mode", ns.core.StringValue("UseLocal"))
|
||||||
|
# self.tapbridge.SetAttribute("Mode",
|
||||||
|
# ns.core.IntegerValue(ns.tap_bridge.TapBridge.USE_LOCAL))
|
||||||
|
tap = self.tapbridge.Install(netif.node, ns3dev)
|
||||||
|
# tap.SetMode(ns.tap_bridge.TapBridge.USE_LOCAL)
|
||||||
|
logging.info("using TAP device %s for %s/%s", netif.localname, netif.node.name, netif.name)
|
||||||
|
subprocess.check_call(['tunctl', '-t', netif.localname, '-n'])
|
||||||
|
# check_call([IP_BIN, 'link', 'set', 'dev', netif.localname, \
|
||||||
|
# 'address', '%s' % netif.hwaddr])
|
||||||
|
subprocess.check_call([constants.IP_BIN, 'link', 'set', netif.localname, 'up'])
|
||||||
|
tap.SetAttribute("DeviceName", ns.core.StringValue(netif.localname))
|
||||||
|
self._ns3devs[netif] = ns3dev
|
||||||
|
self._tapdevs[netif] = tap
|
||||||
|
|
||||||
|
|
||||||
|
class Ns3WifiNet(CoreNs3Net):
|
||||||
|
def __init__(self, *args, **kwds):
|
||||||
|
"""
|
||||||
|
Uses a WifiHelper to create an ns-3 based Wifi network.
|
||||||
|
"""
|
||||||
|
rate = kwds.pop('rate', 'OfdmRate54Mbps')
|
||||||
|
CoreNs3Net.__init__(self, *args, **kwds)
|
||||||
|
self.wifi = ns.wifi.WifiHelper().Default()
|
||||||
|
self.wifi.SetStandard(ns.wifi.WIFI_PHY_STANDARD_80211a)
|
||||||
|
self.wifi.SetRemoteStationManager(
|
||||||
|
"ns3::ConstantRateWifiManager",
|
||||||
|
"DataMode",
|
||||||
|
ns.core.StringValue(rate),
|
||||||
|
"NonUnicastMode",
|
||||||
|
ns.core.StringValue(rate)
|
||||||
|
)
|
||||||
|
self.mac = ns.wifi.NqosWifiMacHelper.Default()
|
||||||
|
self.mac.SetType("ns3::AdhocWifiMac")
|
||||||
|
|
||||||
|
channel = ns.wifi.YansWifiChannelHelper.Default()
|
||||||
|
self.phy = ns.wifi.YansWifiPhyHelper.Default()
|
||||||
|
self.phy.SetChannel(channel.Create())
|
||||||
|
|
||||||
|
def getns3dev(self, node):
|
||||||
|
"""
|
||||||
|
Get the ns3 NetDevice using the WifiHelper.
|
||||||
|
"""
|
||||||
|
devs = self.wifi.Install(self.phy, self.mac, node)
|
||||||
|
return devs.Get(0)
|
||||||
|
|
||||||
|
|
||||||
|
class Ns3WimaxNet(CoreNs3Net):
|
||||||
|
def __init__(self, *args, **kwds):
|
||||||
|
CoreNs3Net.__init__(self, *args, **kwds)
|
||||||
|
self.wimax = ns.wimax.WimaxHelper()
|
||||||
|
self.scheduler = ns.wimax.WimaxHelper.SCHED_TYPE_SIMPLE
|
||||||
|
self.phy = ns.wimax.WimaxHelper.SIMPLE_PHY_TYPE_OFDM
|
||||||
|
# base station node list
|
||||||
|
self.bsnodes = []
|
||||||
|
|
||||||
|
def setbasestation(self, node):
|
||||||
|
self.bsnodes.append(node)
|
||||||
|
|
||||||
|
def getns3dev(self, node):
|
||||||
|
if node in self.bsnodes:
|
||||||
|
devtype = ns.wimax.WimaxHelper.DEVICE_TYPE_BASE_STATION
|
||||||
|
else:
|
||||||
|
devtype = ns.wimax.WimaxHelper.DEVICE_TYPE_SUBSCRIBER_STATION
|
||||||
|
nodes = ns.network.NodeContainer(node)
|
||||||
|
devs = self.wimax.Install(nodes, devtype, self.phy, self.scheduler)
|
||||||
|
if node not in self.bsnodes:
|
||||||
|
devs.Get(0).SetModulationType(ns.wimax.WimaxPhy.MODULATION_TYPE_QAM16_12)
|
||||||
|
# debug
|
||||||
|
self.wimax.EnableAscii("wimax-device-%s" % node.name, devs)
|
||||||
|
return devs.Get(0)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def ipv4netifaddr(netif):
|
||||||
|
for addr in netif.addrlist:
|
||||||
|
if ':' in addr:
|
||||||
|
# skip ipv6
|
||||||
|
continue
|
||||||
|
ip = ns.network.Ipv4Address(addr.split('/')[0])
|
||||||
|
mask = ns.network.Ipv4Mask('/' + addr.split('/')[1])
|
||||||
|
return ip, mask
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
def addflow(self, node1, node2, upclass, downclass):
|
||||||
|
"""
|
||||||
|
Add a Wimax service flow between two nodes.
|
||||||
|
"""
|
||||||
|
netif1, ns3dev1 = self.findns3dev(node1)
|
||||||
|
netif2, ns3dev2 = self.findns3dev(node2)
|
||||||
|
if not netif1 or not netif2:
|
||||||
|
raise ValueError("interface not found")
|
||||||
|
addr1, mask1 = self.ipv4netifaddr(netif1)
|
||||||
|
addr2, mask2 = self.ipv4netifaddr(netif2)
|
||||||
|
clargs1 = (addr1, mask1, addr2, mask2) + downclass
|
||||||
|
clargs2 = (addr2, mask2, addr1, mask1) + upclass
|
||||||
|
clrec1 = ns.wimax.IpcsClassifierRecord(*clargs1)
|
||||||
|
clrec2 = ns.wimax.IpcsClassifierRecord(*clargs2)
|
||||||
|
ns3dev1.AddServiceFlow(self.wimax.CreateServiceFlow(
|
||||||
|
ns.wimax.ServiceFlow.SF_DIRECTION_DOWN,
|
||||||
|
ns.wimax.ServiceFlow.SF_TYPE_RTPS, clrec1)
|
||||||
|
)
|
||||||
|
ns3dev1.AddServiceFlow(self.wimax.CreateServiceFlow(
|
||||||
|
ns.wimax.ServiceFlow.SF_DIRECTION_UP,
|
||||||
|
ns.wimax.ServiceFlow.SF_TYPE_RTPS, clrec2)
|
||||||
|
)
|
||||||
|
ns3dev2.AddServiceFlow(self.wimax.CreateServiceFlow(
|
||||||
|
ns.wimax.ServiceFlow.SF_DIRECTION_DOWN,
|
||||||
|
ns.wimax.ServiceFlow.SF_TYPE_RTPS, clrec2)
|
||||||
|
)
|
||||||
|
ns3dev2.AddServiceFlow(self.wimax.CreateServiceFlow(
|
||||||
|
ns.wimax.ServiceFlow.SF_DIRECTION_UP,
|
||||||
|
ns.wimax.ServiceFlow.SF_TYPE_RTPS, clrec1)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Ns3Session(Session):
|
||||||
|
"""
|
||||||
|
A Session that starts an ns-3 simulation thread.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, _id, persistent=False, duration=600):
|
||||||
|
self.duration = duration
|
||||||
|
self.nodes = ns.network.NodeContainer()
|
||||||
|
self.mobhelper = ns.mobility.MobilityHelper()
|
||||||
|
Session.__init__(self, _id)
|
||||||
|
|
||||||
|
def run(self, vis=False):
|
||||||
|
"""
|
||||||
|
Run the ns-3 simulation and return the simulator thread.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def runthread():
|
||||||
|
ns.core.Simulator.Stop(ns.core.Seconds(self.duration))
|
||||||
|
logging.info("running ns-3 simulation for %d seconds", self.duration)
|
||||||
|
if vis:
|
||||||
|
try:
|
||||||
|
import visualizer
|
||||||
|
except ImportError:
|
||||||
|
logging.exception("visualizer is not available")
|
||||||
|
ns.core.Simulator.Run()
|
||||||
|
else:
|
||||||
|
visualizer.start()
|
||||||
|
else:
|
||||||
|
ns.core.Simulator.Run()
|
||||||
|
|
||||||
|
# self.evq.run() # event queue may have WayPointMobility events
|
||||||
|
self.set_state(EventTypes.RUNTIME_STATE, send_event=True)
|
||||||
|
t = threading.Thread(target=runthread)
|
||||||
|
t.daemon = True
|
||||||
|
t.start()
|
||||||
|
return t
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
# TODO: the following line tends to segfault ns-3 (and therefore core-daemon)
|
||||||
|
ns.core.Simulator.Destroy()
|
||||||
|
Session.shutdown(self)
|
||||||
|
|
||||||
|
def addnode(self, name):
|
||||||
|
"""
|
||||||
|
A convenience helper for Session.addobj(), for adding CoreNs3Nodes
|
||||||
|
to this session. Keeps a NodeContainer for later use.
|
||||||
|
"""
|
||||||
|
n = self.create_node(cls=CoreNs3Node, name=name)
|
||||||
|
self.nodes.Add(n)
|
||||||
|
return n
|
||||||
|
|
||||||
|
def setupconstantmobility(self):
|
||||||
|
"""
|
||||||
|
Install a ConstantPositionMobilityModel.
|
||||||
|
"""
|
||||||
|
palloc = ns.mobility.ListPositionAllocator()
|
||||||
|
for i in xrange(self.nodes.GetN()):
|
||||||
|
(x, y, z) = ((100.0 * i) + 50, 200.0, 0.0)
|
||||||
|
palloc.Add(ns.core.Vector(x, y, z))
|
||||||
|
node = self.nodes.Get(i)
|
||||||
|
node.position.set(x, y, z)
|
||||||
|
self.mobhelper.SetPositionAllocator(palloc)
|
||||||
|
self.mobhelper.SetMobilityModel("ns3::ConstantPositionMobilityModel")
|
||||||
|
self.mobhelper.Install(self.nodes)
|
||||||
|
|
||||||
|
def setuprandomwalkmobility(self, bounds, time=10, speed=25.0):
|
||||||
|
"""
|
||||||
|
Set up the random walk mobility model within a bounding box.
|
||||||
|
- bounds is the max (x, y, z) boundary
|
||||||
|
- time is the number of seconds to maintain the current speed
|
||||||
|
and direction
|
||||||
|
- speed is the maximum speed, with node speed randomly chosen
|
||||||
|
from [0, speed]
|
||||||
|
"""
|
||||||
|
x, y, z = map(float, bounds)
|
||||||
|
self.mobhelper.SetPositionAllocator(
|
||||||
|
"ns3::RandomBoxPositionAllocator",
|
||||||
|
"X",
|
||||||
|
ns.core.StringValue("ns3::UniformRandomVariable[Min=0|Max=%s]" % x),
|
||||||
|
"Y",
|
||||||
|
ns.core.StringValue("ns3::UniformRandomVariable[Min=0|Max=%s]" % y),
|
||||||
|
"Z",
|
||||||
|
ns.core.StringValue("ns3::UniformRandomVariable[Min=0|Max=%s]" % z)
|
||||||
|
)
|
||||||
|
self.mobhelper.SetMobilityModel(
|
||||||
|
"ns3::RandomWalk2dMobilityModel",
|
||||||
|
"Mode", ns.core.StringValue("Time"),
|
||||||
|
"Time", ns.core.StringValue("%ss" % time),
|
||||||
|
"Speed",
|
||||||
|
ns.core.StringValue("ns3::UniformRandomVariable[Min=0|Max=%s]" % speed),
|
||||||
|
"Bounds", ns.core.StringValue("0|%s|0|%s" % (x, y))
|
||||||
|
)
|
||||||
|
self.mobhelper.Install(self.nodes)
|
||||||
|
|
||||||
|
def startns3mobility(self, refresh_ms=300):
|
||||||
|
"""
|
||||||
|
Start a thread that updates CORE nodes based on their ns-3
|
||||||
|
positions.
|
||||||
|
"""
|
||||||
|
self.set_state(EventTypes.INSTANTIATION_STATE)
|
||||||
|
self.mobilitythread = threading.Thread(
|
||||||
|
target=self.ns3mobilitythread,
|
||||||
|
args=(refresh_ms,))
|
||||||
|
self.mobilitythread.daemon = True
|
||||||
|
self.mobilitythread.start()
|
||||||
|
|
||||||
|
def ns3mobilitythread(self, refresh_ms):
|
||||||
|
"""
|
||||||
|
Thread target that updates CORE nodes every refresh_ms based on
|
||||||
|
their ns-3 positions.
|
||||||
|
"""
|
||||||
|
valid_states = (
|
||||||
|
EventTypes.RUNTIME_STATE.value,
|
||||||
|
EventTypes.INSTANTIATION_STATE.value
|
||||||
|
)
|
||||||
|
while self.state in valid_states:
|
||||||
|
for i in xrange(self.nodes.GetN()):
|
||||||
|
node = self.nodes.Get(i)
|
||||||
|
x, y, z = node.getns3position()
|
||||||
|
if (x, y, z) == node.position.get():
|
||||||
|
continue
|
||||||
|
# from WayPointMobility.setnodeposition(node, x, y, z)
|
||||||
|
node.position.set(x, y, z)
|
||||||
|
node_data = node.data(0)
|
||||||
|
self.broadcast_node(node_data)
|
||||||
|
self.sdt.updatenode(node.id, flags=0, x=x, y=y, z=z)
|
||||||
|
time.sleep(0.001 * refresh_ms)
|
||||||
|
|
||||||
|
def setupmobilitytracing(self, net, filename, nodes):
|
||||||
|
"""
|
||||||
|
Start a tracing thread using the ASCII output from the ns3
|
||||||
|
mobility helper.
|
||||||
|
"""
|
||||||
|
net.mobility = WayPointMobility(session=self, _id=net.id)
|
||||||
|
net.mobility.setendtime()
|
||||||
|
net.mobility.refresh_ms = 300
|
||||||
|
net.mobility.empty_queue_stop = False
|
||||||
|
of = ns.network.OutputStreamWrapper(filename, filemode=0o777)
|
||||||
|
self.mobhelper.EnableAsciiAll(of)
|
||||||
|
self.mobilitytracethread = threading.Thread(
|
||||||
|
target=self.mobilitytrace,
|
||||||
|
args=(net, filename, nodes)
|
||||||
|
)
|
||||||
|
self.mobilitytracethread.daemon = True
|
||||||
|
self.mobilitytracethread.start()
|
||||||
|
|
||||||
|
def mobilitytrace(self, net, filename, nodes, verbose):
|
||||||
|
nodemap = {}
|
||||||
|
# move nodes to initial positions
|
||||||
|
for node in nodes:
|
||||||
|
x, y, z = node.getns3position()
|
||||||
|
net.mobility.setnodeposition(node, x, y, z)
|
||||||
|
nodemap[node.GetId()] = node
|
||||||
|
|
||||||
|
logging.info("mobilitytrace opening '%s'", filename)
|
||||||
|
|
||||||
|
f = None
|
||||||
|
try:
|
||||||
|
f = open(filename)
|
||||||
|
f.seek(0, 2)
|
||||||
|
|
||||||
|
sleep = 0.001
|
||||||
|
kickstart = True
|
||||||
|
while True:
|
||||||
|
if self.state != EventTypes.RUNTIME_STATE.value:
|
||||||
|
break
|
||||||
|
line = f.readline()
|
||||||
|
if not line:
|
||||||
|
time.sleep(sleep)
|
||||||
|
if sleep < 1.0:
|
||||||
|
sleep += 0.001
|
||||||
|
continue
|
||||||
|
sleep = 0.001
|
||||||
|
items = dict(x.split("=") for x in line.split())
|
||||||
|
logging.info("trace: %s %s %s", items['node'], items['pos'], items['vel'])
|
||||||
|
x, y, z = map(float, items['pos'].split(':'))
|
||||||
|
vel = map(float, items['vel'].split(':'))
|
||||||
|
node = nodemap[int(items['node'])]
|
||||||
|
net.mobility.addwaypoint(time=0, nodenum=node.id, x=x, y=y, z=z, speed=vel)
|
||||||
|
if kickstart:
|
||||||
|
kickstart = False
|
||||||
|
self.event_loop.add_event(0, net.mobility.start)
|
||||||
|
self.event_loop.run()
|
||||||
|
else:
|
||||||
|
if net.mobility.state != net.mobility.STATE_RUNNING:
|
||||||
|
net.mobility.state = net.mobility.STATE_RUNNING
|
||||||
|
self.event_loop.add_event(0, net.mobility.runround)
|
||||||
|
except IOError:
|
||||||
|
logging.exception("mobilitytrace error opening: %s", filename)
|
||||||
|
finally:
|
||||||
|
if f:
|
||||||
|
f.close()
|
19
ns3/setup.py
Normal file
19
ns3/setup.py
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import glob
|
||||||
|
|
||||||
|
from setuptools import setup
|
||||||
|
|
||||||
|
_EXAMPLES_DIR = "share/corens3/examples"
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name="core-ns3",
|
||||||
|
version="5.5.2",
|
||||||
|
packages=[
|
||||||
|
"corens3",
|
||||||
|
],
|
||||||
|
data_files=[(_EXAMPLES_DIR, glob.glob("examples/*"))],
|
||||||
|
description="Python ns-3 components of CORE",
|
||||||
|
url="https://github.com/coreemu/core",
|
||||||
|
author="Boeing Research & Technology",
|
||||||
|
license="GPLv2",
|
||||||
|
long_description="Python scripts and modules for building virtual simulated networks."
|
||||||
|
)
|
Loading…
Add table
Reference in a new issue